From 02dfce0307ba0fe02f0f3da619b6fb4deabaed8c Mon Sep 17 00:00:00 2001 From: Joel Einbinder Date: Thu, 8 Jul 2021 16:20:54 -0500 Subject: [PATCH 1/3] feat(drag): page.dragAndDrop --- docs/src/api/class-frame.md | 14 +++++++ docs/src/api/class-page.md | 14 +++++++ src/client/frame.ts | 6 +++ src/client/page.ts | 4 ++ src/dispatchers/frameDispatcher.ts | 4 ++ src/protocol/channels.ts | 16 ++++++++ src/protocol/protocol.yml | 9 ++++ src/protocol/validator.ts | 8 ++++ src/server/frames.ts | 18 ++++++++ tests/page/page-drag.spec.ts | 6 +++ types/types.d.ts | 66 ++++++++++++++++++++++++++++++ 11 files changed, 165 insertions(+) diff --git a/docs/src/api/class-frame.md b/docs/src/api/class-frame.md index d3456c693d52a..f3ddfdc804a4c 100644 --- a/docs/src/api/class-frame.md +++ b/docs/src/api/class-frame.md @@ -374,6 +374,20 @@ Optional event-specific initialization properties. ### option: Frame.dispatchEvent.timeout = %%-input-timeout-%% +## async method: Frame.dragAndDrop +### param: Frame.dragAndDrop.selector1 +- `selector1` <[string]> + +### param: Frame.dragAndDrop.selector2 +- `selector2` <[string]> +### option: Frame.dragAndDrop.force = %%-input-force-%% + +### option: Frame.dragAndDrop.noWaitAfter = %%-input-no-wait-after-%% + +### option: Frame.dragAndDrop.timeout = %%-input-timeout-%% + +### option: Frame.dragAndDrop.timeout = %%-input-trial-%% + ## 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 d20b8ff4ed1d9..45e6667b29b85 100644 --- a/docs/src/api/class-page.md +++ b/docs/src/api/class-page.md @@ -811,6 +811,20 @@ Optional event-specific initialization properties. ### option: Page.dispatchEvent.timeout = %%-input-timeout-%% +## async method: Page.dragAndDrop +### param: Page.dragAndDrop.selector1 +- `selector1` <[string]> + +### param: Page.dragAndDrop.selector2 +- `selector2` <[string]> +### option: Page.dragAndDrop.force = %%-input-force-%% + +### option: Page.dragAndDrop.noWaitAfter = %%-input-no-wait-after-%% + +### option: Page.dragAndDrop.timeout = %%-input-timeout-%% + +### option: Page.dragAndDrop.timeout = %%-input-trial-%% + ## 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/src/client/frame.ts b/src/client/frame.ts index 8886961d0a954..680d34f159766 100644 --- a/src/client/frame.ts +++ b/src/client/frame.ts @@ -298,6 +298,12 @@ export class Frame extends ChannelOwner { + return await channel.dragAndDrop({ selector1, selector2, ...options }); + }); + } + async tap(selector: string, options: channels.FrameTapOptions = {}) { return this._wrapApiCall(async (channel: channels.FrameChannel) => { return await channel.tap({ selector, ...options }); diff --git a/src/client/page.ts b/src/client/page.ts index 76024265f71ba..b8b6765d2a938 100644 --- a/src/client/page.ts +++ b/src/client/page.ts @@ -505,6 +505,10 @@ export class Page extends ChannelOwner { + return await this._frame.dragAndDrop(metadata, params.selector1, params.selector2, params); + } + async tap(params: channels.FrameTapParams, metadata: CallMetadata): Promise { return await this._frame.tap(metadata, params.selector, params); } diff --git a/src/protocol/channels.ts b/src/protocol/channels.ts index 0fe8296ca5355..6448ba9de5393 100644 --- a/src/protocol/channels.ts +++ b/src/protocol/channels.ts @@ -1310,6 +1310,7 @@ export interface FrameChannel extends Channel { check(params: FrameCheckParams, metadata?: Metadata): Promise; click(params: FrameClickParams, metadata?: Metadata): Promise; content(params?: FrameContentParams, metadata?: Metadata): Promise; + dragAndDrop(params: FrameDragAndDropParams, metadata?: Metadata): Promise; dblclick(params: FrameDblclickParams, metadata?: Metadata): Promise; dispatchEvent(params: FrameDispatchEventParams, metadata?: Metadata): Promise; evaluateExpression(params: FrameEvaluateExpressionParams, metadata?: Metadata): Promise; @@ -1448,6 +1449,21 @@ export type FrameContentOptions = {}; export type FrameContentResult = { value: string, }; +export type FrameDragAndDropParams = { + selector1: string, + selector2: string, + force?: boolean, + noWaitAfter?: boolean, + timeout?: number, + trial?: boolean, +}; +export type FrameDragAndDropOptions = { + force?: boolean, + noWaitAfter?: boolean, + timeout?: number, + trial?: boolean, +}; +export type FrameDragAndDropResult = void; export type FrameDblclickParams = { selector: string, force?: boolean, diff --git a/src/protocol/protocol.yml b/src/protocol/protocol.yml index 812b97e8eee75..b63d2cf7f6d30 100644 --- a/src/protocol/protocol.yml +++ b/src/protocol/protocol.yml @@ -1177,6 +1177,15 @@ Frame: returns: value: string + dragAndDrop: + parameters: + selector1: string + selector2: string + force: boolean? + noWaitAfter: boolean? + timeout: number? + trial: boolean? + dblclick: parameters: selector: string diff --git a/src/protocol/validator.ts b/src/protocol/validator.ts index a4355a13f0eec..a9347c59db017 100644 --- a/src/protocol/validator.ts +++ b/src/protocol/validator.ts @@ -587,6 +587,14 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme { trial: tOptional(tBoolean), }); scheme.FrameContentParams = tOptional(tObject({})); + scheme.FrameDragAndDropParams = tObject({ + selector1: tString, + selector2: tString, + force: tOptional(tBoolean), + noWaitAfter: tOptional(tBoolean), + timeout: tOptional(tNumber), + trial: tOptional(tBoolean), + }); scheme.FrameDblclickParams = tObject({ selector: tString, force: tOptional(tBoolean), diff --git a/src/server/frames.ts b/src/server/frames.ts index 3c514828bfef4..1815ec8460a08 100644 --- a/src/server/frames.ts +++ b/src/server/frames.ts @@ -982,6 +982,24 @@ export class Frame extends SdkObject { }, this._page._timeoutSettings.timeout(options)); } + async dragAndDrop(metadata: CallMetadata, selector1: string, selector2: string, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) { + const controller = new ProgressController(metadata, this); + await controller.run(async progress => { + await dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector1, async handle => { + return handle._retryPointerAction(progress, 'move and down', false, async point => { + await this._page.mouse.move(point.x, point.y); + await this._page.mouse.down(); + }, options); + })); + await dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector2, async handle => { + return handle._retryPointerAction(progress, 'move and up', false, async point => { + await this._page.mouse.move(point.x, point.y); + await this._page.mouse.up(); + }, options); + })); + }, this._page._timeoutSettings.timeout(options)); + } + async tap(metadata: CallMetadata, selector: string, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions) { const controller = new ProgressController(metadata, this); return controller.run(async progress => { diff --git a/tests/page/page-drag.spec.ts b/tests/page/page-drag.spec.ts index d8b70bd5363dc..0f13e3d4a255d 100644 --- a/tests/page/page-drag.spec.ts +++ b/tests/page/page-drag.spec.ts @@ -228,6 +228,12 @@ 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 work with the helper method', async ({page, server}) => { + await page.goto(server.PREFIX + '/drag-n-drop.html'); + await page.dragAndDrop('#source', '#target'); + expect(await page.$eval('#target', target => target.contains(document.querySelector('#source')))).toBe(true); // could not find source in target + }); + 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 b560ed5ee4666..3c817c8d86724 100644 --- a/types/types.d.ts +++ b/types/types.d.ts @@ -1396,6 +1396,39 @@ export interface Page { timeout?: number; }): Promise; + /** + * @param selector1 + * @param selector2 + * @param options + */ + dragAndDrop(selector1: string, selector2: string, options?: { + /** + * Whether to bypass the [actionability](https://playwright.dev/docs/actionability) checks. Defaults to `false`. + */ + force?: boolean; + + /** + * Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can + * opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to + * inaccessible pages. Defaults to `false`. + */ + noWaitAfter?: boolean; + + /** + * Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by + * using the + * [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout) + * or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout) methods. + */ + timeout?: number; + + /** + * When set, this method only performs the [actionability](https://playwright.dev/docs/actionability) checks and skips the action. Defaults to + * `false`. Useful to wait until the element is ready for the action without performing it. + */ + trial?: boolean; + }): Promise; + /** * This method changes the `CSS media type` through the `media` argument, and/or the `'prefers-colors-scheme'` media * feature, using the `colorScheme` argument. @@ -3623,6 +3656,39 @@ export interface Frame { timeout?: number; }): Promise; + /** + * @param selector1 + * @param selector2 + * @param options + */ + dragAndDrop(selector1: string, selector2: string, options?: { + /** + * Whether to bypass the [actionability](https://playwright.dev/docs/actionability) checks. Defaults to `false`. + */ + force?: boolean; + + /** + * Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can + * opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to + * inaccessible pages. Defaults to `false`. + */ + noWaitAfter?: boolean; + + /** + * Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by + * using the + * [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout) + * or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout) methods. + */ + timeout?: number; + + /** + * When set, this method only performs the [actionability](https://playwright.dev/docs/actionability) checks and skips the action. Defaults to + * `false`. Useful to wait until the element is ready for the action without performing it. + */ + trial?: boolean; + }): Promise; + /** * This method waits for an element matching `selector`, waits for [actionability](https://playwright.dev/docs/actionability) checks, focuses the * element, fills it and triggers an `input` event after filling. Note that you can pass an empty string to clear the input From 2b201ff916c3822b8e2909c7f34ae5766e0d67a6 Mon Sep 17 00:00:00 2001 From: Joel Einbinder Date: Mon, 12 Jul 2021 17:15:22 -0500 Subject: [PATCH 2/3] pavel comments --- docs/src/api/class-frame.md | 2 +- docs/src/api/class-page.md | 2 +- src/server/frames.ts | 10 ++++++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/src/api/class-frame.md b/docs/src/api/class-frame.md index f3ddfdc804a4c..e2b184cac7d19 100644 --- a/docs/src/api/class-frame.md +++ b/docs/src/api/class-frame.md @@ -386,7 +386,7 @@ Optional event-specific initialization properties. ### option: Frame.dragAndDrop.timeout = %%-input-timeout-%% -### option: Frame.dragAndDrop.timeout = %%-input-trial-%% +### option: Frame.dragAndDrop.trial = %%-input-trial-%% ## async method: Frame.evalOnSelector * langs: diff --git a/docs/src/api/class-page.md b/docs/src/api/class-page.md index 45e6667b29b85..b03db54e78d4b 100644 --- a/docs/src/api/class-page.md +++ b/docs/src/api/class-page.md @@ -823,7 +823,7 @@ Optional event-specific initialization properties. ### option: Page.dragAndDrop.timeout = %%-input-timeout-%% -### option: Page.dragAndDrop.timeout = %%-input-trial-%% +### option: Page.dragAndDrop.trial = %%-input-trial-%% ## async method: Page.emulateMedia diff --git a/src/server/frames.ts b/src/server/frames.ts index 1815ec8460a08..a24f38d5e5441 100644 --- a/src/server/frames.ts +++ b/src/server/frames.ts @@ -989,13 +989,19 @@ export class Frame extends SdkObject { return handle._retryPointerAction(progress, 'move and down', false, async point => { await this._page.mouse.move(point.x, point.y); await this._page.mouse.down(); - }, options); + }, { + ...options, + timeout: progress.timeUntilDeadline(), + }); })); await dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector2, async handle => { return handle._retryPointerAction(progress, 'move and up', false, async point => { await this._page.mouse.move(point.x, point.y); await this._page.mouse.up(); - }, options); + }, { + ...options, + timeout: progress.timeUntilDeadline(), + }); })); }, this._page._timeoutSettings.timeout(options)); } From 91bd41ebeb97f9c344c15acad1aded6787f7458a Mon Sep 17 00:00:00 2001 From: Joel Einbinder Date: Mon, 19 Jul 2021 12:18:46 -0500 Subject: [PATCH 3/3] source and target --- docs/src/api/class-frame.md | 8 +-- docs/src/api/class-page.md | 8 +-- docs/src/api/params.md | 12 +++- src/client/frame.ts | 4 +- src/client/page.ts | 4 +- src/dispatchers/frameDispatcher.ts | 2 +- src/protocol/channels.ts | 4 +- src/protocol/protocol.yml | 4 +- src/protocol/validator.ts | 4 +- src/server/frames.ts | 6 +- types/types.d.ts | 106 ++++++++++++++--------------- 11 files changed, 86 insertions(+), 76 deletions(-) diff --git a/docs/src/api/class-frame.md b/docs/src/api/class-frame.md index e2b184cac7d19..44f46bcb2a3ac 100644 --- a/docs/src/api/class-frame.md +++ b/docs/src/api/class-frame.md @@ -375,11 +375,11 @@ Optional event-specific initialization properties. ### option: Frame.dispatchEvent.timeout = %%-input-timeout-%% ## async method: Frame.dragAndDrop -### param: Frame.dragAndDrop.selector1 -- `selector1` <[string]> -### param: Frame.dragAndDrop.selector2 -- `selector2` <[string]> +### param: Frame.dragAndDrop.source = %%-input-source-%% + +### param: Frame.dragAndDrop.target = %%-input-target-%% + ### option: Frame.dragAndDrop.force = %%-input-force-%% ### option: Frame.dragAndDrop.noWaitAfter = %%-input-no-wait-after-%% diff --git a/docs/src/api/class-page.md b/docs/src/api/class-page.md index b03db54e78d4b..a993da69ca930 100644 --- a/docs/src/api/class-page.md +++ b/docs/src/api/class-page.md @@ -812,11 +812,11 @@ Optional event-specific initialization properties. ### option: Page.dispatchEvent.timeout = %%-input-timeout-%% ## async method: Page.dragAndDrop -### param: Page.dragAndDrop.selector1 -- `selector1` <[string]> -### param: Page.dragAndDrop.selector2 -- `selector2` <[string]> +### param: Page.dragAndDrop.source = %%-input-source-%% + +### param: Page.dragAndDrop.target = %%-input-target-%% + ### option: Page.dragAndDrop.force = %%-input-force-%% ### option: Page.dragAndDrop.noWaitAfter = %%-input-no-wait-after-%% diff --git a/docs/src/api/params.md b/docs/src/api/params.md index 131efabe4692f..f7fd21e5b138f 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -44,7 +44,17 @@ Whether to bypass the [actionability](./actionability.md) checks. Defaults to `f ## input-selector - `selector` <[string]> -A selector to search for element. If there are multiple elements satisfying the selector, the first will be used. See +A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See +[working with selectors](./selectors.md) for more details. + +## input-source +- `source` <[string]> +A selector to search for an element to drag. If there are multiple elements satisfying the selector, the first will be used. See +[working with selectors](./selectors.md) for more details. + +## input-target +- `target` <[string]> +A selector to search for an element to drop onto. If there are multiple elements satisfying the selector, the first will be used. See [working with selectors](./selectors.md) for more details. ## input-position diff --git a/src/client/frame.ts b/src/client/frame.ts index 680d34f159766..ce722c7696704 100644 --- a/src/client/frame.ts +++ b/src/client/frame.ts @@ -298,9 +298,9 @@ export class Frame extends ChannelOwner { - return await channel.dragAndDrop({ selector1, selector2, ...options }); + return await channel.dragAndDrop({ source, target, ...options }); }); } diff --git a/src/client/page.ts b/src/client/page.ts index b8b6765d2a938..d49c294bac06b 100644 --- a/src/client/page.ts +++ b/src/client/page.ts @@ -505,8 +505,8 @@ export class Page extends ChannelOwner { - return await this._frame.dragAndDrop(metadata, params.selector1, params.selector2, params); + return await this._frame.dragAndDrop(metadata, params.source, params.target, params); } async tap(params: channels.FrameTapParams, metadata: CallMetadata): Promise { diff --git a/src/protocol/channels.ts b/src/protocol/channels.ts index 6448ba9de5393..0957519591d9d 100644 --- a/src/protocol/channels.ts +++ b/src/protocol/channels.ts @@ -1450,8 +1450,8 @@ export type FrameContentResult = { value: string, }; export type FrameDragAndDropParams = { - selector1: string, - selector2: string, + source: string, + target: string, force?: boolean, noWaitAfter?: boolean, timeout?: number, diff --git a/src/protocol/protocol.yml b/src/protocol/protocol.yml index b63d2cf7f6d30..06f0f3ca0e545 100644 --- a/src/protocol/protocol.yml +++ b/src/protocol/protocol.yml @@ -1179,8 +1179,8 @@ Frame: dragAndDrop: parameters: - selector1: string - selector2: string + source: string + target: string force: boolean? noWaitAfter: boolean? timeout: number? diff --git a/src/protocol/validator.ts b/src/protocol/validator.ts index a9347c59db017..a36c08a7252fe 100644 --- a/src/protocol/validator.ts +++ b/src/protocol/validator.ts @@ -588,8 +588,8 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme { }); scheme.FrameContentParams = tOptional(tObject({})); scheme.FrameDragAndDropParams = tObject({ - selector1: tString, - selector2: tString, + source: tString, + target: tString, force: tOptional(tBoolean), noWaitAfter: tOptional(tBoolean), timeout: tOptional(tNumber), diff --git a/src/server/frames.ts b/src/server/frames.ts index a24f38d5e5441..08502310a3379 100644 --- a/src/server/frames.ts +++ b/src/server/frames.ts @@ -982,10 +982,10 @@ export class Frame extends SdkObject { }, this._page._timeoutSettings.timeout(options)); } - async dragAndDrop(metadata: CallMetadata, selector1: string, selector2: string, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) { + async dragAndDrop(metadata: CallMetadata, source: string, target: string, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) { const controller = new ProgressController(metadata, this); await controller.run(async progress => { - await dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector1, async handle => { + await dom.assertDone(await this._retryWithProgressIfNotConnected(progress, source, async handle => { return handle._retryPointerAction(progress, 'move and down', false, async point => { await this._page.mouse.move(point.x, point.y); await this._page.mouse.down(); @@ -994,7 +994,7 @@ export class Frame extends SdkObject { timeout: progress.timeUntilDeadline(), }); })); - await dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector2, async handle => { + await dom.assertDone(await this._retryWithProgressIfNotConnected(progress, target, async handle => { return handle._retryPointerAction(progress, 'move and up', false, async point => { await this._page.mouse.move(point.x, point.y); await this._page.mouse.up(); diff --git a/types/types.d.ts b/types/types.d.ts index 3c817c8d86724..428ca1c6b24f8 100644 --- a/types/types.d.ts +++ b/types/types.d.ts @@ -1125,7 +1125,7 @@ export interface Page { * zero timeout disables this. * * Shortcut for main frame's [frame.check(selector[, options])](https://playwright.dev/docs/api/class-frame#frame-check). - * @param selector A selector to search for element. If there are multiple elements satisfying the selector, the first will be used. See [working with selectors](https://playwright.dev/docs/selectors) for more details. + * @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See [working with selectors](https://playwright.dev/docs/selectors) for more details. * @param options */ check(selector: string, options?: { @@ -1180,7 +1180,7 @@ export interface Page { * zero timeout disables this. * * Shortcut for main frame's [frame.click(selector[, options])](https://playwright.dev/docs/api/class-frame#frame-click). - * @param selector A selector to search for element. If there are multiple elements satisfying the selector, the first will be used. See [working with selectors](https://playwright.dev/docs/selectors) for more details. + * @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See [working with selectors](https://playwright.dev/docs/selectors) for more details. * @param options */ click(selector: string, options?: { @@ -1295,7 +1295,7 @@ export interface Page { * * Shortcut for main frame's * [frame.dblclick(selector[, options])](https://playwright.dev/docs/api/class-frame#frame-dblclick). - * @param selector A selector to search for element. If there are multiple elements satisfying the selector, the first will be used. See [working with selectors](https://playwright.dev/docs/selectors) for more details. + * @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See [working with selectors](https://playwright.dev/docs/selectors) for more details. * @param options */ dblclick(selector: string, options?: { @@ -1381,7 +1381,7 @@ export interface Page { * await page.dispatchEvent('#source', 'dragstart', { dataTransfer }); * ``` * - * @param selector A selector to search for element. If there are multiple elements satisfying the selector, the first will be used. See [working with selectors](https://playwright.dev/docs/selectors) for more details. + * @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See [working with selectors](https://playwright.dev/docs/selectors) for more details. * @param type DOM event type: `"click"`, `"dragstart"`, etc. * @param eventInit Optional event-specific initialization properties. * @param options @@ -1397,11 +1397,11 @@ export interface Page { }): Promise; /** - * @param selector1 - * @param selector2 + * @param source + * @param target * @param options */ - dragAndDrop(selector1: string, selector2: string, options?: { + dragAndDrop(source: string, target: string, options?: { /** * Whether to bypass the [actionability](https://playwright.dev/docs/actionability) checks. Defaults to `false`. */ @@ -1541,7 +1541,7 @@ export interface Page { * * Shortcut for main frame's * [frame.fill(selector, value[, options])](https://playwright.dev/docs/api/class-frame#frame-fill). - * @param selector A selector to search for element. If there are multiple elements satisfying the selector, the first will be used. See [working with selectors](https://playwright.dev/docs/selectors) for more details. + * @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See [working with selectors](https://playwright.dev/docs/selectors) for more details. * @param value Value to fill for the ``, `