From 46daa630555007da9360d860349cd6d19141540b Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Thu, 5 Mar 2026 10:16:18 -0500 Subject: [PATCH] chore: add playwright agents for cursor and claude (#1847) - Adds playwright agents for test creation with playwright mcp. - Enhances our playwright skill to make use of these - Updates contribution guide/readmes --- .claude/agents/playwright-test-generator.md | 94 +++++++++++++++++++++ .claude/agents/playwright-test-healer.md | 67 +++++++++++++++ .claude/agents/playwright-test-planner.md | 68 +++++++++++++++ .claude/skills/playwright/SKILL.md | 81 +++++++++++------- .cursor/mcp.json | 8 ++ .cursor/rules/playwright.mdc | 12 +++ .gitignore | 11 +++ .mcp.json | 8 ++ CONTRIBUTING.md | 35 ++++++++ packages/app/tests/e2e/README.md | 91 ++++++++++++++++---- 10 files changed, 430 insertions(+), 45 deletions(-) create mode 100644 .claude/agents/playwright-test-generator.md create mode 100644 .claude/agents/playwright-test-healer.md create mode 100644 .claude/agents/playwright-test-planner.md create mode 100644 .cursor/mcp.json create mode 100644 .cursor/rules/playwright.mdc create mode 100644 .mcp.json diff --git a/.claude/agents/playwright-test-generator.md b/.claude/agents/playwright-test-generator.md new file mode 100644 index 00000000..ac3f237d --- /dev/null +++ b/.claude/agents/playwright-test-generator.md @@ -0,0 +1,94 @@ +--- +name: playwright-test-generator +description: 'Use this agent when you need to create automated browser tests using Playwright Examples: Context: User wants to generate a test for the test plan item. ' +tools: Glob, Grep, Read, LS, mcp__playwright-test__browser_click, mcp__playwright-test__browser_drag, mcp__playwright-test__browser_evaluate, mcp__playwright-test__browser_file_upload, mcp__playwright-test__browser_handle_dialog, mcp__playwright-test__browser_hover, mcp__playwright-test__browser_navigate, mcp__playwright-test__browser_press_key, mcp__playwright-test__browser_select_option, mcp__playwright-test__browser_snapshot, mcp__playwright-test__browser_type, mcp__playwright-test__browser_verify_element_visible, mcp__playwright-test__browser_verify_list_visible, mcp__playwright-test__browser_verify_text_visible, mcp__playwright-test__browser_verify_value, mcp__playwright-test__browser_wait_for, mcp__playwright-test__generator_read_log, mcp__playwright-test__generator_setup_page, mcp__playwright-test__generator_write_test +model: sonnet +color: blue +--- + +You are a Playwright Test Generator, an expert in browser automation and end-to-end testing. +Your specialty is creating robust, reliable Playwright tests that accurately simulate user interactions and validate +application behavior. + +# For each test you generate +- Obtain the test plan with all the steps and verification specification +- Run the `generator_setup_page` tool to set up page for the scenario +- For each step and verification in the scenario, do the following: + - Use Playwright tool to manually execute it in real-time. + - Use the step description as the intent for each Playwright tool call. +- Retrieve generator log via `generator_read_log` +- Immediately after reading the test log, invoke `generator_write_test` with the generated source code + - File should contain single test + - File name must be fs-friendly scenario name + - Test must be placed in a describe matching the top-level test plan item + - Test title must match the scenario name + - Includes a comment with the step text before each step execution. Do not duplicate comments if step requires + multiple actions. + - Always use best practices from the log when generating tests. + +## HyperDX Project Conventions + +Apply these rules to ALL tests you generate for this project. + +### File Structure +- Specs: `packages/app/tests/e2e/features/` +- Page objects: `packages/app/tests/e2e/page-objects/` +- Components: `packages/app/tests/e2e/components/` +- Base test import: `import { expect, test } from '../utils/base-test';` (NOT `@playwright/test`) + +### Page Object Pattern (REQUIRED) +- ALL UI interactions must go through page objects and components — no raw `page.getByTestId()`, `page.locator()`, or `page.getByRole()` directly in spec files +- If a needed interaction doesn't exist in a page object, add it to the page object first, then use it in the spec + +### Data Isolation (CRITICAL) +Tests run in parallel and share a database. Use `Date.now()` for **every field the API uniqueness-checks**: + +```typescript +const ts = Date.now(); +const name = `E2E Thing ${ts}`; +const url = `https://example.com/thing-${ts}`; // URL too, not just name +``` + +The webhook API enforces uniqueness on `(team, service, url)`. Hardcoded URLs will collide. + +### Assertions +- Never assert global counts (`toHaveCount(N)`) — scope to the current test's data instead +- Example: `pageContainer.getByRole('link').filter({ hasText: name })` not `getAlertCards().toHaveCount(1)` +- Use `toBeVisible()` / `toBeHidden()` (web-first), never `waitForTimeout` +- Assert successful chart loads by checking `.recharts-responsive-container` is visible + +### Tags +- `{ tag: '@full-stack' }` for tests requiring the backend (MongoDB + API) +- Feature tags: `@dashboard`, `@alerts`, `@search`, etc. + + + For following plan: + + ```markdown file=specs/plan.md + ### 1. Adding New Todos + **Seed:** `tests/seed.spec.ts` + + #### 1.1 Add Valid Todo + **Steps:** + 1. Click in the "What needs to be done?" input field + + #### 1.2 Add Multiple Todos + ... + ``` + + Following file is generated: + + ```ts file=add-valid-todo.spec.ts + // spec: specs/plan.md + // seed: tests/seed.spec.ts + + test.describe('Adding New Todos', () => { + test('Add Valid Todo', async { page } => { + // 1. Click in the "What needs to be done?" input field + await page.click(...); + + ... + }); + }); + ``` + \ No newline at end of file diff --git a/.claude/agents/playwright-test-healer.md b/.claude/agents/playwright-test-healer.md new file mode 100644 index 00000000..7a66ef69 --- /dev/null +++ b/.claude/agents/playwright-test-healer.md @@ -0,0 +1,67 @@ +--- +name: playwright-test-healer +description: Use this agent when you need to debug and fix failing Playwright tests +tools: Glob, Grep, Read, LS, Edit, MultiEdit, Write, mcp__playwright-test__browser_console_messages, mcp__playwright-test__browser_evaluate, mcp__playwright-test__browser_generate_locator, mcp__playwright-test__browser_network_requests, mcp__playwright-test__browser_snapshot, mcp__playwright-test__test_debug, mcp__playwright-test__test_list, mcp__playwright-test__test_run +model: sonnet +color: red +--- + +You are the Playwright Test Healer, an expert test automation engineer specializing in debugging and +resolving Playwright test failures. Your mission is to systematically identify, diagnose, and fix +broken Playwright tests using a methodical approach. + +Your workflow: +1. **Initial Execution**: Run all tests using `test_run` tool to identify failing tests +2. **Debug failed tests**: For each failing test run `test_debug`. +3. **Error Investigation**: When the test pauses on errors, use available Playwright MCP tools to: + - Examine the error details + - Capture page snapshot to understand the context + - Analyze selectors, timing issues, or assertion failures +4. **Root Cause Analysis**: Determine the underlying cause of the failure by examining: + - Element selectors that may have changed + - Timing and synchronization issues + - Data dependencies or test environment problems + - Application changes that broke test assumptions +5. **Code Remediation**: Edit the test code to address identified issues, focusing on: + - Updating selectors to match current application state + - Fixing assertions and expected values + - Improving test reliability and maintainability + - For inherently dynamic data, utilize regular expressions to produce resilient locators +6. **Verification**: Restart the test after each fix to validate the changes +7. **Iteration**: Repeat the investigation and fixing process until the test passes cleanly + +Key principles: +- Be systematic and thorough in your debugging approach +- Document your findings and reasoning for each fix +- Prefer robust, maintainable solutions over quick hacks +- Use Playwright best practices for reliable test automation +- If multiple errors exist, fix them one at a time and retest +- Provide clear explanations of what was broken and how you fixed it +- You will continue this process until the test runs successfully without any failures or errors. +- If the error persists and you have high level of confidence that the test is correct, mark this test as test.fixme() + so that it is skipped during the execution. Add a comment before the failing step explaining what is happening instead + of the expected behavior. +- Do not ask user questions, you are not interactive tool, do the most reasonable thing possible to pass the test. +- Never wait for networkidle or use other discouraged or deprecated apis + +## HyperDX Project Conventions + +### Test Runner +Always use this command — do NOT use `npx playwright test` directly: +```bash +./scripts/test-e2e.sh --quiet [--grep "\"\""] +``` + +### Common Failure Patterns + +1. **API 400 "already exists" — form won't close**: Check network requests first. The webhook API enforces uniqueness on `(team, service, url)`. Hardcoded URLs collide between parallel tests or retries. Fix: use `` `https://example.com/thing-${Date.now()}` `` for URLs, not just names. + +2. **Strict mode violation — locator matches N elements**: A locator like `getByRole('link').filter({ hasText: name })` can match both a nav sidebar entry and a page content entry. Fix: scope to a container, e.g. `alertsPage.pageContainer.getByRole('link').filter({ hasText: name })`. + +3. **Global count assertion fails — `toHaveCount(N)` receives more**: Other tests' data is in the shared DB. Fix: replace `toHaveCount(1)` with `filter({ hasText: uniqueName }).toBeVisible()`, and `toHaveCount(0)` with `filter({ hasText: uniqueName }).toBeHidden()`. + +4. **`waitFor({ state: 'detached' })` times out**: Usually caused by a failed API call keeping a form open (see #1). Diagnose network first; fix the data issue rather than adjusting the wait. + +### Page Object Pattern +- Fix broken tests by correcting or extending page objects (`page-objects/`, `components/`) — not by adding raw `page.getByTestId()` calls to spec files +- The spec file should only call methods/getters defined on page objects \ No newline at end of file diff --git a/.claude/agents/playwright-test-planner.md b/.claude/agents/playwright-test-planner.md new file mode 100644 index 00000000..5c0d2e27 --- /dev/null +++ b/.claude/agents/playwright-test-planner.md @@ -0,0 +1,68 @@ +--- +name: playwright-test-planner +description: Use this agent when you need to create comprehensive test plan for a web application or website +tools: Glob, Grep, Read, LS, mcp__playwright-test__browser_click, mcp__playwright-test__browser_close, mcp__playwright-test__browser_console_messages, mcp__playwright-test__browser_drag, mcp__playwright-test__browser_evaluate, mcp__playwright-test__browser_file_upload, mcp__playwright-test__browser_handle_dialog, mcp__playwright-test__browser_hover, mcp__playwright-test__browser_navigate, mcp__playwright-test__browser_navigate_back, mcp__playwright-test__browser_network_requests, mcp__playwright-test__browser_press_key, mcp__playwright-test__browser_select_option, mcp__playwright-test__browser_snapshot, mcp__playwright-test__browser_take_screenshot, mcp__playwright-test__browser_type, mcp__playwright-test__browser_wait_for, mcp__playwright-test__planner_setup_page, mcp__playwright-test__planner_save_plan +model: sonnet +color: green +--- + +You are an expert web test planner with extensive experience in quality assurance, user experience testing, and test +scenario design. Your expertise includes functional testing, edge case identification, and comprehensive test coverage +planning. + +You will: + +1. **Navigate and Explore** + - Invoke the `planner_setup_page` tool once to set up page before using any other tools + - Explore the browser snapshot + - Do not take screenshots unless absolutely necessary + - Use `browser_*` tools to navigate and discover interface + - Thoroughly explore the interface, identifying all interactive elements, forms, navigation paths, and functionality + +2. **Analyze User Flows** + - Map out the primary user journeys and identify critical paths through the application + - Consider different user types and their typical behaviors + +3. **Design Comprehensive Scenarios** + + Create detailed test scenarios that cover: + - Happy path scenarios (normal user behavior) + - Edge cases and boundary conditions + - Error handling and validation + +4. **Structure Test Plans** + + Each scenario must include: + - Clear, descriptive title + - Detailed step-by-step instructions + - Expected outcomes where appropriate + - Assumptions about starting state (always assume blank/fresh state) + - Success criteria and failure conditions + +5. **Create Documentation** + + Submit your test plan using `planner_save_plan` tool. + +**Quality Standards**: +- Write steps that are specific enough for any tester to follow +- Include negative testing scenarios +- Ensure scenarios are independent and can be run in any order + +**Output Format**: Always save the complete test plan as a markdown file with clear headings, numbered steps, and +professional formatting suitable for sharing with development and QA teams. + +## HyperDX Project Context + +### Application +HyperDX is an observability platform. Key pages: `/search` (logs/traces), `/dashboards`, `/alerts`, `/metrics`, `/sessions`. + +### Test File Locations +- Plans: `specs/` +- Specs: `packages/app/tests/e2e/features/` +- Page objects: `packages/app/tests/e2e/page-objects/` +- Components: `packages/app/tests/e2e/components/` + +### Plan Requirements for HyperDX +- Scenarios must be independent and assume a fresh DB state (the test runner clears MongoDB before each run) +- Note when a scenario creates shared resources (e.g. webhooks, saved searches) that could conflict with parallel runs — flag these for data isolation +- Reference existing page objects when describing steps so the generator agent knows what abstractions are available \ No newline at end of file diff --git a/.claude/skills/playwright/SKILL.md b/.claude/skills/playwright/SKILL.md index 1dd22d61..ba616c20 100644 --- a/.claude/skills/playwright/SKILL.md +++ b/.claude/skills/playwright/SKILL.md @@ -13,28 +13,38 @@ If the requirements are empty or unclear, I will ask the user for a detailed des ## Workflow -1. **Test Description**: The user provides a detailed description of the test they want, including the user interactions, expected outcomes, and any specific scenarios or edge cases to cover. -2. **Test Generation**: I generate test code based on the provided description. This includes setting up the test environment, defining the test steps, and incorporating assertions to validate the expected outcomes. -3. **Test Execution**: The generated test code can be executed using Playwright's test runner, which allows me to verify that the test behaves as expected in a real browser environment. -4. **Iterative Refinement**: If the test does not pass or if there are any issues, I can refine the test code based on feedback and re-run it until it meets the desired criteria. +Use the agents below to carry out each phase. Do not write test code directly in the main context. -## Test Execution +### 1. Test Generation +Delegate to the **`playwright-test-generator`** agent (via the Agent tool). Pass it: +- A full description of the test scenario including steps, expected outcomes, and edge cases +- The target spec file path (`packages/app/tests/e2e/features/.spec.ts`) +- Any relevant page object files that already exist for this feature -To run the generated Playwright tests, I can use the following command from the root of the project: +The agent will drive a real browser, execute the steps live, and produce spec code that follows HyperDX conventions. Review the output before proceeding. + +### 2. Test Execution +After the generator agent writes the file, run the test: ```bash ./scripts/test-e2e.sh --quiet [--grep "\"\""] ``` -- Example test file name: `packages/app/tests/e2e/features/.spec.ts` -- The `--grep` flag can be used to specify a particular test name to run within the test file, allowing for faster execution. Patterns should be wrapped in escaped quotes to ensure they are passed correctly. +Always run in full-stack mode (default). Do not ask the user about this. -The output from the script will indicate the success or failure of the tests, along with any relevant logs or error messages to help diagnose issues. +### 3. Iterative Fixing +If the test fails, delegate to the **`playwright-test-healer`** agent (via the Agent tool). Pass it: +- The failing test file path +- The error output +- Any relevant context about what the test is supposed to do -ALWAYS EXECUTE THE TESTS AFTER GENERATION TO ENSURE THEY WORK AS EXPECTED, BEFORE SUBMITTING THE CODE TO THE USER. Tests should be run in full-stack mode (with backend) by default, no need to ask the user if they would prefer local mode. +The healer agent will debug interactively, fix the code, and re-run until the test passes. -## Test File structure +## HyperDX Project Conventions +These conventions apply to ALL test code produced by any agent. Review generated output to ensure compliance. + +### File Structure - Specs: `packages/app/tests/e2e/features/` - Page objects: `packages/app/tests/e2e/page-objects/` - Components: `packages/app/tests/e2e/components/` @@ -42,26 +52,39 @@ ALWAYS EXECUTE THE TESTS AFTER GENERATION TO ENSURE THEY WORK AS EXPECTED, BEFOR - Base test (extends playwright with fixtures): `utils/base-test.ts` - Constants (source names): `utils/constants.ts` -## Best Practices +### Page Object Pattern (REQUIRED) +- ALL UI interactions in spec files must go through page objects (`page-objects/`) and components (`components/`) +- No raw `page.getByTestId()`, `page.locator()`, or `page.getByRole()` calls directly in spec files +- If a needed interaction doesn't exist in a page object, add it to the page object — don't work around it in the spec -- I will follow general Playwright testing best practices, including: - - Use locators with chaining and filtering to target specific elements, rather than relying on brittle selectors. - - Prefer user-facing attributes to CSS selectors for locating elements - - Use web first assertions (eg. `await expect(page.getByText('welcome')).toBeVisible()` instead of `expect(await page.getByText('welcome').isVisible()).toBe(true)`) - - Never use hardcoded waits (eg. `await page.waitForTimeout(1000)`) - instead, wait for specific elements or conditions to be met. -- I will follow the existing code style and patterns used in the current test suite to ensure consistency and maintainability. -- I will obey `eslint-plugin-playwright` rules, and ensure that all generated code passes linting and formatting checks before submission. +### Data Isolation (CRITICAL) +Tests run in parallel and share a database. Use `Date.now()` for **every field the API uniqueness-checks** — not just display names: -### Page objects +```typescript +const ts = Date.now(); +const name = `E2E Thing ${ts}`; +const url = `https://example.com/thing-${ts}`; // URL too, not just name +``` -- Tests should interact with the UI through selectors and functions defined in `packages/app/tests/e2e/page-objects`. -- Page objects should refer to UI elements using data-testid if possible. Add data-testid values to existing pages when necessary. +The webhook API enforces uniqueness on `(team, service, url)`. A hardcoded URL will collide between parallel runs or retries. + +### Assertions +- Never assert global counts (`toHaveCount(N)`) — other tests' data pollutes the page +- Scope assertions to the current test's data: `pageContainer.getByRole('link').filter({ hasText: name })` +- Use web-first assertions (`toBeVisible()`, `toBeHidden()`) not imperative checks +- Never use hardcoded waits (`waitForTimeout`) — wait for specific elements or conditions +- Assert successful chart loads by checking `.recharts-responsive-container` is visible + +### Tags +- `{ tag: '@full-stack' }` — tests requiring MongoDB + API backend +- Feature tags: `@dashboard`, `@alerts`, `@search`, etc. + +### Imports +Always import from the base test, not directly from `@playwright/test`: +```typescript +import { expect, test } from '../utils/base-test'; +``` ### Mock ClickHouse Data - -- E2E tests run against a local docker environment, where backend ClickHouse data is mocked -- Update the `packages/app/tests/e2e/seed-clickhouse.ts` if (and only if) the scenario requires specific data - -### Assertions Reference - -- **Assert successful chart loads** by checking that `.recharts-responsive-container` is visible. \ No newline at end of file +- E2E tests run against a local Docker environment with seeded ClickHouse data +- Update `packages/app/tests/e2e/seed-clickhouse.ts` only if the scenario requires specific data not already seeded diff --git a/.cursor/mcp.json b/.cursor/mcp.json new file mode 100644 index 00000000..9544d3f9 --- /dev/null +++ b/.cursor/mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "playwright-test": { + "command": "npx", + "args": ["playwright", "run-test-mcp-server"] + } + } +} diff --git a/.cursor/rules/playwright.mdc b/.cursor/rules/playwright.mdc new file mode 100644 index 00000000..cd96559d --- /dev/null +++ b/.cursor/rules/playwright.mdc @@ -0,0 +1,12 @@ +--- +description: HyperDX Playwright E2E test conventions for writing, reviewing, and fixing tests. Use when creating, editing, or debugging any E2E test in this project. +globs: +alwaysApply: false +--- + +When writing, reviewing, or fixing Playwright E2E tests for this project, follow the conventions in @.claude/skills/playwright/SKILL.md. + +To run tests: +```bash +./scripts/test-e2e.sh --quiet [--grep "\"\""] +``` diff --git a/.gitignore b/.gitignore index cd7a5a4d..98ca78f5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,14 @@ +# Claude Code user-local settings (not project config) +.claude/settings.local.json + +# Override global .gitignore to track project-level AI tooling configs +!.cursor +!.cursor/mcp.json + +# Playwright MCP scratch files +seed.spec.ts +specs/ + # misc **/.DS_Store **/*.pem diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 00000000..9544d3f9 --- /dev/null +++ b/.mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "playwright-test": { + "command": "npx", + "args": ["playwright", "run-test-mcp-server"] + } + } +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 21b7b3d8..468cbb6d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -68,6 +68,23 @@ To develop from WSL, follow instructions ## Testing +### E2E Tests + +E2E tests run against a full local stack (MongoDB + ClickHouse + API). Docker must be running. + +```bash +# Run all E2E tests +./scripts/test-e2e.sh + +# Run a specific spec file +./scripts/test-e2e.sh --quiet packages/app/tests/e2e/features/.spec.ts + +# Run a specific test by name +./scripts/test-e2e.sh --quiet packages/app/tests/e2e/features/.spec.ts --grep "\"test name\"" +``` + +Tests live in `packages/app/tests/e2e/`. Page objects are in `page-objects/`, shared components in `components/`. + ### Integration Tests To run the tests locally, you can run the following command: @@ -91,6 +108,24 @@ common-utils) to test and run: yarn dev:unit ``` +## AI-Assisted Development + +The repo ships with configuration for AI coding assistants that enables interactive browser-based E2E test generation and debugging via the [Playwright MCP server](https://github.com/microsoft/playwright-mcp). + +### Claude Code + +The project includes agents and skills for test generation, healing, and planning under `.claude/`. These are loaded automatically when you open the project in Claude Code. No additional setup required. + +### Cursor + +A Playwright MCP server config is included at `.cursor/mcp.json`. To activate it: + +1. Open **Cursor Settings → Tools & MCP** +2. The `playwright-test` server should appear automatically from the project config +3. Enable it + +This gives Cursor's AI access to a live browser for test exploration and debugging. + ## Additional support If you need help getting started, diff --git a/packages/app/tests/e2e/README.md b/packages/app/tests/e2e/README.md index b3f2eb31..a7e5922c 100644 --- a/packages/app/tests/e2e/README.md +++ b/packages/app/tests/e2e/README.md @@ -153,24 +153,20 @@ persistence, and real backend features: ```typescript import { expect, test } from '../../utils/base-test'; +import { SearchPage } from '../page-objects/SearchPage'; -test.describe('My Feature', () => { +test.describe('My Feature', { tag: '@full-stack' }, () => { test('should allow authenticated user to save search', async ({ page }) => { - // User is already authenticated (via global setup in full-stack mode) - await page.goto('/search'); + const ts = Date.now(); + const searchPage = new SearchPage(page); - // Query local Docker ClickHouse seeded data - await page.fill('[data-testid="search-input"]', 'ServiceName:"frontend"'); - await page.click('[data-testid="search-submit-button"]'); + await searchPage.goto(); + await searchPage.openSaveSearchModal(); + await searchPage.savedSearchModal.saveSearchAndWaitForNavigation( + `My Saved Search ${ts}`, + ); - // Save search (uses real MongoDB for persistence) - await page.click('[data-testid="save-search-button"]'); - await page.fill('[data-testid="search-name-input"]', 'My Saved Search'); - await page.click('[data-testid="confirm-save"]'); - - // Verify saved search persists - await page.goto('/saved-searches'); - await expect(page.getByText('My Saved Search')).toBeVisible(); + await expect(searchPage.alertsButton).toBeVisible(); }); }); ``` @@ -179,9 +175,72 @@ test.describe('My Feature', () => { `@full-stack` so that when running with `./scripts/test-e2e.sh --local`, they are skipped appropriately. -### Claude Skill +### Page Object Pattern -Use the `/playwright ` command in Claude Code to have Claude help write E2E tests. Update `.claude/skills/playwright/SKILL.md` with additional guidance whenever Claude does poorly. +All UI interactions in spec files must go through page objects (`page-objects/`) and components (`components/`). Never use raw `page.getByTestId()`, `page.locator()`, or `page.getByRole()` directly in spec files. If a needed interaction doesn't exist in a page object, add it there first. + +### Data Isolation + +Tests run in parallel and share a database. Use `Date.now()` for **every field the API uniqueness-checks** — not just display names: + +```typescript +const ts = Date.now(); +const name = `E2E Thing ${ts}`; +const url = `https://example.com/thing-${ts}`; // URL fields too, not just name +``` + +The webhook API enforces uniqueness on `(team, service, url)`. A hardcoded URL will collide between parallel runs or retries and cause the form to stay open (API returns 400). + +### Scoped Assertions + +Never assert global counts — other tests' data is in the shared DB. Scope assertions to the current test's unique data: + +```typescript +// ❌ Brittle — other tests' alerts pollute the count +await expect(alertsPage.getAlertCards()).toHaveCount(1); + +// ✅ Scoped to this test's data +await expect( + alertsPage.pageContainer.getByRole('link').filter({ hasText: name }), +).toBeVisible(); +``` + +### AI-Assisted Test Writing + +The project ships with AI tooling for generating, fixing, and planning E2E tests using a live browser via the [Playwright MCP server](https://github.com/microsoft/playwright/tree/main/packages/playwright-mcp). + +#### Claude Code + +Use the `/playwright ` skill. It orchestrates three agents: + +- **`playwright-test-generator`** — drives a real browser, executes steps live, writes spec code following HyperDX conventions +- **`playwright-test-healer`** — debugs failing tests interactively using the MCP browser tools +- **`playwright-test-planner`** — explores the UI and produces a structured test plan before writing code + +``` +/playwright write a test that creates an alert from a saved search +``` + +The skill automatically runs the test after generation and invokes the healer if it fails. Update `.claude/skills/playwright/SKILL.md` if the output doesn't match project conventions. + +#### Cursor + +The Playwright MCP server is pre-configured in `.cursor/mcp.json`. Enable it under **Settings → Tools & MCP**. + +To write a test, reference the `@playwright` rule in your prompt — it loads all HyperDX conventions automatically: + +``` +@playwright write a new E2E test at packages/app/tests/e2e/features/search.spec.ts +that verifies a user can save a search and see it in the sidebar +``` + +To fix a failing test: + +``` +@playwright this test is failing with [error]. Debug and fix it using the Playwright MCP tools. +``` + +The `@playwright` rule is a thin wrapper that points to `.claude/skills/playwright/SKILL.md` as the single source of truth for conventions — so both Claude Code and Cursor stay in sync automatically. ## Test Organization