Skip to content

chore: playwright#6562

Open
kyle-ssg wants to merge 276 commits intomainfrom
chore/playwright
Open

chore: playwright#6562
kyle-ssg wants to merge 276 commits intomainfrom
chore/playwright

Conversation

@kyle-ssg
Copy link
Member

@kyle-ssg kyle-ssg commented Jan 20, 2026

  • I have read the Contributing Guide.
  • I have added information to docs/ if required so people know about the feature.
  • I have filled in the "Changes" section below.
  • I have filled in the "How did you test this code" section below.

Changes

Playwright Test Improvements

Faster Tests

  • Inputs are really fast compared to TestCafe
  • Tests are passing in CI around 2m30s vs ~4m

Better Local Development

  • Tests are re-runable via ui (you'll have to run npm run test:teardown before clicking play again)
  • Tests come with videos and clickable traces (these get uploaded from GitHub on failures too)
  • Tests can be ran very quickly and can be repeated (E2E_REPEAT=x) and exit on first failed test (E2E_RETRIES=0)
image

main_large

Added Claude Commands

  • /e2e - Run all tests (OSS + Enterprise)
  • /e2e-oss - Run OSS tests, auto-fix failures, re-run until passing
  • /e2e-ee - Run Enterprise tests, auto-fix failures, re-run until passing
  • /e2e-create - Create a new test following existing patterns

Note: You can have the tests repeat to guarantee no flakiness

  • /e2e 5 → Runs all tests with E2E_REPEAT=5 (runs once, then 5 more times if passing)
  • /e2e-oss 5 → Same for OSS tests only
  • /e2e-ee 5 → Same for enterprise tests only
image

HTML Reports

  • Interactive dashboard with search/filter
  • Test timeline and duration metrics
  • Detailed error messages with stack traces

e.g. this is a downloaded report, we can access reports of failures as well as videos / interactive traces.

image image image image

CI/CD Features

GitHub Integration

  • Sticky PR comments with failure summaries
  • Direct links to ZIP artifacts containing:
  • HTML report
  • Videos of failures
  • Interactive traces
  • Screenshots
  • Automatic artifact cleanup
image image

How did you test this code?

  • No code changes, all e2e refactoring
  • Run local image and keep track of any failures
image

…chore/componentise-feature-override-row

# Conflicts:
#	frontend/web/components/feature-override/FeatureOverrideRow.tsx
# Conflicts:
#	frontend/README.md
#	frontend/package-lock.json
# Conflicts:
#	frontend/e2e/helpers.cafe.ts
#	frontend/e2e/tests/invite-test.ts
#	frontend/package-lock.json
module.exports = [

new webpack.DefinePlugin({
E2E: process.env.E2E,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing E2E from DefinePlugin causes production ReferenceError

High Severity

Removing E2E: process.env.E2E from webpack's DefinePlugin breaks production. ValueEditor.js and organisation-store.js still reference bare E2E as a global variable. Previously, DefinePlugin replaced every E2E reference with the literal undefined at build time, which is safe. Now, E2E remains as an undeclared variable in the compiled bundle, causing a ReferenceError at runtime in production (where window.E2E is never set). The window.E2E = true injection in test-setup.ts only runs during Playwright tests, not in production.

Fix in Cursor Fix in Web

const CHANNEL_ID = 'C0102JZRG3G'; // infra_tests channel ID
const failedJsonPath = path.join(__dirname, 'test-results', 'failed.json');
const failedData = JSON.parse(fs.readFileSync(failedJsonPath, 'utf-8'));
const failedCount = failedData.failedTests?.length || 0;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File read before existence check causes crash

Low Severity

failed.json is read eagerly at module scope (line 8) with fs.readFileSync, but the fs.existsSync guard is at line 82 — after the read. If the file doesn't exist (e.g., tests crashed before the reporter could write it), the script throws on line 8 and the existence check is never reached. The guard needs to come before the read.

Additional Locations (1)

Fix in Cursor Fix in Web

API_IMAGE: ${{ inputs.api-image }}
E2E_IMAGE: ${{ inputs.e2e-image }}
E2E_CONCURRENCY: ${{ inputs.concurrency }}
E2E_RETRIES: 2
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

E2E_RETRIES appears misindented outside env mapping

Medium Severity

E2E_RETRIES: 2 appears to be indented at a different level than the other environment variables under env:. The other env vars (opts, API_IMAGE, E2E_CONCURRENCY, SLACK_TOKEN) are consistently indented 2 spaces deeper than env:, but E2E_RETRIES appears to be at the same level as env: itself. This would place it outside the env mapping, meaning it won't be set as an environment variable — or it could cause a YAML parse error entirely.

Fix in Cursor Fix in Web

const CHANNEL_ID = 'C0102JZRG3G'; // infra_tests channel ID
const failedJsonPath = path.join(__dirname, 'test-results', 'failed.json');
const failedData = JSON.parse(fs.readFileSync(failedJsonPath, 'utf-8'));
const failedCount = failedData.failedTests?.length || 0;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slack reporter crashes before file existence check runs

High Severity

The fs.readFileSync on line 8 runs at module load time, before the fs.existsSync guard on line 82 has a chance to execute. When failed.json doesn't exist (e.g., if global setup fails before any tests run), the script throws an unhandled ENOENT error and crashes instead of gracefully exiting with the "No failed.json found" message.

Additional Locations (1)

Fix in Cursor Fix in Web

API_IMAGE: ${{ inputs.api-image }}
E2E_IMAGE: ${{ inputs.e2e-image }}
E2E_CONCURRENCY: ${{ inputs.concurrency }}
E2E_RETRIES: 2
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

E2E_RETRIES not forwarded into Docker container

Medium Severity

The CI workflow sets E2E_RETRIES: 2 as a step-level environment variable, but the Makefile only passes E2E_CONCURRENCY to the Docker container via cross-env. Since the docker-compose environment section for the frontend service also doesn't list E2E_RETRIES, this value never reaches run-with-retry.ts inside the container, which defaults to 1 retry instead of the intended 2.

Additional Locations (1)

Fix in Cursor Fix in Web

const CHANNEL_ID = 'C0102JZRG3G'; // infra_tests channel ID
const failedJsonPath = path.join(__dirname, 'test-results', 'failed.json');
const failedData = JSON.parse(fs.readFileSync(failedJsonPath, 'utf-8'));
const failedCount = failedData.failedTests?.length || 0;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File read executes before existence check, crashes on missing file

Medium Severity

The fs.readFileSync on failedJsonPath at module level (line 8) executes before the fs.existsSync guard on line 82. If failed.json doesn't exist (e.g., tests failed during global setup before any test ran), the script throws an unhandled ENOENT error and crashes instead of gracefully exiting. The existence check on line 82 is effectively dead code and never runs in the missing-file scenario. The check and early exit need to come before the file read.

Additional Locations (1)

Fix in Cursor Fix in Web

"test": "npm run test:bundle && cross-env NODE_ENV=production E2E=true ts-node -T ./e2e/index.cafe",
"test:staging": "npm run test:bundle:staging && cross-env NODE_ENV=production E2E=true ENV=staging ts-node -T ./e2e/index.cafe",
"test": "npx -y tsx e2e/run-with-retry.ts",
"test:run": "cross-env npm run bundle && npx playwright test",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invalid cross-env usage without environment variable

High Severity

The test:run script uses cross-env without specifying any environment variables to set. The syntax cross-env npm run bundle is invalid because cross-env requires at least one VAR=value pair before the command (e.g., cross-env E2E=true npm run bundle). This will cause the script to fail or behave unexpectedly when executed directly.

Fix in Cursor Fix in Web

log('Add project permissions to the Role')
await click(byId(`role-0`))
await click(byId('permissions-tab'))
await click(byId('permissions-tab'))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate click on permissions tab in role test

Medium Severity

The permissions-tab element is clicked twice in succession with identical calls to click(byId('permissions-tab')). This appears to be a copy-paste error and is redundant. While unlikely to cause test failures, it adds unnecessary delay and could cause issues if the UI responds unexpectedly to double-clicks.

Fix in Cursor Fix in Web

await login(E2E_NON_ADMIN_USER_WITH_ENV_PERMISSIONS, PASSWORD)
await gotoProject(PROJECT_NAME)
await waitForElementVisible(byId('switch-environment-production'))
await waitForElementVisible(byId('switch-environment-production'))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate wait for same element in environment test

Low Severity

The switch-environment-production element is waited for twice in consecutive lines using identical waitForElementVisible(byId('switch-environment-production')) calls. This is redundant code that serves no purpose and should be reduced to a single call.

Fix in Cursor Fix in Web

// Skip cleanup on retries to preserve failed.json and test artifacts
if (isRetry) {
playwrightCmd.push('E2E_SKIP_CLEANUP=1');
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Retry logic broken by missing artifact preservation

Medium Severity

The retry mechanism sets E2E_SKIP_CLEANUP=1 to preserve test artifacts but never checks this variable. Playwright cleans the outputDir (including .last-run.json) at the start of each test run by default. This breaks the --last-failed flag at line 151, which depends on .last-run.json to identify failed tests. On retry, all tests will re-run instead of just the failed ones, defeating the purpose of the retry optimization and wasting execution time.

Additional Locations (1)

Fix in Cursor Fix in Web

# Conflicts:
#	frontend/e2e/tests/segment-test.ts
#	frontend/package-lock.json
const CHANNEL_ID = 'C0102JZRG3G'; // infra_tests channel ID
const failedJsonPath = path.join(__dirname, 'test-results', 'failed.json');
const failedData = JSON.parse(fs.readFileSync(failedJsonPath, 'utf-8'));
const failedCount = failedData.failedTests?.length || 0;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File read crashes before existence check is reached

High Severity

fs.readFileSync(failedJsonPath, 'utf-8') on line 8 executes unconditionally at module load, before the fs.existsSync guard on line 82 is ever reached. If failed.json doesn't exist, the script crashes with an unhandled ENOENT error. Since this script only runs on CI test failures (if: failure()), the file may well be absent — for example, when global setup fails before any test executes. The existence check and early process.exit(0) are dead code in the missing-file scenario. Moving the file read after the existence check (or into main()) would fix this.

Additional Locations (1)

Fix in Cursor Fix in Web

API_IMAGE: ${{ inputs.api-image }}
E2E_IMAGE: ${{ inputs.e2e-image }}
E2E_CONCURRENCY: ${{ inputs.concurrency }}
E2E_RETRIES: 2
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

E2E_RETRIES not forwarded to Docker container in CI

Medium Severity

E2E_RETRIES: 2 is set in the CI workflow step env but never reaches the Docker container. The Makefile explicitly forwards E2E_CONCURRENCY to the container via cross-env in the sh -c command, but E2E_RETRIES is not included. The docker-compose-e2e-tests.yml frontend service environment section also doesn't list it. So run-with-retry.ts inside the container always uses the default value of 1, making the CI setting ineffective.

Additional Locations (1)

Fix in Cursor Fix in Web

kyle-ssg and others added 2 commits February 25, 2026 11:02
The CI workflow sets E2E_RETRIES=2 but the Makefile only forwarded
E2E_CONCURRENCY to the container, so retries defaulted to 1.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
const CHANNEL_ID = 'C0102JZRG3G'; // infra_tests channel ID
const failedJsonPath = path.join(__dirname, 'test-results', 'failed.json');
const failedData = JSON.parse(fs.readFileSync(failedJsonPath, 'utf-8'));
const failedCount = failedData.failedTests?.length || 0;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File read crashes before existence check executes

Medium Severity

The top-level fs.readFileSync(failedJsonPath, 'utf-8') on line 8 executes at module load time, before the fs.existsSync(failedJsonPath) guard on line 82. If failed.json doesn't exist (e.g., global setup failure prevents any tests from running), the script crashes with an unhandled error instead of gracefully exiting. The existence check needs to be moved before the file read.

Additional Locations (1)

Fix in Cursor Fix in Web

|| (docker compose logs flagsmith-api; exit 1)
@echo "Running E2E tests..."
@docker compose run --name e2e-test-run frontend \
sh -c 'npx cross-env E2E_CONCURRENCY=${E2E_CONCURRENCY} npm run test -- $(opts)' \
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

E2E_RETRIES not forwarded to Docker container

Medium Severity

The Makefile test target passes E2E_CONCURRENCY via cross-env to the Docker container but omits E2E_RETRIES. The CI workflow sets E2E_RETRIES: 2 as a step-level env var, but since the Makefile doesn't include it in the cross-env command and it's not in docker-compose-e2e-tests.yml, the test runner inside the container defaults to 1 retry instead of the intended 2.

Additional Locations (1)

Fix in Cursor Fix in Web

@talissoncosta
Copy link
Contributor

talissoncosta commented Feb 27, 2026

Great work on the Playwright migration @kyle-ssg ! The DX improvements (trace viewer, DOM snapshots, interactive UI mode) are huge.

I tested extensively using both /e2e and make test. Here are my findings:

Test Results

Using make test with --grep @oss:

10 passed ✅

Using make test with --grep @enterprise:

4 passed, 2 failed ❌ (change-request-test, roles-test)

Using /e2e (all tests):

14 passed, 2 failed ❌ (same tests)

Observations

1. Enterprise tests fail against OSS API

Two tests consistently fail locally:

  • change-request-test → POST /create-change-request/ returns 405
  • roles-test → POST /roles/ returns 405

Root cause: docker-compose-e2e-tests.yml uses target: oss-api, but these tests require enterprise-only endpoints.

2. Concurrency mismatch

  • .claude/commands/e2e.md uses E2E_CONCURRENCY=20 → causes browser crashes locally
  • Makefile uses E2E_CONCURRENCY=3 → stable

3. waitForTimeout() anti-patterns

Found instances in e2e-helpers.playwright.ts, though e2e.md states "NEVER use page.waitForTimeout()".

CI handles OSS/Enterprise correctly

Looking at platform-pull-request.yml:

Job API Image Tests
run-e2e-tests oss-api --grep @oss
run-e2e-tests-private-cloud private-cloud-unified --grep "@oss|@enterprise"

Can't build private-cloud locally

Tried changing to target: private-cloud-unified but build fails - requires github_private_cloud_token for private repos.

Questions

  1. Is --grep @oss the intended way to run tests locally?

  2. Should docs/commands default to OSS-only tests for local development?

  3. Should we align concurrency settings? (Makefile uses 3, /e2e command uses 20)


None of these are blockers. The PR is really good and ready to merge! I just wanted to raise these observations for awareness and to clarify the expected local development workflow.

talissoncosta
talissoncosta previously approved these changes Feb 27, 2026
Zaimwa9
Zaimwa9 previously approved these changes Mar 2, 2026
Copy link
Contributor

@Zaimwa9 Zaimwa9 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 🚀

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

const CHANNEL_ID = 'C0102JZRG3G'; // infra_tests channel ID
const failedJsonPath = path.join(__dirname, 'test-results', 'failed.json');
const failedData = JSON.parse(fs.readFileSync(failedJsonPath, 'utf-8'));
const failedCount = failedData.failedTests?.length || 0;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File read executes before existence check crashes script

Medium Severity

The failed.json file is read and parsed at module top-level (lines 7–9) with fs.readFileSync and JSON.parse, but the fs.existsSync guard that checks whether the file exists doesn't run until line 82. If failed.json is absent (e.g., global setup fails before any test runs), the script throws an uncaught ENOENT error at module load time, before reaching the existence check or the main().catch() handler. The read and existence check need to be reordered so the guard runs first.

Additional Locations (1)

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api Issue related to the REST API chore front-end Issue related to the React Front End Dashboard

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate E2E tests to playwright

3 participants