From 8f4f0559a70ecdcaf8f6317f12e4aedcf908b55f Mon Sep 17 00:00:00 2001 From: Oliver Stenbom Date: Sun, 11 Jan 2026 20:12:13 +0100 Subject: [PATCH] Code it wrote and code it fixed --- .../prompts/playwright-test-generator.md | 53 ++ .../prompts/playwright-test-healer.md | 37 ++ .../prompts/playwright-test-planner.md | 44 ++ trace-viewer/opencode.json | 100 +++ trace-viewer/specs/README.md | 3 + .../components/SearchCombobox.tsx | 10 +- .../test-e2e/search/basic-search.spec.ts | 548 +++++++++++++++++ trace-viewer/test-e2e/seed.spec.ts | 7 + .../tests/search/search-functionality.plan.md | 573 ++++++++++++++++++ 9 files changed, 1371 insertions(+), 4 deletions(-) create mode 100644 trace-viewer/.opencode/prompts/playwright-test-generator.md create mode 100644 trace-viewer/.opencode/prompts/playwright-test-healer.md create mode 100644 trace-viewer/.opencode/prompts/playwright-test-planner.md create mode 100644 trace-viewer/opencode.json create mode 100644 trace-viewer/specs/README.md create mode 100644 trace-viewer/test-e2e/search/basic-search.spec.ts create mode 100644 trace-viewer/test-e2e/seed.spec.ts create mode 100644 trace-viewer/tests/search/search-functionality.plan.md diff --git a/trace-viewer/.opencode/prompts/playwright-test-generator.md b/trace-viewer/.opencode/prompts/playwright-test-generator.md new file mode 100644 index 0000000..08f1991 --- /dev/null +++ b/trace-viewer/.opencode/prompts/playwright-test-generator.md @@ -0,0 +1,53 @@ +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. + + + 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(...); + + ... + }); + }); + ``` + + +Context: User wants to generate a test for the test plan item. \ No newline at end of file diff --git a/trace-viewer/.opencode/prompts/playwright-test-healer.md b/trace-viewer/.opencode/prompts/playwright-test-healer.md new file mode 100644 index 0000000..70173c4 --- /dev/null +++ b/trace-viewer/.opencode/prompts/playwright-test-healer.md @@ -0,0 +1,37 @@ +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 diff --git a/trace-viewer/.opencode/prompts/playwright-test-planner.md b/trace-viewer/.opencode/prompts/playwright-test-planner.md new file mode 100644 index 0000000..59c50f1 --- /dev/null +++ b/trace-viewer/.opencode/prompts/playwright-test-planner.md @@ -0,0 +1,44 @@ +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. diff --git a/trace-viewer/opencode.json b/trace-viewer/opencode.json new file mode 100644 index 0000000..a31a71c --- /dev/null +++ b/trace-viewer/opencode.json @@ -0,0 +1,100 @@ +{ + "$schema": "https://opencode.ai/config.json", + "mcp": { + "playwright-test": { + "type": "local", + "command": [ + "npx", + "playwright", + "run-test-mcp-server" + ], + "enabled": true + } + }, + "tools": { + "playwright*": false + }, + "agent": { + "playwright-test-generator": { + "description": "Use this agent when you need to create automated browser tests using Playwright", + "mode": "subagent", + "prompt": "{file:.opencode/prompts/playwright-test-generator.md}", + "tools": { + "ls": true, + "glob": true, + "grep": true, + "read": true, + "playwright-test*browser_click": true, + "playwright-test*browser_drag": true, + "playwright-test*browser_evaluate": true, + "playwright-test*browser_file_upload": true, + "playwright-test*browser_handle_dialog": true, + "playwright-test*browser_hover": true, + "playwright-test*browser_navigate": true, + "playwright-test*browser_press_key": true, + "playwright-test*browser_select_option": true, + "playwright-test*browser_snapshot": true, + "playwright-test*browser_type": true, + "playwright-test*browser_verify_element_visible": true, + "playwright-test*browser_verify_list_visible": true, + "playwright-test*browser_verify_text_visible": true, + "playwright-test*browser_verify_value": true, + "playwright-test*browser_wait_for": true, + "playwright-test*generator_read_log": true, + "playwright-test*generator_setup_page": true, + "playwright-test*generator_write_test": true + } + }, + "playwright-test-healer": { + "description": "Use this agent when you need to debug and fix failing Playwright tests", + "mode": "subagent", + "prompt": "{file:.opencode/prompts/playwright-test-healer.md}", + "tools": { + "ls": true, + "glob": true, + "grep": true, + "read": true, + "edit": true, + "write": true, + "playwright-test*browser_console_messages": true, + "playwright-test*browser_evaluate": true, + "playwright-test*browser_generate_locator": true, + "playwright-test*browser_network_requests": true, + "playwright-test*browser_snapshot": true, + "playwright-test*test_debug": true, + "playwright-test*test_list": true, + "playwright-test*test_run": true + } + }, + "playwright-test-planner": { + "description": "Use this agent when you need to create comprehensive test plan for a web application or website", + "mode": "subagent", + "prompt": "{file:.opencode/prompts/playwright-test-planner.md}", + "tools": { + "ls": true, + "glob": true, + "grep": true, + "read": true, + "playwright-test*browser_click": true, + "playwright-test*browser_close": true, + "playwright-test*browser_console_messages": true, + "playwright-test*browser_drag": true, + "playwright-test*browser_evaluate": true, + "playwright-test*browser_file_upload": true, + "playwright-test*browser_handle_dialog": true, + "playwright-test*browser_hover": true, + "playwright-test*browser_navigate": true, + "playwright-test*browser_navigate_back": true, + "playwright-test*browser_network_requests": true, + "playwright-test*browser_press_key": true, + "playwright-test*browser_select_option": true, + "playwright-test*browser_snapshot": true, + "playwright-test*browser_take_screenshot": true, + "playwright-test*browser_type": true, + "playwright-test*browser_wait_for": true, + "playwright-test*planner_setup_page": true, + "playwright-test*planner_save_plan": true + } + } + } +} \ No newline at end of file diff --git a/trace-viewer/specs/README.md b/trace-viewer/specs/README.md new file mode 100644 index 0000000..48a788b --- /dev/null +++ b/trace-viewer/specs/README.md @@ -0,0 +1,3 @@ +# Specs + +This is a directory for test plans. diff --git a/trace-viewer/src/trace-viewer/components/SearchCombobox.tsx b/trace-viewer/src/trace-viewer/components/SearchCombobox.tsx index c7c7617..96b7196 100644 --- a/trace-viewer/src/trace-viewer/components/SearchCombobox.tsx +++ b/trace-viewer/src/trace-viewer/components/SearchCombobox.tsx @@ -90,9 +90,9 @@ export function SearchCombobox(props: SearchComboboxProps) { // Sync external query changes (e.g., when cleared externally) createEffect(() => setLocalValue(props.query)); - // Auto-open when there's text and results, but allow manual close + // Auto-open when there's text (to show results or "No results found"), but allow manual close createEffect(() => { - if (localValue().length > 0 && props.results.length > 0) { + if (localValue().length > 0) { setIsOpen(true); } else { setIsOpen(false); @@ -216,13 +216,15 @@ export function SearchCombobox(props: SearchComboboxProps) { - - + diff --git a/trace-viewer/test-e2e/search/basic-search.spec.ts b/trace-viewer/test-e2e/search/basic-search.spec.ts new file mode 100644 index 0000000..527024b --- /dev/null +++ b/trace-viewer/test-e2e/search/basic-search.spec.ts @@ -0,0 +1,548 @@ +// spec: tests/search/search-functionality.plan.md +// seed: tests/search/seed.spec.ts + +import { expect, test, type APIRequestContext } from "@playwright/test"; + +const TRACE_API_URL = "http://localhost:9295"; + +// Helper to generate unique trace IDs +function generateTraceId(suffix: string): string { + return `search${suffix}`.padEnd(32, "0").slice(0, 32); +} + +// Shared trace data setup function +async function setupTraceData( + request: APIRequestContext, + traceIdHex: string, + testStartTime: number, +) { + const testEndTime = testStartTime + 3000; // 3 second test + + // Send OTLP traces via POST /v1/traces + await request.post(`${TRACE_API_URL}/v1/traces`, { + data: { + resourceSpans: [ + // Playwright test spans + { + resource: { + attributes: [ + { + key: "service.name", + value: { stringValue: "playwright-tests" }, + }, + ], + }, + scopeSpans: [ + { + scope: { name: "playwright", version: "1.50.0" }, + spans: [ + // Test span (root) + { + traceId: traceIdHex, + spanId: "span00000001", + name: "playwright.test", + kind: 1, // INTERNAL + startTimeUnixNano: `${testStartTime}000000`, + endTimeUnixNano: `${testEndTime}000000`, + attributes: [ + { + key: "test.case.title", + value: { stringValue: "Search functionality test" }, + }, + ], + status: { code: 1 }, + events: [], + links: [], + }, + // Step: Navigate to login page + { + traceId: traceIdHex, + spanId: "span00000002", + parentSpanId: "span00000001", + name: "playwright.test.step", + kind: 1, // INTERNAL + startTimeUnixNano: `${testStartTime + 100}000000`, + endTimeUnixNano: `${testStartTime + 500}000000`, + attributes: [ + { + key: "test.step.title", + value: { stringValue: "Navigate to login page" }, + }, + ], + status: { code: 1 }, + events: [], + links: [], + }, + // Step: Fill login form + { + traceId: traceIdHex, + spanId: "span00000003", + parentSpanId: "span00000001", + name: "playwright.test.step", + kind: 1, // INTERNAL + startTimeUnixNano: `${testStartTime + 600}000000`, + endTimeUnixNano: `${testStartTime + 1200}000000`, + attributes: [ + { + key: "test.step.title", + value: { stringValue: "Fill login form" }, + }, + ], + status: { code: 1 }, + events: [], + links: [], + }, + // Step: Submit credentials + { + traceId: traceIdHex, + spanId: "span00000004", + parentSpanId: "span00000001", + name: "playwright.test.step", + kind: 1, // INTERNAL + startTimeUnixNano: `${testStartTime + 1300}000000`, + endTimeUnixNano: `${testStartTime + 1800}000000`, + attributes: [ + { + key: "test.step.title", + value: { stringValue: "Submit credentials" }, + }, + ], + status: { code: 1 }, + events: [], + links: [], + }, + ], + }, + ], + }, + // Browser spans with HTTP requests + { + resource: { + attributes: [ + { + key: "service.name", + value: { stringValue: "playwright-browser" }, + }, + ], + }, + scopeSpans: [ + { + scope: { name: "playwright-browser", version: "1.0" }, + spans: [ + // HTTP GET to playwright.dev + { + traceId: traceIdHex, + spanId: "span00000010", + name: "HTTP GET", + kind: 3, // CLIENT + startTimeUnixNano: `${testStartTime + 150}000000`, + endTimeUnixNano: `${testStartTime + 400}000000`, + attributes: [ + { + key: "http.request.method", + value: { stringValue: "GET" }, + }, + { + key: "server.address", + value: { stringValue: "playwright.dev" }, + }, + { + key: "url.full", + value: { stringValue: "https://playwright.dev/docs" }, + }, + ], + status: { code: 1 }, + events: [], + links: [], + }, + // Another HTTP GET to playwright.dev + { + traceId: traceIdHex, + spanId: "span00000011", + name: "HTTP GET", + kind: 3, // CLIENT + startTimeUnixNano: `${testStartTime + 450}000000`, + endTimeUnixNano: `${testStartTime + 600}000000`, + attributes: [ + { + key: "http.request.method", + value: { stringValue: "GET" }, + }, + { + key: "server.address", + value: { stringValue: "playwright.dev" }, + }, + { + key: "url.full", + value: { stringValue: "https://playwright.dev/api" }, + }, + ], + status: { code: 1 }, + events: [], + links: [], + }, + // HTTP POST request + { + traceId: traceIdHex, + spanId: "span00000012", + name: "HTTP POST", + kind: 3, // CLIENT + startTimeUnixNano: `${testStartTime + 700}000000`, + endTimeUnixNano: `${testStartTime + 1100}000000`, + attributes: [ + { + key: "http.request.method", + value: { stringValue: "POST" }, + }, + { + key: "server.address", + value: { stringValue: "api.example.com" }, + }, + { + key: "url.full", + value: { stringValue: "https://api.example.com/auth" }, + }, + ], + status: { code: 1 }, + events: [], + links: [], + }, + // Another HTTP GET + { + traceId: traceIdHex, + spanId: "span00000013", + name: "HTTP GET", + kind: 3, // CLIENT + startTimeUnixNano: `${testStartTime + 1200}000000`, + endTimeUnixNano: `${testStartTime + 1500}000000`, + attributes: [ + { + key: "http.request.method", + value: { stringValue: "GET" }, + }, + { + key: "server.address", + value: { stringValue: "cdn.example.com" }, + }, + { + key: "url.full", + value: { stringValue: "https://cdn.example.com/assets" }, + }, + ], + status: { code: 1 }, + events: [], + links: [], + }, + ], + }, + ], + }, + // External API spans + { + resource: { + attributes: [ + { + key: "service.name", + value: { stringValue: "api-service" }, + }, + ], + }, + scopeSpans: [ + { + scope: { name: "api", version: "1.0" }, + spans: [ + { + traceId: traceIdHex, + spanId: "span00000020", + name: "POST /api/auth", + kind: 2, // SERVER + startTimeUnixNano: `${testStartTime + 750}000000`, + endTimeUnixNano: `${testStartTime + 1050}000000`, + attributes: [ + { + key: "http.route", + value: { stringValue: "/api/auth" }, + }, + ], + status: { code: 1 }, + events: [], + links: [], + }, + { + traceId: traceIdHex, + spanId: "span00000021", + name: "DB query users", + kind: 1, // INTERNAL + startTimeUnixNano: `${testStartTime + 800}000000`, + endTimeUnixNano: `${testStartTime + 950}000000`, + attributes: [ + { + key: "db.system", + value: { stringValue: "postgresql" }, + }, + ], + status: { code: 1 }, + events: [], + links: [], + }, + ], + }, + ], + }, + ], + }, + }); + + // Send test.json via PUT + await request.put(`${TRACE_API_URL}/otel-playwright-reporter/test.json`, { + headers: { + "X-Trace-Id": traceIdHex, + }, + data: { + name: "Search functionality test", + describes: ["Search", "Basic Search"], + file: "search/basic-search.spec.ts", + line: 10, + status: "passed", + traceId: traceIdHex, + startTimeUnixNano: `${testStartTime}000000`, + endTimeUnixNano: `${testEndTime}000000`, + }, + }); +} + +// Helper to load trace in the viewer +async function loadTrace( + page: import("@playwright/test").Page, + traceIdHex: string, +) { + await page.goto("/"); + await page + .getByTestId("api-url-input") + .fill(`${TRACE_API_URL}/otel-trace-viewer/${traceIdHex}`); + await page.getByTestId("load-api-button").click(); + + // Wait for trace to load + await expect(page.getByTestId("test-name")).toBeVisible(); +} + +test.describe("Basic Search Functionality", () => { + test.describe.configure({ mode: "serial" }); + + // Use a shared trace ID for all tests in this suite to avoid setup overhead + const traceIdHex = generateTraceId("basicsearch1"); + let testStartTime: number; + + test.beforeAll(async ({ request }) => { + testStartTime = Date.now(); + await setupTraceData(request, traceIdHex, testStartTime); + }); + + test("1.1. should focus search input when clicking on search box", async ({ + page, + }) => { + // 1. Load a trace with spans and external requests + await loadTrace(page, traceIdHex); + + // Verify search combobox is visible in the header + const searchInput = page.getByPlaceholder("Search spans..."); + await expect(searchInput).toBeVisible(); + + // Verify keyboard shortcut hint (/) is visible when not focused + const keyboardHint = page.locator("kbd").filter({ hasText: "/" }); + await expect(keyboardHint).toBeVisible(); + + // 2. Click on the search combobox + await searchInput.click(); + + // 3. Verify the search input is focused + await expect(searchInput).toBeFocused(); + + // Verify keyboard shortcut hint (/) is hidden when focused + await expect(keyboardHint).not.toBeVisible(); + }); + + test("1.2. should focus search input when pressing / key", async ({ + page, + }) => { + // 1. Load a trace with spans + await loadTrace(page, traceIdHex); + + const searchInput = page.getByPlaceholder("Search spans..."); + + // Verify search combobox shows / keyboard hint when not focused + const keyboardHint = page.locator("kbd").filter({ hasText: "/" }); + await expect(keyboardHint).toBeVisible(); + + // 2. Click somewhere on the page to ensure search is not focused + await page.getByTestId("test-name").click(); + await expect(searchInput).not.toBeFocused(); + + // 3. Press the / key + await page.keyboard.press("/"); + + // 4. Verify the search input is focused + await expect(searchInput).toBeFocused(); + + // The / character should NOT be typed into the input + await expect(searchInput).toHaveValue(""); + }); + + test("1.3. should display search results when typing a query", async ({ + page, + }) => { + // 1. Load a trace with HTTP GET spans + await loadTrace(page, traceIdHex); + + const searchInput = page.getByPlaceholder("Search spans..."); + + // 2. Click on the search combobox + await searchInput.click(); + + // 3. Type 'GET' into the search input + await searchInput.fill("GET"); + + // 4. Wait for search results to appear (200ms debounce + rendering) + await page.waitForTimeout(300); + + // Verify search results dropdown opens after typing + const dropdown = page.locator( + '[data-scope="combobox"][data-part="content"]', + ); + await expect(dropdown).toBeVisible(); + + // Results show spans matching 'GET' + // Each result displays the matched text and parent span title + const resultItems = page.locator( + '[data-scope="combobox"][data-part="item"]', + ); + await expect(resultItems.first()).toBeVisible(); + + // Verify results contain GET-related content + await expect(dropdown).toContainText("GET"); + }); + + test("1.4. should show 'No results found' for queries with no matches", async ({ + page, + }) => { + // 1. Load a trace with spans + await loadTrace(page, traceIdHex); + + const searchInput = page.getByPlaceholder("Search spans..."); + + // 2. Click on the search combobox + await searchInput.click(); + + // 3. Type a non-existent query + await searchInput.fill("zzzznonexistent"); + + // 4. Wait for search to execute (200ms debounce) + await page.waitForTimeout(300); + + // Verify dropdown shows 'No results found' message + const dropdown = page.locator( + '[data-scope="combobox"][data-part="content"]', + ); + await expect(dropdown).toBeVisible(); + await expect(dropdown).toContainText("No results found"); + + // No span results are displayed + const resultItems = page.locator( + '[data-scope="combobox"][data-part="item"]', + ); + await expect(resultItems).toHaveCount(0); + }); + + test("1.5. should clear search when clicking the clear button", async ({ + page, + }) => { + // 1. Load a trace with spans + await loadTrace(page, traceIdHex); + + const searchInput = page.getByPlaceholder("Search spans..."); + + // 2. Type a search query + await searchInput.click(); + await searchInput.fill("GET"); + + // Wait for results to appear + await page.waitForTimeout(300); + const dropdown = page.locator( + '[data-scope="combobox"][data-part="content"]', + ); + await expect(dropdown).toBeVisible(); + + // Verify clear button appears when there is search text + const clearButton = page.getByTestId("search-clear-button"); + await expect(clearButton).toBeVisible(); + + // 3. Click the clear (X) button + await clearButton.click(); + + // 4. Verify search is cleared + // Clicking clear button empties the search input + await expect(searchInput).toHaveValue(""); + + // Search results dropdown closes + await expect(dropdown).not.toBeVisible(); + }); + + test("1.6. should close search dropdown when pressing Escape", async ({ + page, + }) => { + // 1. Load a trace with spans + await loadTrace(page, traceIdHex); + + const searchInput = page.getByPlaceholder("Search spans..."); + + // 2. Type a search query to open results dropdown + await searchInput.click(); + await searchInput.fill("GET"); + + // Wait for results dropdown to appear + await page.waitForTimeout(300); + const dropdown = page.locator( + '[data-scope="combobox"][data-part="content"]', + ); + await expect(dropdown).toBeVisible(); + + // 3. Press the Escape key + await page.keyboard.press("Escape"); + + // 4. Verify dropdown closes + await expect(dropdown).not.toBeVisible(); + + // Search text remains in the input + await expect(searchInput).toHaveValue("GET"); + }); + + test("1.7. should highlight matched text in search results", async ({ + page, + }) => { + // 1. Load a trace with spans + await loadTrace(page, traceIdHex); + + const searchInput = page.getByPlaceholder("Search spans..."); + + // 2. Type a specific search query + await searchInput.click(); + await searchInput.fill("playwright.dev"); + + // 3. Wait for search results + await page.waitForTimeout(300); + + const dropdown = page.locator( + '[data-scope="combobox"][data-part="content"]', + ); + await expect(dropdown).toBeVisible(); + + // Verify matched portions of text are highlighted with yellow background + // The highlighting uses class "bg-yellow-200 font-semibold" + const highlightedText = dropdown.locator(".bg-yellow-200.font-semibold"); + await expect(highlightedText.first()).toBeVisible(); + + // Verify the highlighted text contains part of the search query + const highlightedContent = await highlightedText.first().textContent(); + expect(highlightedContent?.toLowerCase()).toContain("playwright"); + }); +}); diff --git a/trace-viewer/test-e2e/seed.spec.ts b/trace-viewer/test-e2e/seed.spec.ts new file mode 100644 index 0000000..ef5ce4c --- /dev/null +++ b/trace-viewer/test-e2e/seed.spec.ts @@ -0,0 +1,7 @@ +import { test, expect } from '@playwright/test'; + +test.describe('Test group', () => { + test('seed', async ({ page }) => { + // generate code here. + }); +}); diff --git a/trace-viewer/tests/search/search-functionality.plan.md b/trace-viewer/tests/search/search-functionality.plan.md new file mode 100644 index 0000000..c2f5c84 --- /dev/null +++ b/trace-viewer/tests/search/search-functionality.plan.md @@ -0,0 +1,573 @@ +# Trace Viewer Search Functionality Test Plan + +## Application Overview + +This test plan covers the search functionality within the Trace Viewer application. The search allows users to find spans by their names, attributes (key-value pairs), and other indexed fields like kind, title, and serviceName. The search uses fuzzy matching (uFuzzy) and displays results in a dropdown combobox. Tests cover basic search, attribute search, zoomed viewport states, result selection, and keyboard interactions. + +## Test Scenarios + +### 1. Basic Search Functionality + +**Seed:** `tests/search/seed.spec.ts` + +#### 1.1. should focus search input when clicking on search box + +**File:** `tests/search/basic-search.spec.ts` + +**Steps:** + 1. Load a trace with spans and external requests + 2. Click on the search combobox + 3. Verify the search input is focused + +**Expected Results:** + - Search combobox is visible in the header + - Search input becomes active/focused after clicking + - Keyboard shortcut hint (/) is hidden when focused + +#### 1.2. should focus search input when pressing / key + +**File:** `tests/search/basic-search.spec.ts` + +**Steps:** + 1. Load a trace with spans + 2. Click somewhere on the timeline to ensure search is not focused + 3. Press the / key + 4. Verify the search input is focused + +**Expected Results:** + - Search combobox shows / keyboard hint when not focused + - Pressing / focuses the search input + - The / character is NOT typed into the input + +#### 1.3. should display search results when typing a query + +**File:** `tests/search/basic-search.spec.ts` + +**Steps:** + 1. Load a trace with HTTP GET spans + 2. Click on the search combobox + 3. Type 'GET' into the search input + 4. Wait for search results to appear + +**Expected Results:** + - Search results dropdown opens after typing + - Results show spans matching 'GET' + - Each result displays the matched text and parent span title + - Results count is shown if results exceed 50 items + +#### 1.4. should show 'No results found' for queries with no matches + +**File:** `tests/search/basic-search.spec.ts` + +**Steps:** + 1. Load a trace with spans + 2. Click on the search combobox + 3. Type a non-existent query like 'zzzznonexistent' + 4. Observe the dropdown + +**Expected Results:** + - Dropdown shows 'No results found' message + - No span results are displayed + +#### 1.5. should clear search when clicking the clear button + +**File:** `tests/search/basic-search.spec.ts` + +**Steps:** + 1. Load a trace with spans + 2. Type a search query + 3. Click the clear (X) button that appears + 4. Verify search is cleared + +**Expected Results:** + - Clear button appears when there is search text + - Clicking clear button empties the search input + - Search results dropdown closes + - Details panel returns to default state + +#### 1.6. should close search dropdown when pressing Escape + +**File:** `tests/search/basic-search.spec.ts` + +**Steps:** + 1. Load a trace with spans + 2. Type a search query to open results dropdown + 3. Press the Escape key + 4. Verify dropdown closes + +**Expected Results:** + - Search results dropdown closes on Escape + - Search text remains in the input + - Search input may remain focused or blur depending on behavior + +#### 1.7. should highlight matched text in search results + +**File:** `tests/search/basic-search.spec.ts` + +**Steps:** + 1. Load a trace with spans + 2. Type a specific search query like 'playwright.dev' + 3. Observe the search results + +**Expected Results:** + - Matched portions of text are highlighted with yellow background + - Highlighting uses the fuzzy match ranges from uFuzzy + +### 2. Search for Attributes in Spans + +**Seed:** `tests/search/seed.spec.ts` + +#### 2.1. should find spans by attribute value + +**File:** `tests/search/attribute-search.spec.ts` + +**Steps:** + 1. Load a trace with HTTP spans containing 'playwright.dev' in server.address + 2. Type 'playwright.dev' in the search + 3. Observe the search results + +**Expected Results:** + - Results show 'server.address: playwright.dev' format + - Each result shows the parent span title (HTTP GET) + - Multiple matches are returned for all spans with this attribute + +#### 2.2. should find spans by attribute key + +**File:** `tests/search/attribute-search.spec.ts` + +**Steps:** + 1. Load a trace with spans containing http.request.method attribute + 2. Type 'request method' in the search (fuzzy match) + 3. Observe the search results + +**Expected Results:** + - Results show spans with http.request.method attribute + - Fuzzy matching works for attribute keys with dots and underscores + +#### 2.3. should find spans by special fields (kind, name, title) + +**File:** `tests/search/attribute-search.spec.ts` + +**Steps:** + 1. Load a trace with spans of different kinds + 2. Type 'internal' in the search + 3. Observe the search results + +**Expected Results:** + - Results show spans where kind is 'internal' + - Results are displayed with just the value (not key: value format for special fields) + +#### 2.4. should find test step spans by step name + +**File:** `tests/search/attribute-search.spec.ts` + +**Steps:** + 1. Load a trace with test steps + 2. Type 'Navigate' in the search + 3. Observe the search results + +**Expected Results:** + - Results include test steps with 'Navigate' in their title + - Results show the full step title as matched text + +#### 2.5. should perform fuzzy matching for typos + +**File:** `tests/search/attribute-search.spec.ts` + +**Steps:** + 1. Load a trace with spans + 2. Type a query with minor typos like 'playwrght' (missing 'i') + 3. Observe the search results + +**Expected Results:** + - Fuzzy matching finds results despite typo + - Results show spans containing 'playwright' + +#### 2.6. should limit results to maximum 50 and show count + +**File:** `tests/search/attribute-search.spec.ts` + +**Steps:** + 1. Load a trace with many spans (50+) + 2. Type a broad search query like 'GET' + 3. Scroll through results + +**Expected Results:** + - Maximum 50 results are displayed in the dropdown + - Footer shows 'Showing 50 of N results' when there are more than 50 matches + +### 3. Search in Zoomed In State + +**Seed:** `tests/search/seed.spec.ts` + +#### 3.1. should search and find spans while zoomed into timeline + +**File:** `tests/search/zoomed-in-search.spec.ts` + +**Steps:** + 1. Load a trace with spans + 2. Zoom into the timeline (click and drag to select a range) + 3. Perform a search for spans + 4. Verify search works regardless of zoom level + +**Expected Results:** + - Search returns all matching spans, not just visible ones + - Results include spans outside the current viewport + - Hovering results can show position indicator even if span is off-screen + +#### 3.2. should show hover indicator when result span is visible + +**File:** `tests/search/zoomed-in-search.spec.ts` + +**Steps:** + 1. Load a trace and zoom into a specific time range + 2. Search for a span that exists within the zoomed range + 3. Hover over the search result + +**Expected Results:** + - Position indicator line appears at the span's start time + - Details panel shows the hovered span's details + - Span is highlighted in the timeline if visible + +#### 3.3. should handle search result hover when span is outside viewport + +**File:** `tests/search/zoomed-in-search.spec.ts` + +**Steps:** + 1. Load a trace and zoom into a narrow time range at the beginning + 2. Search for a span that exists later in the trace (outside viewport) + 3. Hover over the search result + +**Expected Results:** + - Details panel shows the span information + - Position indicator may not be visible if span start is off-screen + - Timeline does not automatically scroll or pan to the span + +#### 3.4. should maintain search results after zooming + +**File:** `tests/search/zoomed-in-search.spec.ts` + +**Steps:** + 1. Load a trace and perform a search + 2. While results are open, zoom the timeline + 3. Verify search results remain stable + +**Expected Results:** + - Search results remain displayed after zoom + - Results are not re-filtered based on viewport + - Selecting a result still works after zooming + +### 4. Search in Zoomed Out State + +**Seed:** `tests/search/seed.spec.ts` + +#### 4.1. should search and find spans in zoomed out overview + +**File:** `tests/search/zoomed-out-search.spec.ts` + +**Steps:** + 1. Load a trace with spans + 2. Double-click to reset zoom to full trace view + 3. Perform a search for spans + +**Expected Results:** + - Search returns all matching spans + - Full trace is visible in timeline + - Search works identically to zoomed in state + +#### 4.2. should show position indicator in full timeline view + +**File:** `tests/search/zoomed-out-search.spec.ts` + +**Steps:** + 1. Load a trace and reset to full view (double-click) + 2. Search for a span + 3. Hover over a search result + +**Expected Results:** + - Position indicator line appears at span's start time + - Indicator is visible since full trace is in view + - Details panel shows span information + +#### 4.3. should find spans across entire trace duration + +**File:** `tests/search/zoomed-out-search.spec.ts` + +**Steps:** + 1. Load a trace with spans at beginning, middle, and end + 2. Reset to full view + 3. Search for a term that matches spans across the trace + +**Expected Results:** + - Results include spans from all parts of the trace + - Results are ordered by search relevance, not by time + +### 5. Selection of Search Results + +**Seed:** `tests/search/seed.spec.ts` + +#### 5.1. should select span when clicking on search result + +**File:** `tests/search/result-selection.spec.ts` + +**Steps:** + 1. Load a trace with spans + 2. Perform a search + 3. Click on a search result + +**Expected Results:** + - Search dropdown closes after selection + - Timeline position locks to the selected span's start time + - Position indicator changes from thin hover line to thick locked line + - Details panel shows the selected span's information + - Clear button appears in search input + +#### 5.2. should select span when pressing Enter on highlighted result + +**File:** `tests/search/result-selection.spec.ts` + +**Steps:** + 1. Load a trace with spans + 2. Perform a search + 3. Use arrow keys to navigate to a result + 4. Press Enter to select + +**Expected Results:** + - Dropdown closes after Enter + - Selected span is locked in details panel + - Timeline shows locked position indicator + +#### 5.3. should navigate through results with arrow keys + +**File:** `tests/search/result-selection.spec.ts` + +**Steps:** + 1. Load a trace with multiple matching spans + 2. Perform a search that returns multiple results + 3. Press ArrowDown to move through results + 4. Press ArrowUp to move backwards + +**Expected Results:** + - ArrowDown highlights next result + - ArrowUp highlights previous result + - Highlighted result shows hover state in details panel + - Position indicator moves as navigation changes + +#### 5.4. should update details panel when hovering search results + +**File:** `tests/search/result-selection.spec.ts` + +**Steps:** + 1. Load a trace with spans + 2. Perform a search + 3. Hover over different search results + +**Expected Results:** + - Details panel updates to show hovered span + - Time indicator in header shows span's timestamp + - Screenshot (if visible) corresponds to hover time + +#### 5.5. should show span details with attributes on selection + +**File:** `tests/search/result-selection.spec.ts` + +**Steps:** + 1. Load a trace with HTTP spans + 2. Search for a specific HTTP request + 3. Click to select the result + +**Expected Results:** + - Details panel shows span title and duration + - All attributes are displayed (http.request.method, url.full, etc.) + - Kind is displayed (client, internal, etc.) + - Time range is shown + +#### 5.6. should unlock position when clicking on timeline after selection + +**File:** `tests/search/result-selection.spec.ts` + +**Steps:** + 1. Load a trace and select a search result (locking position) + 2. Click anywhere on the timeline + 3. Observe the position indicator behavior + +**Expected Results:** + - Locked position unlocks on timeline click + - Position indicator returns to thin hover line + - Details panel switches to hover mode + +#### 5.7. should unlock position when pressing Escape after selection + +**File:** `tests/search/result-selection.spec.ts` + +**Steps:** + 1. Load a trace and select a search result (locking position) + 2. Press Escape key + 3. Observe the position indicator behavior + +**Expected Results:** + - Locked position unlocks on Escape + - Details panel returns to hover mode + +#### 5.8. should maintain locked state while hovering new search results + +**File:** `tests/search/result-selection.spec.ts` + +**Steps:** + 1. Load a trace and select a search result (locking position) + 2. Perform a new search without clearing + 3. Hover over new search results + +**Expected Results:** + - Locked position indicator remains visible (thick line) + - A secondary hover indicator appears for new hover + - Mode switches to 'search-override' showing both indicators + - Details panel shows hovered span while maintaining lock + +### 6. Search Edge Cases and Error Handling + +**Seed:** `tests/search/seed.spec.ts` + +#### 6.1. should handle empty search gracefully + +**File:** `tests/search/edge-cases.spec.ts` + +**Steps:** + 1. Load a trace with spans + 2. Focus the search input + 3. Submit empty search (just press Enter) + +**Expected Results:** + - No results are shown + - Application does not crash + - Dropdown remains closed + +#### 6.2. should handle special characters in search + +**File:** `tests/search/edge-cases.spec.ts` + +**Steps:** + 1. Load a trace with spans + 2. Search for terms with special characters like '/' or ':' or '.' + 3. Observe search behavior + +**Expected Results:** + - Search handles special characters gracefully + - Results include spans with matching special characters + - No JavaScript errors occur + +#### 6.3. should debounce search input + +**File:** `tests/search/edge-cases.spec.ts` + +**Steps:** + 1. Load a trace with spans + 2. Type rapidly into the search input + 3. Observe network/performance behavior + +**Expected Results:** + - Search is debounced (200ms delay before search executes) + - Typing quickly does not cause excessive re-renders + - Final search query is processed correctly + +#### 6.4. should handle very long search queries + +**File:** `tests/search/edge-cases.spec.ts` + +**Steps:** + 1. Load a trace with spans + 2. Type a very long search query (100+ characters) + 3. Observe application behavior + +**Expected Results:** + - Application handles long queries without crashing + - Search either finds results or shows 'no results' + - UI remains responsive + +#### 6.5. should handle search when no trace is loaded + +**File:** `tests/search/edge-cases.spec.ts` + +**Steps:** + 1. Navigate to application without loading a trace + 2. Attempt to use search if accessible + +**Expected Results:** + - Search is either disabled or handles gracefully + - No errors when trace data is unavailable + +#### 6.6. should preserve search text when switching between hover and locked modes + +**File:** `tests/search/edge-cases.spec.ts` + +**Steps:** + 1. Load a trace and perform a search + 2. Select a result (enters locked mode) + 3. Click on timeline (unlocks) + 4. Observe search input state + +**Expected Results:** + - Search text remains in input after mode changes + - Clear button remains visible if text is present + - Can immediately search again without re-typing + +### 7. Search Integration with Timeline Panels + +**Seed:** `tests/search/seed.spec.ts` + +#### 7.1. should highlight matching spans in Steps Timeline + +**File:** `tests/search/timeline-integration.spec.ts` + +**Steps:** + 1. Load a trace with test steps + 2. Search for a step name + 3. Observe the Steps Timeline panel + +**Expected Results:** + - Matching spans in Steps Timeline are visually highlighted + - Non-matching spans have reduced visibility + - Highlighting updates as search query changes + +#### 7.2. should highlight matching spans in External Spans panel + +**File:** `tests/search/timeline-integration.spec.ts` + +**Steps:** + 1. Load a trace with external HTTP spans + 2. Search for 'HTTP GET' + 3. Observe the External Spans panel + +**Expected Results:** + - Matching HTTP spans are highlighted + - Search highlighting works across all timeline panels + - Scrolling to see more spans maintains highlighting + +#### 7.3. should scroll details panel to focused span on search result hover + +**File:** `tests/search/timeline-integration.spec.ts` + +**Steps:** + 1. Load a trace with many spans + 2. Perform a search + 3. Hover over a result that corresponds to a span not currently in view + +**Expected Results:** + - Details panel scrolls to show the hovered span + - Span details card is visible after scroll + - Focused span has visual indicator (ring or border) + +#### 7.4. should navigate to parent span from search result + +**File:** `tests/search/timeline-integration.spec.ts` + +**Steps:** + 1. Load a trace with nested spans + 2. Search for a child span + 3. Select the result and observe details panel + 4. Click the 'Parent' navigation button + +**Expected Results:** + - Parent span details are shown after clicking parent button + - Position remains locked + - Navigation works within the locked state