const { test, expect } = require('@playwright/test'); const { readFileSync } = require('node:fs'); const { openClassic, showPlayingFixture } = require('./helpers/classic-fixture'); const before = process.env.CAPTURE_BEFORE === '1'; const phoneCss = readFileSync('frontend/styles.css', 'utf8').replaceAll('(display-mode: standalone)', '(min-width: 0px)').replaceAll('env(safe-area-inset-bottom)', '34px'); async function setup(page, width, layout = 'glass-stage') { await page.setViewportSize({ width, height: 844 }); if (width < 900) await page.route('**/styles.css', r => r.fulfill({ contentType: 'text/css', body: phoneCss })); await openClassic(page, { populated: true, standalone: width < 900, settings: { autoPreload: false } }); await showPlayingFixture(page); await page.addStyleTag({ content: '*,*::before,*::after{animation:none!important;transition:none!important;caret-color:transparent!important}' }); await page.evaluate(layout => { data.settings.layout = layout; applyAppearance(); document.getElementById('notesPanel').classList.remove('hidden'); relatedVideos = data.history.slice(1, 4); renderRelated(); document.getElementById('miniTitle').textContent = current.meta.title; document.getElementById('miniChannel').textContent = current.meta.channel; document.getElementById('miniProgressFill').style.width = '25%'; SectionRail.refresh(); }, layout); await page.evaluate(async () => { await document.fonts.ready; await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r))); }); } for (const width of [390, 1440]) test(`panel rail and bottom clearance at ${width}`, async ({ page }, info) => { await setup(page, width); // The desktop rail was hidden in the old build; expose it for baseline evidence. if (before && width === 1440) await page.addStyleTag({ content: 'html[data-layout="glass-stage"] #sectionRail{display:flex!important}' }); for (const section of ['player', 'meta', 'notes', 'related', 'list']) { await page.locator(`#sectionRail [data-sec="${section}"]`).click(); await page.evaluate(() => { document.getElementById('miniBar').classList.remove('hidden'); updateBottomChrome(); }); await page.waitForTimeout(100); if (!before) { const metrics = await page.evaluate(() => { const rail = document.getElementById('sectionRail'), header = document.querySelector('#listPane .list-header'); const r = rail.getBoundingClientRect(), h = header.getBoundingClientRect(); const m = document.getElementById('miniBar').getBoundingClientRect(), p = document.getElementById('miniProgress').getBoundingClientRect(), nav = document.getElementById('bottomNav').getBoundingClientRect(); return { parent: rail.parentElement.id, railBottom: r.bottom, headingTop: h.top, miniBottom: m.bottom, progressBottom: p.bottom, navTop: nav.height ? nav.top : innerHeight, overflow: getComputedStyle(document.getElementById('miniBar')).overflow }; }); expect(metrics.parent).toBe('listPane'); expect(metrics.railBottom).toBeLessThanOrEqual(metrics.headingTop); expect(metrics.miniBottom).toBeLessThanOrEqual(metrics.navTop); expect(metrics.progressBottom).toBeLessThanOrEqual(metrics.miniBottom); expect(metrics.overflow).toBe('hidden'); } await page.screenshot({ path: `/tmp/done9-${before ? 'before' : 'after'}-${info.project.name}-${width}-${section}.png` }); } if (!before) { await page.evaluate(() => { const body = document.querySelector('.body'); body.scrollTop = body.scrollHeight; const cards = document.getElementById('cards'); cards.scrollTop = cards.scrollHeight; document.getElementById('miniBar').classList.remove('hidden'); updateBottomChrome(); }); await page.waitForTimeout(100); const tail = await page.locator('#cards .card').last().boundingBox(), mini = await page.locator('#miniBar').boundingBox(); expect(tail.y + tail.height).toBeLessThanOrEqual(mini.y - 8); } }); module.exports = { setup }; for (const width of [390, 1440]) test(`Classic panel layout unchanged at ${width}`, async ({ page, context }, info) => { const baseline = await context.newPage(); await baseline.route('**/theme-glass-panel.css', r => r.fulfill({ contentType: 'text/css', body: '' })); await baseline.route('**/glass-panel-layout.js', r => r.fulfill({ contentType: 'text/javascript', body: '' })); await setup(baseline, width, 'classic'); await setup(page, width, 'classic'); const geometry = p => p.locator('#listPane,#playerPane,#sectionRail,#miniBar,.notes-tabs').evaluateAll(nodes => nodes.map(el => { const r = el.getBoundingClientRect(), s = getComputedStyle(el); return [el.id, r.x,r.y,r.width,r.height,s.padding,s.position,s.overflow]; })); expect(await geometry(page)).toEqual(await geometry(baseline)); const a = await baseline.screenshot({ path: `/tmp/done9-classic-before-${info.project.name}-${width}.png` }); const b = await page.screenshot({ path: `/tmp/done9-classic-after-${info.project.name}-${width}.png` }); const { differentPixels } = require('./helpers/png-diff'); expect(differentPixels(a, b)).toBeLessThanOrEqual(32); await baseline.close(); });