Skip to content

test(e2e): authenticated Baseball route crawler, replace broken script (#373) - #850

Merged
njrini99-code merged 3 commits into
batch/bbh-finish-0714from
task/ci-gates-373-crawler
Jul 15, 2026
Merged

test(e2e): authenticated Baseball route crawler, replace broken script (#373)#850
njrini99-code merged 3 commits into
batch/bbh-finish-0714from
task/ci-gates-373-crawler

Conversation

@njrini99-code

Copy link
Copy Markdown
Owner

#373 — route crawler auth extension

Prior state: the file the issue's own evidence section cited (e2e/route-crawler/route-crawler.spec.ts) never existed. The actual artifact was scripts/route-crawler-baseball.mjs, a standalone Node script that POSTed JSON credentials to ${BASE_URL}/api/auth/login — an endpoint that does not exist anywhere in this repo (only src/app/api/golf/auth/login/route.ts exists, and it's Golf-specific; BaseballHelm auth is a client-side Supabase form flow, not a JSON login API). So signIn() always failed, and main() exited 0 ("No baseball coach credentials — skipping authenticated crawl") regardless of whether E2E_BASEBALL_COACH_EMAIL/PASSWORD were actually configured in CI. Its "contract test" (scripts/__tests__/route-crawler-baseball.test.mjs) only regex-checked the source text for strings like announcements — it could never have caught any of this. Neither file was wired into any GitHub Actions workflow.

What this PR does: deletes both and replaces them with a real Playwright-based crawler.

e2e/baseball-route-crawler.spec.ts (new)

Runs under the existing baseball-coach / baseball-player Playwright projects (see playwright.config.ts) — same persisted storageState auth baseball-smoke.spec.ts already established via playwright/baseball-auth.setup.ts. No new login code.

  • Discovery, not a hardcoded list: queries the live rendered DOM for visible <nav> a[href] links (covers both the main sidebar — FairwaySidebar, aria-label="Main navigation" — and any hub-subnav strip, e.g. src/app/baseball/(dashboard)/_components/hub-sub-nav.tsx, since both are real Next <Link>s inside a <nav>). This catches a link that's registered in nav-registry.ts but silently fails to render — something a static source-parse (the old script's approach) structurally cannot catch.
  • Frontier crawl: starts from Command Center (coach) / Player Today (player), then keeps discovering newly-revealed hub-subnav links as it visits each hub.
  • Public sample routes (AC bullet): best-effort discovers any /baseball/(player|team|program)/[id] or /baseball/packet/[token] links surfaced anywhere on an authenticated page (not just <nav> — these are typically "share"/"view public profile" affordances), caps at 3, and re-verifies each in a fresh, unauthenticated browser context to confirm it renders without requiring login. Only real linked routes are probed — never guessed IDs — so this stays safe against seed-data drift and is naturally a no-op if nothing is linked.
  • Writes a per-role JSON report to test-results/baseball-route-crawler-{coach,player}-report.json.

e2e/helpers/route-health.ts (new, shared with baseball-smoke.spec.ts)

One gotoAndAssessRouteHealth() covering every failure mode the AC calls for: HTTP 4xx/5xx, guard-bounce (redirect to /login), wrong-sport redirect (leaving /baseball/, most notably into /golf/), a rendered React/Next error boundary, a stuck loading spinner (checked after an explicit settle), and near-blank page (very little visible body text after settling). baseball-smoke.spec.ts now imports ERROR_BOUNDARY_TEXT_RE from here instead of duplicating the regex inline — small refactor, no behavior change, single source of truth going forward.

CI wiring

Added as its own step inside playwright.yml's e2e job (Run BaseballHelm authenticated route crawler (advisory)), gated on the same baseball-auth-ready readiness check, plus an Upload route crawler report artifact step (if: always(), tolerant of no files).

Deliberately NOT folded into #372's new required ci.yml gate. DOM-driven visible-link discovery and the stuck-spinner/near-blank heuristics are genuinely new, unproven surface area in this codebase — no existing helper to lean on, and inherently more flake-prone than a fixed route list or a plain HTTP status check. Bundling it into an already-required gate before it's demonstrated stable would compound #372's own flake risk (noted explicitly in the original ticket research). It runs as its own isolated step in the already-advisory e2e job for now, with its own artifact — a failure here is visible and diagnosable without blocking any merge. Promote to a hard gate as a deliberate follow-up once it's proven clean across several main runs.

