- Introduced `.prettierrc.json` to enforce consistent code formatting across the project. - Updated unit and e2e test files to address formatting issues, improve readability, and ensure alignment with the new Prettier configuration.
139 lines
3.6 KiB
TypeScript
139 lines
3.6 KiB
TypeScript
import { expect, type Locator, type Page, type Request, type Response } from "@playwright/test";
|
|
|
|
const CRITICAL_RESOURCE_TYPES = new Set(["document", "stylesheet", "script", "image", "font"]);
|
|
const ABORTED_REQUEST_PATTERNS = ["ERR_ABORTED", "NS_BINDING_ABORTED"];
|
|
const BENIGN_PAGE_ERRORS = ["ResizeObserver loop limit exceeded"];
|
|
|
|
function isSameOrigin(url: string, baseOrigin: string) {
|
|
try {
|
|
return new URL(url).origin === baseOrigin;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function isCriticalResource(url: string, resourceType: string) {
|
|
if (CRITICAL_RESOURCE_TYPES.has(resourceType)) {
|
|
return true;
|
|
}
|
|
|
|
try {
|
|
const pathname = new URL(url).pathname;
|
|
return /manifest(\.webmanifest|\.json)$/i.test(pathname);
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export function attachPageHealthGuards(page: Page, baseURL: string) {
|
|
const baseOrigin = new URL(baseURL).origin;
|
|
const pageErrors: string[] = [];
|
|
const failedResources: string[] = [];
|
|
|
|
const onPageError = (error: Error) => {
|
|
if (BENIGN_PAGE_ERRORS.some((pattern) => error.message.includes(pattern))) {
|
|
return;
|
|
}
|
|
|
|
pageErrors.push(error.message);
|
|
};
|
|
|
|
const onRequestFailed = (request: Request) => {
|
|
const errorText = request.failure()?.errorText || "Request failed";
|
|
if (ABORTED_REQUEST_PATTERNS.some((pattern) => errorText.includes(pattern))) {
|
|
return;
|
|
}
|
|
|
|
if (!isSameOrigin(request.url(), baseOrigin)) {
|
|
return;
|
|
}
|
|
|
|
const resourceType = request.resourceType();
|
|
if (!isCriticalResource(request.url(), resourceType)) {
|
|
return;
|
|
}
|
|
|
|
failedResources.push(`${resourceType} ${request.url()} (${errorText})`);
|
|
};
|
|
|
|
const onResponse = (response: Response) => {
|
|
if (response.status() < 400) {
|
|
return;
|
|
}
|
|
|
|
const request = response.request();
|
|
if (!isSameOrigin(response.url(), baseOrigin)) {
|
|
return;
|
|
}
|
|
|
|
const resourceType = request.resourceType();
|
|
if (!isCriticalResource(response.url(), resourceType)) {
|
|
return;
|
|
}
|
|
|
|
failedResources.push(`${response.status()} ${resourceType} ${response.url()}`);
|
|
};
|
|
|
|
page.on("pageerror", onPageError);
|
|
page.on("requestfailed", onRequestFailed);
|
|
page.on("response", onResponse);
|
|
|
|
return {
|
|
async expectHealthy() {
|
|
expect(pageErrors, "Unexpected uncaught browser errors were recorded.").toEqual([]);
|
|
expect(failedResources, "Critical same-origin resources failed to load.").toEqual([]);
|
|
},
|
|
dispose() {
|
|
page.off("pageerror", onPageError);
|
|
page.off("requestfailed", onRequestFailed);
|
|
page.off("response", onResponse);
|
|
},
|
|
};
|
|
}
|
|
|
|
export async function expectBodyHasContent(page: Page, minimumLength = 20) {
|
|
await expect
|
|
.poll(
|
|
async () => {
|
|
const text = await page
|
|
.locator("body")
|
|
.innerText()
|
|
.catch(() => "");
|
|
return text.replace(/\s+/g, " ").trim().length;
|
|
},
|
|
{ timeout: 15_000 }
|
|
)
|
|
.toBeGreaterThan(minimumLength);
|
|
}
|
|
|
|
export async function settlePage(page: Page) {
|
|
await page.waitForLoadState("domcontentloaded");
|
|
await page.waitForLoadState("networkidle", { timeout: 10_000 }).catch(() => {});
|
|
}
|
|
|
|
export async function expectOneVisible(locators: Locator[], timeout = 15_000) {
|
|
await expect
|
|
.poll(
|
|
async () => {
|
|
for (const locator of locators) {
|
|
if (await locator.isVisible().catch(() => false)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
},
|
|
{ timeout }
|
|
)
|
|
.toBe(true);
|
|
}
|
|
|
|
export function requiredEnv(name: string) {
|
|
const value = process.env[name];
|
|
if (!value) {
|
|
throw new Error(`${name} is required for the live smoke gate.`);
|
|
}
|
|
|
|
return value;
|
|
}
|