Skip to content

fix(static_checks): Playwright C# assertions + real Windows CI proofs + C# test_file resolution (follow-up to #4)#5

Merged
rlabarca merged 5 commits into
mainfrom
fix/csharp-playwright-windows-ci
Jun 29, 2026
Merged

fix(static_checks): Playwright C# assertions + real Windows CI proofs + C# test_file resolution (follow-up to #4)#5
rlabarca merged 5 commits into
mainfrom
fix/csharp-playwright-windows-ci

Conversation

@rlabarca

Copy link
Copy Markdown
Owner

Follow-up to #4, addressing the three review notes left on that PR. Stacked on fix/windows-static-checks-3 so the diff is just the new work; it will retarget to main automatically when #4 merges.

What this addresses

1. C# Playwright assertions false-flagged HOLLOW (review note)
check_csharp's no_assertions guard now recognizes Playwright fluent assertions — Expect(...) / Assertions.Expect(...) chained to a To*Async() matcher (ToBeVisibleAsync, ToHaveTextAsync, ToContainTextAsync, …). A bare Expect(x) with no matcher chain is still flagged. (static_checks RULE-31, PROOF-55.)
To keep the broader structural acceptance from masking weak tests, Pass-2 audit criteria gained a presence/visibility-only assertion WEAK check (audit_criteria.md → Criteria-Version 17).

2. Windows behavior was simulation-only (the original ask)
RULE-29/30 are now verified for real on a windows-latest GitHub Action (.github/workflows/windows-proofs.yml) via a new @windows proof tier — PROOF-53 (native msvcrt lock path, _HAS_FCNTL genuinely False) and PROOF-54 (--load-criteria under the native console codec) — committed back by CI, alongside the existing host simulations (PROOF-50/52). The schema_proof_format anchor (RULE-4) and spec_format.md (Format-Version 8) admit the platform-gated tier; the feature-scoped overwrite is per (feature, tier), so host runs that skip these tests never clobber the CI-emitted file.

3. Empty test_file blocked purlin:audit on C# (review note)
Under dotnet test, TestCase.CodeFilePath is often null, so the xUnit logger emits a blank test_file. static_checks.py now exposes --resolve-source <test_name>, which derives the declaring type from the fully-qualified test_name and locates its .cs file (skipping bin/obj); the audit skill uses it as a fallback. Native population still requires RunConfiguration.CollectSourceInformation=true + full PDBs — now documented. (static_checks RULE-32 / PROOF-56, skill_audit RULE-16 / PROOF-16.)

