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

Add end2end tests #470

Merged
merged 8 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from 3 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
4 changes: 4 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,7 @@ jobs:
run: |
echo "Running in $BROWSER"
npm run test:${{ matrix.browser }}
- name: Run end2end
run: |
echo "Running in $BROWSER"
npm run test-e2e:${{ matrix.browser }}
12 changes: 8 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@
"pretty:check": "prettier --check ./",
"pretty:fix": "prettier --write ./",
"format": "npm run pretty:fix && npm run lint:fix",
"test:chrome": "node tests/run.mjs --browser chrome",
"test:firefox": "node tests/run.mjs --browser firefox",
"test:safari": "node tests/run.mjs --browser safari",
"test:edge": "node tests/run.mjs --browser edge"
"test:chrome": "node tests/run-unittests.mjs --browser chrome",
"test:firefox": "node tests/run-unittests.mjs --browser firefox",
"test:safari": "node tests/run-unittests.mjs --browser safari",
"test:edge": "node tests/run-unittests.mjs --browser edge",
"test-e2e:chrome": "node tests/run-end2end.mjs --browser chrome",
"test-e2e:firefox": "node tests/run-end2end.mjs --browser firefox",
"test-e2e:safari": "node tests/run-end2end.mjs --browser safari",
"test-e2e:edge": "node tests/run-end2end.mjs --browser edge"
},
"devDependencies": {
"@babel/core": "^7.21.3",
Expand Down
2 changes: 2 additions & 0 deletions resources/main.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class MainBenchmarkClient {
constructor() {
window.addEventListener("DOMContentLoaded", () => this.prepareUI());
this._showSection(window.location.hash);
globalThis.dispatchEvent(new Event("SpeedometerReady"));
}

start() {
Expand Down Expand Up @@ -150,6 +151,7 @@ class MainBenchmarkClient {
this.showResultsDetails();
else
this.showResultsSummary();
globalThis.dispatchEvent(new Event("SpeedometerDone"));
}

handleError(error) {
Expand Down
2 changes: 1 addition & 1 deletion resources/shared/benchmark.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class BenchmarkSuite {
const result = await test.runAndRecord(params, this, test, this.record);
measuredValues.tests[test.name] = result;
measuredValues.total += result.total;
onProgress?.(test.name);
measuredValues.totalPrepare += result.onProgress?.(test.name);
}

performance.mark(suiteEndLabel);
Expand Down
75 changes: 13 additions & 62 deletions tests/run.mjs → tests/helper.mjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
#! /usr/bin/env node
/* eslint-disable-next-line no-unused-vars */
import commandLineUsage from "command-line-usage";
import commandLineArgs from "command-line-args";
import serve from "./server.mjs";

import { Builder, Capabilities } from "selenium-webdriver";
import commandLineArgs from "command-line-args";
import commandLineUsage from "command-line-usage";
import assert from "assert";

const optionDefinitions = [
{ name: "browser", type: String, description: "Set the browser to test, choices are [safari, firefox, chrome]. By default the $BROWSER env variable is used." },
Expand Down Expand Up @@ -65,63 +63,9 @@ switch (BROWSER) {
}
}

const PORT = options.port;
export const PORT = options.port;
const server = serve(PORT);

let driver;

function printTree(node) {
console.log(node.title);

for (const test of node.tests) {
console.group();
if (test.state === "passed") {
console.log("\x1b[32m✓", `\x1b[0m${test.title}`);
} else {
console.log("\x1b[31m✖", `\x1b[0m${test.title}`);
console.group();
console.log(`\x1b[31m${test.error.name}: ${test.error.message}`);
console.groupEnd();
}
console.groupEnd();
}

for (const suite of node.suites) {
console.group();
printTree(suite);
console.groupEnd();
}
}

async function test() {
driver = await new Builder().withCapabilities(capabilities).build();
try {
await driver.get(`http://localhost:${PORT}/tests/index.html`);

const { testResults, stats } = await driver.executeAsyncScript(function (callback) {
const returnResults = () =>
callback({
stats: globalThis.testRunner.stats,
testResults: globalThis.testResults,
});
if (window.testResults)
returnResults();
else
window.addEventListener("test-complete", returnResults, { once: true });
});

printTree(testResults);

console.log("\nChecking for passed tests...");
assert(stats.passes > 0);
console.log("Checking for failed tests...");
assert(stats.failures === 0);
} finally {
console.log("\nTests complete!");
driver.quit();
server.close();
}
}
export let driver;

process.on("unhandledRejection", (err) => {
console.error(err);
Expand All @@ -131,5 +75,12 @@ process.once("uncaughtException", (err) => {
console.error(err);
process.exit(1);
});
process.on("exit", () => stop());

setImmediate(test);
driver = await new Builder().withCapabilities(capabilities).build();

export function stop() {
server.close();
if (driver)
driver.close();
}
89 changes: 89 additions & 0 deletions tests/run-end2end.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#! /usr/bin/env node
/* eslint-disable-next-line no-unused-vars */
import assert from "assert";
import { driver, PORT, stop } from "./helper.mjs";

import { Suites } from "../resources/tests.mjs";

async function testPage(url) {
console.log(`Testing: ${url}`);
await driver.get(`http://localhost:${PORT}/${url}`);
await driver.executeAsyncScript((callback) => {
if (globalThis.benchmarkClient)
callback();
else
window.addEventListener("SpeedometerReady", () => callback(), { once: true });
});
console.log(" - Awaiting Benchmark");
const metrics = await driver.executeAsyncScript((callback) => {
window.addEventListener("SpeedometerDone", () => callback(globalThis.benchmarkClient.metrics));
globalThis.benchmarkClient.start();
});
validateMetrics(metrics);
return metrics;
}

function validateMetrics(metrics) {
for (const [name, metric] of Object.entries(metrics))
validateMetric(name, metric);
assert(metrics.Geomean.mean > 0);
assert(metrics.Score.mean > 0);
}

function validateMetric(name, metric) {
assert(metric.name === name);
assert(metric.mean >= 0);
}

async function testIterations() {
const metrics = await testPage("index.html?iterationCount=3");
Suites.forEach((suite) => {
if (!suite.disabled) {
const metric = metrics[suite.name];
assert(metric.values.length === 3);
}
});
assert(metrics.Geomean.values.length === 3);
assert(metrics.Score.values.length === 3);
}

async function testAll() {
const metrics = await testPage("index.html?iterationCount=1&tags=all");
Suites.forEach((suite) => {
assert(suite.name in metrics);
const metric = metrics[suite.name];
assert(metric.values.length === 1);
});
assert(metrics.Geomean.values.length === 1);
assert(metrics.Score.values.length === 1);
}

async function testDeveloperMode() {
const params = ["developerMode", "iterationCount=1", "warmupBeforeSync=2", "waitBeforeSync=2", "shuffleSeed=123", "suites=Perf-Dashboard"];
const metrics = await testPage(`index.html?${params.join("&")}`);
Suites.forEach((suite) => {
if (suite.name === "Perf-Dashboard") {
const metric = metrics[suite.name];
assert(metric.values.length === 1);
} else {
assert(!(suite.name in metrics));
}
});
}

async function test() {
try {
await driver.manage().setTimeouts({ script: 60000 });
await testIterations();
await testAll();
await testDeveloperMode();
console.log("\nTests complete!");
} catch (e) {
console.error("\nTests failed!");
throw e;
} finally {
stop();
}
}

setImmediate(test);
57 changes: 57 additions & 0 deletions tests/run-unittests.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#! /usr/bin/env node
/* eslint-disable-next-line no-unused-vars */
import assert from "assert";
import { driver, PORT, stop } from "./helper.mjs";

function printTree(node) {
console.log(node.title);

for (const test of node.tests) {
console.group();
if (test.state === "passed") {
console.log("\x1b[32m✓", `\x1b[0m${test.title}`);
} else {
console.log("\x1b[31m✖", `\x1b[0m${test.title}`);
console.group();
console.log(`\x1b[31m${test.error.name}: ${test.error.message}`);
console.groupEnd();
}
console.groupEnd();
}

for (const suite of node.suites) {
console.group();
printTree(suite);
console.groupEnd();
}
}

async function test() {
try {
await driver.get(`http://localhost:${PORT}/tests/index.html`);

const { testResults, stats } = await driver.executeAsyncScript(function (callback) {
const returnResults = () =>
callback({
stats: globalThis.testRunner.stats,
testResults: globalThis.testResults,
});
if (window.testResults)
returnResults();
else
window.addEventListener("test-complete", returnResults, { once: true });
});

printTree(testResults);

console.log("\nChecking for passed tests...");
assert(stats.passes > 0);
console.log("Checking for failed tests...");
assert(stats.failures === 0);
} finally {
console.log("\nTests complete!");
stop();
}
}

setImmediate(test);
8 changes: 5 additions & 3 deletions tests/server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@ const MIME_TYPES = {
};

const STATIC_PATH = path.join(process.cwd(), "./");
console.log(STATIC_PATH);
const toBool = [() => true, () => false];

export default function serve(port) {
if (!port)
throw new Error("Port is required");

const prepareFile = async (url) => {
const paths = [STATIC_PATH, url];
if (url.endsWith("/"))
const paths = [STATIC_PATH, url.pathname];
if (url.pathname.endsWith("/"))
paths.push("index.html");
const filePath = path.join(...paths);
const pathTraversal = !filePath.startsWith(STATIC_PATH);
Expand All @@ -38,7 +39,8 @@ export default function serve(port) {

const server = http
.createServer(async (req, res) => {
const file = await prepareFile(req.url);
const url = new URL(`http://${process.env.HOST ?? "localhost"}${req.url}`);
const file = await prepareFile(url);
const statusCode = file.found ? 200 : 404;
const mimeType = MIME_TYPES[file.ext] || MIME_TYPES.default;
res.writeHead(statusCode, { "Content-Type": mimeType });
Expand Down
Loading