-
Notifications
You must be signed in to change notification settings - Fork 727
NO-ISSUE: Add TechPreview and feature-gate E2E test filtering infrastructure #16770
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
55b3e4f
7d6a006
323ed75
0a8fe50
f89ba7b
5e49d9c
a117649
506c07d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,252 @@ | ||
| # Tech Preview E2E Test Filtering Design | ||
|
|
||
| **Date:** 2026-07-16 | ||
| **Status:** Approved (updated to include per-feature-gate support) | ||
|
|
||
| ## Goal | ||
|
|
||
| Populate `test-prow-e2e-techpreview.sh` and `test-prow-playwright-e2e-techpreview.sh` so that CI | ||
| jobs running against `TechPreviewNoUpgrade` clusters execute only the tests that specifically | ||
| require tech preview or a specific feature gate to be enabled, and skip everything else. | ||
|
|
||
| A test can declare one of two levels of dependency: | ||
|
|
||
| - **TechPreview overall** — the entire `TechPreviewNoUpgrade` feature set must be on. | ||
| - **Specific feature gate** — a named gate (e.g. `OLMv1`, `NodeSwap`) must be enabled. Tests at | ||
| this level run on *any* cluster where the gate is enabled, regardless of whether the gate is in | ||
| TechPreview or has graduated to GA. | ||
|
|
||
| This lets a test continue to run correctly after its feature gate graduates without changing the | ||
| test itself. | ||
|
|
||
| ## Background | ||
|
|
||
| - `SERVER_FLAGS.techPreview` (boolean) — set by the server when the cluster's FeatureGate | ||
| `featureSet` is `TechPreviewNoUpgrade`. | ||
| - The OpenShift FeatureGate CR (`config.openshift.io/v1`, cluster-scoped) tracks individual gate | ||
| states. `.status.featureGates[]` lists enabled/disabled gates per feature set; the current | ||
| cluster's active gates are the ones corresponding to `.spec.featureSet`. | ||
| - `oc get featuregate cluster -o jsonpath={.spec.featureSet}` returns `TechPreviewNoUpgrade` on | ||
| tech-preview clusters, or empty string on standard clusters. | ||
| - Playwright already uses `{ tag: […] }` annotations (`@admin`, `@smoke`, etc.) and supports | ||
| `--grep` for tag-based filtering. | ||
| - Cypress uses a `before()` / `cy.exec` pattern (see | ||
| `frontend/packages/dev-console/integration-tests/support/commands/hooks.ts`) to skip entire | ||
| suites based on the cluster's feature set. | ||
| - `KubernetesClient` (`frontend/e2e/clients/kubernetes-client.ts`) exposes `customObjectsApi` but | ||
| only has `getCustomResource` (namespaced); a `getClusterCustomResource` method needs to be added. | ||
|
|
||
| ## Tag Convention | ||
|
|
||
| | Tag | Meaning | | ||
| |-----|---------| | ||
| | `@tech-preview` | Test requires `SERVER_FLAGS.techPreview === true` (TechPreviewNoUpgrade overall) | | ||
| | `@feature-gate` | Test requires a specific named feature gate; fixture determines which and enforces it | | ||
|
|
||
| Both tags are included in the techpreview scripts. The `@feature-gate` tag is also included in the | ||
| regular scripts so tests naturally start passing there as gates graduate to GA — no script change | ||
| needed at graduation time. | ||
|
|
||
| Example: | ||
|
|
||
| ```ts | ||
| // Requires TechPreview overall | ||
| test.describe('OLMv1 catalog', { tag: ['@admin', '@tech-preview'] }, () => { … }); | ||
|
|
||
| // Requires a specific feature gate (runs on TP and GA once the gate graduates) | ||
| test.describe('NodeSwap scheduling', { tag: ['@admin', '@feature-gate'] }, () => { … }); | ||
| ``` | ||
|
|
||
| ## Playwright Design | ||
|
|
||
| ### `techPreviewOnly` fixture | ||
|
|
||
| Added to the `TestFixtures` type and `base.extend()` chain in `frontend/e2e/fixtures/index.ts`. | ||
| Reads `SERVER_FLAGS.techPreview` from the page and skips if the cluster is not on TechPreview. | ||
|
|
||
| ```ts | ||
| type TestFixtures = { | ||
| cleanup: CleanupFixture; | ||
| techPreviewOnly: void; | ||
| requireFeatureGate: (gateName: string) => Promise<void>; | ||
| }; | ||
|
|
||
| // In base.extend(): | ||
| techPreviewOnly: async ({ page }, use) => { | ||
| const isTechPreview = await page.evaluate(() => window.SERVER_FLAGS.techPreview); | ||
| test.skip(!isTechPreview, 'This test requires a TechPreviewNoUpgrade cluster'); | ||
| await use(); | ||
| }, | ||
| ``` | ||
|
|
||
| ### `requireFeatureGate` fixture | ||
|
|
||
| Also added to `fixtures/index.ts`. Returns a callable that a test invokes with the gate name it | ||
| needs. Queries the FeatureGate cluster CR via `KubernetesClient` and skips if the gate is not in | ||
| the enabled list for the cluster's active feature set. | ||
|
|
||
| ```ts | ||
| requireFeatureGate: async ({ k8sClient }, use) => { | ||
| await use(async (gateName: string) => { | ||
| const fg = await k8sClient.getClusterCustomResource( | ||
| 'config.openshift.io', 'v1', 'featuregates', 'cluster', | ||
| ); | ||
| const featureSet: string = fg?.spec?.featureSet ?? ''; | ||
| const section = (fg?.status?.featureGates ?? []).find( | ||
| (s: any) => s.featureSet === featureSet, | ||
| ); | ||
| const enabled: string[] = (section?.enabled ?? []).map((g: any) => g.name); | ||
| test.skip(!enabled.includes(gateName), `Feature gate '${gateName}' is not enabled on this cluster`); | ||
| }); | ||
| }, | ||
| ``` | ||
|
|
||
| Usage in a test: | ||
|
|
||
| ```ts | ||
| test('NodeSwap pod scheduling', async ({ page, requireFeatureGate }) => { | ||
| await requireFeatureGate('NodeSwap'); | ||
| // … rest of test | ||
| }); | ||
| ``` | ||
|
|
||
| ### `KubernetesClient.getClusterCustomResource` | ||
|
|
||
| A new method on `KubernetesClient` using the existing `customObjectsApi` to fetch cluster-scoped | ||
| custom resources (FeatureGate, ClusterVersion, etc.): | ||
|
|
||
| ```ts | ||
| async getClusterCustomResource( | ||
| group: string, | ||
| version: string, | ||
| plural: string, | ||
| name: string, | ||
| ): Promise<any> { | ||
| return this.coApi.getClusterCustomObject({ group, version, plural, name }); | ||
| } | ||
| ``` | ||
|
|
||
| ### `test-prow-playwright-e2e-techpreview.sh` | ||
|
|
||
| Mirrors `test-prow-playwright-e2e.sh` (same env setup, same artifact copy trap) but passes a grep | ||
| filter so only TechPreview or feature-gate tests run: | ||
|
|
||
| ```bash | ||
| ./integration-tests/test-playwright-e2e.sh -- --grep "@tech-preview|@feature-gate" "$@" | ||
| ``` | ||
|
|
||
| No new `playwright.config.ts` project needed. | ||
|
|
||
| ### `test-prow-playwright-e2e.sh` (regular script — minor addition) | ||
|
|
||
| The regular script gains a `--grep @feature-gate` path so that feature-gate tests also run there. | ||
| On standard clusters, the `requireFeatureGate` fixture skips any test whose gate is not yet | ||
| enabled; as gates graduate the tests pass automatically. | ||
|
|
||
| ```bash | ||
| # In the e2e/release branch of the regular script: | ||
| ./integration-tests/test-playwright-e2e.sh -- --grep @feature-gate "$@" | ||
| ``` | ||
|
|
||
| This is added as a new scenario or combined with the existing `e2e` scenario — exact wiring to | ||
| be decided at implementation time based on CI job structure. | ||
|
|
||
| ## Cypress Design | ||
|
|
||
| ### Inverted guard hook — TechPreview overall | ||
|
|
||
| A new shared support helper at `frontend/integration-tests/support/tech-preview-guard.ts`. | ||
| Skips the entire Cypress suite if the cluster is *not* on TechPreview: | ||
|
|
||
| ```ts | ||
| before(function () { | ||
| cy.exec('oc get featuregate cluster -o jsonpath={.spec.featureSet}', { | ||
| failOnNonZeroExit: false, | ||
| }).then((result) => { | ||
| if (result.stdout.trim() !== 'TechPreviewNoUpgrade') { | ||
| this.skip(); | ||
| } | ||
| }); | ||
| }); | ||
| ``` | ||
|
|
||
| ### Inverted guard hook — specific feature gate | ||
|
|
||
| An additional helper (same file or a companion `feature-gate-guard.ts`) that accepts a gate name | ||
| and skips the suite unless that gate appears in the enabled list: | ||
|
|
||
| ```ts | ||
| export function requireFeatureGate(gateName: string): void { | ||
| before(function () { | ||
| cy.exec( | ||
| `oc get featuregate cluster -o jsonpath='{.status.featureGates[*].enabled[*].name}'`, | ||
| { failOnNonZeroExit: false }, | ||
| ).then((result) => { | ||
| const enabled = result.stdout.split(/\s+/).filter(Boolean); | ||
| if (!enabled.includes(gateName)) { | ||
| this.skip(); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
| ``` | ||
|
|
||
| Cypress packages call this in their support entry point alongside the TechPreview guard. | ||
|
|
||
| ### `test-prow-e2e-techpreview.sh` | ||
|
|
||
| Mirrors `test-prow-e2e.sh` (same env setup, same `create-user.sh` and CSP check) but runs only | ||
| the Cypress packages that have TechPreview or feature-gate suites: | ||
|
|
||
| ```bash | ||
| ./integration-tests/test-cypress.sh -p olm -h true | ||
| ``` | ||
|
|
||
| Additional packages are added here as tech-preview / feature-gate Cypress tests are written. | ||
|
|
||
| ## File Inventory | ||
|
|
||
| | File | Action | | ||
| |------|--------| | ||
| | `test-prow-playwright-e2e-techpreview.sh` | Populate (mirrors playwright script + `--grep "@tech-preview\|@feature-gate"`) | | ||
| | `test-prow-e2e-techpreview.sh` | Populate (mirrors cypress script + TP/feature-gate packages only) | | ||
| | `frontend/e2e/fixtures/index.ts` | Add `techPreviewOnly` and `requireFeatureGate` fixtures | | ||
| | `frontend/e2e/clients/kubernetes-client.ts` | Add `getClusterCustomResource` method | | ||
| | `frontend/integration-tests/support/tech-preview-guard.ts` | Create — TechPreview Cypress guard + `requireFeatureGate` helper | | ||
|
|
||
| ## How Tests Participate | ||
|
|
||
| ### Playwright — TechPreview overall | ||
|
|
||
| 1. Add `@tech-preview` to the `test.describe` or `test()` tags. | ||
| 2. Add `techPreviewOnly` to the fixture destructuring list. | ||
| 3. Remove any manual `test.skip(isTechPreview, …)` guard — the fixture handles it. | ||
|
|
||
| ### Playwright — specific feature gate | ||
|
|
||
| 1. Add `@feature-gate` to the tags. | ||
| 2. Call `await requireFeatureGate('GateName')` as the first line of the test body. | ||
|
|
||
| ### Cypress — TechPreview overall | ||
|
|
||
| 1. Import `tech-preview-guard` in the package's support entry point. | ||
| 2. Add the package to `test-prow-e2e-techpreview.sh`. | ||
|
|
||
| ### Cypress — specific feature gate | ||
|
|
||
| 1. Call `requireFeatureGate('GateName')` in the package's support entry point (or individual spec | ||
| `before()` if only some tests in the package need it). | ||
| 2. Add the package to `test-prow-e2e-techpreview.sh`. | ||
|
|
||
| ## Lifecycle: Feature Gate Graduation | ||
|
|
||
| When a gate graduates from TechPreview to GA: | ||
|
|
||
| - **Playwright**: no test change needed. The `requireFeatureGate` fixture will find the gate in the | ||
| enabled list on GA clusters, and the test already runs via `--grep @feature-gate` in the regular | ||
| script. | ||
| - **Cypress**: no test change needed. The `requireFeatureGate` guard passes on GA clusters once the | ||
| gate is in the enabled list. | ||
| - **What does change**: the gate may move from `.status.featureGates[featureSet=TechPreviewNoUpgrade].enabled` | ||
| to being enabled by default (not listed as disabled in any feature set). The fixture and guard | ||
| both check the enabled list, so this is transparent. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,3 +14,4 @@ dynamic-demo-plugin | |
| tsconfig.json | ||
| e2e/tsconfig.json | ||
| e2e/package.json | ||
| integration-tests/tsconfig.json | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,8 @@ export interface SharedTestConfig { | |
|
|
||
| type TestFixtures = { | ||
| cleanup: CleanupFixture; | ||
| techPreviewOnly: void; | ||
| requireFeatureGate: (gateName: string) => Promise<void>; | ||
| }; | ||
|
|
||
| type WorkerFixtures = { | ||
|
|
@@ -58,6 +60,52 @@ export const test = base.extend<TestFixtures, WorkerFixtures>({ | |
| { scope: 'worker' }, | ||
| ], | ||
|
|
||
| techPreviewOnly: async ({ k8sClient }, use) => { | ||
| let isTechPreview = false; | ||
| try { | ||
| const fg = await k8sClient.getClusterCustomResource( | ||
| 'config.openshift.io', | ||
| 'v1', | ||
| 'featuregates', | ||
| 'cluster', | ||
| ); | ||
| isTechPreview = (fg as any)?.spec?.featureSet === 'TechPreviewNoUpgrade'; | ||
| } catch { | ||
| // If the CR can't be read, don't skip — surface the infrastructure error | ||
| throw new Error( | ||
| `techPreviewOnly: could not read FeatureGate CR 'cluster': check cluster connectivity`, | ||
| ); | ||
| } | ||
| test.skip(!isTechPreview, 'This test requires a TechPreviewNoUpgrade cluster'); | ||
|
Comment on lines
+63
to
+79
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
git ls-files frontend/e2e/fixtures/index.ts
wc -l frontend/e2e/fixtures/index.ts
cat -n frontend/e2e/fixtures/index.ts | sed -n '1,180p'Repository: openshift/console Length of output: 4895 🏁 Script executed: #!/bin/bash
set -euo pipefail
wc -l frontend/e2e/fixtures/index.ts
sed -n '1,180p' frontend/e2e/fixtures/index.ts | nl -baRepository: openshift/console Length of output: 230 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Inspect the Kubernetes client method and nearby usages of feature gate handling.
ast-grep outline frontend/e2e/clients/kubernetes-client.ts --view expanded || true
printf '\n---\n'
rg -n "getClusterCustomResource|featureGates|featureSet|TechPreviewNoUpgrade" frontend/e2e -S
printf '\n---\n'
cat -n frontend/e2e/clients/kubernetes-client.ts | sed -n '1,260p'Repository: openshift/console Length of output: 14376 🏁 Script executed: #!/bin/bash
set -euo pipefail
ast-grep outline frontend/e2e/clients/kubernetes-client.ts --view expanded || true
printf '\n---\n'
rg -n "getClusterCustomResource|featureGates|featureSet|TechPreviewNoUpgrade" frontend/e2e -S
printf '\n---\n'
sed -n '1,260p' frontend/e2e/clients/kubernetes-client.ts | cat -nRepository: openshift/console Length of output: 14376 🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n "interface FeatureGate|type FeatureGate|featureSet:|featureGates:" frontend -S
printf '\n---\n'
rg -n "FeatureGate" frontend -g '!**/node_modules/**' -SRepository: openshift/console Length of output: 1250 🏁 Script executed: #!/bin/bash
set -euo pipefail
wc -l frontend/integration-tests/support/tech-preview-guard.ts
sed -n '1,220p' frontend/integration-tests/support/tech-preview-guard.ts | cat -nRepository: openshift/console Length of output: 1629 Guard against malformed FeatureGate payloads before skipping tests.
📍 Affects 1 file
🤖 Prompt for AI AgentsSource: Path instructions |
||
| await use(); | ||
| }, | ||
|
|
||
| requireFeatureGate: async ({ k8sClient }, use) => { | ||
| await use(async (gateName: string) => { | ||
| let enabled: string[] = []; | ||
| try { | ||
| const fg = await k8sClient.getClusterCustomResource( | ||
| 'config.openshift.io', | ||
| 'v1', | ||
| 'featuregates', | ||
| 'cluster', | ||
| ); | ||
| const featureSet: string = (fg as any)?.spec?.featureSet ?? ''; | ||
| const sections: any[] = (fg as any)?.status?.featureGates ?? []; | ||
| const section = sections.find((s: any) => s.featureSet === featureSet); | ||
| enabled = (section?.enabled ?? []).map((g: any) => String(g.name)); | ||
| } catch (err) { | ||
| throw new Error( | ||
| `requireFeatureGate('${gateName}'): could not read FeatureGate CR 'cluster': ${err}`, | ||
| ); | ||
| } | ||
| test.skip( | ||
| !enabled.includes(gateName), | ||
| `Feature gate '${gateName}' is not enabled on this cluster`, | ||
| ); | ||
| }); | ||
| }, | ||
|
|
||
| cleanup: async ({}, use, testInfo) => { | ||
| const testName = testInfo.titlePath.join(' > '); | ||
| const fixture = createCleanupFixture(testName); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import { test, expect } from '../../fixtures'; | ||
|
|
||
| test.describe('OLMv1 Software Catalog', { tag: ['@admin', '@tech-preview'] }, () => { | ||
| test('Software Catalog page is accessible on TechPreview clusters', async ({ | ||
| page, | ||
| techPreviewOnly, | ||
| }) => { | ||
| await page.goto('/'); | ||
| await expect(page).toHaveURL(/\/k8s\/cluster\/clusterserviceversions|\/software-catalog/, { | ||
| timeout: 30_000, | ||
|
Comment on lines
+9
to
+10
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '--- target file ---'
sed -n '1,120p' frontend/e2e/tests/olm/olmv1-catalog.spec.ts | cat -n
echo
echo '--- similar URL assertions in frontend/e2e/tests/olm ---'
rg -n "toHaveURL\\(" frontend/e2e/tests/olm -SRepository: openshift/console Length of output: 884 Constrain the URL regex. Group the alternation and anchor it to the route boundary/end of URL so only 🤖 Prompt for AI AgentsSource: Path instructions |
||
| }); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| module.exports = { | ||
| root: true, | ||
| env: { | ||
| 'cypress/globals': true, | ||
| node: true, | ||
| }, | ||
| extends: ['plugin:cypress/recommended'], | ||
| plugins: ['cypress'], | ||
| parser: '@typescript-eslint/parser', | ||
| parserOptions: { | ||
| project: './tsconfig.json', | ||
| tsconfigRootDir: __dirname, | ||
| ecmaVersion: 2018, | ||
|
Comment on lines
+9
to
+13
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: rg -n "sourceType|export function" frontend/integration-tests/.eslintrc.cjs frontend/integration-tests/support/tech-preview-guard.tsRepository: openshift/console Length of output: 276 Configure ESLint for module syntax. 🤖 Prompt for AI Agents |
||
| }, | ||
| rules: { | ||
| 'no-console': 'off', | ||
| 'cypress/unsafe-to-chain-command': 'off', | ||
| }, | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| /** | ||
| * Cypress support helpers for TechPreview and per-feature-gate guards. | ||
| * | ||
| * Usage (in a Cypress package's support entry point): | ||
| * | ||
| * // Skip entire suite unless cluster is TechPreviewNoUpgrade: | ||
| * import './tech-preview-guard'; | ||
| * | ||
| * // Skip entire suite unless a specific feature gate is enabled: | ||
| * import { requireFeatureGate } from './tech-preview-guard'; | ||
| * requireFeatureGate('OLMv1'); | ||
| */ | ||
|
|
||
| before(function () { | ||
| cy.exec('oc get featuregate cluster -o jsonpath={.spec.featureSet}', { | ||
| failOnNonZeroExit: false, | ||
| }).then((result) => { | ||
| if (result.stdout.trim() !== 'TechPreviewNoUpgrade') { | ||
| this.skip(); | ||
| } | ||
| }); | ||
|
Comment on lines
+14
to
+21
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: set -e
git ls-files frontend/integration-tests/support/tech-preview-guard.ts
wc -l frontend/integration-tests/support/tech-preview-guard.ts
cat -n frontend/integration-tests/support/tech-preview-guard.tsRepository: openshift/console Length of output: 1686 🏁 Script executed: set -e
rg -n "requireFeatureGate|tech-preview-guard|failOnNonZeroExit" frontend/integration-tests -SRepository: openshift/console Length of output: 775 🌐 Web query:
💡 Result: When using cy.exec in Cypress, setting the option failOnNonZeroExit to false prevents the test from failing if the executed command returns a non-zero exit code [1][2]. The cy.exec command yields an object that allows you to inspect the results of the command. As of Cypress v15, this result object includes the following key properties [2][3]: exitCode: The numeric exit code returned by the command (formerly named code in older versions) [3]. stdout: The standard output string from the command [2]. stderr: The standard error string from the command [2]. Example usage: cy.exec('your-command', { failOnNonZeroExit: false }).then((result) => { // Use result.exitCode to check if the command succeeded or failed expect(result.exitCode).to.eq(1); // Use result.stdout or result.stderr to verify output cy.log(result.stdout); }); Note: If you are upgrading from an older version of Cypress, ensure you use exitCode, as the code property was renamed in v15 [3]. The command must still eventually exit, or it will time out according to the configured execTimeout [1][2]. Citations:
Surface 🤖 Prompt for AI Agents |
||
| }); | ||
|
|
||
| export function requireFeatureGate(gateName: string): void { | ||
| before(function () { | ||
| cy.exec( | ||
| "oc get featuregate cluster -o jsonpath='{range .status.featureGates[*]}{range .enabled[*]}{.name}{\"\\n\"}{end}{end}'", | ||
| { failOnNonZeroExit: false }, | ||
| ).then((result) => { | ||
| const enabled = result.stdout | ||
| .split('\n') | ||
| .map((s) => s.trim()) | ||
| .filter(Boolean); | ||
| if (!enabled.includes(gateName)) { | ||
| this.skip(); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
The Playwright documentation describes an older contract.
docs/superpowers/specs/2026-07-16-techpreview-e2e-test-filtering-design.md#L64-L80: document FeatureGate-CR lookup throughk8sClient, notSERVER_FLAGS.docs/superpowers/specs/2026-07-16-techpreview-e2e-test-filtering-design.md#L89-L100: show valid narrowing for the CR response.docs/superpowers/specs/2026-07-16-techpreview-e2e-test-filtering-design.md#L113-L126: describe the existing helper and its current return contract.docs/superpowers/plans/2026-07-16-techpreview-e2e-test-filtering.md#L28-L55: update the implementation steps to match the actual fixture.📍 Affects 2 files
docs/superpowers/specs/2026-07-16-techpreview-e2e-test-filtering-design.md#L64-L80(this comment)docs/superpowers/specs/2026-07-16-techpreview-e2e-test-filtering-design.md#L89-L100docs/superpowers/specs/2026-07-16-techpreview-e2e-test-filtering-design.md#L113-L126docs/superpowers/plans/2026-07-16-techpreview-e2e-test-filtering.md#L28-L55🤖 Prompt for AI Agents