Skip to content

[codex] Fix marker resolution and duplicate mapping validation#63

Open
satvik007 wants to merge 5 commits intomainfrom
fix/marker-resolution-duplicate-mapping
Open

[codex] Fix marker resolution and duplicate mapping validation#63
satvik007 wants to merge 5 commits intomainfrom
fix/marker-resolution-duplicate-mapping

Conversation

@satvik007
Copy link
Copy Markdown
Collaborator

@satvik007 satvik007 commented Apr 6, 2026

Summary

This branch fixes the remaining result-mapping issues in qas-cli by making parsed marker metadata the source of truth for upload mapping and by validating duplicate target mappings earlier in the flow.

The branch also bumps the package version to 0.6.1.

Root Cause

The previous uploader logic inferred identity from result.name, which is a display field and may include Playwright describe(...) titles. When those describe titles contained multiple QA Sphere markers, the uploader could map a result to the wrong run test case simply because that marker appeared earlier in the string.

There was also a second safety issue after duplicate-target detection was introduced: duplicate validation could still happen after side effects had already occurred. The initial fix moved that check ahead of run creation, but --create-tcases could still create new test cases before the duplicate rejection fired.

What Changed

  • Added explicit marker metadata and resolution state to TestCaseResult
  • Added shared mapping helpers so handler and uploader use the same marker-based mapping rules
  • Switched uploader mapping from name scanning to explicit marker resolution
  • Updated Playwright parsing to:
    • fan out one result per annotation marker
    • extract fallback markers from spec.title only, not describe prefixes
    • preserve resolved-none when only the describe title contains markers
  • Updated JUnit and Allure parsing to populate marker metadata directly where possible
  • Updated handler resolution logic so parser decisions are authoritative:
    • resolved stays resolved
    • resolved-none is not re-parsed from display names
    • needs-project-resolution still allows the project-aware JUnit fallback path
  • Added duplicate-target detection based on resolved target test cases instead of string matching
  • Kept --force as the override for duplicate-target failures
  • Moved duplicate validation ahead of side effects for implicit uploads:
    • before run creation
    • before --create-tcases creation calls
  • Split --create-tcases handling into a read-only planning step and a post-validation creation/finalization step
  • Updated CLI/README text so --force and mapping behavior describe duplicate-target handling explicitly
  • Bumped package.json and root package-lock.json metadata to 0.6.1

Why Reviewers Should Care

This is primarily a correctness fix.

Without these changes, a Playwright suite could upload results to the wrong QA Sphere test cases even when the result count looked plausible. In failure cases, the CLI could also leave behind unwanted artifacts:

  • orphaned runs from post-create duplicate rejection
  • orphaned test cases from --create-tcases when duplicate validation failed too late

The branch makes parser intent explicit and ensures duplicate rejection happens before either of those side effects.

Suggested Review Order

  1. src/utils/result-upload/types.ts
  2. src/utils/result-upload/mapping.ts
  3. src/utils/result-upload/parsers/playwrightJsonParser.ts
  4. src/utils/result-upload/ResultUploadCommandHandler.ts
  5. src/utils/result-upload/ResultUploader.ts
  6. src/tests/playwright-json-parsing.spec.ts
  7. src/tests/result-upload.spec.ts

Tests Added

  • Playwright regression where describe titles contain unrelated markers but spec.title contains the real one
  • Playwright regression where only the describe title contains markers and the result must remain unmatched
  • Upload regression proving duplicate-target mappings fail before creating a new run
  • Upload regression proving duplicate-target mappings fail before creating new test cases under --create-tcases
  • Existing duplicate-target and force-path coverage retained

Validation

  • npm run check
  • npm test

Notes

  • Follow-up package version in this branch: 0.6.1

gemini-code-assist[bot]

This comment was marked as outdated.

claude[bot]

This comment was marked as outdated.

@satvik007 satvik007 requested a review from ramilamparo April 6, 2026 07:38
claude[bot]

This comment was marked as outdated.

@satvik007 satvik007 marked this pull request as ready for review April 8, 2026 20:15
claude[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Collaborator

@ramilamparo ramilamparo left a comment

Choose a reason for hiding this comment

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

Looks good. This edge case makes sense to me.

})

