From 35626751a3d236d8f021fe3baa2ccce63a496b86 Mon Sep 17 00:00:00 2001 From: t Date: Tue, 25 Aug 2026 10:06:20 -0300 Subject: [PATCH] docs(agents): the runOnce key rule this repo hands contributors was the broken half MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rhdh-e2e-test-utils#149 fixed this in the library's own guide, but the file someone writing an overlay spec actually reads still said: Prefix with workspace name: "tech-radar-setup", "argocd-deploy" That is the advice that produced #3318. Both examples above it used a literal key around configure() and deploy(), so the two together read as an endorsement of exactly the shape that breaks. The rule now says why the "and projects" half matters — the flag directory is keyed on the runner PID alone, so nothing in the path comes from the project — and gives the fix as `${key}-${rhdh.deploymentConfig.namespace}`, which is what deploy() has always done internally and why deploy() was never affected. Both examples carry it. A literal key stays correct for setup that is genuinely shared; bulk-import has one of each on purpose, so the contrast is named rather than left as "avoid literals". And nesting is marked as no rescue: a project-shared outer key skips before deploy() is reached. RHIDP-16456. --- AGENTS.md | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index df288d6b5a..3660ea1a65 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -219,7 +219,7 @@ Playwright's `beforeAll` runs once **per worker**, not once per test run. When a ```typescript test.beforeAll(async ({ rhdh }) => { - await test.runOnce("tech-radar-setup", async () => { + await test.runOnce(`tech-radar-setup-${rhdh.deploymentConfig.namespace}`, async () => { await rhdh.configure({ auth: "keycloak" }); // Expensive: deploys an external service to the cluster @@ -244,7 +244,7 @@ If a test **does** need an env var that was set inside `runOnce`, extract it fro ```typescript test.beforeAll(async ({ rhdh }) => { - await test.runOnce("my-setup", async () => { + await test.runOnce(`my-setup-${rhdh.deploymentConfig.namespace}`, async () => { await rhdh.configure({ auth: "keycloak" }); await $`bash deploy-service.sh ${rhdh.deploymentConfig.namespace}`; await rhdh.deploy(); @@ -258,8 +258,23 @@ test.beforeAll(async ({ rhdh }) => { ``` **Key rules:** -- The `key` (first argument) must be **globally unique** across all spec files and projects. Prefix with workspace name: `"tech-radar-setup"`, `"argocd-deploy"`. -- Nesting is safe — `deploy()` uses `runOnce` internally, wrapping it in an outer `runOnce` is harmless. +- The `key` (first argument) must be **globally unique** across all spec files **and projects**. A workspace prefix covers the first half; the second half is what bit us. The flag file is keyed by the key string alone, in a directory keyed only on the Playwright runner PID: + + ```ts + const flagDir = path.join(os.tmpdir(), `playwright-once-${process.ppid}`); + const flagFile = path.join(flagDir, `${key}.done`); + ``` + + Nothing in that path comes from the project. So when one spec runs in two projects — which is what adding an `-app-next` lane does — the first project's setup satisfies the second, and the second skips its own. For a block that deploys, that means no deployment at all, then a failure much later on a missing element with nothing pointing at the cause (#3318). + +- **End the key with the namespace whenever the setup belongs to one project**, which is what `deploy()` does internally (`deploy-${namespace}`) and why `deploy()` was never affected: + + ```typescript + await test.runOnce(`my-plugin-setup-${rhdh.deploymentConfig.namespace}`, async () => { ... }); + ``` + + A literal key is right when the setup really is shared — an operator installed once into a fixed namespace every project then uses. `bulk-import` has one of each, deliberately. The key is where you say which you mean. +- Nesting is safe — `deploy()` uses `runOnce` internally, wrapping it in an outer `runOnce` is harmless. It does **not** rescue a project-shared outer key, though: that skips before `deploy()` is reached, so its own protection never gets a say. - Uses file-based flags in `/tmp/` scoped to the Playwright runner process. Flags reset automatically between test runs. ### RHDH Deployment Flow