chore: initialize from frontend template 4dc033c
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
import {
|
||||
expect,
|
||||
test as base,
|
||||
type ConsoleMessage,
|
||||
type Page,
|
||||
} from "@playwright/test";
|
||||
|
||||
function ignoredConsole(message: ConsoleMessage) {
|
||||
return (
|
||||
message.type() === "warning" &&
|
||||
message.text().includes("NO_COLOR")
|
||||
);
|
||||
}
|
||||
|
||||
export const test = base.extend({
|
||||
page: async ({ page }, use) => {
|
||||
const failures: string[] = [];
|
||||
const onConsole = (message: ConsoleMessage) => {
|
||||
if (
|
||||
["error", "warning"].includes(message.type()) &&
|
||||
!ignoredConsole(message)
|
||||
) {
|
||||
failures.push(`console:${message.type()}:${message.text()}`);
|
||||
}
|
||||
};
|
||||
const onPageError = (error: Error) => {
|
||||
failures.push(`pageerror:${error.name}`);
|
||||
};
|
||||
const onRequestFailed = (request: import("@playwright/test").Request) => {
|
||||
failures.push(
|
||||
`requestfailed:${request.method()}:${new URL(request.url()).pathname}:${request.failure()?.errorText ?? "unknown"}`,
|
||||
);
|
||||
};
|
||||
page.on("console", onConsole);
|
||||
page.on("pageerror", onPageError);
|
||||
page.on("requestfailed", onRequestFailed);
|
||||
await use(page);
|
||||
page.off("console", onConsole);
|
||||
page.off("pageerror", onPageError);
|
||||
page.off("requestfailed", onRequestFailed);
|
||||
expect(failures, "unexpected browser console/page errors").toEqual([]);
|
||||
},
|
||||
});
|
||||
|
||||
export { expect };
|
||||
export type { Page };
|
||||
@@ -0,0 +1,69 @@
|
||||
import { vi } from "vitest";
|
||||
|
||||
export function createDeterministicClock(start = 0) {
|
||||
let current = start;
|
||||
return Object.freeze({
|
||||
now: () => current,
|
||||
sleep: async (milliseconds: number, signal?: AbortSignal) => {
|
||||
if (signal?.aborted) throw new DOMException("aborted", "AbortError");
|
||||
current += Math.max(0, milliseconds);
|
||||
},
|
||||
advance(milliseconds: number) {
|
||||
current += Math.max(0, milliseconds);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function createSeededRandom(seed = 1) {
|
||||
let state = seed >>> 0;
|
||||
return () => {
|
||||
state = (state * 1_664_525 + 1_013_904_223) >>> 0;
|
||||
return state / 0x1_0000_0000;
|
||||
};
|
||||
}
|
||||
|
||||
export function createControlledScheduler() {
|
||||
const callbacks: Array<() => void> = [];
|
||||
return Object.freeze({
|
||||
callbacks,
|
||||
setTimeout: vi.fn((callback: () => void) => {
|
||||
callbacks.push(callback);
|
||||
return callbacks.length - 1;
|
||||
}),
|
||||
clearTimeout: vi.fn(),
|
||||
runNext() {
|
||||
callbacks.shift()?.();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function createRecordingStorage() {
|
||||
const values = new Map<string, string>();
|
||||
const operations: Array<
|
||||
Readonly<{ operation: "get" | "set" | "remove"; key: string }>
|
||||
> = [];
|
||||
const storage: Storage = {
|
||||
getItem(key) {
|
||||
operations.push({ operation: "get", key });
|
||||
return values.get(key) ?? null;
|
||||
},
|
||||
setItem(key, value) {
|
||||
operations.push({ operation: "set", key });
|
||||
values.set(key, value);
|
||||
},
|
||||
removeItem(key) {
|
||||
operations.push({ operation: "remove", key });
|
||||
values.delete(key);
|
||||
},
|
||||
clear() {
|
||||
values.clear();
|
||||
},
|
||||
key(index) {
|
||||
return [...values.keys()][index] ?? null;
|
||||
},
|
||||
get length() {
|
||||
return values.size;
|
||||
},
|
||||
};
|
||||
return Object.freeze({ storage, operations, values });
|
||||
}
|
||||
Reference in New Issue
Block a user