From e5ace7692ab69964956ac8ac5d4dcd6484ddd076 Mon Sep 17 00:00:00 2001 From: jhodgkin Date: Sun, 12 Jul 2026 23:40:47 -0600 Subject: [PATCH] Add Fingerbank e2e coverage; fix two test races found along the way - deep-check.spec.ts: new test asserting a Fingerbank ID + confidence label appears (targets the Nintendo device specifically, skips if it's not currently on the network -- not something the test suite controls). First run false-skipped because it checked row.count() before waiting for the device table to actually render. - device-ratio.spec.ts: badge counts and displayed text were read as two separate one-shot queries (.count()/.textContent() don't auto-retry like expect() matchers), which raced a background poll once and failed. Wrapped the whole comparison in expect().toPass() so it retries atomically instead. Confirmed fixed: 4/4 clean runs with retries disabled. 15/15 e2e tests green across 8 spec files. Co-Authored-By: Claude Sonnet 5 --- e2e/tests/deep-check.spec.ts | 29 +++++++++++++++++++++++++++++ e2e/tests/device-ratio.spec.ts | 21 +++++++++++++-------- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/e2e/tests/deep-check.spec.ts b/e2e/tests/deep-check.spec.ts index d3631a0..ce87a97 100644 --- a/e2e/tests/deep-check.spec.ts +++ b/e2e/tests/deep-check.spec.ts @@ -30,6 +30,35 @@ test("deep check button runs a check and shows results for an unknown device", a await expect(resultRow).not.toBeEmpty(); }); +test("deep check surfaces a Fingerbank identification with a confidence label", async ({ page }) => { + test.skip(!PASSWORD, "LOCAL_PASSWORD not set"); + + await page.goto("/"); + await page.getByPlaceholder("Username").fill(USERNAME); + await page.getByPlaceholder("Password").fill(PASSWORD); + await page.getByRole("button", { name: "Sign in" }).click(); + // Wait for the table to actually render before checking whether the + // target device is in it -- checking row.count() too early always finds + // zero rows and skips the test regardless of whether the device is really + // on the network. Bit me on the first run of this test. + await expect(page.locator(".device-table tbody tr").first()).toBeVisible({ timeout: 15_000 }); + + // The Nintendo device (192.168.1.106) reliably gets a Fingerbank match, if + // it's still on the network -- skip rather than fail if it's gone, since + // this specific IP isn't something the test suite controls. + const row = page.locator('tr[data-ip="192.168.1.106"]'); + test.skip((await row.count()) === 0, "192.168.1.106 not currently on the network"); + + await row.getByRole("button", { name: /deep check/i }).click(); + await expect(page.locator(".deep-check-row").first()).toBeVisible({ timeout: 20_000 }); + + const resultText = await page.locator(".deep-check-row").first().textContent(); + expect(resultText).toContain("Fingerbank ID"); + // Confidence label should always accompany a score -- never a bare + // number, which could be mistaken for a confirmed identification. + expect(resultText).toMatch(/low confidence|moderate confidence|high confidence|very high confidence/); +}); + test("known devices have no deep check button", async ({ page }) => { test.skip(!PASSWORD, "LOCAL_PASSWORD not set"); diff --git a/e2e/tests/device-ratio.spec.ts b/e2e/tests/device-ratio.spec.ts index df82793..2a6c9ea 100644 --- a/e2e/tests/device-ratio.spec.ts +++ b/e2e/tests/device-ratio.spec.ts @@ -12,12 +12,17 @@ test("known/unknown ratio matches the actual badge counts", async ({ page }) => await page.getByRole("button", { name: "Sign in" }).click(); await expect(page.locator(".device-table tbody tr").first()).toBeVisible({ timeout: 15_000 }); - const knownCount = await page.locator(".device-badge.known").count(); - const unknownCount = await page.locator(".device-badge.unknown").count(); - - const ratioText = await page.locator(".device-ratio").textContent(); - expect(ratioText).toBe(`${knownCount} known / ${unknownCount} unknown`); - - const countText = await page.locator(".device-count").textContent(); - expect(countText).toBe(`(${knownCount + unknownCount})`); + // Re-reads badge counts and the displayed text together, retrying the + // whole comparison if they're ever caught mid-render (e.g. a background + // poll landing between the two reads) instead of a one-shot comparison — + // .count() and .textContent() don't auto-wait/retry the way expect() + // matchers do, so a bare single read raced a poll and failed once already. + await expect(async () => { + const knownCount = await page.locator(".device-badge.known").count(); + const unknownCount = await page.locator(".device-badge.unknown").count(); + const ratioText = await page.locator(".device-ratio").textContent(); + const countText = await page.locator(".device-count").textContent(); + expect(ratioText).toBe(`${knownCount} known / ${unknownCount} unknown`); + expect(countText).toBe(`(${knownCount + unknownCount})`); + }).toPass({ timeout: 10_000 }); });