import { test, expect } from "@playwright/test"; test("JobHistory shows jobs and job detail navigates", async ({ page }) => { await page.goto("/stocks"); await page.waitForSelector("table tbody tr"); const firstRow = page.locator("tbody tr").first(); const symbol = (await firstRow.locator("td a").textContent()) || ""; const ticker = symbol.trim(); // Enqueue background analyze via API to get deterministic jobId const base = new URL(page.url()).origin; const resp = await page.request.post(`${base}/api/analyze`, { data: { ticker, background: true }, }); expect([200, 202]).toContain(resp.status()); const body = await resp.json(); const jobId = body.jobId || body.job?.id; expect(jobId).toBeTruthy(); // Navigate to stock detail page by clicking the symbol link (avoids aborted navigations) const symbolLink = firstRow.locator('td a'); await symbolLink.click(); await page.waitForURL(new RegExp(`/stocks/${ticker}`), { timeout: 10000 }); // Wait up to 10s for JobHistory to show at least one job await page.waitForSelector('text=Job History', { timeout: 10000 }); await page.waitForFunction((id) => { const els = Array.from(document.querySelectorAll('div')).filter(el => el.textContent?.includes(id)); return els.length > 0; }, jobId, { timeout: 10000 }); // Click Details for the job (opens internal route) const detailsLink = page.locator(`a:has-text("Details")`).first(); await detailsLink.click(); // Should navigate to /jobs/:jobId await page.waitForSelector('h1'); const h1 = await page.locator('h1').textContent(); expect(h1).toContain(jobId); // Try cancelling the job via API - may be already processed, but endpoint should respond const cancelResp = await page.request.post(`${base}/api/jobs/${jobId}/cancel`); expect(cancelResp.ok()).toBeTruthy(); const cancelBody = await cancelResp.json(); expect(Object.prototype.hasOwnProperty.call(cancelBody, 'cancelled')).toBeTruthy(); });