test('Should extract fallback marker from spec title, not describe titles', async () => {
const jsonContent = JSON.stringify({
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The newly created JSON files is the same as this. Import it instead.

run(
`playwright-json-upload -r ${runURL} ./src/tests/fixtures/playwright-json/describe-marker-unmatched.json`
)
).rejects.toThrowError()
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm getting deprecation notices, use toThrow instead.

Comment on lines +597 to +625
private resolveMarker(result: TestCaseResult, projectCode: string): TestCaseMarker | null {
if (result.markerResolution === 'resolved' && result.marker) {
return result.marker.projectCode.toLowerCase() === projectCode.toLowerCase()
? result.marker
: null
}

if (result.markerResolution === 'resolved-none') {
return null
}

if (!result.name) {
result.markerResolution = 'resolved-none'
return null
}

const seq = this.markerParser.extractSeq(result.name, projectCode)
if (seq === null) {
result.markerResolution = 'resolved-none'
return null
}

result.marker = {
projectCode: this.markerParser.detectProjectCode(result.name) || projectCode,
seq,
}
result.markerResolution = 'resolved'
return result.marker
}
Copy link
Copy Markdown
Collaborator

@ramilamparo ramilamparo Apr 9, 2026

Choose a reason for hiding this comment

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

I think this is still the job of parsers. If we move it to the parsers, we can simplify the code a bit. We can drop markerResolution field and derive this information whether marker field is null or not.

But I understand that the parsers (specifically JUnit) needs context about the project code to correctly derive marker from the test name. We can skip it for now.

@satvik007 satvik007 force-pushed the fix/marker-resolution-duplicate-mapping branch from 025c707 to c8094a5 Compare April 10, 2026 16:24
@claude
Copy link
Copy Markdown

claude bot commented Apr 10, 2026

Claude finished @satvik007's task in 11m 34s —— View job


Code Review: Fix marker resolution and duplicate mapping validation

  • Read review instructions
  • Gather PR context and diff
  • Read changed files for context
  • Launch independent review subagent
  • Validate novel findings against the code
  • Post review with new inline comments
  • Clean up outdated bot comments and progress comments

Summary

Submitted a COMMENT review after deduplicating against 10+ existing inline comments and 4 prior bot review summaries.

1 new inline comment:

  • ResultUploadCommandHandler.ts:188 — Subtle behavior change: Playwright reports whose only markers appear in describe titles will now fail auto-project-code detection (previously they auto-detected, even though they would still fail to map). The fix is correct in spirit, but worth either documenting or relaxing the gate so non-binding project-code detection can still walk result.name. Suggested code change inline.

Verified sound and not re-flagged:

  • Plan/finalize ordering for --create-tcases (validates duplicates before any side effect)
  • assignResolvedTarget correctly suppresses same-title fan-in via allowDuplicateTarget
  • skipUploaderDuplicateValidation gating logic
  • New regression tests for describe-title collision and unmatched describe-only markers
  • Investigation report MD files removed in 240fb10

Cleanup performed:

  • Minimized 3 prior claude[bot] review summaries as OUTDATED (kept the latest)
  • Deleted 3 progress-tracking issue comments

Copy link
Copy Markdown

@claude claude bot left a comment

Choose a reason for hiding this comment

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

Review Summary

Re-reviewed the latest commits (dab04d5, 240fb10, c8094a5). The plan/finalize split for --create-tcases correctly closes the orphan-tcase window: validateDuplicateMappings now runs before any side effect (run creation and test case creation), and assignResolvedTarget correctly suppresses the same-title fan-in case via allowDuplicateTarget. The earlier investigation report files were also removed in 240fb10.

One new finding (1 inline comment):

  • ResultUploadCommandHandler.ts:188 — Subtle behavior change: Playwright reports whose only markers appear in describe titles will now fail auto-project-code detection (previously they auto-detected the project, even though they would still fail to map results). Suggested an easy relaxation of the gate that preserves the mapping fix while keeping the prior detection UX.

Prior findings still applicable (not re-posted):

  • resolveMarker cross-project inconsistency at line 597–602
  • Redundant resolveMarkers call at line 135 (after getTCaseIds already resolved them)
  • DRY violation between validateAndPrintDuplicateMappings and printDuplicateMappings
  • Double getReusableTCasesInDefaultFolder call (plan + create paths)
  • validateDuplicateMappings re-iterates results that were already mapped in getTCaseIds
  • Missing dedicated unit tests for mapping.ts (semantics of allowDuplicateTarget suppression worth a doc comment too)
  • Missing end-to-end test for assignResolvedTarget same-title fan-in succeeding under --create-tcases
  • @ramilamparo: dedupe inline JSON in playwright-json-parsing.spec.ts:527 with the new fixture file; replace toThrowError with toThrow

Items reviewed and found sound:

  • New plan/finalize ordering for --create-tcases
  • assignResolvedTarget setting allowDuplicateTarget = true only when results.length > 1
  • skipUploaderDuplicateValidation logic (handler validates pre-creation, uploader validates only when reusing an existing run)
  • Allure and JUnit parsers populating marker/markerResolution correctly
  • New regression tests for describe-title collision and unmatched describe-only markers

if (result.markerResolution === 'resolved' && result.marker?.projectCode) {
return result.marker.projectCode
}
if (result.markerResolution !== 'resolved-none' && result.name) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Behavior change: Playwright auto-project-code detection regresses for files where only describe titles contain markers

Previously, detectProjectCodeFromTCaseNames unconditionally scanned result.name (which for Playwright includes the full prefixed path: "Test Case Folders (TEST-002, TEST-003) › viewer..."), so a project code like TEST could still be auto-detected even when the spec title itself had no marker.

With the new gating on line 188 (result.markerResolution !== 'resolved-none'), the Playwright parser now sets 'resolved-none' for those exact results (because getParsedMarkerFromText(spec.title) ignores the describe-title markers — which is the correct mapping fix). The handler then also skips the name-based fallback for project-code detection. Net effect: a Playwright report whose only marker hints live in describe titles will now bail out with "Could not detect project code…" and require the user to pass --project-code explicitly.

This is arguably correct (those describe markers were always misleading), but it’s a user-visible behavior change not called out in the PR description. Two options:

  1. Document the change in the PR / release notes so users with such reports know to add --project-code.
  2. Or relax the gate here so project-code detection (which is non-binding and never decides the actual mapping) can still walk result.name even when markerResolution === 'resolved-none'. e.g.:
if (result.name) {
    const code = this.markerParser.detectProjectCode(result.name)
    if (code) return code
}

Project-code detection only picks the project; it doesn't choose which test case to map to, so loosening it here can't reintroduce the original wrong-mapping bug.

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