Skip to content
File

Blob: worker/tests/layout.mjs

javascript119 lines
1// Visual/accessibility states with an explicitly offline board fixture.
2import assert from 'node:assert/strict';
3import fs from 'node:fs/promises';
4import { chromium } from 'playwright-core';
5await fs.mkdir('../artifacts/worker', { recursive: true });
6const browser = await chromium.connectOverCDP(
7 process.env.RADIO_CDP_URL ?? 'http://127.0.0.1:19223',
8);
9const contexts = [],
10 result = { viewports: [] },
11 errors = [];
12try {
13 for (const width of [320, 390, 768, 1024, 1380]) {
14 const context = await browser.newContext({
15 viewport: { width, height: 900 },
16 reducedMotion: 'reduce',
17 });
18 contexts.push(context);
19 await context.route('**/api/status', (route) =>
20 route.fulfill({ json: { online: false, viewers: 0, controller: null, generation: null } }),
21 );
22 const page = await context.newPage();
23 page.on('pageerror', (e) => errors.push(e.message));
24 await page.goto(process.env.RADIO_TEST_URL ?? 'http://localhost:11880');
25 await page.getByText('Board offline.', { exact: false }).waitFor();
26 assert.equal(await page.locator('#connect').isDisabled(), true);
27 assert.equal(await page.getByTestId('chip-temperature').textContent(), '–– °C');
28 await page.getByRole('button', { name: 'Spectrum', exact: true }).click();
29 await page.getByRole('heading', { name: 'Live spectrum' }).waitFor();
30 await page.evaluate(
31 () => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve))),
32 );
33 const metrics = await page.evaluate(() => ({
34 width: innerWidth,
35 overflow: document.documentElement.scrollWidth > innerWidth,
36 allButtonWidths: [...document.querySelectorAll('button')]
37 .filter((x) => x.checkVisibility())
38 .map((x) => Math.round(x.getBoundingClientRect().width)),
39 motion: [...document.querySelectorAll('.wire-flow')].map(
40 (x) => getComputedStyle(x).animationName,
41 ),
42 wires: [...document.querySelectorAll('.wire-track')].map((path) => {
43 const [branch, segment] = path.parentElement.dataset.wire.split('-');
44 const mobile = branch === 'wifi';
45 const startAnchor = mobile
46 ? 'source-wifi'
47 : segment === 'source'
48 ? `source-${branch}`
49 : `sfu-${branch}`;
50 const endAnchor = mobile
51 ? 'sfu-input'
52 : segment === 'source'
53 ? `sfu-${branch}`
54 : `leaf-${branch}`;
55 const start = path.getPointAtLength(0).matrixTransform(path.getScreenCTM());
56 const end = path
57 .getPointAtLength(path.getTotalLength())
58 .matrixTransform(path.getScreenCTM());
59 const source = document
60 .querySelector(`[data-anchor="${startAnchor}"]`)
61 .getBoundingClientRect();
62 const target = document
63 .querySelector(`[data-anchor="${endAnchor}"]`)
64 .getBoundingClientRect();
65 return {
66 branch,
67 segment,
68 startDistance: Math.hypot(
69 start.x - source.right,
70 start.y - (source.top + source.height / 2),
71 ),
72 endDistance: Math.hypot(
73 end.x - (mobile ? target.left + target.width / 2 : target.left),
74 end.y - (mobile ? target.top : target.top + target.height / 2),
75 ),
76 };
77 }),
78 }));
79 assert.equal(metrics.overflow, false);
80 assert.ok(metrics.motion.every((x) => x === 'none'));
81 assert.equal(metrics.wires.length, width >= 1024 ? 6 : 1);
82 assert.ok(metrics.wires.every((wire) => wire.startDistance < 0.75 && wire.endDistance < 0.75));
83 assert.equal(
84 metrics.wires.filter((wire) => wire.branch === 'robot').length,
85 width >= 1024 ? 2 : 0,
86 );
87 result.viewports.push({ width, overflow: metrics.overflow, wiresAligned: true });
88 await page.getByRole('button', { name: 'Close signal explanation' }).click();
89 await page.evaluate(() => window.scrollTo(0, 0));
90 await page.keyboard.press('Control+Home');
91 await page.locator('body').click({ position: { x: 5, y: 5 } });
92 await page.keyboard.press('Tab');
93 assert.equal(
94 await page.evaluate(() => document.activeElement?.textContent),
95 'Skip to the exhibit',
96 );
97 await page.keyboard.press('Enter');
98 assert.equal(new URL(page.url()).hash, '#exhibit');
99 result.keyboard = true;
100 if (width === 390)
101 await page.screenshot({ path: '../artifacts/worker/mobile-offline.png', fullPage: true });
102 }
103 assert.deepEqual(errors, []);
104 result.passed = true;
105} catch (error) {
106 result.passed = false;
107 result.error = error.message;
108 process.exitCode = 1;
109} finally {
110 result.errors = errors;
111 await fs.writeFile(
112 '../artifacts/worker/layout-result.json',
113 JSON.stringify(result, null, 2) + '\n',
114 );
115 console.log(JSON.stringify(result));
116 for (const c of contexts) await c.close();
117 await browser.close();
118}