Also: bumped .claude-plugin/plugin.json to 0.9.5 to match VERSION (the manifest wasn't bumped during the v0.9.5 release; purlin_version caught the drift). Normalized the pytest proof plugin's test_file to forward slashes (no-op on POSIX).

Verification

  • purlin:status: 40/40 features VERIFIED, integrity 100%.
  • Windows Action: green on windows-latest — PROOF-53/54 pass natively.
  • Independent audit: PROOF-53/54/55/56 STRONG; 0 WEAK/0 HOLLOW.
  • No version bump (stays 0.9.5); the v0.9.5 tag is moved to the validated commit.

🤖 Generated with Claude Code

@LCbarsenault

Copy link
Copy Markdown

Validated v0.9.5 (branch tip 04cdca7) end-to-end against a real Blazor Server / xUnit / Playwright project — .NET 7, Windows 11, Python 3.12. The suite is 92 proofs across 14 features, every one with test_file: "", so C# auto-discovery gets exercised on every single proof. Summary: #1 and #3 fully fixed; #2 common path fixed (unblocks us); 3 resolver edge cases worth a look before merge.

1. Playwright assertions — FIXED ✅

  • Nav_ShowsUser_AndSignOut (verifies only via Assertions.Expect(...).ToBeVisibleAsync(), no Assert.*) → pass / "structural checks passed". Same for the other Expect-only e2e methods. No more no_assertions/HOLLOW false flag.
  • Negative control (added a throwaway fixture): a genuinely assertion-free method is still flagged — no_assertions: "test method has no Assert./.Should()/.Verify()/Expect(...).To*Async() call" — while a Playwright-only sibling passes. Correct in both directions.

3. Windows toolchain — CLEAN ✅

Every subcommand ran with no fcntl ModuleNotFoundError and no cp1252 decode/encode crash on Windows 11 / Python 3.12: --load-criteria (Criteria-Version 17), --read-cache, --check-proof-file, full Pass-1, and --resolve-source. The full purlin:audit ran end-to-end across all 14 features / 92 proofs (Pass 0.5 → Pass 1 → Pass 2) with no toolchain errors, 100% test_file auto-resolution, and 0 HOLLOW from Pass 1.

2. C# auto-discovery — common path FIXED ✅, edge cases below

The original blocker is gone: I resolved all 92 empty-test_file proofs via --resolve-source; 0 unresolved, 0 Pass-1 false HOLLOW. This includes the class-name≠file-name case (ReqExplorerCsvTests lives in ReqExplorerViewTests.cs).

Per your request I pushed on the edge cases with throwaway fixtures. resolve_test_file_from_name takes parts[-2] as the type and searches for \b(?:class|struct|record|interface)\s+<type>\b, picking the stem-match else the alphabetically-first file. That produces three mis-resolutions:

Shape FQN tested Resolved to Result
Nested Ns.Outer+Inner.M (xUnit's actual FQN) ❌ UNRESOLVED
Nested Ns.Outer.Inner.M (dotted) probe_nested.cs
Generic Ns.Gen`1.M (xUnit's actual FQN) ❌ UNRESOLVED
Generic Ns.Gen.M (bare) probe_generic.cs
Partial (method in file _b) Ns.P.MethodInB probe_partial_**a**.cs ❌ wrong half
Same name, 2 namespaces Probe.Alpha.Dup.M dup_alpha.cs ✅ (only by alpha sort)
Same name, 2 namespaces Probe.Beta.Dup.M dup_**alpha**.cs ❌ namespace ignored

Root causes + suggested fixes:

  • Same name across namespaces (highest impact). parts[-2] is the bare type name; the namespace is discarded, so Beta.Dup.M lands on the Alpha file. Duplicate simple names (Tests, Helpers, Handler) across namespaces are common. Fix: when >1 file declares the type, disambiguate with the namespace from the FQN — prefer a file whose content also has namespace ...Beta (or the namespace segment(s) before the type).
  • Nested +. xUnit's fully-qualified name uses Outer+Inner; re.escape makes + literal so class Outer+Inner never matches. Fix: treat + (and .) as nested separators and search on the innermost type name.
  • Generic `N. Backtick-arity suffix never matches class Gen<T>. Fix: strip a trailing `\d+ from the type token before building the regex. (Low priority — open generic test classes aren't runnable in xUnit, so this is mostly academic.)
  • Partial split across files. Both halves declare class P; the resolver returns the alphabetically-first file, which may not hold the method, so Pass-1 then scans the wrong file. Fix: among type matches, prefer the file that also contains the method name; if still ambiguous, return all candidates.

These only bite projects that actually have nested/generic/duplicate-name/partial test classes — our committed suite has none, which is why the 92-proof sweep was clean. They're not blockers for us; flagging them as the pre-merge feedback you asked for.

Integrity held at 14/14 features VERIFIED, 100% throughout.


Install note (Claude Code side, not purlin): on a corp network that blocks SSH (port 22), the documented git@github.com: URL can't clone. Worked around it with git config --global url."https://github.com/".insteadOf "git@github.com:". Separately, /plugin marketplace add <url>#branch --scope project folded --scope project into the branch ref (fatal: Remote branch '<branch> --scope project' not found) — putting the flag first, or adding via a local clone path, avoided it. Both are CC-side; mentioning in case other testers hit them.

@rlabarca

Copy link
Copy Markdown
Owner Author

Confirmed working on the reporter's real .NET 7 / Blazor / xUnit / Playwright project, 40/40 features VERIFIED at 100% integrity, Windows Action green. Merging into main (right after #4) for v0.9.5.

@rlabarca
rlabarca deleted the branch main June 29, 2026 20:10
@rlabarca rlabarca closed this Jun 29, 2026
@rlabarca rlabarca reopened this Jun 29, 2026
@rlabarca
rlabarca changed the base branch from fix/windows-static-checks-3 to main June 29, 2026 20:12
rlabarca and others added 5 commits June 29, 2026 16:12
… CI proofs

Pass-1 C# no_assertions now recognizes Playwright fluent assertions —
Expect(...)/Assertions.Expect(...) chained to a To*Async() matcher — so tests
that assert solely through Playwright are no longer false-flagged HOLLOW
(PR #4 review). A bare Expect(x) with no matcher chain is still flagged
(RULE-31, PROOF-55).

To keep the broader structural acceptance from masking weak tests, Pass-2 audit
criteria gain a "presence/visibility-only assertion" WEAK check
(audit_criteria Criteria-Version 16 -> 17).

Windows RULE-29/30 are now verified for real on a windows-latest GitHub Actions
runner (.github/workflows/windows-proofs.yml) via the new @windows proof tier —
PROOF-53 (native msvcrt lock path) and PROOF-54 (--load-criteria under the
native console codec) — committed back by CI, in addition to the host
simulations (PROOF-50/52). The schema_proof_format anchor (RULE-4) and
spec_format.md (Format-Version 8) admit the platform-gated windows tier; the
feature-scoped overwrite is per (feature, tier), so host runs that skip these
tests never clobber the CI-emitted proof file.

Also bumps .claude-plugin/plugin.json to 0.9.5 to match VERSION (the manifest
was not bumped during the v0.9.5 release; purlin_version caught the drift).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
static_checks 29/29 rules proved across unit/integration/e2e + the new @windows
tier (PROOF-53/54 from windows-latest CI). All 85 proofs pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…udit reachability)

PR #4 follow-up: under `dotnet test`, TestCase.CodeFilePath is often null, so the
xUnit logger emits proof entries with an empty test_file — which blocked
purlin:audit from locating the .cs code for Pass 1 (scan) and Pass 2 (read).

static_checks.py gains resolve_test_file_from_name() + a `--resolve-source
<test_name>` CLI: it derives the declaring type from the fully-qualified
test_name (segment before the final .method) and searches the project's source
files (skipping bin/obj/node_modules/.git) for its class/struct/record/interface
declaration, returning the repo-relative POSIX path of the best match (RULE-32,
PROOF-56). skills/audit/SKILL.md uses this as a fallback when test_file is empty,
and documents CollectSourceInformation + full PDBs as the native way to populate
it (skill_audit RULE-16, PROOF-16). docs/testing-workflow-guide.md notes both.

Also normalizes the pytest proof plugin's test_file to forward slashes
(item.fspath.relto yields OS-native separators on Windows) — a no-op on POSIX —
and normalizes the committed windows-tier proof file accordingly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
static_checks 30/30 (vhash=01ad747d), skill_audit 16/16 (vhash=f670fc7a).
Adds RULE-32/PROOF-56 (C# --resolve-source) and RULE-16/PROOF-16 (audit fallback docs).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@rlabarca
rlabarca force-pushed the fix/csharp-playwright-windows-ci branch from 04cdca7 to d161c46 Compare June 29, 2026 20:13
@rlabarca
rlabarca merged commit 986fef5 into main Jun 29, 2026
1 check passed
@rlabarca
rlabarca deleted the fix/csharp-playwright-windows-ci branch June 29, 2026 20:13
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.

2 participants