From b245a7486cb16fbc7b9234abb3556d03bde32701 Mon Sep 17 00:00:00 2001 From: Jesse Wright Date: Sat, 4 Jul 2026 15:14:58 +0000 Subject: [PATCH] test: make browser e2e tests deterministic and leak-free The e2e tests slept a fixed 1000ms after clicking execute/clear and then asserted on div[id=result]; on slow runners the reasoner takes longer than the sleep, so the assertion reads an empty div (#704). Replace both sleeps with page.waitForFunction() polling the result div (non-empty after execute, then assert the exact expected text; empty after clear) with a generous 90s timeout inside the existing 120s jest timeout. Also move the browser lifecycle into try/finally so browser.close() always runs: when an assertion threw, the leaked browser kept the jest worker alive, which is the mechanism behind the historic 60-minute Windows CI hangs (#385). Closes #704 Closes #385 Co-Authored-By: Claude Fable 5 --- __tests__/e2e-test.ts | 49 +++++++++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/__tests__/e2e-test.ts b/__tests__/e2e-test.ts index 89890154..57cc294c 100644 --- a/__tests__/e2e-test.ts +++ b/__tests__/e2e-test.ts @@ -3,6 +3,10 @@ import { firefox, chromium, type BrowserType } from 'playwright'; import { createTestApp } from '../__test_utils__/serve'; import { data } from '../data/socrates'; +// Generous timeout for the reasoner to produce a result on slow CI runners, +// kept within the overall 120s jest timeout of each test below +const RESULT_TIMEOUT = 90_000; + describe('Testing browsers', () => { let server: Server; @@ -24,28 +28,41 @@ describe('Testing browsers', () => { ([browserType, browserName]) => { it(`should be able to call the execute function in ${browserName}`, async () => { const browser = await browserType.launch(); - const page = await browser.newPage(); - await page.goto('http://localhost:3001/'); - await expect(page.textContent('textarea[id=data]').then((r) => r?.trim())).resolves.toEqual(data.trim()); - await expect(page.textContent('div[id=result]').then((r) => r?.trim())).resolves.toEqual(''); + // Ensure the browser is always closed, even when an expectation fails; + // a leaked browser stops the jest worker from exiting and hangs CI + try { + const page = await browser.newPage(); - await page.click('button[id=execute]'); - // Time for new result to be inserted in the DOM - await new Promise((res) => { setTimeout(res, 1000); }); + await page.goto('http://localhost:3001/'); + await expect(page.textContent('textarea[id=data]').then((r) => r?.trim())).resolves.toEqual(data.trim()); + await expect(page.textContent('div[id=result]').then((r) => r?.trim())).resolves.toEqual(''); - await expect(page.textContent('textarea[id=data]').then((r) => r?.trim())).resolves.toEqual(data.trim()); - await expect(page.textContent('div[id=result]').then((r) => r?.trim())).resolves.toEqual('@prefix : .:Socrates a :Mortal.'); + await page.click('button[id=execute]'); + // Wait for the reasoner to insert the result into the DOM rather + // than sleeping for a fixed amount of time + await page.waitForFunction( + () => (document.querySelector('div[id=result]')?.textContent ?? '').trim() !== '', + undefined, + { timeout: RESULT_TIMEOUT }, + ); - await page.click('button[id=clear]'); - // Time for new result to be inserted in the DOM - await new Promise((res) => { setTimeout(res, 1000); }); + await expect(page.textContent('textarea[id=data]').then((r) => r?.trim())).resolves.toEqual(data.trim()); + await expect(page.textContent('div[id=result]').then((r) => r?.trim())).resolves.toEqual('@prefix : .:Socrates a :Mortal.'); - await expect(page.textContent('textarea[id=data]').then((r) => r?.trim())).resolves.toEqual(data.trim()); - await expect(page.textContent('div[id=result]').then((r) => r?.trim())).resolves.toEqual(''); + await page.click('button[id=clear]'); + // Wait for the result to be removed from the DOM + await page.waitForFunction( + () => (document.querySelector('div[id=result]')?.textContent ?? '').trim() === '', + undefined, + { timeout: RESULT_TIMEOUT }, + ); - await page.close(); - await browser.close(); + await expect(page.textContent('textarea[id=data]').then((r) => r?.trim())).resolves.toEqual(data.trim()); + await expect(page.textContent('div[id=result]').then((r) => r?.trim())).resolves.toEqual(''); + } finally { + await browser.close(); + } }, 120_000); }, );