From 2561b5d720670df9fb306275e6fa3cdf9ea012d0 Mon Sep 17 00:00:00 2001 From: robbond Date: Sat, 15 Aug 2026 13:55:59 +0100 Subject: [PATCH] test(e2e): repair investigation textarea selectors --- components/reasoning-workspace.jsx | 1 + components/scenario-form.jsx | 1 + tests/e2e/happy-path.spec.js | 32 ++++++++++++++-------------- tests/e2e/long-investigation.spec.js | 26 +++++++++++----------- tests/e2e/recovery-states.spec.js | 14 ++++++------ 5 files changed, 38 insertions(+), 36 deletions(-) diff --git a/components/reasoning-workspace.jsx b/components/reasoning-workspace.jsx index d6ff539..4b89e0d 100644 --- a/components/reasoning-workspace.jsx +++ b/components/reasoning-workspace.jsx @@ -680,6 +680,7 @@ export default function ReasoningWorkspace({ onChange={(e) => setAnswer(e.target.value)} rows={4} disabled={updateStatus === "loading"} + data-testid="response-textarea" className="w-full rounded-lg border border-gray-300 px-4 py-3 text-sm focus:border-gray-500 focus:outline-none focus:ring-2 focus:ring-gray-400 disabled:cursor-not-allowed disabled:opacity-60" placeholder="What do you know about this?" /> diff --git a/components/scenario-form.jsx b/components/scenario-form.jsx index e4cc43e..14d91c9 100644 --- a/components/scenario-form.jsx +++ b/components/scenario-form.jsx @@ -435,6 +435,7 @@ export default function ScenarioForm() { onChange={(e) => setScenario(e.target.value)} placeholder="What have you noticed?" rows={4} + data-testid="scenario-textarea" className="w-full rounded-lg border border-gray-300 px-4 py-3 text-sm focus:border-gray-500 focus:outline-none focus:ring-2 focus:ring-gray-400 mb-3" />
diff --git a/tests/e2e/happy-path.spec.js b/tests/e2e/happy-path.spec.js index d591e33..97bb694 100644 --- a/tests/e2e/happy-path.spec.js +++ b/tests/e2e/happy-path.spec.js @@ -64,7 +64,7 @@ test("initial page renders idle state with textarea and Analyse button", async ( // Idle: textarea placeholder visible await expect(page.getByRole("textbox")).toBeVisible(); - await expect(page.getByRole("textbox").first()).toHaveAttribute("placeholder", /Describe/); + await expect(page.locator('[data-testid="scenario-textarea"]')).toBeVisible(); // Analyse button present (disabled since no text) const analyseBtn = page.getByRole("button", { name: "Analyse" }); @@ -85,7 +85,7 @@ test("selecting a scenario from dropdown auto-fills central statement and enable }); // Type the scenario text directly and verify Analyse is enabled - const textarea = page.locator("textarea[placeholder*=Describe]"); + const textarea = page.locator('[data-testid="scenario-textarea"]'); await textarea.fill(SCENARIO_LOOKUP["Happy path — complete investigation"].centralStatement); // Analyse button should be enabled (scenario has content) @@ -116,7 +116,7 @@ test("multi-turn investigation: start → answer → start again with new questi }); // Type scenario text directly - const textarea = page.locator("textarea[placeholder*=Describe]"); + const textarea = page.locator('[data-testid="scenario-textarea"]'); await textarea.fill(MULTI_TURN.centralStatement); // Click Analyse @@ -131,7 +131,7 @@ test("multi-turn investigation: start → answer → start again with new questi await expect(workspace).toBeVisible(); // ── Phase 2: Submit first answer ── - const answerTextarea = page.locator('textarea[placeholder*=Answer]'); + const answerTextarea = page.locator('[data-testid="response-textarea"]'); if (await answerTextarea.isVisible({ timeout: 3000 })) { await answerTextarea.fill(MULTI_TURN.answerSequence[0].text); await page.getByRole("button", { name: "Update situation" }).click(); @@ -146,7 +146,7 @@ test("multi-turn investigation: start → answer → start again with new questi } // ── Phase 3: Submit second answer ── - const answerTextarea2 = page.locator('textarea[placeholder*=Answer]'); + const answerTextarea2 = page.locator('[data-testid="response-textarea"]'); if (await answerTextarea2.isVisible({ timeout: 3000 })) { await answerTextarea2.fill(MULTI_TURN.answerSequence[1].text); await page.getByRole("button", { name: "Update situation" }).click(); @@ -174,7 +174,7 @@ test("genuine completion: all unknowns resolved → CompletionCard visible", asy }); // Type and submit - const textarea = page.locator("textarea[placeholder*=Describe]"); + const textarea = page.locator('[data-testid="scenario-textarea"]'); await textarea.fill(COMPLETE.centralStatement); await page.getByRole("button", { name: "Analyse" }).click(); @@ -183,7 +183,7 @@ test("genuine completion: all unknowns resolved → CompletionCard visible", asy await waitForWorkspaceReady(page); // If there's a question, submit it (complete scenario's turn 0 should eventually give terminal) - const answerTextarea = page.locator('textarea[placeholder*=Answer]'); + const answerTextarea = page.locator('[data-testid="response-textarea"]'); if (await answerTextarea.isVisible({ timeout: 3000 })) { await answerTextarea.fill(COMPLETE.answerSequence[0].text); await page.getByRole("button", { name: "Update situation" }).click(); @@ -220,7 +220,7 @@ test("evidence-limit: unknowns remain but no next question → EvidenceLimitCard window.__MOCK_SCENARIO = "evidence-limit"; }); - const textarea = page.locator("textarea[placeholder*=Describe]"); + const textarea = page.locator('[data-testid="scenario-textarea"]'); await textarea.fill(INVESTIGATION_SCENARIOS[2].centralStatement); await page.getByRole("button", { name: "Analyse" }).click(); @@ -260,7 +260,7 @@ test("reset clears all state and returns to idle", async ({ page }) => { }); // Start an investigation - const textarea = page.locator("textarea[placeholder*=Describe]"); + const textarea = page.locator('[data-testid="scenario-textarea"]'); await textarea.fill(COMPLETE.centralStatement); await page.getByRole("button", { name: "Analyse" }).click(); @@ -290,7 +290,7 @@ test("loading overlay appears on submit and disappears after response", async ({ window.__MOCK_SCENARIO = ""; }); - const textarea = page.locator("textarea[placeholder*=Describe]"); + const textarea = page.locator('[data-testid="scenario-textarea"]'); await textarea.fill(INVESTIGATION_SCENARIOS[0].centralStatement); // Verify workspace is NOT visible before submitting @@ -319,7 +319,7 @@ test("update submission replaces response panel with loading card, preserves con }); // Start investigation - const textarea = page.locator("textarea[placeholder*=Describe]"); + const textarea = page.locator('[data-testid="scenario-textarea"]'); await textarea.fill(INVESTIGATION_SCENARIOS[0].centralStatement); await page.getByRole("button", { name: "Analyse" }).click(); @@ -330,7 +330,7 @@ test("update submission replaces response panel with loading card, preserves con await expect(workspace).toBeVisible(); // Submit an answer to trigger update loading - const answerTextarea = page.locator('textarea[placeholder*=Answer]'); + const answerTextarea = page.locator('[data-testid="response-textarea"]'); if (await answerTextarea.isVisible({ timeout: 3000 })) { // Current investigation visible before click await expect(page.getByRole("heading", { name: "Current investigation" })).toBeVisible(); @@ -345,7 +345,7 @@ test("update submission replaces response panel with loading card, preserves con await expect(page.getByRole("heading", { name: "Current investigation" })).toBeVisible({ timeout: 5_000 }); // Response textarea hidden — replaced by loading overlay - const rwTextarea = page.locator('#rw-answer'); + const rwTextarea = page.locator('[data-testid="response-textarea"]'); await expect(rwTextarea).not.toBeVisible({ timeout: 3_000 }); // Loading overlay visible in response panel position @@ -380,7 +380,7 @@ test("investigation map preview: appears after start, topics evolve across mocke window.__MOCK_SCENARIO = "complete"; }); - const textarea = page.locator("textarea[placeholder*=Describe]"); + const textarea = page.locator('[data-testid="scenario-textarea"]'); await textarea.fill(INVESTIGATION_SCENARIOS[0].centralStatement); await page.getByRole("button", { name: "Analyse" }).click(); @@ -405,7 +405,7 @@ test("investigation map preview: appears after start, topics evolve across mocke await expect(byStatus("current")).toHaveCount(1); // Submit first answer → turn 1: one more established, next topic becomes current - const answerTextarea = page.locator('textarea[placeholder*=Answer]'); + const answerTextarea = page.locator('[data-testid="response-textarea"]'); if (await answerTextarea.isVisible({ timeout: 3000 })) { await answerTextarea.fill("The figures are comparable."); await page.getByRole("button", { name: "Update situation" }).click(); @@ -421,7 +421,7 @@ test("investigation map preview: appears after start, topics evolve across mocke await expect(byStatus("current")).toHaveCount(1); // Submit second answer → turn 2 - const answerTextarea2 = page.locator('textarea[placeholder*=Answer]'); + const answerTextarea2 = page.locator('[data-testid="response-textarea"]'); if (await answerTextarea2.isVisible({ timeout: 3000 })) { await answerTextarea2.fill("Complaint rate fell from 2.0 to 1.9 per 100 units."); await page.getByRole("button", { name: "Update situation" }).click(); diff --git a/tests/e2e/long-investigation.spec.js b/tests/e2e/long-investigation.spec.js index 98605e0..4fa73bf 100644 --- a/tests/e2e/long-investigation.spec.js +++ b/tests/e2e/long-investigation.spec.js @@ -59,7 +59,7 @@ async function waitForWorkspaceReady(page, options = {}) { * Returns { success, hasError } for test assertions. */ async function submitAnswer(page, text) { - const textarea = page.locator('textarea[placeholder*=Answer]'); + const textarea = page.locator('[data-testid="response-textarea"]'); if (!(await textarea.isVisible({ timeout: 3000 }).catch(() => false))) { return { success: false, hasError: false }; } @@ -90,7 +90,7 @@ test("long investigation — European market entry with 4 answer turns", async ( window.__MOCK_SCENARIO = "complete"; }); - const textarea = page.locator("textarea[placeholder*=Describe]"); + const textarea = page.locator('[data-testid="scenario-textarea"]'); await textarea.fill(LONG_INVESTIGATION.centralStatement); await page.getByRole("button", { name: "Analyse" }).click(); @@ -133,7 +133,7 @@ test("contradiction fixture — opposing recommendations in investigation card", window.__MOCK_SCENARIO = ""; // use default }); - const textarea = page.locator("textarea[placeholder*=Describe]"); + const textarea = page.locator('[data-testid="scenario-textarea"]'); await textarea.fill(CONTRADICTION.centralStatement); await page.getByRole("button", { name: "Analyse" }).click(); @@ -173,7 +173,7 @@ test("diagnosis scenario — churn investigation renders correctly", async ({ pa window.__MOCK_SCENARIO = ""; }); - const textarea = page.locator("textarea[placeholder*=Describe]"); + const textarea = page.locator('[data-testid="scenario-textarea"]'); await textarea.fill(DIAGNOSIS.centralStatement); await page.getByRole("button", { name: "Analyse" }).click(); @@ -204,7 +204,7 @@ test("comparison scenario — product rating nodes render with edges", async ({ window.__MOCK_SCENARIO = ""; }); - const textarea = page.locator("textarea[placeholder*=Describe]"); + const textarea = page.locator('[data-testid="scenario-textarea"]'); await textarea.fill(COMPARISON.centralStatement); await page.getByRole("button", { name: "Analyse" }).click(); @@ -218,7 +218,7 @@ test("comparison scenario — product rating nodes render with edges", async ({ if (result.success) { // Second turn's answer textarea should appear - const answerTextarea = page.locator('textarea[placeholder*=Answer]'); + const answerTextarea = page.locator('[data-testid="response-textarea"]'); expect(await answerTextarea.isVisible()).toBe(true); // Submit second answer @@ -240,7 +240,7 @@ test("prioritisation scenario — team relocation yields ranked options", async window.__MOCK_SCENARIO = ""; }); - const textarea = page.locator("textarea[placeholder*=Describe]"); + const textarea = page.locator('[data-testid="scenario-textarea"]'); await textarea.fill(PRIORITISATION.centralStatement); await page.getByRole("button", { name: "Analyse" }).click(); @@ -270,7 +270,7 @@ test("rapid successive updates maintain correct loading-state cycling", async ({ window.__MOCK_SCENARIO = ""; }); - const textarea = page.locator("textarea[placeholder*=Describe]"); + const textarea = page.locator('[data-testid="scenario-textarea"]'); await textarea.fill(LONG_INVESTIGATION.centralStatement); // use long scenario central statement await page.getByRole("button", { name: "Analyse" }).click(); @@ -302,19 +302,19 @@ test("answer textarea placeholder updates between turns", async ({ page }) => { window.__MOCK_SCENARIO = ""; }); - const textarea = page.locator("textarea[placeholder*=Describe]"); + const textarea = page.locator('[data-testid="scenario-textarea"]'); await textarea.fill(INVESTIGATION_SCENARIOS[0].centralStatement); await page.getByRole("button", { name: "Analyse" }).click(); // Initial workspace should have an answer textarea with a question-related placeholder - const answerTextarea = page.locator('textarea[placeholder*=Answer]'); + const answerTextarea = page.locator('[data-testid="response-textarea"]'); await expect(answerTextarea).toBeVisible({ timeout: 10_000 }); // Submit first answer await submitAnswer(page, INVESTIGATION_SCENARIOS[0].answerSequence[0]?.text || ""); // After update, new question textarea should be present with potentially different placeholder - const updatedTextarea = page.locator('textarea[placeholder*=Answer]'); + const updatedTextarea = page.locator('[data-testid="response-textarea"]'); await expect(updatedTextarea).toBeVisible(); // The workspace card should still display the current investigation context @@ -334,7 +334,7 @@ test("terminal state renders without answer textarea", async ({ page }) => { window.__MOCK_SCENARIO = "complete"; }); - const textarea = page.locator("textarea[placeholder*=Describe]"); + const textarea = page.locator('[data-testid="scenario-textarea"]'); await textarea.fill(SCENARIO_LOOKUP["Happy path — complete investigation"].centralStatement); await page.getByRole("button", { name: "Analyse" }).click(); @@ -344,7 +344,7 @@ test("terminal state renders without answer textarea", async ({ page }) => { await expect(page.locator('[data-testid="reasoning-workspace"]')).toBeVisible(); // If there's a question (turn 0 gives a question), submit it to reach terminal - const answerTextarea = page.locator('textarea[placeholder*=Answer]'); + const answerTextarea = page.locator('[data-testid="response-textarea"]'); if (await answerTextarea.isVisible({ timeout: 3000 })) { await answerTextarea.fill(SCENARIO_LOOKUP["Happy path — complete investigation"].answerSequence[0]?.text || ""); await page.getByRole("button", { name: "Update situation" }).click(); diff --git a/tests/e2e/recovery-states.spec.js b/tests/e2e/recovery-states.spec.js index 8bee5ca..11ad71a 100644 --- a/tests/e2e/recovery-states.spec.js +++ b/tests/e2e/recovery-states.spec.js @@ -44,7 +44,7 @@ test("provider error: analyse returns failure → error panel visible alongside window.__MOCK_SCENARIO = "error"; }); - const textarea = page.locator("textarea[placeholder*=Describe]"); + const textarea = page.locator('[data-testid="scenario-textarea"]'); await textarea.fill(PROVIDER_ERROR.centralStatement); await page.getByRole("button", { name: "Analyse" }).click(); @@ -73,7 +73,7 @@ test("provider error during update turn: error panel appears after answer submit window.__MOCK_SCENARIO = ""; // normal default for start }); - const textarea = page.locator("textarea[placeholder*=Describe]"); + const textarea = page.locator('[data-testid="scenario-textarea"]'); await textarea.fill(INVESTIGATION_SCENARIOS[0].centralStatement); await page.getByRole("button", { name: "Analyse" }).click(); @@ -86,7 +86,7 @@ test("provider error during update turn: error panel appears after answer submit }); // Submit an answer to trigger the update call (which will now get error) - const answerTextarea = page.locator('textarea[placeholder*=Answer]'); + const answerTextarea = page.locator('[data-testid="response-textarea"]'); if (await answerTextarea.isVisible({ timeout: 3000 })) { await answerTextarea.fill("Some answer that should fail"); await page.getByRole("button", { name: "Update situation" }).click(); @@ -109,7 +109,7 @@ test("malformed response during start: error state visible, workspace still rend window.__MOCK_SCENARIO = "error"; }); - const textarea = page.locator("textarea[placeholder*=Describe]"); + const textarea = page.locator('[data-testid="scenario-textarea"]'); await textarea.fill(MALFORMED_RESPONSE.centralStatement); await page.getByRole("button", { name: "Analyse" }).click(); @@ -136,7 +136,7 @@ test("recovery state persists via sessionStorage after error response", async ({ window.__MOCK_SCENARIO = "error"; }); - const textarea = page.locator("textarea[placeholder*=Describe]"); + const textarea = page.locator('[data-testid="scenario-textarea"]'); await textarea.fill(PROVIDER_ERROR.centralStatement); await page.getByRole("button", { name: "Analyse" }).click(); @@ -167,7 +167,7 @@ test("multiple successive errors maintain UI state without crash", async ({ page window.__MOCK_SCENARIO = "error"; }); - const textarea = page.locator("textarea[placeholder*=Describe]"); + const textarea = page.locator('[data-testid="scenario-textarea"]'); await textarea.fill(PROVIDER_ERROR.centralStatement); // First analyse call @@ -177,7 +177,7 @@ test("multiple successive errors maintain UI state without crash", async ({ page await expect(page.locator('[data-testid="reasoning-workspace"]')).toBeVisible(); // If there's an answer textarea, try another update (also errors) - const answerTextarea = page.locator('textarea[placeholder*=Answer]'); + const answerTextarea = page.locator('[data-testid="response-textarea"]'); if (await answerTextarea.isVisible({ timeout: 2000 })) { await answerTextarea.fill("Second attempt"); await page.getByRole("button", { name: "Update situation" }).click();