From 7085604a269deea4bb67754ec668a25b63c813ba Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 21 Aug 2026 09:05:52 -0700 Subject: [PATCH] fix(ui): correct Automations row semantics (#127252) Amp-Thread-ID: https://ampcode.com/threads/T-01a021f4-b547-7788-a916-d4a94cbd3e3b Co-authored-by: Amp --- ui/src/e2e/cron-remove.e2e.test.ts | 6 +++++- ui/src/pages/cron/view.test.ts | 20 ++++++++++++++++++-- ui/src/pages/cron/view.ts | 20 ++++---------------- ui/src/styles/cron.css | 6 +++++- 4 files changed, 32 insertions(+), 20 deletions(-) diff --git a/ui/src/e2e/cron-remove.e2e.test.ts b/ui/src/e2e/cron-remove.e2e.test.ts index f2d49f8008a6..9e734902b43b 100644 --- a/ui/src/e2e/cron-remove.e2e.test.ts +++ b/ui/src/e2e/cron-remove.e2e.test.ts @@ -63,7 +63,11 @@ suite.define(() => { expect(response?.status()).toBe(200); const row = page.locator(`[data-test-id="cron-row-${job.id}"]`); await row.waitFor({ state: "visible", timeout: 10_000 }); - await row.locator(".cron-table__name-text").click(); + expect(await page.locator(".cron-table__head").getAttribute("role")).toBeNull(); + expect(await row.getAttribute("role")).toBeNull(); + const openTask = row.getByRole("button", { name: /Nightly digest/ }); + await openTask.focus(); + await page.keyboard.press("Enter"); const detail = page.locator('.cron-page[data-panel-mode="job"]'); await detail.waitFor({ state: "visible" }); diff --git a/ui/src/pages/cron/view.test.ts b/ui/src/pages/cron/view.test.ts index fed8803ff32b..2323d70f4e04 100644 --- a/ui/src/pages/cron/view.test.ts +++ b/ui/src/pages/cron/view.test.ts @@ -116,7 +116,22 @@ describe("cron view list pane", () => { expect(onJobsFiltersReset).toHaveBeenCalledTimes(1); }); - it("renders table rows with schedule and status cells and selects on click", () => { + it("does not expose table rows without complete table semantics", () => { + const container = renderView({ jobs: [createJob("job-1")] }); + + for (const row of container.querySelectorAll('[role="row"]')) { + expect(row.closest('[role="table"], [role="grid"], [role="treegrid"]')).not.toBeNull(); + expect( + Array.from(row.children).every((child) => + child.matches( + '[role="cell"], [role="gridcell"], [role="columnheader"], [role="rowheader"]', + ), + ), + ).toBe(true); + } + }); + + it("renders table rows with independent native buttons for opening tasks", () => { const onSelectJob = vi.fn(); const job = createJob("job-1", { trigger: { script: "json({ fire: true })" }, @@ -134,6 +149,7 @@ describe("cron view list pane", () => { const rows = Array.from(container.querySelectorAll(".cron-table__row")); expect(rows).toHaveLength(3); + expect(rows[0]?.getAttribute("role")).toBeNull(); expect(rows[0]?.textContent).toContain("Cron 0 9 * * *"); expect(rows[1]?.classList.contains("cron-table__row--paused")).toBe(true); expect(rows[1]?.textContent).toContain("Paused"); @@ -150,7 +166,7 @@ describe("cron view list pane", () => { "Trigger configured", ); - (rows[1] as HTMLElement).click(); + getElement(rows[1] as Element, ".cron-table__name", HTMLButtonElement).click(); expect(onSelectJob).toHaveBeenCalledWith(paused); }); diff --git a/ui/src/pages/cron/view.ts b/ui/src/pages/cron/view.ts index 5f3cb7b80067..1bad08ad1c1a 100644 --- a/ui/src/pages/cron/view.ts +++ b/ui/src/pages/cron/view.ts @@ -728,7 +728,7 @@ function renderJobsFilterPopover(props: CronProps, active: boolean) { function renderJobsTable(props: CronProps, hasAnyJobsFilters: boolean) { return html`
-
+
${t("cron.jobs.name")} ${t("cron.jobs.schedule")} ${t("cron.jobs.nextRun")} @@ -775,18 +775,10 @@ function renderJobRow(job: CronJob, props: CronProps) { return html`
props.onSelectJob(job)} - @keydown=${(e: KeyboardEvent) => { - if (e.key === "Enter" || e.key === " ") { - e.preventDefault(); - props.onSelectJob(job); - } - }} > - + ${renderJobCell("cron-table__schedule", t("cron.jobs.schedule"), formatCronSchedule(job))} ${renderJobCell("cron-table__next", t("cron.jobs.nextRun"), nextRun)} ${renderJobCell("cron-table__last", t("cron.jobs.lastRun"), renderLastRunCell(job))} ${props.canManage ? html` - e.stopPropagation()} - @keydown=${(e: Event) => e.stopPropagation()} - > + e.stopPropagation()}>