diff --git a/tests/e2e/ext-specs/firefox-vpn.e2e.mjs b/tests/e2e/ext-specs/firefox-vpn.e2e.mjs index 9b4ec6a1d..750907be8 100644 --- a/tests/e2e/ext-specs/firefox-vpn.e2e.mjs +++ b/tests/e2e/ext-specs/firefox-vpn.e2e.mjs @@ -195,6 +195,9 @@ describe('Firefox VPN', function() { it('plays once allowed, its requests going the page\'s way', async function() { proxied.length = 0; const videoTab = await browser.getWindowHandle(); + // Into the player of the case before (its page stays): a test no longer starts in the frame + // the one before ended in (setupGuard.mjs's root afterEach, 2026-10-11). + await enterPlayer(); // A player in a page has no permissions API: its button opens the permissions page. await (await browser.$('.mainplayer .vpn_button')).click(); let permsTab; diff --git a/tests/e2e/setupGuard.mjs b/tests/e2e/setupGuard.mjs index 52affcc91..4f3863a62 100644 --- a/tests/e2e/setupGuard.mjs +++ b/tests/e2e/setupGuard.mjs @@ -29,7 +29,7 @@ export async function guardSetup(setup) { } // The configs' mochaOpts.rootHooks: the setup's failure first, the more useful reason, then -// bidi.mjs's check. +// bidi.mjs's check; and after every test, the window's top again. export const rootHooks = { beforeEach() { if (setupError) { @@ -38,4 +38,19 @@ export const rootHooks = { } bidiRootHooks.beforeEach(); }, + // A test that ended in a frame (a player's) could leave the next test's first browser.url() + // waiting in a frame the navigation removed, until the 120 s timeout: on CI's Linux runner, + // each hang came in a test after one that ended in the player's frame, before its first step + // (autoplay-next-episode, main and #449, 2026-10-11; not every such test hung - a race in the + // driver). framed-player.e2e.mjs did this itself. A window or a session already gone is no + // news; anything else is said, so a reset that stops working shows. + async afterEach() { + try { + await globalThis.browser?.switchFrame(null); + } catch (e) { + if (!/no such (window|frame)|invalid session|session (deleted|not created)/i.test(String(e?.message || e))) { + console.warn('e2e: back to the window\'s top failed after a test:', e); + } + } + }, }; diff --git a/tests/unit/e2eHarness.test.mjs b/tests/unit/e2eHarness.test.mjs index 5237cdf78..c18ec63a4 100644 --- a/tests/unit/e2eHarness.test.mjs +++ b/tests/unit/e2eHarness.test.mjs @@ -40,6 +40,20 @@ describe('guardSetup and rootHooks', () => { globalThis.browser = {isBidi: false, requestedCapabilities: {}}; expect(() => rootHooks.beforeEach()).toThrow(/BiDi is not connected/); }); + + it('leave every test at the top of its window, also where that fails or no browser is up', async () => { + // A test that ended in a frame left the next one's first navigation waiting in it (2026-10-11). + const frames = []; + globalThis.browser = {switchFrame: async (to) => frames.push(to)}; + await rootHooks.afterEach(); + expect(frames).toEqual([null]); + globalThis.browser = {switchFrame: async () => { + throw new Error('no such window'); + }}; + await expect(rootHooks.afterEach()).resolves.toBeUndefined(); + delete globalThis.browser; + await expect(rootHooks.afterEach()).resolves.toBeUndefined(); + }); }); describe('writeFixture', () => {