From bb9a843d1c5ebed651a586ef260a316248a89730 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Wed, 11 Jun 2025 18:18:23 +0100 Subject: [PATCH] chore: remove custom drag&drop implementation for chromium --- .../src/server/chromium/crInput.ts | 19 +- .../src/server/chromium/crPage.ts | 2 +- tests/page/page-drag.spec.ts | 744 +++++++++--------- 3 files changed, 395 insertions(+), 370 deletions(-) diff --git a/packages/playwright-core/src/server/chromium/crInput.ts b/packages/playwright-core/src/server/chromium/crInput.ts index beb030c9bff9a..fe1062b085855 100644 --- a/packages/playwright-core/src/server/chromium/crInput.ts +++ b/packages/playwright-core/src/server/chromium/crInput.ts @@ -31,7 +31,7 @@ export class RawKeyboardImpl implements input.RawKeyboard { constructor( private _client: CRSession, private _isMac: boolean, - private _dragManger: DragManager, + private _dragManager: DragManager | undefined, ) { } _commandsForCode(code: string, modifiers: Set) { @@ -55,8 +55,11 @@ export class RawKeyboardImpl implements input.RawKeyboard { async keydown(progress: Progress, modifiers: Set, keyName: string, description: input.KeyDescription, autoRepeat: boolean): Promise { const { code, key, location, text } = description; - if (code === 'Escape' && await progress.race(this._dragManger.cancelDrag())) - return; + if (code === 'Escape') { + if (this._dragManager && await progress.race(this._dragManager.cancelDrag())) + return; + await progress.race(this._client.send('Input.cancelDragging', {})); + } const commands = this._commandsForCode(code, modifiers); await progress.race(this._client.send('Input.dispatchKeyEvent', { type: text ? 'keyDown' : 'rawKeyDown', @@ -93,9 +96,9 @@ export class RawKeyboardImpl implements input.RawKeyboard { export class RawMouseImpl implements input.RawMouse { private _client: CRSession; private _page: CRPage; - private _dragManager: DragManager; + private _dragManager: DragManager | undefined; - constructor(page: CRPage, client: CRSession, dragManager: DragManager) { + constructor(page: CRPage, client: CRSession, dragManager: DragManager | undefined) { this._page = page; this._client = client; this._dragManager = dragManager; @@ -113,7 +116,7 @@ export class RawMouseImpl implements input.RawMouse { force: buttons.size > 0 ? 0.5 : 0, })); }; - if (forClick) { + if (!this._dragManager || forClick) { // Avoid extra protocol calls related to drag and drop, because click relies on // move-down-up protocol commands being sent synchronously. await actualMove(); @@ -123,7 +126,7 @@ export class RawMouseImpl implements input.RawMouse { } async down(progress: Progress, x: number, y: number, button: types.MouseButton, buttons: Set, modifiers: Set, clickCount: number): Promise { - if (this._dragManager.isDragging()) + if (this._dragManager?.isDragging()) return; await progress.race(this._client.send('Input.dispatchMouseEvent', { type: 'mousePressed', @@ -138,7 +141,7 @@ export class RawMouseImpl implements input.RawMouse { } async up(progress: Progress, x: number, y: number, button: types.MouseButton, buttons: Set, modifiers: Set, clickCount: number): Promise { - if (this._dragManager.isDragging()) { + if (this._dragManager?.isDragging()) { await this._dragManager.drop(progress, x, y, modifiers); return; } diff --git a/packages/playwright-core/src/server/chromium/crPage.ts b/packages/playwright-core/src/server/chromium/crPage.ts index 0edd05aa47d9e..d06ed2072ddd5 100644 --- a/packages/playwright-core/src/server/chromium/crPage.ts +++ b/packages/playwright-core/src/server/chromium/crPage.ts @@ -86,7 +86,7 @@ export class CRPage implements PageDelegate { this._targetId = targetId; this._opener = opener; this._isBackgroundPage = bits.isBackgroundPage; - const dragManager = new DragManager(this); + const dragManager = process.env.PLAYWRIGHT_LEGACY_DRAG_AND_DROP ? new DragManager(this) : undefined; this.rawKeyboard = new RawKeyboardImpl(client, browserContext._browser._platform() === 'mac', dragManager); this.rawMouse = new RawMouseImpl(this, client, dragManager); this.rawTouchscreen = new RawTouchscreenImpl(client); diff --git a/tests/page/page-drag.spec.ts b/tests/page/page-drag.spec.ts index e7e3d178378f3..170060dd677ad 100644 --- a/tests/page/page-drag.spec.ts +++ b/tests/page/page-drag.spec.ts @@ -22,357 +22,440 @@ it.skip(({ browserName, browserMajorVersion }) => browserName === 'chromium' && it.fixme(({ headless, isLinux }) => isLinux && !headless, 'Stray mouse events on Linux headed mess up the tests.'); it.fixme(({ headless, isWindows, browserName }) => isWindows && !headless && browserName === 'webkit', 'WebKit win also send stray mouse events.'); -it.describe('Drag and drop', () => { - it.skip(({ isAndroid }) => isAndroid, 'No drag&drop on Android.'); - - it('should work @smoke', async ({ page, server }) => { - await page.goto(server.PREFIX + '/drag-n-drop.html'); - await page.hover('#source'); - await page.mouse.down(); - await page.hover('#target'); - await page.mouse.up(); - expect(await page.$eval('#target', target => target.contains(document.querySelector('#source')))).toBe(true); // could not find source in target - }); - - it('should send the right events', async ({ server, page, browserName }) => { - await page.goto(server.PREFIX + '/drag-n-drop.html'); - const events = await trackEvents(await page.$('body')); - await page.hover('#source'); - await page.mouse.down(); - await page.hover('#target'); - await page.mouse.up(); - expect(await events.jsonValue()).toEqual([ - 'mousemove at 120;86', - 'mousedown at 120;86', - browserName === 'firefox' ? 'dragstart at 120;86' : 'mousemove at 240;350', - browserName === 'firefox' ? 'mousemove at 240;350' : 'dragstart at 120;86', - 'dragenter at 240;350', - 'dragover at 240;350', - 'drop at 240;350', - 'dragend', - ]); - }); - - it('should not send dragover on the first mousemove', async ({ server, page, browserName }) => { - it.fixme(browserName !== 'chromium'); - - await page.goto(server.PREFIX + '/drag-n-drop.html'); - const events = await trackEvents(await page.$('body')); - await page.hover('#source'); - await page.mouse.down(); - await page.hover('#target'); - expect(await events.jsonValue()).toEqual([ - 'mousemove at 120;86', - 'mousedown at 120;86', - browserName === 'firefox' ? 'dragstart at 120;86' : 'mousemove at 240;350', - browserName === 'firefox' ? 'mousemove at 240;350' : 'dragstart at 120;86', - 'dragenter at 240;350', - ]); - }); +for (const legacy of [true, false]) { + it.describe('Drag and drop ' + (legacy ? 'legacy' : ''), () => { + it.skip(({ isAndroid }) => isAndroid, 'No drag&drop on Android.'); + it.skip(({ mode }) => legacy && mode !== 'default', 'No env variables in out-of-process modes.'); + + it.beforeAll(() => { + if (legacy) + process.env.PLAYWRIGHT_LEGACY_DRAG_AND_DROP = '1'; + }); + it.afterAll(() => { + if (legacy) + delete process.env.PLAYWRIGHT_LEGACY_DRAG_AND_DROP; + }); - it('should work inside iframe', async ({ page, server, browserName, isElectron, isWindows }) => { - it.fixme(isElectron && isWindows, 'Fails on the bots'); - await page.goto(server.EMPTY_PAGE); - const frame = await attachFrame(page, 'myframe', server.PREFIX + '/drag-n-drop.html'); - await page.$eval('iframe', iframe => { - iframe.style.width = '500px'; - iframe.style.height = '600px'; - iframe.style.marginLeft = '80px'; - iframe.style.marginTop = '60px'; + it('should work @smoke', async ({ page, server }) => { + await page.goto(server.PREFIX + '/drag-n-drop.html'); + await page.hover('#source'); + await page.mouse.down(); + await page.hover('#target'); + await page.mouse.up(); + expect(await page.$eval('#target', target => target.contains(document.querySelector('#source')))).toBe(true); // could not find source in target }); - const pageEvents = await trackEvents(await page.$('body')); - const frameEvents = await trackEvents(await frame.$('body')); - await frame.hover('#source'); - await page.mouse.down(); - await frame.hover('#target'); - await page.mouse.up(); - expect(await frame.$eval('#target', target => target.contains(document.querySelector('#source')))).toBe(true); // could not find source in target - expect(await frameEvents.jsonValue()).toEqual([ - 'mousemove at 120;86', - 'mousedown at 120;86', - browserName === 'firefox' ? 'dragstart at 120;86' : 'mousemove at 240;350', - browserName === 'firefox' ? 'mousemove at 240;350' : 'dragstart at 120;86', - 'dragenter at 240;350', - 'dragover at 240;350', - 'drop at 240;350', - 'dragend', - ]); - expect(await pageEvents.jsonValue()).toEqual([]); - }); - it('should cancel on escape', async ({ server, page, browserName }) => { - await page.goto(server.PREFIX + '/drag-n-drop.html'); - const events = await trackEvents(await page.$('body')); - await page.hover('#source'); - await page.mouse.down(); - await page.hover('#target'); - await page.keyboard.press('Escape'); - await page.mouse.up(); - expect(await page.$eval('#target', target => target.contains(document.querySelector('#source')))).toBe(false); // found source in target - expect(await events.jsonValue()).toEqual([ - 'mousemove at 120;86', - 'mousedown at 120;86', - browserName === 'firefox' ? 'dragstart at 120;86' : 'mousemove at 240;350', - browserName === 'firefox' ? 'mousemove at 240;350' : 'dragstart at 120;86', - 'dragenter at 240;350', - browserName === 'chromium' ? null : 'dragover at 240;350', - 'dragend', - 'mouseup at 240;350', - ].filter(Boolean)); - }); + it('should send the right events', async ({ server, page, browserName }) => { + await page.goto(server.PREFIX + '/drag-n-drop.html'); + const events = await trackEvents(await page.$('body')); + await page.hover('#source'); + await page.mouse.down(); + await page.hover('#target'); + await page.mouse.up(); + expect(await events.jsonValue()).toEqual([ + 'mousemove at 120;86', + 'mousedown at 120;86', + browserName === 'firefox' ? 'dragstart at 120;86' : 'mousemove at 240;350', + browserName === 'firefox' ? 'mousemove at 240;350' : 'dragstart at 120;86', + 'dragenter at 240;350', + 'dragover at 240;350', + 'drop at 240;350', + 'dragend', + ]); + }); - it.describe('iframe', () => { - it.fixme(true, 'implement dragging with iframes'); + it('should not send dragover on the first mousemove', async ({ server, page, browserName }) => { + it.fixme(!legacy, 'at least it is consistent between browsers!'); + it.fixme(legacy && browserName !== 'chromium'); - it('should drag into an iframe', async ({ server, page, browserName }) => { await page.goto(server.PREFIX + '/drag-n-drop.html'); - const frame = await attachFrame(page, 'oopif', server.PREFIX + '/drag-n-drop.html'); + const events = await trackEvents(await page.$('body')); + await page.hover('#source'); + await page.mouse.down(); + await page.hover('#target'); + expect(await events.jsonValue()).toEqual([ + 'mousemove at 120;86', + 'mousedown at 120;86', + browserName === 'firefox' ? 'dragstart at 120;86' : 'mousemove at 240;350', + browserName === 'firefox' ? 'mousemove at 240;350' : 'dragstart at 120;86', + 'dragenter at 240;350', + ]); + }); + + it('should work inside iframe', async ({ page, server, browserName, isElectron, isWindows }) => { + it.fixme(isElectron && isWindows, 'Fails on the bots'); + await page.goto(server.EMPTY_PAGE); + const frame = await attachFrame(page, 'myframe', server.PREFIX + '/drag-n-drop.html'); await page.$eval('iframe', iframe => { iframe.style.width = '500px'; iframe.style.height = '600px'; - iframe.style.marginLeft = '500px'; + iframe.style.marginLeft = '80px'; iframe.style.marginTop = '60px'; }); - await page.waitForTimeout(5000); const pageEvents = await trackEvents(await page.$('body')); const frameEvents = await trackEvents(await frame.$('body')); - await page.hover('#source'); + await frame.hover('#source'); await page.mouse.down(); await frame.hover('#target'); await page.mouse.up(); expect(await frame.$eval('#target', target => target.contains(document.querySelector('#source')))).toBe(true); // could not find source in target - expect(await pageEvents.jsonValue()).toEqual([ - 'mousemove', - 'mousedown', - browserName === 'firefox' ? 'dragstart' : 'mousemove', - browserName === 'firefox' ? 'mousemove' : 'dragstart', - ]); expect(await frameEvents.jsonValue()).toEqual([ - 'dragenter', - 'dragover', - 'drop', + 'mousemove at 120;86', + 'mousedown at 120;86', + browserName === 'firefox' ? 'dragstart at 120;86' : 'mousemove at 240;350', + browserName === 'firefox' ? 'mousemove at 240;350' : 'dragstart at 120;86', + 'dragenter at 240;350', + 'dragover at 240;350', + 'drop at 240;350', + 'dragend', ]); + expect(await pageEvents.jsonValue()).toEqual([]); }); - it('should drag out of an iframe', async ({ server, page }) => { + it('should cancel on escape', async ({ server, page, browserName }) => { await page.goto(server.PREFIX + '/drag-n-drop.html'); - const frame = await attachFrame(page, 'oopif', server.PREFIX + '/drag-n-drop.html'); - const pageEvents = await trackEvents(await page.$('body')); - const frameEvents = await trackEvents(await frame.$('body')); - await frame.hover('#source'); + const events = await trackEvents(await page.$('body')); + await page.hover('#source'); await page.mouse.down(); await page.hover('#target'); + await page.keyboard.press('Escape'); await page.mouse.up(); - expect(await page.$eval('#target', target => target.contains(document.querySelector('#source')))).toBe(true); // could not find source in target - expect(await frameEvents.jsonValue()).toEqual([ - 'mousemove', - 'mousedown', - 'dragstart', + expect(await page.$eval('#target', target => target.contains(document.querySelector('#source')))).toBe(false); // found source in target + expect(await events.jsonValue()).toEqual([ + 'mousemove at 120;86', + 'mousedown at 120;86', + browserName === 'firefox' ? 'dragstart at 120;86' : 'mousemove at 240;350', + browserName === 'firefox' ? 'mousemove at 240;350' : 'dragstart at 120;86', + 'dragenter at 240;350', + legacy && browserName === 'chromium' ? null : 'dragover at 240;350', + !legacy && browserName === 'chromium' ? 'dragleave at 240;350' : null, 'dragend', - ]); - expect(await pageEvents.jsonValue()).toEqual([ - 'dragenter', - 'dragover', - 'drop', - ]); + 'mouseup at 240;350', + ].filter(Boolean)); }); - }); - - it('should respect the drop effect', async ({ page, browserName, isLinux, isMac, headless, trace }) => { - it.fixme(browserName === 'webkit' && !isLinux, 'WebKit doesn\'t handle the drop effect correctly outside of linux.'); - it.fixme(browserName === 'webkit' && isLinux && !headless, 'https://github.com/microsoft/playwright/issues/21646'); - it.fixme(browserName === 'chromium' && !isMac && !headless, 'https://github.com/microsoft/playwright/issues/21646'); - it.slow(trace === 'on'); - - expect(await testIfDropped('copy', 'copy')).toBe(true); - expect(await testIfDropped('copy', 'move')).toBe(false); - expect(await testIfDropped('all', 'link')).toBe(true); - expect(await testIfDropped('all', 'none')).toBe(false); - - expect(await testIfDropped('copyMove', 'copy')).toBe(true); - expect(await testIfDropped('copyLink', 'copy')).toBe(true); - expect(await testIfDropped('linkMove', 'copy')).toBe(false); - expect(await testIfDropped('copyMove', 'link')).toBe(false); - expect(await testIfDropped('copyLink', 'link')).toBe(true); - expect(await testIfDropped('linkMove', 'link')).toBe(true); + it.describe('iframe', () => { + it.fixme(({ browserName }) => legacy || browserName !== 'chromium', 'iframes dragging is not implemented'); - expect(await testIfDropped('copyMove', 'move')).toBe(true); - expect(await testIfDropped('copyLink', 'move')).toBe(false); - expect(await testIfDropped('linkMove', 'move')).toBe(true); + it('should drag into an iframe', async ({ server, page, isElectron }) => { + it.fixme(isElectron); - expect(await testIfDropped('uninitialized', 'copy')).toBe(true); - - async function testIfDropped(effectAllowed: string, dropEffect: string) { - await page.setContent(` -
drag target
- this is the drop target - `); - await page.evaluate(({ effectAllowed, dropEffect }) => { - window['dropped'] = false; + await page.goto(server.PREFIX + '/drag-n-drop.html'); + const frame = await attachFrame(page, 'oopif', server.PREFIX + '/drag-n-drop.html'); + await page.$eval('iframe', iframe => { + iframe.style.width = '500px'; + iframe.style.height = '600px'; + iframe.style.marginLeft = '500px'; + iframe.style.marginTop = '60px'; + }); + const pageEvents = await trackEvents(await page.$('body')); + const frameEvents = await trackEvents(await frame.$('body')); + await page.hover('#source'); + await page.mouse.down(); + await frame.hover('#target'); + await page.mouse.up(); + expect(await frame.$eval('#target', target => target.contains(document.querySelector('#source')))).toBe(true); // could not find source in target + expect(await pageEvents.jsonValue()).toEqual([ + 'mousemove at 120;86', + 'mousedown at 120;86', + 'mousemove at 742;412', + 'dragstart at 120;86', + 'dragend', + ]); + expect(await frameEvents.jsonValue()).toEqual([ + 'dragenter at 240;350', + 'dragover at 240;350', + 'drop at 240;350', + ]); + }); - document.querySelector('div').addEventListener('dragstart', event => { - event.dataTransfer.effectAllowed = effectAllowed as any; - event.dataTransfer.setData('text/plain', 'drag data'); + it('should drag out of an iframe', async ({ server, page }) => { + await page.goto(server.PREFIX + '/drag-n-drop.html'); + const frame = await attachFrame(page, 'oopif', server.PREFIX + '/drag-n-drop.html'); + await page.$eval('iframe', iframe => { + iframe.style.width = '500px'; + iframe.style.height = '600px'; + iframe.style.marginLeft = '500px'; + iframe.style.marginTop = '60px'; }); + const pageEvents = await trackEvents(await page.$('body')); + const frameEvents = await trackEvents(await frame.$('body')); + await frame.hover('#source'); + await page.mouse.down(); + await page.hover('#target'); + await page.mouse.up(); + expect(await page.$eval('#target', target => target.contains(document.querySelector('#source')))).toBe(true); // could not find source in target + expect(await frameEvents.jsonValue()).toEqual([ + 'mousemove at 120;86', + 'mousedown at 120;86', + 'dragstart at 120;86', + 'dragend', + ]); + expect(await pageEvents.jsonValue()).toEqual([ + 'dragenter at 240;350', + 'dragover at 240;350', + 'drop at 240;350', + ]); + }); + }); - const dropTarget: HTMLElement = document.querySelector('drop-target'); - dropTarget.addEventListener('dragover', event => { - event.dataTransfer.dropEffect = dropEffect as any; + it('should respect the drop effect', async ({ page, browserName, isLinux, isMac, headless, trace }) => { + it.fixme(browserName === 'webkit' && !isLinux, 'WebKit doesn\'t handle the drop effect correctly outside of linux.'); + it.fixme(browserName === 'webkit' && isLinux && !headless, 'https://github.com/microsoft/playwright/issues/21646'); + it.fixme(browserName === 'chromium' && !isMac && !headless, 'https://github.com/microsoft/playwright/issues/21646'); + it.slow(trace === 'on'); + + expect(await testIfDropped('copy', 'copy')).toBe(true); + expect(await testIfDropped('copy', 'move')).toBe(false); + expect(await testIfDropped('all', 'link')).toBe(true); + expect(await testIfDropped('all', 'none')).toBe(false); + + expect(await testIfDropped('copyMove', 'copy')).toBe(true); + expect(await testIfDropped('copyLink', 'copy')).toBe(true); + expect(await testIfDropped('linkMove', 'copy')).toBe(false); + + expect(await testIfDropped('copyMove', 'link')).toBe(false); + expect(await testIfDropped('copyLink', 'link')).toBe(true); + expect(await testIfDropped('linkMove', 'link')).toBe(true); + + expect(await testIfDropped('copyMove', 'move')).toBe(true); + expect(await testIfDropped('copyLink', 'move')).toBe(false); + expect(await testIfDropped('linkMove', 'move')).toBe(true); + + expect(await testIfDropped('uninitialized', 'copy')).toBe(true); + + async function testIfDropped(effectAllowed: string, dropEffect: string) { + await page.setContent(` +
drag target
+ this is the drop target + `); + await page.evaluate(({ effectAllowed, dropEffect }) => { + window['dropped'] = false; + + document.querySelector('div').addEventListener('dragstart', event => { + event.dataTransfer.effectAllowed = effectAllowed as any; + event.dataTransfer.setData('text/plain', 'drag data'); + }); + + const dropTarget: HTMLElement = document.querySelector('drop-target'); + dropTarget.addEventListener('dragover', event => { + event.dataTransfer.dropEffect = dropEffect as any; + event.preventDefault(); + }); + dropTarget.addEventListener('drop', event => { + window['dropped'] = true; + }); + }, { effectAllowed, dropEffect }); + await page.hover('div'); + await page.mouse.down(); + await page.hover('drop-target'); + await page.mouse.up(); + return await page.evaluate('dropped'); + } + }); + it('should work if the drag is canceled', async ({ page, server }) => { + await page.goto(server.PREFIX + '/drag-n-drop.html'); + await page.evaluate(() => { + document.body.addEventListener('dragstart', event => { event.preventDefault(); - }); - dropTarget.addEventListener('drop', event => { - window['dropped'] = true; - }); - }, { effectAllowed, dropEffect }); - await page.hover('div'); + }, false); + }); + await page.hover('#source'); await page.mouse.down(); - await page.hover('drop-target'); + await page.hover('#target'); await page.mouse.up(); - return await page.evaluate('dropped'); - } - }); - it('should work if the drag is canceled', async ({ page, server }) => { - await page.goto(server.PREFIX + '/drag-n-drop.html'); - await page.evaluate(() => { - document.body.addEventListener('dragstart', event => { - event.preventDefault(); - }, false); + expect(await page.$eval('#target', target => target.contains(document.querySelector('#source')))).toBe(false); }); - await page.hover('#source'); - await page.mouse.down(); - await page.hover('#target'); - await page.mouse.up(); - expect(await page.$eval('#target', target => target.contains(document.querySelector('#source')))).toBe(false); - }); - it('should work if the drag event is captured but not canceled', async ({ page, server }) => { - await page.goto(server.PREFIX + '/drag-n-drop.html'); - await page.evaluate(() => { - document.body.addEventListener('dragstart', event => { - event.stopImmediatePropagation(); - }, false); + it('should work if the drag event is captured but not canceled', async ({ page, server }) => { + await page.goto(server.PREFIX + '/drag-n-drop.html'); + await page.evaluate(() => { + document.body.addEventListener('dragstart', event => { + event.stopImmediatePropagation(); + }, false); + }); + await page.hover('#source'); + await page.mouse.down(); + await page.hover('#target'); + await page.mouse.up(); + expect(await page.$eval('#target', target => target.contains(document.querySelector('#source')))).toBe(true); }); - await page.hover('#source'); - await page.mouse.down(); - await page.hover('#target'); - await page.mouse.up(); - expect(await page.$eval('#target', target => target.contains(document.querySelector('#source')))).toBe(true); - }); - it('should be able to drag the mouse in a frame', async ({ page, server }) => { - await page.goto(server.PREFIX + '/frames/one-frame.html'); - const eventsHandle = await trackEvents(await page.frames()[1].$('html')); - await page.mouse.move(30, 30); - await page.mouse.down(); - await page.mouse.move(60, 60); - await page.mouse.up(); - expect(await eventsHandle.jsonValue()).toEqual(['mousemove at 20;20', 'mousedown at 20;20', 'mousemove at 50;50', 'mouseup at 50;50']); - }); + it('should be able to drag the mouse in a frame', async ({ page, server }) => { + await page.goto(server.PREFIX + '/frames/one-frame.html'); + const eventsHandle = await trackEvents(await page.frames()[1].$('html')); + await page.mouse.move(30, 30); + await page.mouse.down(); + await page.mouse.move(60, 60); + await page.mouse.up(); + expect(await eventsHandle.jsonValue()).toEqual(['mousemove at 20;20', 'mousedown at 20;20', 'mousemove at 50;50', 'mouseup at 50;50']); + }); - it('should work if a frame is stalled', async ({ page, server, toImpl }) => { - await page.goto(server.PREFIX + '/drag-n-drop.html'); - let madeRequest; - const routePromise = new Promise(x => madeRequest = x); - await page.route('**/empty.html', async (route, request) => { - madeRequest(route); + it('should work if a frame is stalled', async ({ page, server, toImpl }) => { + await page.goto(server.PREFIX + '/drag-n-drop.html'); + let madeRequest; + const routePromise = new Promise(x => madeRequest = x); + await page.route('**/empty.html', async (route, request) => { + madeRequest(route); + }); + attachFrame(page, 'frame', server.EMPTY_PAGE).catch(() => {}); + const route = await routePromise; + await page.hover('#source'); + await page.mouse.down(); + await page.hover('#target'); + await page.mouse.up(); + await route.abort(); + expect(await page.$eval('#target', target => target.contains(document.querySelector('#source')))).toBe(true); // could not find source in target }); - attachFrame(page, 'frame', server.EMPTY_PAGE).catch(() => {}); - const route = await routePromise; - await page.hover('#source'); - await page.mouse.down(); - await page.hover('#target'); - await page.mouse.up(); - await route.abort(); - 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 - }); + 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 + }); - 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, + 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, + 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 }, }); - return events; + expect(await eventsHandle.jsonValue()).toEqual([ + { type: 'mousedown', x: 34, y: 7 }, + { type: 'mouseup', x: 10, y: 20 }, + ]); }); - await page.dragAndDrop('#red', '#blue', { - sourcePosition: { x: 34, y: 7 }, - targetPosition: { x: 10, y: 20 }, + + it('should work with locators', async ({ page, server }) => { + await page.goto(server.PREFIX + '/drag-n-drop.html'); + await page.locator('#source').dragTo(page.locator('#target')); + expect(await page.$eval('#target', target => target.contains(document.querySelector('#source')))).toBe(true); // could not find source in target }); - expect(await eventsHandle.jsonValue()).toEqual([ - { type: 'mousedown', x: 34, y: 7 }, - { type: 'mouseup', x: 10, y: 20 }, - ]); - }); - it('should work with locators', async ({ page, server }) => { - await page.goto(server.PREFIX + '/drag-n-drop.html'); - await page.locator('#source').dragTo(page.locator('#target')); - expect(await page.$eval('#target', target => target.contains(document.querySelector('#source')))).toBe(true); // could not find source in target - }); -}); - -it('should work if not doing a drag', async ({ page, isLinux, headless }) => { - const eventsHandle = await trackEvents(await page.$('html')); - await page.mouse.move(50, 50); - await page.mouse.down(); - await page.mouse.move(100, 100); - await page.mouse.up(); - expect(await eventsHandle.jsonValue()).toEqual(['mousemove at 50;50', 'mousedown at 50;50', 'mousemove at 100;100', 'mouseup at 100;100']); -}); - -it('should report event.buttons', async ({ page, browserName }) => { - const logsHandle = await page.evaluateHandle(async () => { - const div = document.createElement('div'); - document.body.appendChild(div); - div.style.width = '200px'; - div.style.height = '200px'; - div.style.backgroundColor = 'blue'; - div.addEventListener('mousedown', onEvent); - div.addEventListener('mousemove', onEvent, { passive: false }); - div.addEventListener('mouseup', onEvent); - const logs = []; - function onEvent(event) { - logs.push({ type: event.type, buttons: event.buttons }); - } - await new Promise(window.builtins.requestAnimationFrame); - return logs; + it('should work if not doing a drag', async ({ page, isLinux, headless }) => { + const eventsHandle = await trackEvents(await page.$('html')); + await page.mouse.move(50, 50); + await page.mouse.down(); + await page.mouse.move(100, 100); + await page.mouse.up(); + expect(await eventsHandle.jsonValue()).toEqual(['mousemove at 50;50', 'mousedown at 50;50', 'mousemove at 100;100', 'mouseup at 100;100']); + }); + + it('should report event.buttons', async ({ page, browserName }) => { + const logsHandle = await page.evaluateHandle(async () => { + const div = document.createElement('div'); + document.body.appendChild(div); + div.style.width = '200px'; + div.style.height = '200px'; + div.style.backgroundColor = 'blue'; + div.addEventListener('mousedown', onEvent); + div.addEventListener('mousemove', onEvent, { passive: false }); + div.addEventListener('mouseup', onEvent); + const logs = []; + function onEvent(event) { + logs.push({ type: event.type, buttons: event.buttons }); + } + await new Promise(window.builtins.requestAnimationFrame); + return logs; + }); + await page.mouse.move(20, 20); + await page.mouse.down(); + await page.mouse.move(40, 40); + await page.mouse.up(); + const logs = await logsHandle.jsonValue(); + expect(logs).toEqual([ + { type: 'mousemove', buttons: 0 }, + { type: 'mousedown', buttons: 1 }, + { type: 'mousemove', buttons: 1 }, + { type: 'mouseup', buttons: 0 }, + ]); + }); + + it('should handle custom dataTransfer', async ({ page, browserName, isWindows }) => { + it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/18013' }); + it.fixme(browserName === 'webkit' && isWindows); + await page.setContent(``); + + const resultPromise = page.evaluate(() => + new Promise(resolve => { + document.addEventListener('dragstart', event => { + event.dataTransfer!.setData('custom-type', 'Hello World'); + }, false); + + document.addEventListener('dragenter', event => { + event.preventDefault(); + }, false); + document.addEventListener('dragover', event => { + event.preventDefault(); + }, false); + + document.addEventListener('drop', event => { + event.preventDefault(); + resolve({ + types: event.dataTransfer!.types, + data: event.dataTransfer!.getData('custom-type'), + }); + }, false); + }) + ); + + await page.hover('[draggable="true"]'); + await page.mouse.down(); + await page.mouse.move(100, 100); + await page.mouse.up(); + + await expect(resultPromise).resolves.toEqual({ + types: ['custom-type'], + data: 'Hello World', + }); + }); + + it('what happens when dragging element is destroyed', async ({ page, browserName }) => { + it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/21621' }); + + await page.setContent(` + +
drop here
+ `); + + await page.evaluate(() => { + document.querySelector('#target').addEventListener('dragover', event => { + document.querySelector('button')?.remove(); + }, false); + + document.querySelector('#target').addEventListener('drop', event => { + document.querySelector('#target').textContent = 'dropped'; + }, false); + }); + + await page.locator('button').dragTo(page.locator('div')); + await expect(page.locator('div')).toHaveText('drop here'); + }); }); - await page.mouse.move(20, 20); - await page.mouse.down(); - await page.mouse.move(40, 40); - await page.mouse.up(); - const logs = await logsHandle.jsonValue(); - expect(logs).toEqual([ - { type: 'mousemove', buttons: 0 }, - { type: 'mousedown', buttons: 1 }, - { type: 'mousemove', buttons: 1 }, - { type: 'mouseup', buttons: 0 }, - ]); -}); +} async function trackEvents(target: ElementHandle) { const eventsHandle = await target.evaluateHandle(target => { @@ -394,64 +477,3 @@ async function trackEvents(target: ElementHandle) { }); return eventsHandle; } - -it('should handle custom dataTransfer', async ({ page, browserName, isWindows }) => { - it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/18013' }); - it.fixme(browserName === 'webkit' && isWindows); - await page.setContent(``); - - const resultPromise = page.evaluate(() => - new Promise(resolve => { - document.addEventListener('dragstart', event => { - event.dataTransfer!.setData('custom-type', 'Hello World'); - }, false); - - document.addEventListener('dragenter', event => { - event.preventDefault(); - }, false); - document.addEventListener('dragover', event => { - event.preventDefault(); - }, false); - - document.addEventListener('drop', event => { - event.preventDefault(); - resolve({ - types: event.dataTransfer!.types, - data: event.dataTransfer!.getData('custom-type'), - }); - }, false); - }) - ); - - await page.hover('[draggable="true"]'); - await page.mouse.down(); - await page.mouse.move(100, 100); - await page.mouse.up(); - - await expect(resultPromise).resolves.toEqual({ - types: ['custom-type'], - data: 'Hello World', - }); -}); - -it('what happens when dragging element is destroyed', async ({ page, browserName }) => { - it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/21621' }); - - await page.setContent(` - -
drop here
- `); - - await page.evaluate(() => { - document.querySelector('#target').addEventListener('dragover', event => { - document.querySelector('button')?.remove(); - }, false); - - document.querySelector('#target').addEventListener('drop', event => { - document.querySelector('#target').textContent = 'dropped'; - }, false); - }); - - await page.locator('button').dragTo(page.locator('div')); - await expect(page.locator('div')).toHaveText('drop here'); -});