From f6485455b206817fe59a86b5e4cb7a45e2b4f86d Mon Sep 17 00:00:00 2001 From: Adam Wright Date: Wed, 16 Sep 2026 17:35:25 +0000 Subject: [PATCH] test(interactors): stop racing the handler that hides the badges This test passed on a retry in CI and was reported as flaky. Retries are set to 2 there, so it went green and nobody saw it. The race: the badges are added to the graph, and the handler that hides them at a zoom where they cannot be read runs afterwards. Reading visibility once could catch the moment in between, when nine badges exist and are still visible. It polls now, both for the badges going away and for them coming back after "Zoom to them" -- the fit and the restyle it triggers are not finished when the click returns either. Run three times with retries off: three passes. Found while auditing for other settings that accept a failure quietly, after --legacy-peer-deps turned out to be doing exactly that. Co-Authored-By: Claude Opus 5 --- e2e/interactor-threshold.spec.ts | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/e2e/interactor-threshold.spec.ts b/e2e/interactor-threshold.spec.ts index b0389ded..24276a7b 100644 --- a/e2e/interactor-threshold.spec.ts +++ b/e2e/interactor-threshold.spec.ts @@ -449,20 +449,39 @@ test.describe('An overlay that cannot be seen yet', () => { { timeout: BOOT_TIMEOUT } ); - const hidden = await page.evaluate(() => { + const visibleBadges = () => + page.evaluate(() => { + const cy = (document.querySelector('#cytoscape') as CytoscapeHost | null)?._cyreg?.cy; + if (!cy) throw new Error('no cytoscape instance on #cytoscape'); + const badges = cy.elements('.InteractorOccurrences'); + return badges.filter((badge) => badge.visible()).length; + }); + + const zoom = await page.evaluate(() => { const cy = (document.querySelector('#cytoscape') as CytoscapeHost | null)?._cyreg?.cy; if (!cy) throw new Error('no cytoscape instance on #cytoscape'); - const badges = cy.elements('.InteractorOccurrences'); - return { zoom: cy.zoom(), visible: badges.filter((badge) => badge.visible()).length }; + return cy.zoom(); }); - test.skip(hidden.zoom >= 0.6, 'this pathway opens close enough in to draw them'); - expect(hidden.visible, 'nothing is drawn at this zoom').toBe(0); + test.skip(zoom >= 0.6, 'this pathway opens close enough in to draw them'); + + // Polled, not read once. The badges are added and the handler that hides + // them at this zoom runs afterwards, so a single read raced it -- the test + // failed on the first attempt and passed on a retry, which CI was + // configured to allow and therefore never reported as anything. + await expect + .poll(visibleBadges, { message: 'nothing is drawn at this zoom', timeout: 20_000 }) + .toBe(0); const reveal = page.getByRole('button', { name: /zoom to them/i }); await expect(reveal, 'the reader is told, rather than left guessing').toHaveCount(1); await reveal.click(); - await page.waitForTimeout(2000); + + // Polled for the same reason: the fit and the restyle it triggers are not + // finished when the click returns. + await expect + .poll(visibleBadges, { message: 'and taken to them', timeout: 20_000 }) + .toBeGreaterThan(0); const after = await page.evaluate(() => { const cy = (document.querySelector('#cytoscape') as CytoscapeHost | null)?._cyreg?.cy;