#!/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));