Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ export function browserFixtureFlags(platform = process.platform, environment: {
return platform === 'linux' && environment.CI === 'true' ? ['--no-sandbox'] : [];
}

/**
* Budget for one headless Chromium launch. A cold start on a loaded hosted
* runner (the full web UI suite runs in parallel) can exceed 20 s by itself.
*/
export const BROWSER_FIXTURE_LAUNCH_TIMEOUT_MS = 60_000;

/** Budget for a test that launches the browser fixture and reads its output. */
export const BROWSER_FIXTURE_TEST_TIMEOUT_MS = 90_000;

/** Chromium subprocesses can still flush their profile after the launcher exits. */
export async function cleanupBrowserFixture(directory: string): Promise<void> {
await rm(directory, { recursive: true, force: true, maxRetries: 10, retryDelay: 200 });
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @vitest-environment jsdom
import { browserFixtureFlags, cleanupBrowserFixture } from './browser-fixture.test-support';
import { BROWSER_FIXTURE_LAUNCH_TIMEOUT_MS, BROWSER_FIXTURE_TEST_TIMEOUT_MS, browserFixtureFlags, cleanupBrowserFixture } from './browser-fixture.test-support';
import React from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { existsSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
Expand Down Expand Up @@ -76,7 +76,7 @@ it.skipIf(!browser).each([
'--window-size=1000,1100', '--virtual-time-budget=200',
`--user-data-dir=${resolve(scratch, 'profile')}`, `--screenshot=${screenshot}`,
'--dump-dom', pathToFileURL(html).href,
], { encoding: 'utf8', windowsHide: true, timeout: 20000, stdio: 'pipe' });
], { encoding: 'utf8', windowsHide: true, timeout: BROWSER_FIXTURE_LAUNCH_TIMEOUT_MS, stdio: 'pipe' });
const result = JSON.parse(output.match(/<pre id="result">(.*?)<\/pre>/s)![1]);
expect(result.wrapped).toEqual(result.original);
expect(result.skinned).toEqual(result.original);
Expand All @@ -99,4 +99,4 @@ it.skipIf(!browser).each([
} finally {
await cleanupBrowserFixture(scratch);
}
}, 30000);
}, BROWSER_FIXTURE_TEST_TIMEOUT_MS);
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { compile } from 'sass';
import { expect, it } from 'vitest';
import { VisualSkinPackageParser } from './parser';
import { VisualSkinRuntime } from './runtime';
import { browserFixtureFlags, cleanupBrowserFixture, rehostSkinImages } from './browser-fixture.test-support';
import { BROWSER_FIXTURE_LAUNCH_TIMEOUT_MS, BROWSER_FIXTURE_TEST_TIMEOUT_MS, browserFixtureFlags, cleanupBrowserFixture, rehostSkinImages } from './browser-fixture.test-support';
import type { VisualSkinPackage } from './types';

