From ccfd8b78318cd6d685eb39c1ed7915f6e6337ee9 Mon Sep 17 00:00:00 2001 From: Subhash Khileri Date: Fri, 6 Mar 2026 18:13:16 +0530 Subject: [PATCH] docs update --- .../core-concepts/playwright-fixtures.md | 6 +- docs/overlay/examples/tech-radar.md | 44 +++--- docs/overlay/tutorials/custom-deployment.md | 128 ++++++++++-------- 3 files changed, 99 insertions(+), 79 deletions(-) diff --git a/docs/guide/core-concepts/playwright-fixtures.md b/docs/guide/core-concepts/playwright-fixtures.md index eba44c1..58c5685 100644 --- a/docs/guide/core-concepts/playwright-fixtures.md +++ b/docs/guide/core-concepts/playwright-fixtures.md @@ -251,11 +251,15 @@ test.describe("Feature B", () => { ### Key: Unique Identifier -The `key` parameter must be unique across all `runOnce` calls in your test run. Use a descriptive name that reflects the operation: +The `key` must be globally unique across **all spec files and projects** in the same Playwright run. If two `runOnce` calls in different files use the same key, only the first one will execute. Use a prefix that includes the workspace or project name: ```typescript +// In tech-radar.spec.ts await test.runOnce("tech-radar-deploy", async () => { ... }); await test.runOnce("tech-radar-data-provider", async () => { ... }); + +// In catalog.spec.ts +await test.runOnce("catalog-deploy", async () => { ... }); await test.runOnce("catalog-seed-data", async () => { ... }); ``` diff --git a/docs/overlay/examples/tech-radar.md b/docs/overlay/examples/tech-radar.md index c24ea2f..c5226ce 100644 --- a/docs/overlay/examples/tech-radar.md +++ b/docs/overlay/examples/tech-radar.md @@ -222,28 +222,30 @@ const setupScript = path.join( ); test.describe("Test tech-radar plugin", () => { - // beforeAll runs once per worker before any tests + // Wrap in runOnce — the external service deployment is expensive + // and should not re-run when Playwright restarts the worker after a test failure test.beforeAll(async ({ rhdh }) => { - // Get the namespace from deployment config - const project = rhdh.deploymentConfig.namespace; - - // Configure RHDH with Keycloak authentication - await rhdh.configure({ auth: "keycloak" }); - - // Deploy the external data provider service - await $`bash ${setupScript} ${project}`; - - // Get the route URL and set as environment variable - // Remove http:// prefix as the config expects just the host - process.env.TECH_RADAR_DATA_URL = ( - await rhdh.k8sClient.getRouteLocation( - project, - "test-backstage-customization-provider", - ) - ).replace("http://", ""); - - // Now deploy RHDH (will use the TECH_RADAR_DATA_URL env var) - await rhdh.deploy(); + await test.runOnce("tech-radar-setup", async () => { + const project = rhdh.deploymentConfig.namespace; + + // Configure RHDH with Keycloak authentication + await rhdh.configure({ auth: "keycloak" }); + + // Deploy the external data provider service + await $`bash ${setupScript} ${project}`; + + // Get the route URL and set as environment variable + // Remove http:// prefix as the config expects just the host + process.env.TECH_RADAR_DATA_URL = ( + await rhdh.k8sClient.getRouteLocation( + project, + "test-backstage-customization-provider", + ) + ).replace("http://", ""); + + // Deploy RHDH (will use the TECH_RADAR_DATA_URL env var) + await rhdh.deploy(); + }); }); // beforeEach runs before each test diff --git a/docs/overlay/tutorials/custom-deployment.md b/docs/overlay/tutorials/custom-deployment.md index 6e61ba6..cc431e4 100644 --- a/docs/overlay/tutorials/custom-deployment.md +++ b/docs/overlay/tutorials/custom-deployment.md @@ -18,26 +18,32 @@ Your plugin tests may require pre-requisites when: ## The Pattern -Deploy pre-requisites **after** `rhdh.configure()` but **before** `rhdh.deploy()`: +Deploy pre-requisites **after** `rhdh.configure()` but **before** `rhdh.deploy()`. Since these operations are expensive and create persistent cluster resources, wrap the entire block in `test.runOnce` to prevent re-execution on worker restarts: ```typescript test.beforeAll(async ({ rhdh }) => { - const project = rhdh.deploymentConfig.namespace; + await test.runOnce("my-plugin-setup", async () => { + const project = rhdh.deploymentConfig.namespace; - // 1. Configure RHDH first - await rhdh.configure({ auth: "keycloak" }); + // 1. Configure RHDH first + await rhdh.configure({ auth: "keycloak" }); - // 2. Deploy pre-requisite service (see examples below) - // ... + // 2. Deploy pre-requisite service (see examples below) + // ... - // 3. Set environment variable if needed for RHDH config - process.env.MY_SERVICE_URL = "..."; + // 3. Set environment variable if needed for RHDH config + process.env.MY_SERVICE_URL = "..."; - // 4. Deploy RHDH (uses the environment variable) - await rhdh.deploy(); + // 4. Deploy RHDH (uses the environment variable) + await rhdh.deploy(); + }); }); ``` +::: tip When is `test.runOnce` needed? +`rhdh.deploy()` already skips automatically on worker restarts. But the pre-deploy steps (deploying external services, running scripts) don't have this protection. `test.runOnce` ensures the **entire setup** runs only once. The `key` must be **globally unique** across all spec files and projects in the same Playwright run — prefix it with your workspace name (e.g., `"tech-radar-setup"`). See [`test.runOnce`](/guide/core-concepts/playwright-fixtures#test-runonce-—-run-any-expensive-operation-once) for details. +::: + ## Examples ### Using TypeScript / k8sClient @@ -48,26 +54,28 @@ You can deploy pre-requisites directly in TypeScript using the Kubernetes client import { test } from "@red-hat-developer-hub/e2e-test-utils/test"; test.beforeAll(async ({ rhdh }) => { - const project = rhdh.deploymentConfig.namespace; - const k8s = rhdh.k8sClient; - - await rhdh.configure({ auth: "keycloak" }); - - // Create a ConfigMap - await k8s.applyConfigMapFromObject( - "my-config", - { "config.json": JSON.stringify({ key: "value" }) }, - project, - ); - - // Create a Secret - await k8s.applySecretFromObject( - "my-secret", - { stringData: { API_KEY: process.env.VAULT_API_KEY } }, - project, - ); - - await rhdh.deploy(); + await test.runOnce("my-plugin-k8s-setup", async () => { + const project = rhdh.deploymentConfig.namespace; + const k8s = rhdh.k8sClient; + + await rhdh.configure({ auth: "keycloak" }); + + // Create a ConfigMap + await k8s.applyConfigMapFromObject( + "my-config", + { "config.json": JSON.stringify({ key: "value" }) }, + project, + ); + + // Create a Secret + await k8s.applySecretFromObject( + "my-secret", + { stringData: { API_KEY: process.env.VAULT_API_KEY } }, + project, + ); + + await rhdh.deploy(); + }); }); ``` @@ -80,21 +88,23 @@ import { test } from "@red-hat-developer-hub/e2e-test-utils/test"; import { $ } from "@red-hat-developer-hub/e2e-test-utils/utils"; test.beforeAll(async ({ rhdh }) => { - const project = rhdh.deploymentConfig.namespace; + await test.runOnce("my-plugin-oc-setup", async () => { + const project = rhdh.deploymentConfig.namespace; - await rhdh.configure({ auth: "keycloak" }); + await rhdh.configure({ auth: "keycloak" }); - // Deploy an app from image - await $`oc new-app my-image:tag --name=my-service --namespace=${project}`; - await $`oc expose svc/my-service --namespace=${project}`; + // Deploy an app from image + await $`oc new-app my-image:tag --name=my-service --namespace=${project}`; + await $`oc expose svc/my-service --namespace=${project}`; - // Get service URL - process.env.MY_SERVICE_URL = await rhdh.k8sClient.getRouteLocation( - project, - "my-service", - ); + // Get service URL + process.env.MY_SERVICE_URL = await rhdh.k8sClient.getRouteLocation( + project, + "my-service", + ); - await rhdh.deploy(); + await rhdh.deploy(); + }); }); ``` @@ -109,11 +119,13 @@ import path from "path"; const setupScript = path.join(import.meta.dirname, "deploy-service.sh"); test.beforeAll(async ({ rhdh }) => { - const project = rhdh.deploymentConfig.namespace; + await test.runOnce("my-plugin-script-setup", async () => { + const project = rhdh.deploymentConfig.namespace; - await rhdh.configure({ auth: "keycloak" }); - await $`bash ${setupScript} ${project}`; - await rhdh.deploy(); + await rhdh.configure({ auth: "keycloak" }); + await $`bash ${setupScript} ${project}`; + await rhdh.deploy(); + }); }); ``` @@ -123,22 +135,24 @@ The tech-radar plugin requires an external data provider: ```typescript test.beforeAll(async ({ rhdh }) => { - const project = rhdh.deploymentConfig.namespace; + await test.runOnce("tech-radar-setup", async () => { + const project = rhdh.deploymentConfig.namespace; - await rhdh.configure({ auth: "keycloak" }); + await rhdh.configure({ auth: "keycloak" }); - // Deploy the data provider service - await $`bash ${setupScript} ${project}`; + // Deploy the data provider service + await $`bash ${setupScript} ${project}`; - // Get URL and set as env var for rhdh-secrets.yaml substitution - process.env.TECH_RADAR_DATA_URL = ( - await rhdh.k8sClient.getRouteLocation( - project, - "test-backstage-customization-provider", - ) - ).replace("http://", ""); + // Get URL and set as env var for rhdh-secrets.yaml substitution + process.env.TECH_RADAR_DATA_URL = ( + await rhdh.k8sClient.getRouteLocation( + project, + "test-backstage-customization-provider", + ) + ).replace("http://", ""); - await rhdh.deploy(); + await rhdh.deploy(); + }); }); ```