/** 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(), /(); 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(); } }