From 9a7bcf70d447d4f1a50c97b73e73780a0611dc52 Mon Sep 17 00:00:00 2001 From: Dan Park Date: Fri, 23 Apr 2021 19:01:49 -0700 Subject: [PATCH 01/10] Add drag-and-drop support. This commit adds drag-and-drop support, leveraging new additions to the CDP Input domain (Input.setInterceptDrags, Input.dispatchDragEvent, and Input.dragIntercepted). --- src/common/Input.ts | 51 ++++++++++++++++++++++++++++ src/common/JSHandle.ts | 20 +++++++++++ test/assets/input/drag-and-drop.html | 37 ++++++++++++++++++++ test/drag-and-drop.spec.ts | 47 +++++++++++++++++++++++++ 4 files changed, 155 insertions(+) create mode 100644 test/assets/input/drag-and-drop.html create mode 100644 test/drag-and-drop.spec.ts diff --git a/src/common/Input.ts b/src/common/Input.ts index c568856a7d647..9c94e0106600f 100644 --- a/src/common/Input.ts +++ b/src/common/Input.ts @@ -17,6 +17,7 @@ import { assert } from './assert.js'; import { CDPSession } from './Connection.js'; import { keyDefinitions, KeyDefinition, KeyInput } from './USKeyboardLayout.js'; +import { Protocol } from 'devtools-protocol'; type KeyDescription = Required< Pick @@ -296,6 +297,14 @@ export interface MouseWheelOptions { deltaY?: number; } +/** + * @public + */ +export interface Point { + x: number; + y: number; +} + /** * The Mouse class operates in main-frame CSS pixels * relative to the top-left corner of the viewport. @@ -485,6 +494,48 @@ export class Mouse { pointerType: 'mouse', }); } + + /** + * Dispatches a `drag` event. + * @param client - CDP session + * @param source - starting point for drag + * @param destination - point to drag to + * ``` + */ + async drag(client: CDPSession, source: Point, destination: Point): Promise { + await client.send('Input.setInterceptDrags', { enabled: true }); + const promise = new Promise((resolve, reject) => { + client.once('Input.dragIntercepted', event => { + client.send('Input.setInterceptDrags', { enabled: false }) + .then(() => client.detach()) + .then(() => resolve(event)) + .catch(error => reject(error)); + }); + }); + await this.move(source.x, source.y); + await this.down(); + await this.move(destination.x, destination.y); + return promise; + } + + /** + * Perfoms a dragenter, dragover, and drop. + * @param destination - point to drop on + * @param data - drag data containing items and operations mask + * ``` + */ + async drop(destination: Point, data: Protocol.Input.DragData) { + const args = { + x: destination.x, + y: destination.y, + data + } + await this.move(destination.x, destination.y); + await this._client.send('Input.dispatchDragEvent', { ...args, type: 'dragEnter' }); + await this._client.send('Input.dispatchDragEvent', { ...args, type: 'dragOver' }); + await this._client.send('Input.dispatchDragEvent', { ...args, type: 'drop' }); + await this.up(); + } } /** diff --git a/src/common/JSHandle.ts b/src/common/JSHandle.ts index db83587be5aa9..3805f56f34e0c 100644 --- a/src/common/JSHandle.ts +++ b/src/common/JSHandle.ts @@ -32,6 +32,7 @@ import { UnwrapPromiseLike, } from './EvalTypes.js'; import { isNode } from '../environment.js'; +import { Point } from './Input.js'; /** * @public */ @@ -497,6 +498,25 @@ export class ElementHandle< await this._page.mouse.click(x, y, options); } + /** + * This method creates and captures a drag event from the element. + */ + async drag(destination:Point): Promise { + await this._scrollIntoViewIfNeeded(); + const source = await this._clickablePoint(); + const client = await this._page.target().createCDPSession(); + return await this._page.mouse.drag(client, source, destination); + } + + /** + * This method triggers a dragenter, dragover, and drop on the element. + */ + async drop(data:Protocol.Input.DragData = { items: [], dragOperationsMask: 1 }): Promise { + await this._scrollIntoViewIfNeeded(); + const destination = await this._clickablePoint(); + await this._page.mouse.drop(destination, data); + } + /** * Triggers a `change` and `input` event once all the provided options have been * selected. If there's no `