Files
imagefind/ui-prototype/player-audit.mjs
T

106 lines
5.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import fs from "node:fs";
import path from "node:path";
import assert from "node:assert/strict";
import { createRequire } from "node:module";
const require = createRequire(new URL("../frontend/package.json", import.meta.url));
const { chromium } = require("playwright");
const baseUrl = process.env.PROTOTYPE_URL || "http://127.0.0.1:4178";
const output = path.resolve(process.env.PLAYER_AUDIT_DIR || ".artifacts/player-round-1");
const cases = [
{ name: "mobile-390", width: 390, height: 844 },
{ name: "desktop-1440", width: 1440, height: 900 }
];
fs.mkdirSync(output, { recursive: true });
const browser = await chromium.launch({ headless: true });
const report = [];
for (const profile of cases) {
const context = await browser.newContext({ viewport: profile, locale: "zh-CN" });
const page = await context.newPage();
const errors = [];
page.on("pageerror", error => errors.push(`pageerror: ${error.message}`));
page.on("console", message => message.type() === "error" && errors.push(`console: ${message.text()}`));
await page.goto(`${baseUrl}/#/player/v1`, { waitUntil: "networkidle" });
const geometry = await page.evaluate(() => {
const stage = document.querySelector(".player-stage-v2").getBoundingClientRect();
const back = document.querySelector('.player-topbar button[aria-label="返回"]').getBoundingClientRect();
const fullscreen = document.querySelector('.player-controls-row button[aria-label="全屏"]').getBoundingClientRect();
const visibleControls = [...document.querySelectorAll(".player-stage-v2 button")].filter(button => {
const box = button.getBoundingClientRect();
const style = getComputedStyle(button);
return style.display !== "none" && style.visibility !== "hidden" && box.width > 0 && box.height > 0;
});
return {
viewportWidth: innerWidth,
documentWidth: document.documentElement.scrollWidth,
stage: { x: stage.x, width: stage.width, height: stage.height, right: stage.right },
ratio: stage.width / stage.height,
backVisible: back.left >= 0 && back.right <= innerWidth,
fullscreenVisible: fullscreen.left >= 0 && fullscreen.right <= innerWidth,
tapHighlight: getComputedStyle(document.querySelector(".main-play")).webkitTapHighlightColor,
controlsBelow44: visibleControls.filter(button => {
const box = button.getBoundingClientRect();
return box.width < 44 || box.height < 44;
}).length
};
});
assert.equal(geometry.documentWidth, geometry.viewportWidth, `${profile.name} 存在横向溢出`);
assert.ok(Math.abs(geometry.ratio - 16 / 9) < 0.02, `${profile.name} 播放舞台不是 16:9`);
assert.equal(geometry.backVisible, true, `${profile.name} 返回按钮被裁切`);
assert.equal(geometry.fullscreenVisible, true, `${profile.name} 全屏按钮被裁切`);
if (profile.width <= 720) assert.equal(geometry.controlsBelow44, 0, `${profile.name} 存在小于 44px 的播放器按钮`);
if (profile.width <= 720) assert.ok(geometry.tapHighlight === "rgba(0, 0, 0, 0)" || geometry.tapHighlight === "transparent", `${profile.name} 仍存在系统蓝色点击高亮`);
assert.equal(await page.locator(".rate-control").textContent(), "1×");
await page.screenshot({ path: path.join(output, `${profile.name}-player.png`), fullPage: true });
await page.locator(".player-tap-surface").click({ position: { x: 10, y: 100 } });
await page.locator(".player-stage-v2.controls-hidden").waitFor({ state: "visible" });
await page.waitForTimeout(150);
await page.getByRole("button", { name: "显示播放控件" }).click({ position: { x: 10, y: 100 } });
await page.locator(".player-stage-v2.controls-visible").waitFor({ state: "visible" });
const beforeSkip = Number(await page.locator('.timeline input[type="range"]').inputValue());
await page.getByRole("button", { name: "快进 10 秒" }).click();
const afterSkip = Number(await page.locator('.timeline input[type="range"]').inputValue());
assert.ok(afterSkip > beforeSkip, `${profile.name} 快进没有更新进度`);
await page.locator(".rate-control").click();
assert.equal(await page.locator(".rate-control").textContent(), "1.25×");
await page.getByRole("button", { name: "时间点", exact: true }).click();
await page.locator('.player-drawer[aria-label="视频时间点"]').waitFor({ state: "visible" });
await page.waitForTimeout(250);
await page.screenshot({ path: path.join(output, `${profile.name}-markers.png`), fullPage: true });
await page.keyboard.press("Escape");
await page.locator(".player-drawer").waitFor({ state: "detached" });
await page.getByRole("button", { name: "合集目录" }).click();
await page.locator('.player-drawer[aria-label="合集目录"]').waitFor({ state: "visible" });
await page.locator(".player-panel-scrim").click({ position: { x: 4, y: 4 } });
await page.locator(".player-drawer").waitFor({ state: "detached" });
await page.locator(".player-title-row").getByRole("button", { name: "喜欢" }).click();
assert.equal(await page.locator(".player-title-row").getByRole("button", { name: "喜欢" }).getAttribute("aria-pressed"), "true");
if (profile.width <= 720) {
await page.locator(".main-play").click();
await page.waitForTimeout(3750);
await page.locator(".player-stage-v2.controls-hidden").waitFor({ state: "visible" });
await page.locator(".player-tap-surface").click({ position: { x: 10, y: 100 } });
await page.locator(".main-play").click();
await page.waitForTimeout(3750);
assert.equal(await page.locator(".player-stage-v2").getAttribute("class").then(value => value.includes("controls-visible")), true, "暂停后控件不应自动隐藏");
}
report.push({ ...profile, geometry, errors: [...new Set(errors)] });
await context.close();
}
await browser.close();
fs.writeFileSync(path.join(output, "report.json"), JSON.stringify(report, null, 2));
console.log(JSON.stringify(report, null, 2));
assert.equal(report.flatMap(item => item.errors).length, 0, "播放器运行时出现控制台错误");