Files
imagefind/scripts/live-0516-ui-acceptance.mjs
T

78 lines
3.9 KiB
JavaScript

#!/usr/bin/env node
import { mkdir } from "node:fs/promises";
import process from "node:process";
import playwright from "../frontend/node_modules/@playwright/test/index.js";
const { chromium } = playwright;
const chunks = [];
for await (const chunk of process.stdin) chunks.push(chunk);
const password = Buffer.concat(chunks).toString("utf8").trim();
if (!password) throw new Error("password is required on stdin");
const baseURL = String(process.argv[2] || "").replace(/\/$/, "");
if (!baseURL) throw new Error("base URL is required");
const output = new URL("../.playwright-results/live-0516/", import.meta.url).pathname;
await mkdir(output, { recursive: true });
const browser = await chromium.launch({ headless: true });
const reports = [];
try {
for (const viewport of [{ name: "desktop", width: 1440, height: 900 }, { name: "mobile", width: 390, height: 844 }]) {
const context = await browser.newContext({ viewport });
const page = await context.newPage();
await page.goto(baseURL, { waitUntil: "domcontentloaded" });
const passwordInput = page.getByLabel("管理员密码");
if (await passwordInput.isVisible().catch(() => false)) {
await passwordInput.fill(password);
const remember = page.locator('.remember-device input[type="checkbox"]');
if (await remember.isChecked()) await remember.uncheck();
await page.getByRole("button", { name: "登录", exact: true }).click();
}
try {
await page.locator(".app-shell").waitFor({ state: "visible", timeout: 30_000 });
await page.locator(".video-card,.home-empty").first().waitFor({ state: "visible", timeout: 30_000 });
} catch (error) {
const message = (await page.locator("body").innerText()).replace(/\s+/g, " ").slice(0, 500);
throw new Error(`${viewport.name} app shell did not become ready: ${message}`, { cause: error });
}
await page.locator(".nav-item-2").click();
await page.locator(".search-coverage").waitFor({ state: "visible", timeout: 15_000 });
await page.waitForFunction(
() => !document.querySelector(".search-coverage")?.textContent?.includes("正在读取"),
undefined,
{ timeout: 30_000 },
);
const coverage = (await page.locator(".search-coverage").innerText()).replace(/\s+/g, " ").trim();
const searchGeometry = await page.evaluate(() => ({
viewport: innerWidth,
document: document.documentElement.scrollWidth,
content: document.querySelector(".content")?.scrollWidth || 0,
}));
if (searchGeometry.document > searchGeometry.viewport + 1 || searchGeometry.content > searchGeometry.viewport + 1) {
throw new Error(`${viewport.name} search page overflows horizontally: ${JSON.stringify(searchGeometry)}`);
}
await page.screenshot({ path: `${output}/${viewport.name}-search.png`, fullPage: true });
await page.locator(".nav-item-1").click();
await page.locator(".video-card").first().click();
await page.locator(".transcript-panel").waitFor({ state: "visible", timeout: 15_000 });
await page.locator(".transcript-toggle").click();
await page.waitForFunction(
() => !document.querySelector(".transcript-toggle")?.textContent?.includes("正在读取"),
undefined,
{ timeout: 30_000 },
);
const transcriptStatus = (await page.locator(".transcript-toggle").innerText()).replace(/\s+/g, " ").trim();
const transcriptItems = await page.locator(".transcript-list>button").count();
await page.screenshot({ path: `${output}/${viewport.name}-player.png`, fullPage: true });
reports.push({ viewport: viewport.name, coverage, transcriptStatus, transcriptItems, searchGeometry });
await page.evaluate(async () => {
const csrf = sessionStorage.getItem("imagefind:csrf") || "";
await fetch("/api/v1/auth/logout", { method: "POST", headers: { "X-CSRF-Token": csrf } });
});
await context.close();
}
} finally {
await browser.close();
}
console.log(JSON.stringify(reports, null, 2));