76 lines
5.8 KiB
TypeScript
76 lines
5.8 KiB
TypeScript
/** CI-only checks against the disposable stack, never against personal documents. */
|
|
import assert from 'node:assert/strict';
|
|
import { chromium, type Request } from 'playwright-core';
|
|
import { visitBrowserDocument } from './brand-navigation.mjs';
|
|
export async function verifyBrandUI(base: string) {
|
|
for (const prefix of process.env.VERIFY_CANVAS_UI === '1' ? ['', '/studio'] : ['']) {
|
|
const icon = await fetch(`${base}${prefix}/favicon.ico`);
|
|
assert.equal(icon.status, 200);
|
|
const bytes = new Uint8Array(await icon.arrayBuffer());
|
|
assert.deepEqual([...bytes.slice(0, 4)], [0, 0, 1, 0]);
|
|
assert.equal(bytes[4], 7, 'favicon includes seven resolutions');
|
|
for (const file of ['logo-light.svg', 'logo-dark.svg', 'icon-dark.svg']) {
|
|
const image = await fetch(`${base}${prefix}/brand/${file}`);
|
|
assert.equal(image.status, 200, prefix + '/' + file);
|
|
assert.match(await image.text(), /<svg/);
|
|
}
|
|
}
|
|
const browser = await chromium.launch({ executablePath: process.env.CHROMIUM_PATH || '/usr/bin/chromium', headless: true, args: ['--no-sandbox', '--disable-dev-shm-usage'] });
|
|
try {
|
|
const page = await browser.newPage({ viewport: { width: 1440, height: 960 } });
|
|
page.setDefaultTimeout(60000);page.setDefaultNavigationTimeout(60000);
|
|
const failures: string[] = [], scripts: {path:string;status?:number;ms:number;error?:string}[] = [];
|
|
const scriptStarted = new WeakMap<Request,number>();
|
|
page.on('request', request => { if(request.resourceType()==='script') scriptStarted.set(request,Date.now()); });
|
|
page.on('response', response => { if(response.request().resourceType()==='script') { scripts.push({path:new URL(response.url()).pathname,status:response.status(),ms:Date.now()-(scriptStarted.get(response.request())??Date.now())}); if(scripts.length>24)scripts.shift(); } });
|
|
page.on('pageerror', error => failures.push(error.message));
|
|
page.on('requestfailed', request => { failures.push(new URL(request.url()).pathname + ': ' + request.failure()?.errorText); if(request.resourceType()==='script') scripts.push({path:new URL(request.url()).pathname,ms:Date.now()-(scriptStarted.get(request)??Date.now()),error:request.failure()?.errorText}); });
|
|
const visit = async (path: string, theme: string) => {
|
|
const started = Date.now();
|
|
try {
|
|
await visitBrowserDocument(page, base + path);
|
|
await page.waitForFunction(expected => document.documentElement.dataset.theme === expected, theme);
|
|
console.log('Brand UI route ready:', path, theme, Date.now() - started);
|
|
} catch (error) {
|
|
console.error('Brand UI navigation diagnostics', JSON.stringify({path, url:page.url(), elapsedMs:Date.now()-started, failures:failures.slice(-20), scripts:scripts.slice(-24), controls:await page.locator('[data-theme-ready]').evaluateAll(elements=>elements.map(el=>({ready:el.getAttribute('data-theme-ready'),display:getComputedStyle(el).display}))), body:(await page.locator('body').innerText({timeout:3000}).catch(()=>'unavailable')).slice(0,1200)}));
|
|
throw error;
|
|
}
|
|
};
|
|
const background = (selector: string) => page.locator(selector).first().evaluate(el => getComputedStyle(el).backgroundColor);
|
|
await visit('/', 'home-light');
|
|
assert.equal(await background('body'), 'rgb(255, 246, 239)');
|
|
assert.ok((await page.title()).includes('啡跃'));
|
|
assert.ok((await page.locator('link[rel="icon"]').all()).length >= 2);
|
|
assert.equal(await page.locator('.endoleap-mark-light').first().isVisible(), true);
|
|
await page.getByRole('button', { name: '切换明暗主题', exact: true }).first().click();
|
|
await page.waitForFunction(() => document.documentElement.dataset.theme === 'home-dark');
|
|
assert.equal(await background('body'), 'rgb(28, 24, 22)');
|
|
assert.equal(await page.locator('.endoleap-mark-dark').first().isVisible(), true);
|
|
await visit('/account', 'home-dark');
|
|
if (process.env.VERIFY_CANVAS_UI === '1') {
|
|
await visit('/graphic', 'canvas-dark');
|
|
assert.equal(await background('body'), 'rgb(20, 20, 20)');
|
|
assert.equal(await page.locator('[class*="boardWrap"] > div').first().evaluate(el => getComputedStyle(el).backgroundColor), 'rgb(255, 255, 255)', 'artwork stays white in dark chrome');
|
|
await page.getByRole('button', { name: '切换明暗主题', exact: true }).first().click();
|
|
await page.waitForFunction(() => document.documentElement.dataset.theme === 'canvas-light');
|
|
await visit('/infinite_canvas', 'canvas-light');
|
|
assert.equal(await background('[class*="viewport"]'), 'rgb(250, 250, 250)');
|
|
} else {
|
|
await page.getByRole('button', { name: '切换明暗主题', exact: true }).first().click();
|
|
}
|
|
await visit('/', 'home-light');
|
|
await page.setViewportSize({ width: 390, height: 844 });
|
|
assert.equal(await page.locator('.endoleap-mark-light').first().isVisible(), true);
|
|
assert.ok(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1), 'mobile home has no horizontal overflow');
|
|
for (const width of [390, 320]) {
|
|
await page.setViewportSize({ width, height: 844 });
|
|
assert.ok(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1), 'Editorial home fits narrow screens');
|
|
const entry = await page.getByRole('navigation', {name: '主导航'}).getByRole('link', {name: /进入工作台/}).boundingBox();
|
|
assert.ok(entry && entry.x >= 0 && entry.x + entry.width <= width, 'Workspace entry is visible, not clipped');
|
|
assert.equal(await page.getByRole('link', {name: /去知识空间逛逛/}).getAttribute('href'), '/documents');
|
|
assert.equal(await page.getByRole('link', {name: '开始创作', exact: true}).getAttribute('href'), '/account?next=/workspace');
|
|
}
|
|
console.log('Endoleap logos, both favicon paths, persisted home/canvas themes and artwork color isolation passed.');
|
|
} finally { await browser.close(); }
|
|
}
|