Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/browser/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,22 @@ export async function dragDropElems(elem, toElem) {
await sendMouse({ type: 'up' });
}

export async function dragElemBy(elem, offsetX = 0, offsetY = 0) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Name is a little odd. I'm trying to follow the pattern of the other helpers in this file, taking x and y offsets as parameters. They work a little differently though, and use "at" terminology, which doesn't make sense here.

But dragElemTo isn't correct, unless I take an x and y location rather than an offset. This is nicer for the helper, worse for the consumer - it's much easier if I can say "Drag it back 5 pixels" instead of "Drag it to spot x = 250".

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it!

const dragIncrementSize = 10; // To mimic real user dragging
const numSteps = Math.ceil(Math.max(Math.abs(offsetX), Math.abs(offsetY)) / dragIncrementSize);

const position = getElementPosition(elem);
await sendMouse({ type: 'move', position: [position.x, position.y] });

await sendMouse({ type: 'down' });
for (let i = 1; i <= numSteps; i++) {
const dx = Math.sign(offsetX) * Math.min(Math.abs(offsetX), dragIncrementSize * i);
const dy = Math.sign(offsetY) * Math.min(Math.abs(offsetY), dragIncrementSize * i);
await sendMouse({ type: 'move', position: [position.x + dx, position.y + dy] });
}
await sendMouse({ type: 'up' });

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to confirm: I'm assuming there's something already that'll release the mouse in between tests, just in case this aborts partway through?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good callout - we actually don't. @web/test-runner-commands has a resetMouse command that moves it back to (0,0) and releases buttons, but we don't use it. We just do the move ourselves. Been like that since the get-go and no clues in the PR discussion.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what @web/test-runner-commands's does, I think we just try to use it...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh cool, yeah let's try using that!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After this PR merges, I'll check test run times and see if it makes a huge difference. I don't think it will, since we only reset the mouse if we've touched it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I don't think we can use resetMouse. I'm running into this issue: modernweb-dev/web#2085. When --watching in Chromium, every time the mouse is reset, the context menu is opened and stays open for the next test (unless you click to close it, and interfere with the test). Doesn't matter if you have devTools open or not.

image

I think we just do it ourselves until this is fixed, and only bother with the left button, since we don't expose any commands that press the middle or right buttons anyways.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to do this in a separate PR, that also adds the ability to not complete the drag.

}

export async function focusElem(elem) {
await cmdSendKeys({ press: 'Shift' }); // Tab moves focus, Escape causes dismissible things to close
elem.focus({ focusVisible: true });
Expand Down
2 changes: 1 addition & 1 deletion src/browser/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import './vdiff.js';
import './axe.js';

export { assert, aTimeout, defineCE, expect, html, nextFrame, oneDefaultPreventedEvent, oneEvent, waitUntil } from '@open-wc/testing';
export { clickAt, clickElem, clickElemAt, dragDropElems, focusElem, hoverAt, hoverElem, hoverElemAt, sendKeys, sendKeysElem, setViewport } from './commands.js';
export { clickAt, clickElem, clickElemAt, dragDropElems, dragElemBy, focusElem, hoverAt, hoverElem, hoverElemAt, sendKeys, sendKeysElem, setViewport } from './commands.js';
export { fixture, waitForElem } from './fixture.js';
export { runConstructor } from './constructor.js';
72 changes: 71 additions & 1 deletion test/browser/commands.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { clickAt, clickElem, clickElemAt, dragDropElems, expect, fixture, focusElem, hoverAt, hoverElem, hoverElemAt, sendKeys, sendKeysElem, setViewport } from '../../src/browser/index.js';
import { clickAt, clickElem, clickElemAt, dragDropElems, dragElemBy, expect, fixture, focusElem, hoverAt, hoverElem, hoverElemAt, sendKeys, sendKeysElem, setViewport } from '../../src/browser/index.js';
import { html } from 'lit';
import { spy } from 'sinon';

describe('commands', () => {
const buttonTemplate = html`<button>text</button>`;
const dragTemplate = html`<div style="position: absolute; top: 95px; left: 95px; width: 10px; height: 10px;"></div>`;
const draggableTemplate = html`
<div>
<div id="dest" style="height: 100px; width: 100px;"></div>
Expand Down Expand Up @@ -194,6 +195,73 @@ describe('commands', () => {

});

describe('drag', () => {
const allPointerEvents = [], dragMoveEvents = [];
let dragStarted = false;
function onPointer(e) {
allPointerEvents.push({ type: e.type, x: e.clientX, y: e.clientY });
if (e.type === 'pointerdown') dragStarted = true;
else if (e.type === 'pointerup') dragStarted = false;
else if (e.type === 'pointermove' && dragStarted) dragMoveEvents.push({ x: e.clientX, y: e.clientY });
}

beforeEach(async() => {
elem = await fixture(dragTemplate);

window.addEventListener('pointerdown', onPointer);
window.addEventListener('pointermove', onPointer);
window.addEventListener('pointerup', onPointer);
});

afterEach(() => {
window.removeEventListener('pointerdown', onPointer);
window.removeEventListener('pointermove', onPointer);
window.removeEventListener('pointerup', onPointer);

allPointerEvents.length = 0;
dragMoveEvents.length = 0;
dragStarted = false;
});

it('should start dragging from the center of element and fire pointer events throughout the full drag flow', async() => {
await dragElemBy(elem, 20, 20);
expect(allPointerEvents).to.deep.equal([
{ type: 'pointermove', x: 100, y: 100 }, // Move to element center
{ type: 'pointerdown', x: 100, y: 100 }, // Start drag
{ type: 'pointermove', x: 110, y: 110 }, // Drag 10px
{ type: 'pointermove', x: 120, y: 120 }, // Drag 10px
{ type: 'pointerup', x: 120, y: 120 }, // End drag
]);
});

it('should move to the target offset in 10px increments to the max', async() => {
await dragElemBy(elem, 25, 0);
expect(dragMoveEvents).to.deep.equal([
{ x: 110, y: 100 },
{ x: 120, y: 100 },
{ x: 125, y: 100 },
]);
});

it('should clamp the shorter axis while the longer axis keeps stepping', async() => {
await dragElemBy(elem, 30, 15);
expect(dragMoveEvents).to.deep.equal([
{ x: 110, y: 110 },
{ x: 120, y: 115 },
{ x: 130, y: 115 },
]);
});

it('should drag in negative directions', async() => {
await dragElemBy(elem, -25, -25);
expect(dragMoveEvents).to.deep.equal([
{ x: 90, y: 90 },
{ x: 80, y: 80 },
{ x: 75, y: 75 },
]);
});
});

describe('drag & drop', () => {

it('should drag & drop element', (done) => {
Expand Down Expand Up @@ -226,6 +294,8 @@ describe('commands', () => {
{ command: 'clickElem', action: (elem) => clickElem(elem) },
{ command: 'clickAt', action: () => clickAt(5, 10) },
{ command: 'clickElemAt', action: (elem) => clickElemAt(elem, 10, 10) },
{ command: 'dragDropElems', action: (elem) => dragDropElems(elem, elem) },
{ command: 'dragElemBy', action: (elem) => dragElemBy(elem, 10, 10) },
{ command: 'hoverElem', action: (elem) => hoverElem(elem) },
{ command: 'hoverAt', action: () => hoverAt(5, 10) },
{ command: 'hoverElemAt', action: (elem) => hoverElemAt(elem, 10, 10) },
Expand Down