Docs

e2e/README.md gets a new "BaseballHelm authenticated route crawler (#373)" section mirroring the existing mandatory-smoke section's style/detail level.

Deferred / out of scope for this PR

  • docs/operations/generated/route-coverage-report.json / scripts/baseball/generate-route-coverage-report.ts — these are normally regenerated against a live running app; I can't run a dev server or drive a browser locally (no browser automation on this box), so I didn't touch the generated report. Worth a follow-up once this crawler has run in CI.
  • Promoting this crawler into BaseballHelm: make authenticated coach/player smoke tests mandatory in CI #372's hard gate, once proven stable.

Gate evidence

  • actionlint .github/workflows/playwright.yml — clean.
  • npm run typecheck (full tsc --noEmit) — clean (caught and fixed two real noUncheckedIndexedAccess issues in the new spec during review — an unguarded .split('#')[0].split('?')[0] chain and an unguarded indexed-array read in the frontier loop; both fixed before this evidence run).
  • npx eslint --max-warnings 0 on all touched TS files — clean.
  • Confirmed via vitest.config.ts that the deleted scripts/__tests__/route-crawler-baseball.test.mjs was never wired into vitest, any npm script, or any CI workflow (grepped .github/workflows/*.yml + package.json) — deletion has zero blast radius.
  • No vitest-run test helpers touched (the new/edited files are Playwright e2e specs and a Playwright-only helper, outside vitest.config.ts's unit project globs).

Do not merge — for review. Independent of #849 (#372 + masking fix): touches a different job's steps in the same playwright.yml file (this PR's new step sits between the existing mandatory-smoke step and the "Run Playwright tests" step that #849 edits), so either can merge first without conflict.

Co-Authored-By: Claude Fable 5 noreply@anthropic.com

@cursor

cursor Bot commented Jul 15, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing

@vercel

vercel Bot commented Jul 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
helmv3 Ignored Ignored Preview Jul 15, 2026 10:05am

Request Review

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (3)
  • main
  • develop
  • release/*

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e3ddd2cc-02dd-4a2d-bc67-d3a4dee450d9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch task/ci-gates-373-crawler
  • 🛠️ helm safety pass
  • 🛠️ dashboard ux pass
  • 🛠️ rls test pass

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@supabase

supabase Bot commented Jul 15, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project qmnssrrolpinvwjjnufo because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@greptile-apps

greptile-apps Bot commented Jul 15, 2026

Copy link
Copy Markdown

Greptile Summary

Replaces the broken scripts/route-crawler-baseball.mjs (which POSTed to a non-existent /api/auth/login endpoint, always silently exited 0, and was never wired into CI) with a real Playwright-based authenticated route crawler. The replacement is architecturally sound — DOM-driven link discovery over a static source parse, shared health-check helper with baseball-smoke.spec.ts, and public-route anonymous re-verification using only links surfaced from real data.

  • New spec (e2e/baseball-route-crawler.spec.ts): discovers visible <nav> links from the live DOM for coach and player roles, runs each through gotoAndAssessRouteHealth, and re-verifies any public profile/packet links in a fresh unauthenticated context; results written to test-results/ as a CI artifact.
  • New helper (e2e/helpers/route-health.ts): centralizes six failure-mode checks (HTTP error, guard bounce, wrong-sport redirect, error boundary, stuck spinner, near-blank) and is now the single source of truth for baseball-smoke.spec.ts's error-boundary regex.
  • Deleted files: scripts/route-crawler-baseball.mjs and its source-text-only contract test — neither was wired into any workflow, so deletion has zero blast radius.
  • Blocking gap: playwright.config.ts is not updated in this PR — baseball-coach/baseball-player projects have testMatch: /baseball-smoke\\.spec\\.ts/ that will prevent the new spec from being collected by those projects, and chromium's testIgnore doesn't exclude the crawler, so the "Run Playwright tests" step will run it unauthenticated and silently pass with zero results.

Confidence Score: 3/5

The new spec is well-designed but won't actually run in CI as-is — playwright.config.ts must be updated before this PR delivers anything.

The core implementation logic (DOM discovery, health checks, anonymous re-verification) is correct and the deleted code is genuinely dead. However, the baseball-coach and baseball-player Playwright projects restrict themselves to baseball-smoke.spec.ts via testMatch, so the crawler spec is never collected for those projects in the route-crawler CI step. Separately, the chromium project's testIgnore doesn't exclude the crawler, so the full-suite step runs it without auth, finds zero nav links, and silently passes. The net result is that the CI step added by this PR does nothing until playwright.config.ts is updated.

playwright.config.ts — needs testMatch expanded for baseball-coach/baseball-player and baseball-route-crawler.spec.ts added to the chromium project's testIgnore.

Important Files Changed

Filename Overview
playwright.config.ts Not modified in this PR, but the root cause of a P1 issue: baseball-coach/baseball-player projects have testMatch: /baseball-smoke\.spec\.ts/ that prevents the new crawler spec from running, and the chromium project's testIgnore doesn't exclude the crawler, causing it to run unauthenticated.
e2e/baseball-route-crawler.spec.ts New Playwright spec implementing DOM-driven nav-link discovery and public-route anonymous re-verification; well-structured, but the frontier deduplication scan is O(n), and the spec can only run correctly once playwright.config.ts is updated to include it in the role project testMatch patterns.
e2e/helpers/route-health.ts New shared helper exporting gotoAndAssessRouteHealth covering six failure modes; the status field in results can be misleadingly 200 for redirect-to-login chains, and the near-blank threshold won't catch nav-present / content-absent partial failures.
e2e/baseball-smoke.spec.ts Minor refactor only: replaces the inline `/Application error
.github/workflows/playwright.yml Adds a new advisory route-crawler step and artifact upload between the mandatory smoke and the full Playwright run; CI wiring is clean (pinned SHA, if-no-files-found: ignore), but the step's tests won't run under the intended role projects due to the testMatch mismatch in playwright.config.ts.
scripts/route-crawler-baseball.mjs Deleted — posted credentials to /api/auth/login, an endpoint that never existed for BaseballHelm; signIn always failed and the script exited 0 silently. Deletion is clean: not wired into any workflow or npm script.
scripts/tests/route-crawler-baseball.test.mjs Deleted — only regex-checked source text for string literals; never wired into vitest or any CI workflow. Safe to remove.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[CI: baseball-auth-ready?] -->|yes| B[Run route crawler step\n--project=baseball-coach\n--project=baseball-player\nbaseball-route-crawler.spec.ts]
    B -->|testMatch mismatch\nonly baseball-smoke matches| C[0 tests collected\nfor role projects]
    A -->|later in job| D[Run Playwright tests\n--project=chromium]
    D -->|testIgnore does NOT\nexclude crawler| E[baseball-route-crawler.spec.ts\npicked up unauthenticated]
    E --> F[Entry route redirect to login\ndiscoverVisibleNavLinks 0 links\nresults empty array]
    F --> G[expect 0 toBe 0\nsilent false-pass]

    subgraph INTENDED[Intended flow after config fix]
        H[baseball-coach project\ntestMatch updated] --> I[crawlAuthenticatedRole\nentry command-center]
        I --> J[discoverVisibleNavLinks\nfrontier visible nav hrefs]
        J --> K{For each route}
        K --> L[gotoAndAssessRouteHealth]
        L -->|ok| M[discover nested subnav links\nexpand frontier]
        L -->|fail| N[record failure reason]
        M --> K
        K -->|done| O[verifyPublicSamplesAnonymously]
        O --> P[writeReport coach-report.json]
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[CI: baseball-auth-ready?] -->|yes| B[Run route crawler step\n--project=baseball-coach\n--project=baseball-player\nbaseball-route-crawler.spec.ts]
    B -->|testMatch mismatch\nonly baseball-smoke matches| C[0 tests collected\nfor role projects]
    A -->|later in job| D[Run Playwright tests\n--project=chromium]
    D -->|testIgnore does NOT\nexclude crawler| E[baseball-route-crawler.spec.ts\npicked up unauthenticated]
    E --> F[Entry route redirect to login\ndiscoverVisibleNavLinks 0 links\nresults empty array]
    F --> G[expect 0 toBe 0\nsilent false-pass]

    subgraph INTENDED[Intended flow after config fix]
        H[baseball-coach project\ntestMatch updated] --> I[crawlAuthenticatedRole\nentry command-center]
        I --> J[discoverVisibleNavLinks\nfrontier visible nav hrefs]
        J --> K{For each route}
        K --> L[gotoAndAssessRouteHealth]
        L -->|ok| M[discover nested subnav links\nexpand frontier]
        L -->|fail| N[record failure reason]
        M --> K
        K -->|done| O[verifyPublicSamplesAnonymously]
        O --> P[writeReport coach-report.json]
    end
Loading

Fix All in Claude Code

Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
e2e/baseball-route-crawler.spec.ts:120-135
**`frontier.includes(nested)` makes the crawl loop quadratic**

Each time a hub page is visited and new subnav links are revealed, `!frontier.includes(nested)` performs a linear scan of the entire frontier array. As the frontier grows (e.g., 12 dashboard tabs × their subnav items), this scan runs for every nested link on every page, making the inner work O(frontier_size) per discovered link. A `Set<string>` tracking enqueued routes (alongside `visited`) would make this O(1) per check and is idiomatic here.

### Issue 2 of 3
e2e/helpers/route-health.ts:95-97
**HTTP 3xx redirects can be silently swallowed before status check**

`page.goto` with `waitUntil: 'domcontentloaded'` follows redirects automatically — by the time `response` is captured, it reflects the final settled URL's response, not the intermediate redirect. A `302 → /login` yields a final 200 on the login page body, so `status >= 400` never fires. The `guard-bounce` check on `finalUrl.includes('/login')` then catches this correctly, but the `http-error` branch misleadingly reports `status: 200` for what was actually a guard redirect. This is a diagnostic / report-accuracy concern rather than a test-coverage gap (the guard-bounce check fires), but it means the `status` field in the emitted JSON report is unreliable for redirect chains. Adding a note in the JSDoc that `status` reflects the terminal response and guard detection is handled separately would prevent confusion when reading the artifact.

### Issue 3 of 3
e2e/helpers/route-health.ts:22
**`NEAR_BLANK_TEXT_THRESHOLD` of 24 chars may be too low to catch thin but non-empty route renders**

`page.locator('body').innerText()` returns all visible text — including the sidebar nav labels (`Command Center`, `Practice`, `Stats Center`, …) which are mounted on every authenticated page. Even a route that rendered only the nav shell and zero content body would score well above 24 characters through nav text alone, so the near-blank check would never fire for a page where the main content area silently failed to render while the nav remained healthy. The threshold is arguably correct for a pure-no-content case (absolute navigation failure), but it won't catch partial content failures (nav present, content slot empty). This is a known heuristic limitation, but worth a comment so future maintainers understand why the threshold is set where it is and don't lower it further thinking it's being conservative.

Reviews (1): Last reviewed commit: "test(e2e): authenticated Baseball route ..." | Re-trigger Greptile

Comment on lines +120 to +135
for (let i = 0; i < frontier.length; i++) {
const route = frontier[i];
if (!route || visited.has(route)) continue;
visited.add(route);

const result = await gotoAndAssessRouteHealth(page, route, {
expectedSportPrefix: '/baseball/',
});
results.push(result);

if (result.ok) {
for (const nested of await discoverVisibleNavLinks(page)) {
if (!visited.has(nested) && !frontier.includes(nested)) frontier.push(nested);
}
for (const link of await discoverPublicSampleLinks(page)) publicSamples.add(link);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 frontier.includes(nested) makes the crawl loop quadratic

Each time a hub page is visited and new subnav links are revealed, !frontier.includes(nested) performs a linear scan of the entire frontier array. As the frontier grows (e.g., 12 dashboard tabs × their subnav items), this scan runs for every nested link on every page, making the inner work O(frontier_size) per discovered link. A Set<string> tracking enqueued routes (alongside visited) would make this O(1) per check and is idiomatic here.

Prompt To Fix With AI
This is a comment left during a code review.
Path: e2e/baseball-route-crawler.spec.ts
Line: 120-135

Comment:
**`frontier.includes(nested)` makes the crawl loop quadratic**

Each time a hub page is visited and new subnav links are revealed, `!frontier.includes(nested)` performs a linear scan of the entire frontier array. As the frontier grows (e.g., 12 dashboard tabs × their subnav items), this scan runs for every nested link on every page, making the inner work O(frontier_size) per discovered link. A `Set<string>` tracking enqueued routes (alongside `visited`) would make this O(1) per check and is idiomatic here.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

Comment on lines +95 to +97
if (status !== null && status >= 400) {
return { route, finalUrl, status, ok: false, failureReason: 'http-error', detail: `HTTP ${status}` };
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 HTTP 3xx redirects can be silently swallowed before status check

page.goto with waitUntil: 'domcontentloaded' follows redirects automatically — by the time response is captured, it reflects the final settled URL's response, not the intermediate redirect. A 302 → /login yields a final 200 on the login page body, so status >= 400 never fires. The guard-bounce check on finalUrl.includes('/login') then catches this correctly, but the http-error branch misleadingly reports status: 200 for what was actually a guard redirect. This is a diagnostic / report-accuracy concern rather than a test-coverage gap (the guard-bounce check fires), but it means the status field in the emitted JSON report is unreliable for redirect chains. Adding a note in the JSDoc that status reflects the terminal response and guard detection is handled separately would prevent confusion when reading the artifact.

Prompt To Fix With AI
This is a comment left during a code review.
Path: e2e/helpers/route-health.ts
Line: 95-97

Comment:
**HTTP 3xx redirects can be silently swallowed before status check**

`page.goto` with `waitUntil: 'domcontentloaded'` follows redirects automatically — by the time `response` is captured, it reflects the final settled URL's response, not the intermediate redirect. A `302 → /login` yields a final 200 on the login page body, so `status >= 400` never fires. The `guard-bounce` check on `finalUrl.includes('/login')` then catches this correctly, but the `http-error` branch misleadingly reports `status: 200` for what was actually a guard redirect. This is a diagnostic / report-accuracy concern rather than a test-coverage gap (the guard-bounce check fires), but it means the `status` field in the emitted JSON report is unreliable for redirect chains. Adding a note in the JSDoc that `status` reflects the terminal response and guard detection is handled separately would prevent confusion when reading the artifact.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code

* "near-blank" — rendered, but with no meaningful content, as opposed to a
* real empty state (which still renders a heading + explanatory copy).
*/
const NEAR_BLANK_TEXT_THRESHOLD = 24;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 NEAR_BLANK_TEXT_THRESHOLD of 24 chars may be too low to catch thin but non-empty route renders

page.locator('body').innerText() returns all visible text — including the sidebar nav labels (Command Center, Practice, Stats Center, …) which are mounted on every authenticated page. Even a route that rendered only the nav shell and zero content body would score well above 24 characters through nav text alone, so the near-blank check would never fire for a page where the main content area silently failed to render while the nav remained healthy. The threshold is arguably correct for a pure-no-content case (absolute navigation failure), but it won't catch partial content failures (nav present, content slot empty). This is a known heuristic limitation, but worth a comment so future maintainers understand why the threshold is set where it is and don't lower it further thinking it's being conservative.

Prompt To Fix With AI
This is a comment left during a code review.
Path: e2e/helpers/route-health.ts
Line: 22

Comment:
**`NEAR_BLANK_TEXT_THRESHOLD` of 24 chars may be too low to catch thin but non-empty route renders**

`page.locator('body').innerText()` returns all visible text — including the sidebar nav labels (`Command Center`, `Practice`, `Stats Center`, …) which are mounted on every authenticated page. Even a route that rendered only the nav shell and zero content body would score well above 24 characters through nav text alone, so the near-blank check would never fire for a page where the main content area silently failed to render while the nav remained healthy. The threshold is arguably correct for a pure-no-content case (absolute navigation failure), but it won't catch partial content failures (nav present, content slot empty). This is a known heuristic limitation, but worth a comment so future maintainers understand why the threshold is set where it is and don't lower it further thinking it's being conservative.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

#373)

scripts/route-crawler-baseball.mjs POSTed credentials to a /api/auth/login
REST endpoint that never existed in this repo (BaseballHelm auth is a
client-side Supabase form, not a JSON login API), so its sign-in always
failed and it exited 0 ("no credentials — skipping") regardless of whether
CI secrets were configured — and it was never wired into any workflow. Its
"contract test" only regex-checked the source text, so it could never catch
this.

Replaces both with e2e/baseball-route-crawler.spec.ts, which runs under the
existing baseball-coach/baseball-player Playwright projects (reusing the
storageState auth baseball-smoke.spec.ts already established — no new login
code) and discovers routes from the live rendered DOM (visible <nav> links,
both the main sidebar and any hub-subnav strip) instead of statically
parsing nav-registry.ts source. e2e/helpers/route-health.ts (shared with
baseball-smoke.spec.ts, which now imports its ERROR_BOUNDARY_TEXT_RE instead
of duplicating the regex) asserts each discovered route isn't a 4xx/5xx,
doesn't bounce to /login, doesn't redirect into /golf/, doesn't render an
error boundary, doesn't get stuck on a loading spinner, and isn't
near-blank. Best-effort discovers public player/team/program/packet links
surfaced on an authenticated page (capped at 3, only real linked routes,
never guessed IDs) and re-verifies each in a fresh unauthenticated context.

Wired into playwright.yml's e2e job as its own step + artifact upload.
Deliberately NOT folded into #372's new required ci.yml gate: DOM-driven
discovery and the stuck-spinner/near-blank heuristics are new, unproven
surface area, so it runs advisory-only until it's demonstrated stable
across several main-branch runs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
njrini99-code added a commit that referenced this pull request Jul 15, 2026
…de changes) (#853)

Syncs 5 living docs + 1 memory file to actual tonight's reality on
batch/bbh-finish-0714 @ 0056bc0, independently re-verified (not copied
from PR claims) via grep/gh API/local test runs:

- PRODUCTION_READINESS_MISSION_2026-07-09.md: dated addendum (history kept
  intact) covering #792-#807 merged, discover-privacy P0 fixed+tested,
  29-surface Living-Annual migration done, tonight's batch merge state
  (#808 merged not "green-pending", #810 still open, #812-#841 + #851 on
  batch branch, #842-#850 still open), and the batch HEAD's 3 currently-red
  CI checks (Business contracts/Unit tests/Import-cycle ratchet).
- ui-migration-map.md + ui-migration-execution-plan.md: code-verified status
  headers — all 29 surfaces executed, Batch H (PR #820) done, zero
  isRedesignEnabled() forks remain under src/app/baseball or
  src/components/baseball.
- BASEBALLHELM_FEATURE_READINESS_MATRIX.md: ran
  check-readiness-matrix.ts (green before and after); upgraded Documents,
  Travel, Practice, Staff/Roles to ready and Practice Effectiveness to
  partial on real new test-coverage PRs (#822-#825); updated Player
  Today/Signals/Videos with tonight's #377 contract tests (#826) and #379
  Phase 4a progress (#851); rollup 10->14 ready. Re-ran the checker
  (route resolution + live owner-issue validation) clean after edits.
- BASEBALLHELM_PRODUCTION_VERDICT.md: reissued (old 2026-06-25 verdict kept
  as history below a new 2026-07-15 section) — honest "batch branch pending
  integration merge + CI" verdict, deferred-minors list, and the
  journey/pipeline vocabulary decision, #379 legacy-backfill scope,
  marketing-root (helm-website-ui/ vs src/app/page.tsx), and dual-wizard
  (ImportWizardClient vs EventImportWizard) open decisions, each grounded
  in a specific file/PR.
- memory/context/baseballhelm-features.md: corrected narrative lines now
  verifiably false (stale 2026-06-30 rollup counts, decision-room
  "unapplied migration"/#405-406 "open", pipeline "7 columns vs 5-stage
  enum", journey "UNVERIFIED source table", discover.ts profile_visibility
  omission, documents #393) — no AUTOGEN blocks in this file, none touched.

Gates: check:readiness-matrix exit 0 (route resolution + live GITHUB_TOKEN
owner-issue validation); readiness-matrix-routes.test.ts 204/204 passing;
no markdownlint config present in repo (skipped per task instructions).
Docs-only change; no product code touched.

Co-authored-by: Fable Integrator <fable@helm.local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Review flagged three CI-wiring gaps that would make the new advisory
crawler step deterministically fail on every main-branch run:

- playwright.config.ts: baseball-coach/baseball-player projects only
  matched baseball-smoke.spec.ts, so the crawler step's --project
  filters resolved zero tests ("No tests found").
- playwright.config.ts: the bare chromium project's testIgnore didn't
  exclude the new spec, so it would also run unauthenticated there.
- .github/workflows/playwright.yml: the crawler step lacked
  continue-on-error, so any future genuine failure/flake would cascade
  into skipping the subsequent required "Run Playwright tests" step,
  contradicting the step's own "advisory, doesn't block" comment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@njrini99-code
njrini99-code force-pushed the task/ci-gates-373-crawler branch from 10e320c to 2faab4f Compare July 15, 2026 09:27
@njrini99-code

Copy link
Copy Markdown
Owner Author

Rebased onto origin/batch/bbh-finish-0714 (clean, no conflicts) and applied all 3 mustFix items from review:

  1. playwright.config.ts — widened baseball-coach and baseball-player project testMatch from /baseball-smoke\.spec\.ts/ to /baseball-(smoke|route-crawler)\.spec\.ts/ so the new advisory step's --project=baseball-coach --project=baseball-player e2e/baseball-route-crawler.spec.ts invocation actually resolves tests instead of hitting "No tests found".
  2. playwright.config.ts — added the crawler spec to the bare chromium project's testIgnore (now /baseball-(smoke|route-crawler)\.spec\.ts|mobile-viewports\.spec\.ts/) so it can't also run unauthenticated there once (1) opened it up to the authed projects.
  3. .github/workflows/playwright.yml — added continue-on-error: true to the "Run BaseballHelm authenticated route crawler (advisory)" step so a genuine future failure/flake can't cascade into skipping the subsequent "Run Playwright tests" step, matching the step's own "advisory, doesn't block" comment.

Gate evidence:

  • actionlint .github/workflows/playwright.yml — clean.
  • npm run typecheck (full tsc --noEmit) — clean.
  • npx eslint --max-warnings 0 playwright.config.ts — clean.
  • Confirmation that the crawler spec resolves under exactly the two authed projects and nowhere else: the sandbox's permission layer hard-blocks any playwright test invocation (including --list/--version, no browser launch involved) — bare npx playwright --version was denied outright. Since I couldn't run the literal command, I verified equivalently by replicating Playwright's own file-matching regex logic directly against the real e2e/*.spec.ts listing (20 files) in Node: chromium's testIgnore now excludes exactly baseball-route-crawler.spec.ts, baseball-smoke.spec.ts, mobile-viewports.spec.ts; baseball-coach/baseball-player's testMatch hits exactly baseball-route-crawler.spec.ts and baseball-smoke.spec.ts, with the crawler spec's own test.describe(..., { tag: '@coach' }) / { tag: '@player' } blocks aligning with each project's existing grep. No stray filenames in e2e/ match the widened regex. Flagging this substitution explicitly rather than silently claiming the literal command ran.
  • No vitest-affected tests: vitest.config.ts has no reference to e2e/playwright, so this config/workflow-only change has no unit-test surface to run.

Co-Authored-By: Claude Fable 5 noreply@anthropic.com

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@njrini99-code
njrini99-code merged commit 25fa82c into batch/bbh-finish-0714 Jul 15, 2026
22 of 28 checks passed
@njrini99-code
njrini99-code deleted the task/ci-gates-373-crawler branch July 15, 2026 10:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant