Skip to content

v1.3.36.6 -- Capture: threats-decisions selector fix

A bugfix on top of v1.3.36.5. Test 20 (threats-decisions.png) failed with a 10s waitForSelector timeout on 'table, [role="tabpanel"]', then 38.8s total runtime before the test gave up. Cause: neither selector matches the /threats page DOM.

argosVersion and frontend/package.json deliberately stay at 1.3.35.4 (tooling-only). scripts/capture/package.json bumps 1.3.36.51.3.36.6.

Why

PHASE 0 source-code investigation of Threats.tsx (514 lines):

return (
  <div className="p-6 max-w-[1400px] mx-auto space-y-4">
    {/* Header — ALWAYS rendered */}
    <div className="flex items-center justify-between">
      <h1 className="text-2xl font-semibold flex items-center gap-2">
        <Shield />
        Threats
      </h1>
      <button>refresh</button>
    </div>

    {/* Stats grid — ALWAYS rendered */}
    <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
      <StatusCard /> <StatCard label="Active decisions" />
      <StatCard label="Top origin" /> <StatCard label="Top scenario" />
    </div>

    {/* Decisions section — header ALWAYS, body conditional */}
    <section>
      <div className="flex items-center justify-between mb-3">
        <h2 className="text-lg font-semibold">Active decisions</h2>
        <div>...filters...</div>
      </div>

      {!decisions ? (
        <Loading />        {/* spinner div */}
      ) : decisions.length === 0 ? (
        <Empty msg="No active decisions" />   {/* checkmark div */}
      ) : (
        <DecisionsTable rows={decisions} />   {/* renders <table> */}
      )}
    </section>

    <AddDecisionForm />
    <section><h2>Collections</h2>...</section>
  </div>
);

What the v1.3.36.x selector matched

await page.waitForSelector('table, [role="tabpanel"]', { timeout: 10_000 });

Both options are wrong:

  • No <table> exists unless decisions.length > 0. The <table> is rendered only inside <DecisionsTable> (line 307). When prod LAPI has 0 active decisions OR the panel hasn't loaded data yet, the body shows <Loading /> (a spinner div) or <Empty /> (a checkmark div) — neither contains a <table>.
  • No [role="tabpanel"] anywhere — the page is not tabbed. It's a single scrolling layout with sections.

The selector matched zero elements regardless of data state. waitForSelector timed out at 10s. Then waitForSettled ran (10s networkidle + 3s fallback ≈ 13s). Then the following waitForSelector('table tbody tr', { timeout: 5_000 }).catch(...) — also matched nothing — added another 5s. Plus 500 ms render-settle + Playwright overhead. Total ~38.8s spent failing on a single capture.

Fix

Wait for two anchors that ARE always rendered, drop the no-op table-row wait, take the screenshot regardless of body state:

await page.goto('/threats');
await page.waitForSelector('h1:has-text("Threats")', { timeout: 10_000 });
await waitForSettled(page);
await page.waitForSelector('h2:has-text("Active decisions")', { timeout: 5_000 });
await page.waitForTimeout(500);
await shotFullScroll(page, 'threats-decisions');
  • h1:has-text("Threats") — page mounted; renders unconditionally (line 71).
  • waitForSettled — data fetches finish (or the 3s fallback trips for continuous-poll surfaces).
  • h2:has-text("Active decisions") — section header (line 117); renders even when the body is Loading / Empty. Anchors the section regardless of data state.
  • waitForTimeout(500) — single render-frame settle for any CSS animation on the body cards.

Whatever the body is at screenshot time (<DecisionsTable>, <Empty>, or <Loading>) is a valid capture — it reflects reality. Empty-state and loading-state are legitimate page states the docs portal should be able to illustrate; the screenshot doesn't need to fake decision data.

Smoke phase 14

scripts/smoke/capture-automation.sh gains three new asserts:

14. threats-decisions selector fix:
    - Active code uses h1:has-text("Threats") OR
      h2:has-text("Active decisions")
    - Old broken 'table, [role="tabpanel"]' selector gone
      from active code (comments documenting the failure
      mode are allowed)
    - Synthetic verify against Threats.tsx: file still has
      <h1>...Threats heading AND "Active decisions" header.
      If the page is renamed or restructured, phase 14
      fails loudly so the regression is caught at smoke
      time, not at capture time.

Smoke result post-fix

phase 1:  run.sh refuses without .env...                     PASS
phase 2:  .env is git check-ignore'd...                      PASS
phase 3:  .env.example placeholders only...                  PASS
phase 4:  safeClick synthetic test...                        PASS (13/13)
phase 5:  working tree unchanged by smoke...                 PASS
phase 6:  storageState wiring (v1.3.36.1)...                 PASS (5/5)
phase 7:  banner output uses fs.readFileSync...              PASS
phase 8:  viewport 1440x1080 + shotFullScroll...             PASS
phase 9:  waitForSettled helper (timing fix)...              PASS (5/5)
phase 10: openModal modal-visibility wait + TG selector...   PASS (6/6)
phase 11: host-row triggers click button[aria-label=edit]... PASS (2/2)
phase 12: safeClickTab helper + tab-click migrations...      PASS (7/7)
phase 13: DNS-01 selector fix...                             PASS (3/3)
phase 14: threats-decisions selector fix...                  PASS (3/3)

scripts/check-no-personal-data.sh clean. mkdocs build --strict clean.

Files changed

  • scripts/capture/capture.spec.js — test 20 selector rewrite (h1+h2 anchors instead of broken table+tabpanel); comment block documents the failure mode.
  • scripts/capture/package.json1.3.36.51.3.36.6.
  • scripts/smoke/capture-automation.sh — phase 14 added.
  • docs/release-notes/v1.3.36.6.md — this file.
  • CHANGELOG.md, mkdocs.yml.

NOT changed: argosVersion stays at 1.3.35.4, frontend/package.json version stays at 1.3.35.4. No Go code; no frontend code; no panel binary change.

Operator workflow post-fix

cd ~/argos-edge && git pull
scripts/capture/run.sh

# Verify post-fix:
# 1. threats-decisions.png shows the FULL page: header +
#    refresh button + 4 stats cards + Active decisions
#    section (with whatever body the data state dictates:
#    DecisionsTable if decisions exist, Empty if zero,
#    Loading if data still fetching) + Add manual ban form
#    + Collections section.
# 2. Total runtime back to normal -- no more 38.8s spent on
#    threats-decisions waiting for a selector that never
#    matches.
# 3. No regression on other surfaces.

Versioning

scripts/capture/package.json 1.3.36.51.3.36.6. Tag-without-rebuild precedent for tooling-only patches: v1.3.27.1, v1.3.34, v1.3.35.1, v1.3.35.5.