From 4fabe5e6e40dbd2e59d971e19ec8519c9a48b32a Mon Sep 17 00:00:00 2001 From: Joel Einbinder Date: Tue, 3 Aug 2021 13:12:34 -0400 Subject: [PATCH] feat(drag): sourcePosition and targetPosition (#7803) --- docs/src/api/class-frame.md | 4 ++++ docs/src/api/class-page.md | 4 ++++ docs/src/api/params.md | 14 +++++++++++++ src/protocol/channels.ts | 4 ++++ src/protocol/protocol.yml | 2 ++ src/protocol/validator.ts | 2 ++ src/server/frames.ts | 4 +++- src/server/types.ts | 6 ++++++ tests/page/page-drag.spec.ts | 35 +++++++++++++++++++++++++++++++ types/types.d.ts | 40 ++++++++++++++++++++++++++++++++++++ 10 files changed, 114 insertions(+), 1 deletion(-) diff --git a/docs/src/api/class-frame.md b/docs/src/api/class-frame.md index ae8c01f132..a1ba9b8fcc 100644 --- a/docs/src/api/class-frame.md +++ b/docs/src/api/class-frame.md @@ -371,6 +371,10 @@ Optional event-specific initialization properties. ### option: Frame.dragAndDrop.timeout = %%-input-timeout-%% ### option: Frame.dragAndDrop.trial = %%-input-trial-%% +### option: Frame.dragAndDrop.sourcePosition = %%-input-source-position-%% + +### option: Frame.dragAndDrop.targetPosition = %%-input-target-position-%% + ## async method: Frame.evalOnSelector * langs: - alias-python: eval_on_selector diff --git a/docs/src/api/class-page.md b/docs/src/api/class-page.md index ce4247a6ab..e6ae6a2472 100644 --- a/docs/src/api/class-page.md +++ b/docs/src/api/class-page.md @@ -807,6 +807,10 @@ Optional event-specific initialization properties. ### option: Page.dragAndDrop.timeout = %%-input-timeout-%% ### option: Page.dragAndDrop.trial = %%-input-trial-%% +### option: Page.dragAndDrop.sourcePosition = %%-input-source-position-%% + +### option: Page.dragAndDrop.targetPosition = %%-input-target-position-%% + ## async method: Page.emulateMedia This method changes the `CSS media type` through the `media` argument, and/or the `'prefers-colors-scheme'` media feature, using the `colorScheme` argument. diff --git a/docs/src/api/params.md b/docs/src/api/params.md index 67c9381360..8468447d5d 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -103,6 +103,20 @@ defaults to 1. See [UIEvent.detail]. When set, this method only performs the [actionability](./actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. +## input-source-position +- `sourcePosition` <[Object]> + - `x` <[float]> + - `y` <[float]> + +Clicks on the source element at this point relative to the top-left corner of the element's padding box. If not specified, some visible point of the element is used. + +## input-target-position +- `targetPosition` <[Object]> + - `x` <[float]> + - `y` <[float]> + +Drops on the target element at this point relative to the top-left corner of the element's padding box. If not specified, some visible point of the element is used. + ## query-selector - `selector` <[string]> diff --git a/src/protocol/channels.ts b/src/protocol/channels.ts index 90d35d9c44..4af0bbb277 100644 --- a/src/protocol/channels.ts +++ b/src/protocol/channels.ts @@ -1462,12 +1462,16 @@ export type FrameDragAndDropParams = { noWaitAfter?: boolean, timeout?: number, trial?: boolean, + sourcePosition?: Point, + targetPosition?: Point, }; export type FrameDragAndDropOptions = { force?: boolean, noWaitAfter?: boolean, timeout?: number, trial?: boolean, + sourcePosition?: Point, + targetPosition?: Point, }; export type FrameDragAndDropResult = void; export type FrameDblclickParams = { diff --git a/src/protocol/protocol.yml b/src/protocol/protocol.yml index f88ca17e2a..d84a70c815 100644 --- a/src/protocol/protocol.yml +++ b/src/protocol/protocol.yml @@ -1188,6 +1188,8 @@ Frame: noWaitAfter: boolean? timeout: number? trial: boolean? + sourcePosition: Point? + targetPosition: Point? dblclick: parameters: diff --git a/src/protocol/validator.ts b/src/protocol/validator.ts index a72c9eee1d..941e5da705 100644 --- a/src/protocol/validator.ts +++ b/src/protocol/validator.ts @@ -597,6 +597,8 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme { noWaitAfter: tOptional(tBoolean), timeout: tOptional(tNumber), trial: tOptional(tBoolean), + sourcePosition: tOptional(tType('Point')), + targetPosition: tOptional(tType('Point')), }); scheme.FrameDblclickParams = tObject({ selector: tString, diff --git a/src/server/frames.ts b/src/server/frames.ts index 92d954c0b6..fd317017db 100644 --- a/src/server/frames.ts +++ b/src/server/frames.ts @@ -983,7 +983,7 @@ export class Frame extends SdkObject { }, this._page._timeoutSettings.timeout(options)); } - async dragAndDrop(metadata: CallMetadata, source: string, target: string, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) { + async dragAndDrop(metadata: CallMetadata, source: string, target: string, options: types.DragActionOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) { const controller = new ProgressController(metadata, this); await controller.run(async progress => { await dom.assertDone(await this._retryWithProgressIfNotConnected(progress, source, !!options.strict, async handle => { @@ -992,6 +992,7 @@ export class Frame extends SdkObject { await this._page.mouse.down(); }, { ...options, + position: options.sourcePosition, timeout: progress.timeUntilDeadline(), }); })); @@ -1001,6 +1002,7 @@ export class Frame extends SdkObject { await this._page.mouse.up(); }, { ...options, + position: options.targetPosition, timeout: progress.timeUntilDeadline(), }); })); diff --git a/src/server/types.ts b/src/server/types.ts index b47b78c311..9eb4890070 100644 --- a/src/server/types.ts +++ b/src/server/types.ts @@ -171,6 +171,12 @@ export type PointerActionOptions = { position?: Point; }; +export type DragActionOptions = { + sourcePosition?: Point; + targetPosition?: Point; +}; + + export type MouseClickOptions = PointerActionOptions & { delay?: number; button?: MouseButton; diff --git a/tests/page/page-drag.spec.ts b/tests/page/page-drag.spec.ts index 0f13e3d4a2..154eb2b0e6 100644 --- a/tests/page/page-drag.spec.ts +++ b/tests/page/page-drag.spec.ts @@ -234,6 +234,41 @@ it.describe('Drag and drop', () => { expect(await page.$eval('#target', target => target.contains(document.querySelector('#source')))).toBe(true); // could not find source in target }); + it('should allow specifying the position', async ({page, server}) => { + await page.setContent(` +
+
+
+
+ `); + const eventsHandle = await page.evaluateHandle(() => { + const events = []; + document.getElementById('red').addEventListener('mousedown', event => { + events.push({ + type: 'mousedown', + x: event.offsetX, + y: event.offsetY, + }); + }); + document.getElementById('blue').addEventListener('mouseup', event => { + events.push({ + type: 'mouseup', + x: event.offsetX, + y: event.offsetY, + }); + }); + return events; + }); + await page.dragAndDrop('#red', '#blue', { + sourcePosition: {x: 34, y: 7}, + targetPosition: {x: 10, y: 20}, + }); + expect(await eventsHandle.jsonValue()).toEqual([ + {type: 'mousedown', x: 34, y: 7}, + {type: 'mouseup', x: 10, y: 20}, + ]); + }); + async function trackEvents(target: ElementHandle) { const eventsHandle = await target.evaluateHandle(target => { const events: string[] = []; diff --git a/types/types.d.ts b/types/types.d.ts index 5ce622f473..046f065fe8 100644 --- a/types/types.d.ts +++ b/types/types.d.ts @@ -1441,12 +1441,32 @@ export interface Page { */ noWaitAfter?: boolean; + /** + * Clicks on the source element at this point relative to the top-left corner of the element's padding box. If not + * specified, some visible point of the element is used. + */ + sourcePosition?: { + x: number; + + y: number; + }; + /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more then one * element, the call throws an exception. */ strict?: boolean; + /** + * Drops on the target element at this point relative to the top-left corner of the element's padding box. If not + * specified, some visible point of the element is used. + */ + targetPosition?: { + x: number; + + y: number; + }; + /** * Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by * using the @@ -3865,12 +3885,32 @@ export interface Frame { */ noWaitAfter?: boolean; + /** + * Clicks on the source element at this point relative to the top-left corner of the element's padding box. If not + * specified, some visible point of the element is used. + */ + sourcePosition?: { + x: number; + + y: number; + }; + /** * When true, the call requires selector to resolve to a single element. If given selector resolves to more then one * element, the call throws an exception. */ strict?: boolean; + /** + * Drops on the target element at this point relative to the top-left corner of the element's padding box. If not + * specified, some visible point of the element is used. + */ + targetPosition?: { + x: number; + + y: number; + }; + /** * Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by * using the