const browser = process.env.OPENBITFUN_TEST_BROWSER ?? ['C:/Program Files/Google/Chrome/Application/chrome.exe', '/usr/bin/chromium'].find(existsSync);
Expand Down Expand Up @@ -60,7 +60,7 @@ it.skipIf(!browser)('reveals chat artwork while loading, preserving opaque defau
document.getElementById('result').textContent=JSON.stringify(results);
</script></body>`;
const file = resolve(scratch, 'history.html'); writeFileSync(file, html);
const output = execFileSync(browser!, [...browserFixtureFlags(), '--headless=new', '--disable-gpu', '--no-first-run', `--user-data-dir=${resolve(scratch, 'profile')}`, '--dump-dom', pathToFileURL(file).href], { windowsHide: true, encoding: 'utf8', timeout: 20000, stdio: 'pipe' });
const output = execFileSync(browser!, [...browserFixtureFlags(), '--headless=new', '--disable-gpu', '--no-first-run', `--user-data-dir=${resolve(scratch, 'profile')}`, '--dump-dom', pathToFileURL(file).href], { windowsHide: true, encoding: 'utf8', timeout: BROWSER_FIXTURE_LAUNCH_TIMEOUT_MS, stdio: 'pipe' });
const results = JSON.parse(output.match(/<pre id="result">(.*?)<\/pre>/s)![1]);
expect(results).toHaveLength(cases.length);
for (const result of results) {
Expand All @@ -75,4 +75,4 @@ it.skipIf(!browser)('reveals chat artwork while loading, preserving opaque defau
document.documentElement.removeAttribute('data-color-scheme');
await cleanupBrowserFixture(scratch);
}
}, 30000);
}, BROWSER_FIXTURE_TEST_TIMEOUT_MS);
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import sharp from 'sharp';
import { expect, it } from 'vitest';
import { VisualSkinRuntime } from './runtime';
import { VisualSkinPackageParser } from './parser';
import { browserFixtureFlags, cleanupBrowserFixture, rehostSkinImages } from './browser-fixture.test-support';
import { BROWSER_FIXTURE_LAUNCH_TIMEOUT_MS, BROWSER_FIXTURE_TEST_TIMEOUT_MS, browserFixtureFlags, cleanupBrowserFixture, rehostSkinImages } from './browser-fixture.test-support';

const browser = process.env.OPENBITFUN_TEST_BROWSER ?? ['C:/Program Files/Google/Chrome/Application/chrome.exe', '/usr/bin/chromium'].find(existsSync);
it.skipIf(!browser).each(['light','dark'])('keeps artwork clear behind glyphs and applies the shadow without changing boxes in %s', async mode => {
Expand All @@ -35,7 +35,7 @@ it.skipIf(!browser).each(['light','dark'])('keeps artwork clear behind glyphs an
document.documentElement.removeAttribute('data-openbitfun-skin-id');result.original=measure();document.documentElement.dataset.openbitfunSkinId='local';document.getElementById('result').textContent=JSON.stringify(result);
</script></body></html>`);
const args=[...browserFixtureFlags(), '--headless=new','--disable-gpu','--hide-scrollbars','--no-first-run',`--user-data-dir=${resolve(scratch,'profile')}`,'--window-size=500,400','--force-device-scale-factor=1','--virtual-time-budget=600',`--screenshot=${shot}`,'--dump-dom',pathToFileURL(file).href];
const out=execFileSync(browser!,args,{encoding:'utf8',windowsHide:true,timeout:20000,stdio:'pipe'});
const out=execFileSync(browser!,args,{encoding:'utf8',windowsHide:true,timeout: BROWSER_FIXTURE_LAUNCH_TIMEOUT_MS,stdio:'pipe'});
const result=JSON.parse(out.match(/<pre id="result">(.*?)<\/pre>/s)![1]);
expect(result.before).toEqual(result.original);expect(result.selectedShadow).toBe('none');expect(result.outsideTarget).not.toBe('reader');
const {data,info}=await sharp(readFileSync(shot)).removeAlpha().raw().toBuffer({resolveWithObject:true});
Expand All @@ -47,4 +47,4 @@ it.skipIf(!browser).each(['light','dark'])('keeps artwork clear behind glyphs an
expect(pixel(93,110)).toEqual([200,40,80]); // no plate or box-shadow around the reader
const glyphs=[];for(let y=80;y<100;y++)for(let x=100;x<220;x++)glyphs.push(pixel(x,y));expect(glyphs.some(p=>p.every(v=>v>230))).toBe(true);
}finally{runtime.dispose();document.body.innerHTML='';document.head.querySelectorAll('style').forEach(n=>n.remove());document.documentElement.removeAttribute('data-color-scheme');await cleanupBrowserFixture(scratch);}
},30000);
}, BROWSER_FIXTURE_TEST_TIMEOUT_MS);
10 changes: 5 additions & 5 deletions src/web-ui/src/infrastructure/visual-skin/materials.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { browserFixtureFlags, cleanupBrowserFixture } from './browser-fixture.test-support';
import { BROWSER_FIXTURE_LAUNCH_TIMEOUT_MS, BROWSER_FIXTURE_TEST_TIMEOUT_MS, browserFixtureFlags, cleanupBrowserFixture } from './browser-fixture.test-support';
// @vitest-environment jsdom
import { existsSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
import { execFileSync } from 'node:child_process';
Expand Down Expand Up @@ -135,7 +135,7 @@ it.skipIf(!browser).each([
}catch(error){document.getElementById('result').textContent=JSON.stringify({error:String(error),stack:error.stack});}})();
</script></body></html>`);
// Explicit input capabilities exercise both media-query branches on every host.
const output = execFileSync(browser!, [...browserFixtureFlags(), '--headless=new','--disable-gpu','--no-first-run',`--blink-settings=primaryPointerType=${pointer},primaryHoverType=${hover}`,`--user-data-dir=${resolve(scratch,'profile')}`,'--virtual-time-budget=500','--dump-dom',pathToFileURL(file).href], { windowsHide: true, encoding: 'utf8', timeout: 20000, stdio: 'pipe' });
const output = execFileSync(browser!, [...browserFixtureFlags(), '--headless=new','--disable-gpu','--no-first-run',`--blink-settings=primaryPointerType=${pointer},primaryHoverType=${hover}`,`--user-data-dir=${resolve(scratch,'profile')}`,'--virtual-time-budget=500','--dump-dom',pathToFileURL(file).href], { windowsHide: true, encoding: 'utf8', timeout: BROWSER_FIXTURE_LAUNCH_TIMEOUT_MS, stdio: 'pipe' });
const encoded = output.match(/<pre id="result">(.*?)<\/pre>/s)![1];
const results = JSON.parse(encoded);
expect(Array.isArray(results), JSON.stringify(results)).toBe(true);
Expand All @@ -152,7 +152,7 @@ it.skipIf(!browser).each([
else expect(results[index + 1].paint.item[2]).toBe(results[index].paint.item[2]);
}
} finally { await cleanupBrowserFixture(scratch); }
}, 30000);
}, BROWSER_FIXTURE_TEST_TIMEOUT_MS);

it.skipIf(!browser)('composes image alpha on the real Button paint layer without fading content or changing geometry', async () => {
const published = await import(/* @vite-ignore */ pathToFileURL(resolve('../../design-system/packages/ui/dist/index.js')).href);
Expand All @@ -176,12 +176,12 @@ it.skipIf(!browser)('composes image alpha on the real Button paint layer without
const track=document.querySelector('[data-openbitfun-part="track"]'),input=document.querySelector('input');result.checked=getComputedStyle(track).backgroundPosition;input.checked=false;result.unchecked=getComputedStyle(track).backgroundPosition;
document.documentElement.removeAttribute('data-openbitfun-skin-id');result.original=measure();document.documentElement.setAttribute('data-openbitfun-skin-id','material');document.getElementById('result').textContent=JSON.stringify(result);
</script></body></html>`);
const output = execFileSync(browser!, [...browserFixtureFlags(), '--headless=new','--disable-gpu','--hide-scrollbars','--no-first-run',`--user-data-dir=${resolve(scratch,'profile')}`,'--window-size=400,300','--force-device-scale-factor=1','--virtual-time-budget=500',`--screenshot=${screenshot}`,'--dump-dom',pathToFileURL(file).href], { windowsHide: true, encoding: 'utf8', timeout: 20000, stdio: 'pipe' });
const output = execFileSync(browser!, [...browserFixtureFlags(), '--headless=new','--disable-gpu','--hide-scrollbars','--no-first-run',`--user-data-dir=${resolve(scratch,'profile')}`,'--window-size=400,300','--force-device-scale-factor=1','--virtual-time-budget=500',`--screenshot=${screenshot}`,'--dump-dom',pathToFileURL(file).href], { windowsHide: true, encoding: 'utf8', timeout: BROWSER_FIXTURE_LAUNCH_TIMEOUT_MS, stdio: 'pipe' });
const result = JSON.parse(output.match(/<pre id="result">(.*?)<\/pre>/s)![1]);
expect(result.paint).toEqual(result.original); expect(result.opacity).toBe('1'); expect(result.textOpacity).toBe('1'); expect(result.textColor).toBe('rgb(204, 0, 0)'); expect(result.image).toContain('linear-gradient');
expect(result.checked).toBe('60% 60%'); expect(result.unchecked).toBe('10% 10%');
const { data, info } = await sharp(readFileSync(screenshot)).removeAlpha().raw().toBuffer({ resolveWithObject: true });
const pixel = [...data.subarray((26 * info.width + 100) * info.channels, (26 * info.width + 100) * info.channels + 3)];
pixel.forEach((value, i) => expect(Math.abs(value - [191,191,255][i]), JSON.stringify({pixel,result})).toBeLessThanOrEqual(2));
} finally { await cleanupBrowserFixture(scratch); }
}, 30000);
}, BROWSER_FIXTURE_TEST_TIMEOUT_MS);
6 changes: 3 additions & 3 deletions src/web-ui/src/infrastructure/visual-skin/paint.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @vitest-environment jsdom
import { browserFixtureFlags, cleanupBrowserFixture, rehostSkinImages } from './browser-fixture.test-support';
import { BROWSER_FIXTURE_LAUNCH_TIMEOUT_MS, BROWSER_FIXTURE_TEST_TIMEOUT_MS, browserFixtureFlags, cleanupBrowserFixture, rehostSkinImages } from './browser-fixture.test-support';
import { readFileSync, existsSync, mkdtempSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { tmpdir } from 'node:os';
Expand Down Expand Up @@ -119,13 +119,13 @@ describe('skin paint ownership', () => {
</script></body></html>`;
const path = resolve(scratch, 'paint.html');
writeFileSync(path, html);
const output = execFileSync(browser!, [...browserFixtureFlags(), '--headless=new', '--disable-gpu', '--no-first-run', '--disable-background-networking', '--disable-extensions', `--user-data-dir=${resolve(scratch, 'profile')}`, '--dump-dom', pathToFileURL(path).href], { windowsHide: true, encoding: 'utf8', timeout: 20000, stdio: 'pipe' });
const output = execFileSync(browser!, [...browserFixtureFlags(), '--headless=new', '--disable-gpu', '--no-first-run', '--disable-background-networking', '--disable-extensions', `--user-data-dir=${resolve(scratch, 'profile')}`, '--dump-dom', pathToFileURL(path).href], { windowsHide: true, encoding: 'utf8', timeout: BROWSER_FIXTURE_LAUNCH_TIMEOUT_MS, stdio: 'pipe' });
const result = JSON.parse(output.match(/<pre id="result">(.*?)<\/pre>/s)![1]);
const canvas = 'rgba(0, 0, 0, 0)';
expect(result).toEqual({ editor:'rgb(243, 237, 225)', monaco:canvas, group:canvas, widget:'rgb(10, 20, 30)', other:'rgb(10, 20, 30)', position:'absolute', zIndex:'7', backdrop:'auto', scrim:'auto', filter:owner === 'local' ? 'none' : 'blur(8px)', transcript:owner === 'local' ? canvas : 'rgb(10, 20, 30)',card:'rgb(10, 20, 30)',restored:'rgba(0, 0, 0, 0)' });
} finally {
await runtime.remove('paint');
await cleanupBrowserFixture(scratch);
}
}, 30000);
}, BROWSER_FIXTURE_TEST_TIMEOUT_MS);
});
10 changes: 5 additions & 5 deletions src/web-ui/src/infrastructure/visual-skin/presentation.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @vitest-environment jsdom
import { browserFixtureFlags, cleanupBrowserFixture, rehostSkinImages } from './browser-fixture.test-support';
import { BROWSER_FIXTURE_LAUNCH_TIMEOUT_MS, BROWSER_FIXTURE_TEST_TIMEOUT_MS, browserFixtureFlags, cleanupBrowserFixture, rehostSkinImages } from './browser-fixture.test-support';
import { existsSync, mkdtempSync, writeFileSync } from 'node:fs';
import { execFileSync } from 'node:child_process';
import { resolve } from 'node:path';
Expand Down Expand Up @@ -72,14 +72,14 @@ it.skipIf(!browser).each([1000, 700])('keeps the real welcome brand centered wit
document.getElementById('result').textContent=JSON.stringify({active,after:{x:after.x,y:after.y,width:after.width,height:after.height},background,logo:document.querySelector('.welcome-scene__logo').getBoundingClientRect().width,brand:document.getElementById('welcome-scene-title').textContent,heading:document.getElementById(root.getAttribute('aria-labelledby')).tagName});
</script></body></html>`;
const file=resolve(scratch,'welcome.html');writeFileSync(file,html);
const output=execFileSync(browser!,[...browserFixtureFlags(), '--headless=new','--disable-gpu','--no-first-run',`--user-data-dir=${resolve(scratch,'profile')}`,'--dump-dom',pathToFileURL(file).href],{windowsHide:true,encoding:'utf8',timeout:20000,stdio:'pipe'});
const output=execFileSync(browser!,[...browserFixtureFlags(), '--headless=new','--disable-gpu','--no-first-run',`--user-data-dir=${resolve(scratch,'profile')}`,'--dump-dom',pathToFileURL(file).href],{windowsHide:true,encoding:'utf8',timeout: BROWSER_FIXTURE_LAUNCH_TIMEOUT_MS,stdio:'pipe'});
const result=JSON.parse(output.match(/<pre id="result">(.*?)<\/pre>/s)![1]);
expect(result.heading).toBe('H1');expect(result.brand).toContain('OpenBitFun');expect(result.logo).toBe(56);
expect(result.background).toBe('rgba(0, 0, 0, 0)');expect(result.active).toEqual(result.after);
expect(result.active.x+result.active.width/2).toBeCloseTo(width/2,0);
expect(result.active.y).toBeLessThan(300);expect(result.active.y).toBeGreaterThan(100);
} finally { await cleanupBrowserFixture(scratch); }
},30000);
}, BROWSER_FIXTURE_TEST_TIMEOUT_MS);


