| index 59058f8..ea14e9e 100644 |
| --- a/test/browser/call.mjs |
| +++ b/test/browser/call.mjs |
| @@ -4330,7 +4330,8 @@ const run = { |
| check(await waitStatus(page, 'You have been signed out.', 10000), |
| 'the signed-out room says so — status: ' + (await statusText(page))); |
| const href = await page.locator('#status a').first().getAttribute('href'); |
| - check(href === '/signin', 'the signed-out room offers the way back in (' + href + ')'); |
| + // Back in, and back to this meeting's holding screen (spec §9.4). |
| + check(href === '/signin?return_to=' + encodeURIComponent('/m/' + sqid + '/join'), 'the signed-out room offers the way back in, to this meeting (' + href + ')'); |
| |
| await ctx.close(); |
| |
| @@ -4974,6 +4975,79 @@ const run = { |
| // white at 4.5:1 or better over white, black and busy video, with and |
| // without its blur; and with no script the room still reads sensibly |
| // inside the window. CALLS_WINDOW_SNAPSHOTS=<dir> keeps pictures. |
| + // Shared chrome R2 (estilo spec/shared-chrome): the holding screen and |
| + // the room hold still while they settle, and the room's title menu |
| + // speaks the account menu's words. The room used to paint a lone self |
| + // tile at filmstrip size and then fill the stage with it, a layout |
| + // shift of 0.43–0.80 on some joins, so the room is loaded eight times. |
| + async chrome() { |
| + // The audit's own viewport, where both shifts were measured. |
| + const ctx = await newContext('member A', { cookie: cookieA, options: { viewport: { width: 1440, height: 900 } } }); |
| + await ctx.addInitScript(() => { |
| + window.__cls = 0; |
| + window.__shifts = []; |
| + new PerformanceObserver((l) => { |
| + for (const e of l.getEntries()) { |
| + if (e.hadRecentInput) continue; |
| + window.__cls += e.value; |
| + window.__shifts.push({ v: +e.value.toFixed(4), t: Math.round(e.startTime), src: (e.sources || []).map((s) => (s.node && (s.node.id || s.node.className)) || '?').join(',') }); |
| + } |
| + }).observe({ type: 'layout-shift', buffered: true }); |
| + }); |
| + const page = await ctx.newPage(); |
| + const cls = () => page.evaluate(() => ({ cls: window.__cls, shifts: window.__shifts })); |
| + |
| + await page.goto(origin + '/m/' + sqid + '/join'); |
| + const ready = await until(async () => await page.locator('#join').isEnabled(), 15000); |
| + check(ready, 'the holding screen is ready — status: ' + (await page.locator('#status').textContent())); |
| + await new Promise((r) => setTimeout(r, 1000)); |
| + const holding = await cls(); |
| + check(holding.cls < 0.01, 'the holding screen holds still while its devices arrive (CLS ' + holding.cls.toFixed(4) + ' ' + JSON.stringify(holding.shifts) + ')'); |
| + check(await page.locator('header[estilo-bar]').count() === 1, 'the holding screen wears the estilo bar'); |
| + |
| + await Promise.all([page.waitForURL('**/m/' + sqid), page.click('#join')]); |
| + await page.waitForFunction(() => !!window.meet, null, { timeout: 20000 }); |
| + check(await until(async () => (await state(page)).admitted, 20000), 'joined from the holding screen'); |
| + await new Promise((r) => setTimeout(r, 1500)); |
| + const joined = await cls(); |
| + check(joined.cls < 0.01, 'holding screen → room holds still (CLS ' + joined.cls.toFixed(4) + ' ' + JSON.stringify(joined.shifts) + ')'); |
| + |
| + // The title menu: the account menu's order and words. |
| + await page.mouse.move(200, 200); |
| + await page.click('#call-nav > summary'); |
| + const menu = (await page.locator('.calls-nav-menu').innerText()).split('\n').map((l) => l.trim()).filter(Boolean); |
| + const want = ['Signed in as', 'Meetings', 'Settings', 'Help', 'Open source', 'Sign out']; |
| + const order = want.map((w) => menu.indexOf(w)); |
| + check(order.every((i, n) => i >= 0 && (n === 0 || i > order[n - 1])), 'the room menu reads ' + want.join(' / ') + ' in that order (' + JSON.stringify(menu) + ')'); |
| + check(await page.getByText('Sign out', { exact: true }).count() === 1, 'the room has exactly one Sign out'); |
| + const onTop = await page.evaluate(() => { |
| + const m = document.querySelector('.calls-nav-menu'); |
| + const r = m.getBoundingClientRect(); |
| + const hit = document.elementFromPoint(r.left + r.width / 2, r.top + 12); |
| + return !!hit && m.contains(hit); |
| + }); |
| + check(onTop, 'the open title menu is above the notices (r3 audit C3)'); |
| + await page.click('#call-nav > summary'); |
| + await leaveRoom(page); |
| + |
| + // The room loaded straight from the list, eight times: the shift |
| + // came on some joins and not others. |
| + let worst = { cls: 0, shifts: [] }; |
| + for (let i = 0; i < 8; i++) { |
| + await page.goto(origin + '/'); |
| + await page.goto(origin + '/m/' + sqid); |
| + await page.waitForFunction(() => !!window.meet, null, { timeout: 20000 }); |
| + await until(async () => (await state(page)).admitted, 20000); |
| + await new Promise((r) => setTimeout(r, 1200)); |
| + const c = await cls(); |
| + if (c.cls >= worst.cls) worst = c; |
| + await leaveRoom(page); |
| + } |
| + check(worst.cls < 0.01, 'the room holds still as its tiles arrive, eight loads from the list (worst CLS ' + worst.cls.toFixed(4) + ' ' + JSON.stringify(worst.shifts) + ')'); |
| + check(errorsFor('member A') === '', 'no console errors (' + errorsFor('member A') + ')'); |
| + await ctx.close(); |
| + }, |
| + |
| async window() { |
| const out = process.env.CALLS_WINDOW_SNAPSHOTS; |
| if (out) mkdirSync(out, { recursive: true }); |