Skip to content

Commit

Permalink
Merge pull request #842 from gemini-testing/HERMIONE-1376.fix_reset_c…
Browse files Browse the repository at this point in the history
…ursor

fix: move cursor relative to the center of body when using "resetCursor" option
  • Loading branch information
DudaGod authored Feb 20, 2024
2 parents 95d798c + 36c4f36 commit 692ae5d
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 26 deletions.
37 changes: 34 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ Hermione is a utility for integration testing of web pages using [WebdriverIO](h
- [Configuring .hermione.conf.js by yourself (a slow way)](#configuring-hermioneconfjs-by-yourself-a-slow-way)
- [Chrome Devtools Protocol](#chrome-devtools-protocol)
- [Webdriver protocol](#webdriver-protocol)
- [Commands API](#commands-api)
- [Browser commands](#browser-commands)
- [clearSession](#clearsession)
- [Element commands](#element-commands)
- [moveCursorTo](#movecursorto)
- [Tests API](#tests-api)
- [Arguments](#arguments)
- [Hooks](#hooks)
Expand Down Expand Up @@ -110,10 +115,15 @@ Hermione is a utility for integration testing of web pages using [WebdriverIO](h
- [Reporters](#reporters)
- [Require modules](#require-modules)
- [Overriding settings](#overriding-settings)
- [Debug mode](#debug-mode)
- [REPL mode](#repl-mode)
- [switchToRepl](#switchtorepl)
- [Test development in runtime](#test-development-in-runtime)
- [How to set up using VSCode](#how-to-set-up-using-vscode)
- [How to set up using Webstorm](#how-to-set-up-using-webstorm)
- [Environment variables](#environment-variables)
- [HERMIONE_SKIP_BROWSERS](#hermione_skip_browsers)
- [HERMIONE_SETS](#hermione_sets)
- [Debug mode](#debug-mode)
- [Programmatic API](#programmatic-api)
- [config](#config)
- [events](#events)
Expand Down Expand Up @@ -391,12 +401,14 @@ node_modules/.bin/hermione

Since Hermione is based on [WebdriverIO v8](https://webdriver.io/docs/api/), all the commands provided by WebdriverIO are available in it. But Hermione also has her own commands.

### clearSession
### Browser commands

#### clearSession

Browser command that clears session state (deletes cookies, clears local and session storages). For example:

```js
it('', async ({ browser }) => {
it('test', async ({ browser }) => {
await browser.url('https://github.com/gemini-testing/hermione');

(await browser.getCookies()).length; // 5
Expand All @@ -411,6 +423,25 @@ it('', async ({ browser }) => {
});
```

### Element commands

#### moveCursorTo

> This command is temporary and will be removed in the next major (hermione@9). Differs from the standard [moveTo](https://webdriver.io/docs/api/element/moveTo/) in that it moves the cursor relative to the top-left corner of the element (like it was in hermione@7).
Move the mouse by an offset of the specified element. If offset is not specified then mouse will be moved to the top-left corder of the element.

Usage:

```typescript
await browser.$(selector).moveCursorTo({ xOffset, yOffset });
```

Available parameters:

* **xOffset** (optional) `Number` – X offset to move to, relative to the top-left corner of the element;
* **yOffset** (optional) `Number` – Y offset to move to, relative to the top-left corner of the element.

## Tests API

### Arguments
Expand Down
1 change: 1 addition & 0 deletions src/browser/commands/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ module.exports = [
"scrollIntoView",
"openAndWait",
"switchToRepl",
"moveCursorTo",
];
34 changes: 34 additions & 0 deletions src/browser/commands/moveCursorTo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { Browser } from "../types";

type MoveToOptions = {
xOffset?: number;
yOffset?: number;
};

// TODO: remove after next major version
export = async (browser: Browser): Promise<void> => {
const { publicAPI: session } = browser;

session.addCommand(
"moveCursorTo",
async function (this: WebdriverIO.Element, { xOffset = 0, yOffset = 0 }: MoveToOptions = {}): Promise<void> {
if (!this.isW3C) {
return this.moveToElement(this.elementId, xOffset, yOffset);
}

const { x, y, width, height } = await this.getElementRect(this.elementId);
const { scrollX, scrollY } = await session.execute(function (this: Window) {
return { scrollX: this.scrollX, scrollY: this.scrollY };
});

const newXOffset = Math.floor(x - scrollX + (typeof xOffset === "number" ? xOffset : width / 2));
const newYOffset = Math.floor(y - scrollY + (typeof yOffset === "number" ? yOffset : height / 2));

return session
.action("pointer", { parameters: { pointerType: "mouse" } })
.move({ x: newXOffset, y: newYOffset })
.perform();
},
true,
);
};
17 changes: 12 additions & 5 deletions src/worker/runner/test-runner/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,18 @@ module.exports = class TestRunner {

await body.scrollIntoView();

const { x = 0, y = 0 } = await session.execute(function () {
return document.body.getBoundingClientRect();
});
// x and y must be integer, wdio will throw error otherwise
await body.moveTo({ xOffset: -Math.floor(x), yOffset: -Math.floor(y) });
if (!session.isW3C) {
const { x = 0, y = 0 } = await session.execute(function () {
return document.body.getBoundingClientRect();
});

return session.moveToElement(body.elementId, -Math.floor(x), -Math.floor(y));
}

await session
.action("pointer", { parameters: { pointerType: "mouse" } })
.move({ x: 0, y: 0 })
.perform();
}
};

Expand Down
80 changes: 62 additions & 18 deletions test/src/worker/runner/test-runner/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,31 @@ describe("worker/runner/test-runner", () => {
const mkElement_ = proto => {
return _.defaults(proto, {
scrollIntoView: sandbox.stub().named("scrollIntoView").resolves(),
moveTo: sandbox.stub().named("moveTo").resolves(),
getSize: sandbox.stub().named("getSize").resolves({ width: 100, height: 500 }),
elementId: 100500,
});
};

const mkActionAPI_ = () => {
const actionStub = {};
actionStub.move = sandbox.stub().named("move").returns(actionStub);
actionStub.perform = sandbox.stub().named("perform").resolves();

return actionStub;
};

const mkBrowser_ = ({ prototype, config, id } = {}) => {
const actionStub = {};
actionStub.move = sandbox.stub().named("move").returns(actionStub);
actionStub.perform = sandbox.stub().named("perform").resolves();

const publicAPI = _.defaults(prototype, {
$: sandbox.stub().named("$").resolves(mkElement_()),
execute: sandbox.stub().named("execute").resolves({ x: 0, y: 0 }),
assertView: sandbox.stub().named("assertView").resolves(),
moveToElement: sandbox.stub().named("moveToElement").resolves(),
action: sandbox.stub().named("getSize").returns(mkActionAPI_()),
isW3C: true,
});
config = _.defaults(config, { resetCursor: true });

Expand Down Expand Up @@ -272,7 +288,7 @@ describe("worker/runner/test-runner", () => {
it('should not move cursor to position "0,0" on body element', async () => {
await run_();

assert.notCalled(body.moveTo);
assert.notCalled(browser.publicAPI.action);
});
});

Expand Down Expand Up @@ -309,32 +325,60 @@ describe("worker/runner/test-runner", () => {
assert.calledOnceWith(body.scrollIntoView);
});

it('should move cursor to position "0,0" on body element', async () => {
await run_();
describe("in jsonwp protocol", () => {
beforeEach(() => {
browser.publicAPI.isW3C = false;
});

assert.calledOnceWith(body.moveTo, { xOffset: 0, yOffset: 0 });
});
it("should scroll before moving cursor", async () => {
await run_();

it("should move cursor correctly if body element has negative coords", async () => {
browser.publicAPI.execute.resolves({ x: -100, y: -500 });
assert.callOrder(body.scrollIntoView, browser.publicAPI.moveToElement);
});

await run_();
it('should move cursor to position "0,0"', async () => {
browser.publicAPI.execute.resolves({ x: 5, y: 5 });
body.elementId = 12345;

assert.calledOnceWith(body.moveTo, { xOffset: 100, yOffset: 500 });
});
await run_();

it("should scroll before moving cursor", async () => {
await run_();
assert.calledOnceWith(browser.publicAPI.moveToElement, 12345, -5, -5);
});

assert.callOrder(body.scrollIntoView, body.moveTo);
it("should floor coords if body element has fractional coords", async () => {
browser.publicAPI.execute.resolves({ x: 10.123, y: 15.899 });
body.elementId = 12345;

await run_();

assert.calledOnceWith(browser.publicAPI.moveToElement, 12345, -10, -15);
});
});

it("should floor coords if body element has fractional coords", async () => {
browser.publicAPI.execute.resolves({ x: 10.123, y: 15.899 });
describe("in w3c protocol", () => {
beforeEach(() => {
browser.publicAPI.isW3C = true;
});

await run_();
it("should scroll before moving cursor", async () => {
await run_();

assert.calledOnceWith(body.moveTo, { xOffset: -10, yOffset: -15 });
assert.callOrder(body.scrollIntoView, browser.publicAPI.action);
});

it('should move cursor to position "0,0"', async () => {
const actionAPI = mkActionAPI_();
browser.publicAPI.action.returns(actionAPI);

await run_();

assert.calledOnceWith(browser.publicAPI.action, "pointer", {
parameters: { pointerType: "mouse" },
});
assert.calledOnceWith(actionAPI.move, { x: 0, y: 0 });
assert.calledOnce(actionAPI.perform);
assert.callOrder(browser.publicAPI.action, actionAPI.move, actionAPI.perform);
});
});
});
});
Expand Down

0 comments on commit 692ae5d

Please sign in to comment.