From 19f340aaf2a2a196f6c0a71baf3bbdd43d4f829f Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Fri, 21 Feb 2025 12:36:39 +0100 Subject: [PATCH] fix(drag-n-drop): send two mousemove events to target to make Angular work --- packages/playwright-core/src/server/frames.ts | 4 + tests/assets/drag-n-drop-manual.html | 129 ++++++++++++++++++ tests/page/page-drag.spec.ts | 47 +++++++ 3 files changed, 180 insertions(+) create mode 100644 tests/assets/drag-n-drop-manual.html diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index 69ef1ecba5..74c05e32d0 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -1210,6 +1210,10 @@ export class Frame extends SdkObject { // Note: do not perform locator handlers checkpoint to avoid moving the mouse in the middle of a drag operation. dom.assertDone(await this._retryWithProgressIfNotConnected(progress, target, options.strict, false /* performActionPreChecks */, async handle => { return handle._retryPointerAction(progress, 'move and up', false, async point => { + // NOTE: Normal browsers emit usually a lot of dragover/mousemove events during drag'n + // drop operations. We want to emit minimal to make the ecosystem work. When using + // Native drag'n drop the browser does emit dropover events instead. + await this._page.mouse.move(point.x, point.y); await this._page.mouse.move(point.x, point.y); await this._page.mouse.up(); }, { diff --git a/tests/assets/drag-n-drop-manual.html b/tests/assets/drag-n-drop-manual.html new file mode 100644 index 0000000000..d876a8ed21 --- /dev/null +++ b/tests/assets/drag-n-drop-manual.html @@ -0,0 +1,129 @@ + + + +
+

+ Select this element, drag it to the Drop Zone and then release the selection to move the element.

+
+
Drop Zone
+ + + \ No newline at end of file diff --git a/tests/page/page-drag.spec.ts b/tests/page/page-drag.spec.ts index c227978f23..1f618d5a76 100644 --- a/tests/page/page-drag.spec.ts +++ b/tests/page/page-drag.spec.ts @@ -53,6 +53,25 @@ it.describe('Drag and drop', () => { ]); }); + it('should send the right events when using dragTo', async ({ server, page, browserName }) => { + await page.goto(server.PREFIX + '/drag-n-drop.html'); + const events = await trackEvents(await page.$('body')); + await page.locator('#source').dragTo(page.locator('#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', + // NOTE: Normal browsers emit usually a lot of dragover events during drag'n + // drop operations. We want to emit minimal (2) to make the ecosystem work. + 'dragover 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'); @@ -293,6 +312,34 @@ 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 manual drag\'n drop and emit correct mousemove events', { + annotation: { + type: 'issue', + description: 'https://github.com/microsoft/playwright/issues/34688', + } + }, async ({ page, server }) => { + await page.goto(server.PREFIX + '/drag-n-drop-manual.html'); + const events = await page.evaluateHandle(() => { + const events = []; + document.addEventListener('drop', (event: MouseEvent) => { + events.push({ + type: event.type, + x: event.clientX, + y: event.clientY, + }); + }); + return events; + }); + await page.dragAndDrop('#source', '#target'); + expect(await events.jsonValue()).toEqual([ + { type: 'mousemove', x: 120, y: 86 }, + // NOTE: Normal browsers emit usually a lot of mousemove events during drag'n + // drop operations. We want to emit minimal(2) to make the ecosystem work. + { type: 'mousemove', x: 240, y: 350 }, + { type: 'mousemove', x: 240, y: 350 }, + ]); + }); + it('should allow specifying the position', async ({ page, server }) => { await page.setContent(`