From 287b2b046599147ce7542e0213c11f41a42a625f Mon Sep 17 00:00:00 2001 From: Paul Elliott Date: Tue, 2 Jun 2026 15:46:38 -0400 Subject: [PATCH 1/2] test(e2e): retry dataset downloads in onPrepare Flaky connectivity to data.kitware.com could fail a single onPrepare download, leaving .tmp without the dataset and causing specs to fail at the render-wait step as if WebGL were broken. Retry each download up to 3 times, write atomically via a .part rename so a partial download is never cached as complete, and check response.ok so an error page is not saved as DICOM. --- wdio.shared.conf.ts | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/wdio.shared.conf.ts b/wdio.shared.conf.ts index e11f9183d..c36a518cc 100644 --- a/wdio.shared.conf.ts +++ b/wdio.shared.conf.ts @@ -111,14 +111,45 @@ export const config: Options.Testrunner = { async onPrepare() { fs.mkdirSync(TEMP_DIR, { recursive: true }); + const RETRIES = 3; + const RETRY_DELAY_MS = 500; + const delay = (ms: number) => + new Promise((resolve) => { + setTimeout(resolve, ms); + }); + const downloadOnce = async (url: string, savePath: string) => { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`HTTP ${response.status} for ${url}`); + } + const data = await response.arrayBuffer(); + // Write to a temp path first so a failed/partial download never leaves a + // corrupt file that the existsSync check would treat as already cached. + const tmpPath = `${savePath}.part`; + fs.writeFileSync(tmpPath, Buffer.from(data)); + fs.renameSync(tmpPath, savePath); + }; + const downloads = TEST_DATASETS.map(async ({ url, name }) => { const savePath = path.join(TEMP_DIR, name); if (fs.existsSync(savePath)) { return; } - const response = await fetch(url); - const data = await response.arrayBuffer(); - fs.writeFileSync(savePath, Buffer.from(data)); + for (let attempt = 1; attempt <= RETRIES; attempt += 1) { + try { + await downloadOnce(url, savePath); + return; + } catch (err) { + if (attempt === RETRIES) { + throw new Error( + `Failed to download ${name} after ${RETRIES} attempts: ${ + (err as Error).message + }` + ); + } + await delay(RETRY_DELAY_MS); + } + } }); await Promise.all(downloads); }, From e1ccdc0cd3ec50ab616be3cb555cbbb679b07117 Mon Sep 17 00:00:00 2001 From: Paul Elliott Date: Tue, 2 Jun 2026 15:46:40 -0400 Subject: [PATCH 2/2] test(e2e): run chrome headless by default for local runs Local wdio launched headed chrome on the WSLg desktop, so browser windows popped up on the host display during every test run. Default to headless (with --enable-unsafe-swiftshader, since chrome no longer auto-falls back to software WebGL) and add a HEADED=1 escape hatch for watching tests. CI behavior is unchanged. --- wdio.chrome.conf.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/wdio.chrome.conf.ts b/wdio.chrome.conf.ts index b8da9ee05..64d04dad4 100644 --- a/wdio.chrome.conf.ts +++ b/wdio.chrome.conf.ts @@ -1,8 +1,10 @@ import { config as sharedConfig, TEMP_DIR } from './wdio.shared.conf'; +const IS_CI = !!(process.env.CI || process.env.GITHUB_ACTIONS); + // Chrome arguments for CI environments const chromeArgs: string[] = []; -if (process.env.CI || process.env.GITHUB_ACTIONS) { +if (IS_CI) { chromeArgs.push( '--no-sandbox', '--disable-dev-shm-usage', @@ -11,6 +13,10 @@ if (process.env.CI || process.env.GITHUB_ACTIONS) { ); } +if (!IS_CI && !process.env.HEADED) { + chromeArgs.push('--headless=new', '--enable-unsafe-swiftshader'); +} + export const config = { ...sharedConfig, capabilities: [