playwright icon indicating copy to clipboard operation
playwright copied to clipboard

[BUG] The first `mouse.move` that triggers dragging doesn't send the `dragOver` event

Open kevin940726 opened this issue 3 years ago • 2 comments

Context:

  • Playwright Version: 1.25.1
  • Operating System: Mac
  • Node.js version: 16.14.0
  • Browser: Chromium

Code Snippet

Click to expand the code snippet
const { chromium, expect } = require( '@playwright/test' );

( async () => {
	const browser = await chromium.launch();
	const context = await browser.newContext();
	const page = await context.newPage();

	await page.setContent( `
	<div id="drop-target" style="border: 1px dotted red; width: 300px; height: 300px;"></div>
	<button draggable="true">Drag me</button>
	` );

	const dragOverPromise = page.evaluate( () => {
		const dropTarget = document.getElementById( 'drop-target' );

		return new Promise( ( resolve ) => {
			dropTarget.addEventListener(
				'dragover',
				() => {
					resolve( true );
				},
				false
			);
		} );
	} );

	await page.hover( 'button[draggable="true"]' );
	await page.mouse.down();
	await page.hover( 'id=drop-target' );

	// Uncomment this line to make the test pass:
	// await page.mouse.up();

	await expect( dragOverPromise ).resolves.toBe( true );

	await page.close();
	await browser.close();
} )();

Describe the bug

We want to make assertions while dragging, so we don't want to call mouse.up() just yet. But then we discovered that sometimes the dragOver event doesn't fire as expected, at least not until mouse.up() is called.

https://github.com/microsoft/playwright/blob/6e1c94b5fefbb7da8392c2b1e2a3a04692c992e8/packages/playwright-core/src/server/chromium/crDragDrop.ts#L113-L119

Once we recognize the dragging state, we only send the dragEnter event but not the dragOver event. Only until we call mouse.move() (or mouse.up() for some reason) we'll get it to send the dragOver event.

https://github.com/microsoft/playwright/blob/6e1c94b5fefbb7da8392c2b1e2a3a04692c992e8/packages/playwright-core/src/server/chromium/crDragDrop.ts#L55-L61

I only tested it in Chromium, I'm not sure if it's the same in other supported browsers.

I wonder if it's a bug or expected behavior? If it's a bug, then the obvious solution might be to just send the dragOver event after the dragEnter event. If it's expected behavior, then what should be the recommended solution to this problem?

kevin940726 avatar Sep 07 '22 06:09 kevin940726

@kevin940726 Thank you for the issue! I can reproduce this. In WebKit and Firefox, this fires dragover event, but in Chromium it does not. All browsers fire dragenter.

Looking at the spec, steps 3.1 and 3.3, it seems like dragenter and dragover should be dispatched for the same mousemove.


However, I don't think that browsers actually do that.

For example, chromium source suggests that dragover is only fired when the new drag target is the same as the previous drag target, which happens on the second mousemove. This behavior seems to be unchanged since at least 2009.

I found similar code patter in WebKit source as well.


To check this behavior manually, load the following page, open Developer Tools and drag the button over the box.

<div id="drop-target" style="border: 1px dotted red; width: 300px; height: 300px;"></div>
<button draggable="true">Drag me</button>
<script>
  const target = document.getElementById('drop-target');
  target.addEventListener('dragenter', () => { console.log('dragenter'); Promise.resolve().then(() => console.log('dragenter microtask')); });
  target.addEventListener('dragover', () => { console.log('dragover'); Promise.resolve().then(() => console.log('dragover microtask')); });
</script>

Console in all browsers shows the following:

dragenter
dragenter microtask
dragover
dragover microtask

This suggests that dragenter and dragover happen in different tasks (not microtasks), and therefore are coming from different mouse move events.


To summarize - I believe that Playwright behavior with Chromium is correct, while Playwright behavior with Firefox and WebKit is wrong.

I'd suggest to issue two hover() calls - this forces all browsers to emit a dragover event. Let me know if this helps.

dgozman avatar Sep 09 '22 00:09 dgozman

Thanks for the detailed explanation and the reference! They helped a lot!

I'd suggest to issue two hover() calls - this forces all browsers to emit a dragover event.

Yeah, we're using this technique in our e2e tests. But it's not really readable nor ergonomic. Playwright's API is supposed to emulate real users. I don't think there's any real user who can teleport their mouse to a specific location in one step, isn't there? I wonder if it's worth it to handle this in Playwright under the hood automatically to hide this complexity?

kevin940726 avatar Sep 16 '22 16:09 kevin940726

I discussed this with the team, and we decided to keep the behavior that's aligned with the browsers. We didn't come up with a good way to mitigate the issue, so we'll add a note to the documentation for any future users that might hit this issue.

dgozman avatar Sep 28 '22 16:09 dgozman

Sounds good enough to me! Thanks for the help @dgozman! I think we can close this now. 👍

kevin940726 avatar Sep 29 '22 12:09 kevin940726