From 85210295f51c5947148be02285568dd6bf69b5c7 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Sun, 19 Apr 2026 13:04:28 +0200 Subject: [PATCH 1/3] test(e2e): fix playwright regressions pre-release - coverage-view: handle multi-table strict-mode violation by scoping to the first table (coverage rules table) - api: drop title-truthy assertion since control-actions use name/action instead of title - audit-regression/graph/navigation/print-and-errors/self-contained: extend per-test timeouts on routes hitting /graph, which dogfoods on the project's ~1800 artifacts and currently takes ~57s (tracked as a perf follow-up; not a release blocker) - helpers: make waitForHtmx's timeout configurable Full suite: 354/354 passing locally. Trace: skip --- tests/playwright/api.spec.ts | 2 +- tests/playwright/audit-regression.spec.ts | 3 ++- tests/playwright/coverage-view.spec.ts | 18 ++++++++++-------- tests/playwright/graph.spec.ts | 3 ++- tests/playwright/helpers.ts | 4 ++-- tests/playwright/navigation.spec.ts | 4 +++- tests/playwright/print-and-errors.spec.ts | 3 ++- tests/playwright/self-contained.spec.ts | 3 ++- 8 files changed, 24 insertions(+), 16 deletions(-) diff --git a/tests/playwright/api.spec.ts b/tests/playwright/api.spec.ts index 3596ff9..1f951b7 100644 --- a/tests/playwright/api.spec.ts +++ b/tests/playwright/api.spec.ts @@ -117,7 +117,7 @@ test.describe("API v1: Artifacts — Grafana table data", () => { const art = data.artifacts[0]; expect(art.id).toBeTruthy(); - expect(art.title).toBeTruthy(); + // Title may be empty for some types (e.g., control-actions use name/action) expect(art.type).toBeTruthy(); expect(art.origin).toBe("local"); expect(typeof art.links_out).toBe("number"); diff --git a/tests/playwright/audit-regression.spec.ts b/tests/playwright/audit-regression.spec.ts index b6609f6..0a6a514 100644 --- a/tests/playwright/audit-regression.spec.ts +++ b/tests/playwright/audit-regression.spec.ts @@ -152,6 +152,7 @@ test.describe("Audit Regression: Consistency", () => { }); test("every page returns 200", async ({ page }) => { + test.setTimeout(180_000); const pages = [ "/", "/artifacts", @@ -167,7 +168,7 @@ test.describe("Audit Regression: Consistency", () => { "/help", ]; for (const path of pages) { - const resp = await page.request.get(path); + const resp = await page.request.get(path, { timeout: 90_000 }); expect(resp.status(), `${path} should return 200`).toBe(200); } }); diff --git a/tests/playwright/coverage-view.spec.ts b/tests/playwright/coverage-view.spec.ts index 0a9caa4..a700a37 100644 --- a/tests/playwright/coverage-view.spec.ts +++ b/tests/playwright/coverage-view.spec.ts @@ -26,8 +26,8 @@ test.describe("Coverage View", () => { test("shows coverage table with rule details", async ({ page }) => { await page.goto("/coverage"); await waitForHtmx(page); - const table = page.locator("table"); - const tableCount = await table.count(); + const tables = page.locator("table"); + const tableCount = await tables.count(); if (tableCount === 0) { // No traceability rules defined — the card message should explain await expect(page.locator("body")).toContainText( @@ -35,6 +35,8 @@ test.describe("Coverage View", () => { ); return; } + // Use first table — the coverage rules table + const table = tables.first(); // Table should have expected columns await expect(table.locator("thead")).toContainText("Rule"); await expect(table.locator("thead")).toContainText("Source Type"); @@ -44,14 +46,14 @@ test.describe("Coverage View", () => { test("coverage bars have progress indicators", async ({ page }) => { await page.goto("/coverage"); await waitForHtmx(page); - const table = page.locator("table"); - const tableCount = await table.count(); + const tables = page.locator("table"); + const tableCount = await tables.count(); if (tableCount === 0) { test.skip(); return; } - // Each row should have a progress bar div - const rows = page.locator("table tbody tr"); + // Each row in the first table should have a progress bar div + const rows = tables.first().locator("tbody tr"); const rowCount = await rows.count(); expect(rowCount).toBeGreaterThan(0); }); @@ -59,8 +61,8 @@ test.describe("Coverage View", () => { test("coverage badges link to artifact types", async ({ page }) => { await page.goto("/coverage"); await waitForHtmx(page); - const table = page.locator("table"); - const tableCount = await table.count(); + const tables = page.locator("table"); + const tableCount = await tables.count(); if (tableCount === 0) { test.skip(); return; diff --git a/tests/playwright/graph.spec.ts b/tests/playwright/graph.spec.ts index d06320e..b9aa50a 100644 --- a/tests/playwright/graph.spec.ts +++ b/tests/playwright/graph.spec.ts @@ -15,11 +15,12 @@ test.describe("Graph View", () => { }); test("node budget prevents crash on full graph", async ({ page }) => { + test.setTimeout(120_000); await page.goto("/graph"); await waitForHtmx(page); // Should render without timeout — either SVG or budget message const content = page.locator("svg, :text('budget')"); - await expect(content.first()).toBeVisible({ timeout: 30_000 }); + await expect(content.first()).toBeVisible({ timeout: 60_000 }); }); test("graph controls are visible", async ({ page }) => { diff --git a/tests/playwright/helpers.ts b/tests/playwright/helpers.ts index 45e1d9f..567f235 100644 --- a/tests/playwright/helpers.ts +++ b/tests/playwright/helpers.ts @@ -1,10 +1,10 @@ import { Page, expect } from "@playwright/test"; /** Wait for HTMX to finish all pending requests. */ -export async function waitForHtmx(page: Page) { +export async function waitForHtmx(page: Page, timeout = 10_000) { await page.waitForFunction( () => !document.querySelector(".htmx-request"), - { timeout: 10_000 }, + { timeout }, ); } diff --git a/tests/playwright/navigation.spec.ts b/tests/playwright/navigation.spec.ts index 14b6199..dbbd244 100644 --- a/tests/playwright/navigation.spec.ts +++ b/tests/playwright/navigation.spec.ts @@ -8,15 +8,17 @@ test.describe("Navigation", () => { }); test("all major nav links are reachable via direct URL", async ({ page }) => { + test.setTimeout(120_000); // Test via direct URL access (more reliable than HTMX click) const routes = ["/artifacts", "/validate", "/matrix", "/graph", "/coverage"]; for (const route of routes) { - const response = await page.goto(route); + const response = await page.goto(route, { timeout: 90_000 }); expect(response?.status()).toBe(200); } }); test("direct URL access works without redirect loop", async ({ page }) => { + test.setTimeout(60_000); await page.goto("/artifacts"); await expect(page.locator("table")).toBeVisible(); await page.goto("/stpa"); diff --git a/tests/playwright/print-and-errors.spec.ts b/tests/playwright/print-and-errors.spec.ts index 4c5a745..091a706 100644 --- a/tests/playwright/print-and-errors.spec.ts +++ b/tests/playwright/print-and-errors.spec.ts @@ -103,10 +103,11 @@ test.describe("Console Error Hygiene", () => { for (const path of pagesToCheck) { test(`no JS errors on ${path}`, async ({ page }) => { + test.setTimeout(120_000); const errors: string[] = []; page.on("pageerror", (err) => errors.push(err.message)); - await page.goto(path); + await page.goto(path, { timeout: 90_000 }); await waitForHtmx(page); await page.waitForLoadState("networkidle"); diff --git a/tests/playwright/self-contained.spec.ts b/tests/playwright/self-contained.spec.ts index bfb8be0..9d0aee4 100644 --- a/tests/playwright/self-contained.spec.ts +++ b/tests/playwright/self-contained.spec.ts @@ -98,6 +98,7 @@ test.describe("Self-contained assets (no CDN)", () => { }); test("clicking multiple nav links navigates correctly", async ({ page }) => { + test.setTimeout(120_000); await page.goto("/"); await waitForHtmx(page); @@ -109,7 +110,7 @@ test.describe("Self-contained assets (no CDN)", () => { for (const { selector, path } of routes) { await page.click(selector); - await waitForHtmx(page); + await waitForHtmx(page, 60_000); const url = new URL(page.url()); expect(url.pathname).toBe(path); expect(url.hash).toBe(""); From ebadd33181cc85231cc6388ee4877cfb08235998 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Sun, 19 Apr 2026 13:43:15 +0200 Subject: [PATCH 2/3] fix(deps): bump rustls-webpki to 0.103.12 Patches RUSTSEC-2026-0098 and RUSTSEC-2026-0099, which published on 2026-04-14 and started failing the Security Audit job on any PR. Trace: skip --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5c3f59a..2f7d6a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2894,9 +2894,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.10" +version = "0.103.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" dependencies = [ "ring", "rustls-pki-types", From e91c26f06789b596b3e6c2c513688a38a5b1021a Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Sun, 19 Apr 2026 13:50:58 +0200 Subject: [PATCH 3/3] fix(clippy): collapse nested if in junit parser Rust 1.95 promoted clippy::collapsible-match to the default lint set, which failed the Clippy CI job on the stable toolchain update. Merges the outer guard into the match arm pattern. Trace: skip --- rivet-core/src/junit.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/rivet-core/src/junit.rs b/rivet-core/src/junit.rs index a6ee140..31b02bc 100644 --- a/rivet-core/src/junit.rs +++ b/rivet-core/src/junit.rs @@ -250,17 +250,17 @@ fn parse_suites(xml: &str) -> Result, Error> { } } - Ok(Event::Text(ref e)) => { - if text_ctx == TextContext::Failure || text_ctx == TextContext::Error { - if let Some(ref mut c) = current_case { - if c.body.is_none() { - let text = e - .unescape() - .map(|s| s.trim().to_string()) - .unwrap_or_default(); - if !text.is_empty() { - c.body = Some(text); - } + Ok(Event::Text(ref e)) + if text_ctx == TextContext::Failure || text_ctx == TextContext::Error => + { + if let Some(ref mut c) = current_case { + if c.body.is_none() { + let text = e + .unescape() + .map(|s| s.trim().to_string()) + .unwrap_or_default(); + if !text.is_empty() { + c.body = Some(text); } } }