Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make e2e tests run headless #321

Merged
merged 2 commits into from
Oct 3, 2024
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
12 changes: 4 additions & 8 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,14 @@ jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- run: npx lockfile-lint --path package-lock.json --validate-https
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
env:
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: "true"
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm install
- uses: david-tejada/puppeteer-headful@master
with:
args: npm test
- run: npm test
47 changes: 23 additions & 24 deletions e2e/keyboardClicking.test.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import { keyTap } from "@hurdlegroup/robotjs";
import { type Frame, type Page } from "puppeteer";
import { type Frame } from "puppeteer";
import { rangoCommandWithoutTarget } from "./utils/rangoCommands";
import { sleep } from "./utils/testHelpers";

async function testKeyboardClickingHighlighting(page: Page | Frame) {
await page.waitForSelector(".rango-hint");
async function testKeyboardClickingHighlighting(frame?: Frame) {
const pageOrFrame = frame ?? page;
await pageOrFrame.waitForSelector(".rango-hint");

const borderWidthBefore = await page.$eval(".rango-hint", (element) => {
const inner = element.shadowRoot!.querySelector(".inner")!;
return Number.parseInt(window.getComputedStyle(inner).borderWidth, 10);
});
const borderWidthBefore = await pageOrFrame.$eval(
".rango-hint",
(element) => {
const inner = element.shadowRoot!.querySelector(".inner")!;
return Number.parseInt(window.getComputedStyle(inner).borderWidth, 10);
}
);

keyTap("a");
await page.keyboard.type("a");
await sleep(100);

const [borderWidthAfter, borderColorAfter] = await page.$eval(
const [borderWidthAfter, borderColorAfter] = await pageOrFrame.$eval(
".rango-hint",
(element) => {
const inner = element.shadowRoot!.querySelector(".inner")!;
Expand All @@ -26,11 +29,11 @@ async function testKeyboardClickingHighlighting(page: Page | Frame) {
expect(borderWidthAfter).toBe(borderWidthBefore + 1);
expect(borderColorAfter).toMatch(/^rgba\(.+0\.7\)$/);

keyTap("a");
await page.keyboard.type("a");
await sleep(100);

const [borderWidthAfterCompletion, borderColorAfterCompletion] =
await page.$eval(".rango-hint", (element) => {
await pageOrFrame.$eval(".rango-hint", (element) => {
const inner = element.shadowRoot!.querySelector(".inner")!;
const style = window.getComputedStyle(inner);
return [Number.parseInt(style.borderWidth, 10), style.borderColor];
Expand Down Expand Up @@ -82,16 +85,14 @@ describe("With hints in main frame", () => {
});

test("Typing the hint characters clicks the link", async () => {
keyTap("a");
keyTap("a");

await page.keyboard.type("aa");
await page.waitForNavigation();

expect(page.url()).toBe("http://localhost:8080/singleLink.html#");
});

test("Typing one hint character marks the hint with a border 1px wider and opacity 0.7 and resets after typing the second character", async () => {
await testKeyboardClickingHighlighting(page);
await testKeyboardClickingHighlighting();
});
});

Expand All @@ -117,15 +118,13 @@ describe("With hints in other frames", () => {
});

test("Typing the hint characters clicks the link", async () => {
const frame = await page.$("iframe");
const contentFrame = await frame!.contentFrame();
await contentFrame.waitForSelector(".rango-hint");

keyTap("a");
keyTap("a");
const $frame = await page.$("iframe");
const frame = await $frame!.contentFrame();
await frame.waitForSelector(".rango-hint");

await contentFrame.waitForNavigation();
await page.keyboard.type("aa");
await frame.waitForNavigation();

expect(contentFrame.url()).toBe("http://localhost:8080/singleLink.html#");
expect(frame.url()).toBe("http://localhost:8080/singleLink.html#");
});
});
6 changes: 3 additions & 3 deletions e2e/noContentScript.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import clipboard from "clipboardy";
import { type ResponseToTalon } from "../src/typings/RequestFromTalon";
import {
rangoCommandWithTarget,
rangoCommandWithoutTarget,
} from "./utils/rangoCommands";
import { storageClipboard } from "./utils/serviceWorker";
import { sleep } from "./utils/testHelpers";

beforeEach(async () => {
Expand All @@ -14,7 +14,7 @@ describe("Direct clicking", () => {
test("If no content script is loaded in the current page it sends the command to talon to type the characters", async () => {
await rangoCommandWithTarget("directClickElement", ["a"]);
await sleep(300);
const clip = clipboard.readSync();
const clip = await storageClipboard.readText();
const response = JSON.parse(clip) as ResponseToTalon;
const found = response.actions.find(
(action) => action.name === "typeTargetCharacters"
Expand All @@ -27,7 +27,7 @@ describe("Direct clicking", () => {
describe("Background commands", () => {
test("Commands that don't need the content script are still able to run", async () => {
await rangoCommandWithoutTarget("copyLocationProperty", "href");
const clip = clipboard.readSync();
const clip = await storageClipboard.readText();
const response = JSON.parse(clip) as ResponseToTalon;
const action = response.actions[0]!;

Expand Down
2 changes: 2 additions & 0 deletions e2e/scroll.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
import { sleep } from "./utils/testHelpers";
import { getHintForElement } from "./utils/getHintForElement";

jest.retryTimes(3);

function getCenter(element: Element) {
const { top, height } = element.getBoundingClientRect();
return top + height / 2;
Expand Down
25 changes: 12 additions & 13 deletions e2e/utils/rangoCommands.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
/* eslint-disable no-await-in-loop */
import { keyTap } from "@hurdlegroup/robotjs";
import clipboard from "clipboardy";
import { storageClipboard, runTestRequest } from "./serviceWorker";
import { sleep } from "./testHelpers";

async function waitForCompletion() {
async function waitResponseReady() {
let message: any;

while (!message || message.type !== "response") {
const clip = clipboard.readSync();
const clip = await storageClipboard.readText();

try {
message = JSON.parse(clip) as unknown;
} catch {}
} catch {
// Ignore parsing errors
}

await sleep(10);
}
Expand All @@ -32,11 +33,10 @@ export async function rangoCommandWithTarget(
},
};

const commandString = JSON.stringify(command);
const request = JSON.stringify(command);

clipboard.writeSync(commandString);
keyTap("3", ["control", "shift"]);
await waitForCompletion();
await runTestRequest(request);
await waitResponseReady();
}

export async function rangoCommandWithoutTarget(
Expand All @@ -52,9 +52,8 @@ export async function rangoCommandWithoutTarget(
},
};

const commandString = JSON.stringify(command);
const request = JSON.stringify(command);

clipboard.writeSync(commandString);
keyTap("3", ["control", "shift"]);
await waitForCompletion();
await runTestRequest(request);
await waitResponseReady();
}
41 changes: 41 additions & 0 deletions e2e/utils/serviceWorker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { TargetType } from "puppeteer";

async function getServiceWorker() {
const workerTarget = await browser.waitForTarget(
(target) => target.type() === TargetType.SERVICE_WORKER
);

return (await workerTarget.worker())!;
}

/**
* This is used to make headless testing possible. Because in Chrome for Testing
* `document.execCommand` doesn't work we need a way to test without using the
* real clipboard. It reads and writes from and to local storage.
*/
export const storageClipboard = {
async readText() {
const worker = await getServiceWorker();
const { clipboard } = (await worker.evaluate(async () =>
chrome.storage.local.get("clipboard")
)) as { clipboard: string };

return clipboard;
},
async writeText(text: string) {
const worker = await getServiceWorker();

await worker.evaluate(async (text) => {
await chrome.storage.local.set({ clipboard: text });
}, text);
},
};

export async function runTestRequest(request: string) {
const worker = await getServiceWorker();

await worker.evaluate(async (request) => {
await chrome.storage.local.set({ clipboard: request });
dispatchEvent(new CustomEvent("handle-test-request"));
}, request);
}
2 changes: 1 addition & 1 deletion jest-puppeteer.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ module.exports = {
launch: {
dumpio: false,
devtools: false,
headless: false,
product: "chrome",
executablePath: process.env.PUPPETEER_EXEC_PATH,
args: [
"--remote-debugging-port=9222",
"--no-sandbox",
`--disable-extensions-except=${EXTENSION_PATH}`,
`--load-extension=${EXTENSION_PATH}`,
Expand Down
Loading