diff --git a/.claude/skills/playwright/SKILL.md b/.claude/skills/playwright/SKILL.md new file mode 100644 index 00000000..1dd22d61 --- /dev/null +++ b/.claude/skills/playwright/SKILL.md @@ -0,0 +1,67 @@ +--- +name: playwright +description: Writes end-to-end tests code using Playwright browser automation. +--- + +# Playwright End-to-End Test Writer + +I am a Playwright End-to-End Test Writer. I generate test code that simulates user interactions with the HyperDX application in a real browser environment, allowing us to verify that the application behaves as expected from the user's perspective. + +I will write tests covering these requirements: $ARGUMENTS. + +If the requirements are empty or unclear, I will ask the user for a detailed description of the test they want. + +## 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. + +## Test Execution + +To run the generated Playwright tests, I can use the following command from the root of the project: + +```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. + +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. + +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. + +## Test File structure + +- Specs: `packages/app/tests/e2e/features/` +- Page objects: `packages/app/tests/e2e/page-objects/` +- Components: `packages/app/tests/e2e/components/` +- Utilities: `packages/app/tests/e2e/utils/` +- Base test (extends playwright with fixtures): `utils/base-test.ts` +- Constants (source names): `utils/constants.ts` + +## Best Practices + +- 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. + +### Page objects + +- 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. + +### 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 diff --git a/agent_docs/README.md b/agent_docs/README.md index 9b445d8d..f31316be 100644 --- a/agent_docs/README.md +++ b/agent_docs/README.md @@ -16,7 +16,6 @@ Instead of stuffing all instructions into `CLAUDE.md` (which goes into every con - **`tech_stack.md`** - Technology choices, UI component patterns, library usage - **`development.md`** - Development workflows, testing strategy, common tasks, debugging - **`code_style.md`** - Code patterns and best practices (read only when actively coding) -- **`e2e.md`** - Patterns and workflows for Playwright end-to-end testing ## Usage Pattern diff --git a/agent_docs/e2e.md b/agent_docs/e2e.md deleted file mode 100644 index 0cfa637a..00000000 --- a/agent_docs/e2e.md +++ /dev/null @@ -1,27 +0,0 @@ -# Playwright E2E Tests - -## File structure - -- E2E tests are located in `packages/app/tests/e2e/features` - -## Page objects - -- 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. - -## Running the tests - -To verify that the tests pass: - -```sh -./scripts/test-e2e.sh -``` - -## 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 - -## Best Practices - -- **Assert successful chart loads** by checking that `.recharts-responsive-container` is visible. \ No newline at end of file diff --git a/packages/app/playwright.config.ts b/packages/app/playwright.config.ts index c747b8b1..a36ae3bd 100644 --- a/packages/app/playwright.config.ts +++ b/packages/app/playwright.config.ts @@ -3,6 +3,8 @@ import path from 'path'; // Check if we should use full-stack mode (with backend) const USE_FULLSTACK = process.env.E2E_FULLSTACK === 'true'; +// Check if we should use next dev (hot reload) instead of build + start +const USE_DEV = process.env.E2E_USE_DEV === 'true'; const AUTH_FILE = path.join(__dirname, 'tests/e2e/.auth/user.json'); // Timeout configuration constants (in milliseconds) @@ -81,8 +83,10 @@ export default defineConfig({ stderr: 'pipe', }, { - command: - 'SERVER_URL=http://localhost:29000 PORT=28081 yarn build && SERVER_URL=http://localhost:29000 PORT=28081 yarn start', + // Full UI: Alerts + Dashboards. Not local mode; Alerts enabled; + command: USE_DEV + ? 'SERVER_URL=http://localhost:29000 PORT=28081 next dev --webpack' + : 'SERVER_URL=http://localhost:29000 PORT=28081 yarn build && SERVER_URL=http://localhost:29000 PORT=28081 yarn start', port: 28081, reuseExistingServer: !process.env.CI, timeout: APP_SERVER_STARTUP_TIMEOUT_MS, @@ -92,8 +96,9 @@ export default defineConfig({ ] : { // Local mode: Frontend only - command: - 'NEXT_PUBLIC_IS_LOCAL_MODE=true yarn build && NEXT_PUBLIC_IS_LOCAL_MODE=true PORT=8081 yarn start', + command: USE_DEV + ? 'NEXT_PUBLIC_IS_LOCAL_MODE=true PORT=8081 next dev --webpack' + : 'NEXT_PUBLIC_IS_LOCAL_MODE=true yarn build && NEXT_PUBLIC_IS_LOCAL_MODE=true PORT=8081 yarn start', port: 8081, reuseExistingServer: !process.env.CI, timeout: APP_SERVER_STARTUP_TIMEOUT_MS, diff --git a/packages/app/scripts/run-e2e.js b/packages/app/scripts/run-e2e.js index eff97db0..8cf559b0 100755 --- a/packages/app/scripts/run-e2e.js +++ b/packages/app/scripts/run-e2e.js @@ -10,6 +10,7 @@ * yarn test:e2e --ui --local # Open UI (local mode) * yarn test:e2e --debug # Debug mode (full-stack) * yarn test:e2e --debug --local # Debug (local mode) + * yarn test:e2e --dev # Hot reload (next dev) instead of build+start */ import { spawn } from 'child_process'; @@ -26,10 +27,11 @@ const args = process.argv.slice(2); const useLocal = args.includes('--local'); const useUI = args.includes('--ui'); const useDebug = args.includes('--debug'); +const useDev = args.includes('--dev'); // Remove our custom flags from args const playwrightArgs = args.filter( - arg => !['--local', '--ui', '--debug'].includes(arg), + arg => !['--local', '--ui', '--debug', '--dev'].includes(arg), ); // Build playwright command @@ -55,6 +57,7 @@ playwrightCmd.push(...playwrightArgs); const env = { ...process.env, ...(!useLocal && { E2E_FULLSTACK: 'true' }), + ...(useDev && { E2E_USE_DEV: 'true' }), }; // Full-stack: inject DEFAULT_CONNECTIONS/DEFAULT_SOURCES from fixture so the API gets them @@ -72,7 +75,7 @@ if (!useLocal) { // eslint-disable-next-line no-console console.info(`Running: ${playwrightCmd.join(' ')}`); // eslint-disable-next-line no-console -console.info(`Mode: ${useLocal ? 'Local (frontend only)' : 'Full-stack'}`); +console.info(`Mode: ${useLocal ? 'Local (frontend only)' : 'Full-stack'}${useDev ? ' + dev (hot reload)' : ''}`); const child = spawn('npx', playwrightCmd, { stdio: 'inherit', diff --git a/packages/app/tests/e2e/README.md b/packages/app/tests/e2e/README.md index cb275bf0..b3f2eb31 100644 --- a/packages/app/tests/e2e/README.md +++ b/packages/app/tests/e2e/README.md @@ -179,6 +179,10 @@ test.describe('My Feature', () => { `@full-stack` so that when running with `./scripts/test-e2e.sh --local`, they are skipped appropriately. +### Claude Skill + +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. + ## Test Organization ```