From a1cfb570c15508491f77840e29738a2c747aff31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Wed, 11 Nov 2026 01:47:48 +0100 Subject: [PATCH] test(browser): wait for the file's own iframe before the first command Every browser test iframe is named "vitest-iframe" and the Playwright provider looks the frame up by that name. Once a page runs a second file, the first file's iframe can still be found until its detach event arrives, so the second file's first click lands in a frame that is going away and fails with "Frame was detached". With fifteen browser files and at most twelve pages, three pages now run two files each, and a full local run failed this way in four of five tries. The setup now waits until the frame found by name is its own. --- tests/browser/setup.ts | 10 ++++++++++ vitest.config.mts | 25 +++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/tests/browser/setup.ts b/tests/browser/setup.ts index 2a6647028..837dbdc05 100644 --- a/tests/browser/setup.ts +++ b/tests/browser/setup.ts @@ -16,6 +16,7 @@ import "@/app/globals.css"; import { afterEach, beforeAll, beforeEach, vi } from "vitest"; +import { commands } from "vitest/browser"; import { loadMessages } from "@/lib/i18n/load-locale"; @@ -24,7 +25,16 @@ import { rememberAddress, restoreAddress } from "./url"; document.documentElement.classList.add("dark"); +declare module "vitest/browser" { + interface BrowserCommands { + /** Waits until the provider's frame lookup finds this file's iframe. */ + awaitOwnFrame: () => Promise; + } +} + beforeAll(async () => { + // See `awaitOwnFrame` in vitest.config.mts. + await commands.awaitOwnFrame(); await loadMessages("en"); }); diff --git a/vitest.config.mts b/vitest.config.mts index 995c34e16..cb9f7a9c8 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -165,6 +165,31 @@ export default defineConfig({ }), instances: [{ browser: "chromium" }], screenshotFailures: false, + commands: { + // Every test iframe is named "vitest-iframe", and the provider + // finds the one to act on by that name. When a page runs a + // second file, the first file's iframe can still be found + // until its detach event arrives, so the second file's first + // click lands in a frame that is going away ("Frame was + // detached"). The setup waits here until the iframe found by + // name is this file's own. + async awaitOwnFrame({ page, testPath }) { + const isOwn = () => { + const url = page.frame("vitest-iframe")?.url(); + if (!url || !testPath) return false; + return new URL(url).searchParams.get("iframeId") === testPath; + }; + const deadline = Date.now() + 5_000; + while (!isOwn()) { + if (Date.now() > deadline) { + throw new Error( + `the vitest-iframe found by name never became ${testPath}`, + ); + } + await new Promise((done) => setTimeout(done, 10)); + } + }, + }, }, }, },