it.skipIf(!browser)('retains readable settings, tables and overlays when a hidden welcome remains mounted', async () => {
Expand All @@ -94,8 +94,8 @@ it.skipIf(!browser)('retains readable settings, tables and overlays when a hidde
document.getElementById('viewport').setAttribute('data-openbitfun-state','empty');result.landing=style('frame');result.landingToolbar=style('toolbar');document.getElementById('viewport').removeAttribute('data-openbitfun-state');result.restored=style('frame');document.getElementById('result').textContent=JSON.stringify(result);
</script></body></html>`;
const file=resolve(scratch,'reading.html');writeFileSync(file,html);
const output=execFileSync(browser!,[...browserFixtureFlags(), '--headless=new','--disable-gpu','--no-first-run',`--user-data-dir=${resolve(scratch,'profile')}`,'--dump-dom',pathToFileURL(file).href],{windowsHide:true,encoding:'utf8',timeout:20000,stdio:'pipe'});
const output=execFileSync(browser!,[...browserFixtureFlags(), '--headless=new','--disable-gpu','--no-first-run',`--user-data-dir=${resolve(scratch,'profile')}`,'--dump-dom',pathToFileURL(file).href],{windowsHide:true,encoding:'utf8',timeout: BROWSER_FIXTURE_LAUNCH_TIMEOUT_MS,stdio:'pipe'});
const result=JSON.parse(output.match(/<pre id="result">(.*?)<\/pre>/s)![1]);
expect(result).toEqual({frame:'rgb(250, 246, 235)',viewport:'rgb(250, 246, 235)',toolbar:'rgb(232, 220, 196)',card:'rgb(255, 252, 243)',menu:'rgb(255, 252, 243)',landing:'rgba(0, 0, 0, 0)',landingToolbar:'rgba(0, 0, 0, 0)',restored:'rgb(250, 246, 235)'});
} finally { runtime.dispose();await cleanupBrowserFixture(scratch); }
},30000);
}, BROWSER_FIXTURE_TEST_TIMEOUT_MS);
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @vitest-environment jsdom
import { browserFixtureFlags, cleanupBrowserFixture } from './browser-fixture.test-support';
import { BROWSER_FIXTURE_LAUNCH_TIMEOUT_MS, BROWSER_FIXTURE_TEST_TIMEOUT_MS, browserFixtureFlags, cleanupBrowserFixture } from './browser-fixture.test-support';
import React, { act } from 'react';
import { createRoot } from 'react-dom/client';
import { renderToStaticMarkup } from 'react-dom/server';
Expand Down Expand Up @@ -78,7 +78,7 @@ it.skipIf(!browser).each(['light','dark'])('protects real settings, skill and ca
document.documentElement.removeAttribute('data-openbitfun-skin-id');
document.getElementById('result').textContent=JSON.stringify({restingMaterial,focusedMaterial,toolbarColor,calendarPlanes,headingPlates,headingColor,shadow,before,after:targets.map(measure),paints,gap,disabled:targets.map(e=>getComputedStyle(e,'::before').content)});
</script></body></html>`);
const out = execFileSync(browser!, [...browserFixtureFlags(), '--headless=new','--disable-gpu','--no-first-run',`--user-data-dir=${resolve(scratch,'profile')}`,'--virtual-time-budget=200','--dump-dom',pathToFileURL(html).href], {encoding:'utf8',windowsHide:true,timeout:20000,stdio:'pipe'});
const out = execFileSync(browser!, [...browserFixtureFlags(), '--headless=new','--disable-gpu','--no-first-run',`--user-data-dir=${resolve(scratch,'profile')}`,'--virtual-time-budget=200','--dump-dom',pathToFileURL(html).href], {encoding:'utf8',windowsHide:true,timeout: BROWSER_FIXTURE_LAUNCH_TIMEOUT_MS,stdio:'pipe'});
const result=JSON.parse(out.match(/<pre id="result">(.*?)<\/pre>/s)![1]);
expect(result.calendarPlanes).toEqual(['openbitfun-todos__calendar-body']);
expect(result.paints.length).toBeGreaterThan(0);
Expand All @@ -92,4 +92,4 @@ it.skipIf(!browser).each(['light','dark'])('protects real settings, skill and ca
expect(result.headingColor).toBe('rgb(23, 37, 52)');expect(result.shadow).toContain('2px');
expect(result.disabled.every((content:string)=>['none','normal'].includes(content))).toBe(true);
} finally { await cleanupBrowserFixture(scratch); }
}, 30000);
}, BROWSER_FIXTURE_TEST_TIMEOUT_MS);
Loading
Loading