From 96be17463e5695ebde24a487cbabf518f22e6083 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 14 Oct 2021 17:37:29 +0200 Subject: [PATCH] feat(codgen): support positioned clicks in a canvas (#9503) --- .../server/supplements/injected/recorder.ts | 12 ++++ .../src/server/supplements/recorder/csharp.ts | 2 + .../src/server/supplements/recorder/java.ts | 4 ++ .../server/supplements/recorder/javascript.ts | 2 + .../src/server/supplements/recorder/python.ts | 4 ++ .../supplements/recorder/recorderActions.ts | 3 + .../src/server/supplements/recorder/utils.ts | 2 + tests/config/browserTest.ts | 1 + tests/inspector/cli-codegen-1.spec.ts | 60 +++++++++++++++++++ 9 files changed, 90 insertions(+) diff --git a/packages/playwright-core/src/server/supplements/injected/recorder.ts b/packages/playwright-core/src/server/supplements/injected/recorder.ts index e56c771913..5b472b99d3 100644 --- a/packages/playwright-core/src/server/supplements/injected/recorder.ts +++ b/packages/playwright-core/src/server/supplements/injected/recorder.ts @@ -249,6 +249,7 @@ export class Recorder { this._performAction({ name: 'click', selector: this._hoveredModel!.selector, + position: positionForEvent(event), signals: [], button: buttonForEvent(event), modifiers: modifiersForEvent(event), @@ -567,6 +568,17 @@ function buttonForEvent(event: MouseEvent): 'left' | 'middle' | 'right' { return 'left'; } +function positionForEvent(event: MouseEvent): Point |undefined { + const targetElement = (event.target as HTMLElement); + if (targetElement.nodeName !== 'CANVAS') + return; + const rect = targetElement.getBoundingClientRect(); + return { + x: event.clientX - rect.left, + y: event.clientY - rect.top, + }; +} + function consumeEvent(e: Event) { e.preventDefault(); e.stopPropagation(); diff --git a/packages/playwright-core/src/server/supplements/recorder/csharp.ts b/packages/playwright-core/src/server/supplements/recorder/csharp.ts index 9238cc3fb6..fc9c0e97ff 100644 --- a/packages/playwright-core/src/server/supplements/recorder/csharp.ts +++ b/packages/playwright-core/src/server/supplements/recorder/csharp.ts @@ -106,6 +106,8 @@ export class CSharpLanguageGenerator implements LanguageGenerator { options.modifiers = modifiers; if (action.clickCount > 2) options.clickCount = action.clickCount; + if (action.position) + options.position = action.position; if (!Object.entries(options).length) return `${method}Async(${quote(action.selector)})`; const optionsString = formatObject(options, ' ', (isPage ? 'Page' : 'Frame') + method + 'Options'); diff --git a/packages/playwright-core/src/server/supplements/recorder/java.ts b/packages/playwright-core/src/server/supplements/recorder/java.ts index 935677f5f8..5313ec99e2 100644 --- a/packages/playwright-core/src/server/supplements/recorder/java.ts +++ b/packages/playwright-core/src/server/supplements/recorder/java.ts @@ -102,6 +102,8 @@ export class JavaLanguageGenerator implements LanguageGenerator { options.modifiers = modifiers; if (action.clickCount > 2) options.clickCount = action.clickCount; + if (action.position) + options.position = action.position; const optionsText = formatClickOptions(options, isPage); return `${method}(${quote(action.selector)}${optionsText ? ', ' : ''}${optionsText})`; } @@ -222,6 +224,8 @@ function formatClickOptions(options: MouseClickOptions, isPage: boolean) { lines.push(` .setModifiers(Arrays.asList(${options.modifiers.map(m => `KeyboardModifier.${m.toUpperCase()}`).join(', ')}))`); if (options.clickCount) lines.push(` .setClickCount(${options.clickCount})`); + if (options.position) + lines.push(` .setPosition(${options.position.x}, ${options.position.y})`); if (!lines.length) return ''; lines.unshift(`new ${isPage ? 'Page' : 'Frame'}.ClickOptions()`); diff --git a/packages/playwright-core/src/server/supplements/recorder/javascript.ts b/packages/playwright-core/src/server/supplements/recorder/javascript.ts index 3107fee7c4..2baef302ba 100644 --- a/packages/playwright-core/src/server/supplements/recorder/javascript.ts +++ b/packages/playwright-core/src/server/supplements/recorder/javascript.ts @@ -120,6 +120,8 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator { options.modifiers = modifiers; if (action.clickCount > 2) options.clickCount = action.clickCount; + if (action.position) + options.position = action.position; const optionsString = formatOptions(options); return `${method}(${quote(action.selector)}${optionsString})`; } diff --git a/packages/playwright-core/src/server/supplements/recorder/python.ts b/packages/playwright-core/src/server/supplements/recorder/python.ts index 863b6dc125..7bc4ce5378 100644 --- a/packages/playwright-core/src/server/supplements/recorder/python.ts +++ b/packages/playwright-core/src/server/supplements/recorder/python.ts @@ -111,6 +111,8 @@ export class PythonLanguageGenerator implements LanguageGenerator { options.modifiers = modifiers; if (action.clickCount > 2) options.clickCount = action.clickCount; + if (action.position) + options.position = action.position; const optionsString = formatOptions(options, true); return `${method}(${quote(action.selector)}${optionsString})`; } @@ -198,6 +200,8 @@ function formatValue(value: any): string { return `[${value.map(formatValue).join(', ')}]`; if (typeof value === 'string') return quote(value); + if (typeof value === 'object') + return JSON.stringify(value); return String(value); } diff --git a/packages/playwright-core/src/server/supplements/recorder/recorderActions.ts b/packages/playwright-core/src/server/supplements/recorder/recorderActions.ts index 50cf8133f5..66ddac6c91 100644 --- a/packages/playwright-core/src/server/supplements/recorder/recorderActions.ts +++ b/packages/playwright-core/src/server/supplements/recorder/recorderActions.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import type { Point } from '../../../common/types'; + export type ActionName = 'check' | 'click' | @@ -37,6 +39,7 @@ export type ClickAction = ActionBase & { button: 'left' | 'middle' | 'right', modifiers: number, clickCount: number, + position?: Point, }; export type CheckAction = ActionBase & { diff --git a/packages/playwright-core/src/server/supplements/recorder/utils.ts b/packages/playwright-core/src/server/supplements/recorder/utils.ts index ec6a57d422..247ec22a49 100644 --- a/packages/playwright-core/src/server/supplements/recorder/utils.ts +++ b/packages/playwright-core/src/server/supplements/recorder/utils.ts @@ -31,6 +31,8 @@ export function toClickOptions(action: actions.ClickAction): { method: 'click' | options.modifiers = modifiers; if (action.clickCount > 2) options.clickCount = action.clickCount; + if (action.position) + options.position = action.position; return { method, options }; } diff --git a/tests/config/browserTest.ts b/tests/config/browserTest.ts index 0cbf684963..e2aec8dcbc 100644 --- a/tests/config/browserTest.ts +++ b/tests/config/browserTest.ts @@ -69,6 +69,7 @@ export const playwrightFixtures: Fixtures { expect(message.text()).toBe('click'); }); + test('should make a positioned click on a canvas', async ({ page, openRecorder }) => { + const recorder = await openRecorder(); + + await recorder.setContentAndWait(` + + + `); + + const selector = await recorder.waitForHighlight(() => recorder.page.hover('canvas', { + position: { x: 250, y: 250 }, + })); + expect(selector).toBe('canvas'); + const [message, sources] = await Promise.all([ + page.waitForEvent('console', msg => msg.type() !== 'error'), + recorder.waitForOutput('JavaScript', 'click'), + recorder.page.click('canvas', { + position: { x: 250, y: 250 }, + }) + ]); + + expect(sources.get('JavaScript').text).toContain(` + // Click canvas + await page.click('canvas', { + position: { + x: 250, + y: 250 + } + });`); + + expect(sources.get('Python').text).toContain(` + # Click canvas + page.click("canvas", position={"x":250,"y":250})`); + + expect(sources.get('Python Async').text).toContain(` + # Click canvas + await page.click("canvas", position={"x":250,"y":250})`); + + expect(sources.get('Java').text).toContain(` + // Click canvas + page.click("canvas", new Page.ClickOptions() + .setPosition(250, 250));`); + + expect(sources.get('C#').text).toContain(` + // Click canvas + await page.ClickAsync("canvas", new PageClickOptions + { + Position = new Position + { + X = 250, + Y = 250, + }, + });`); + expect(message.text()).toBe('click 250 250'); + }); + test('should work with TrustedTypes', async ({ page, openRecorder }) => { const recorder = await openRecorder();