Automate visual failure comparisons in /review tests - #36666
Conversation
Discover public AzDO snapshot failures and publish bounded baseline, actual, and diff panels from trusted pre-activation without changing the deterministic merge-readiness gate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a280b482-e102-4ca0-9ff9-1cfe1946e21f
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 36666Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 36666" |
|
Azure Pipelines: 1 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
Skill Validation Results
✅ Skill Validation Results —
|
There was a problem hiding this comment.
Pull request overview
This PR enhances the /review tests GitHub workflow by automatically collecting visual snapshot failure evidence from public AzDO endpoints, publishing bounded baseline/actual/diff PNGs to a durable repo branch, and surfacing them via a companion expandable PR comment that the main AI analysis links to (instead of embedding many image URLs).
Changes:
- Adds trusted pre-activation steps to gather context, publish visual comparison assets/comment (non-blocking), and pass context to the agent via a short-lived artifact.
- Introduces a new PowerShell publisher (
Publish-TestVisualAssets.ps1) plus Pester coverage to validate URL/path safety, PNG bounds, and comment/update behavior. - Updates the review skill prompt and CI facts docs to guide linking to
visualAssets.commentUrland keeping visuals out of the main safe-output constrained comment.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| .github/workflows/copilot-review-tests.md | Adds permissions + trusted pre-activation gather/publish steps; switches agent to consume a downloaded context artifact; updates prompt instructions to link to visual companion comment. |
| .github/workflows/copilot-review-tests.lock.yml | Regenerates compiled workflow to reflect the new pre-activation steps, permissions, and artifact download path wiring. |
| .github/skills/review-test-failures/SKILL.md | Documents new visualEvidence / visualAssets fields and instructs the agent to link to the companion comment without embedding image URLs. |
| .github/skills/review-test-failures/scripts/Publish-TestVisualAssets.Tests.ps1 | Adds Pester tests for the publisher’s validation, markdown rendering, and “create vs update” comment behavior. |
| .github/skills/review-test-failures/scripts/Publish-TestVisualAssets.ps1 | New publisher script: downloads bounded PNGs, resolves baselines from the tested commit, writes comparison panels, publishes assets to review-tests-assets, and posts/updates the companion comment. |
| .github/skills/review-test-failures/scripts/Gather-TestFailureContext.Tests.ps1 | Adds tests for visual snapshot parsing, retry attachment selection, and environment directory hint extraction. |
| .github/skills/review-test-failures/scripts/Gather-TestFailureContext.ps1 | Adds public resultsbybuild discovery + per-result detail/attachments inspection to populate visualEvidence in context output. |
| .github/scripts/Review-Tests.ps1 | Updates local runner to optionally publish visual assets/comment before invoking the agent, and instructs linking to visualAssets.commentUrl. |
| .github/docs/maui-ci-facts.md | Documents the new /review tests visual evidence pipeline and its non-impact on deterministic verdict ceilings. |
The maui-bot PAT lacks the 'gist' scope, so the gist-based image host never rendered (Post log warned 'gist create failed'). Replace it with the git blobs/trees API flow proven in PR #36666: upload each staged PNG as a base64 blob, build a tree on the review-tests-assets branch tip, commit, and fast-forward the ref (with retry for the shared-branch race against the /review tests GHA). Images are then referenced by commit-pinned raw.githubusercontent.com URLs. This needs only the repo/contents scope the maui-bot token already has (it posts reviews and applies labels) and never places the token in any URL or command (gh api reads it from GH_TOKEN), satisfying the pipeline security rules. The embed stays fully best-effort and resets $LASTEXITCODE so it can never fail the Post task. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 15d2af20-e4ab-4e88-9011-cfbd83513bc0
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a280b482-e102-4ca0-9ff9-1cfe1946e21f
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
.github/skills/review-test-failures/scripts/Publish-TestVisualAssets.ps1:215
Invoke-DownloadFilefollows HTTP redirects without validating the final destination. Since the attachment/baseline URLs are treated as untrusted input, an allowed AzDO/GitHub URL could redirect to an unexpected host and bypass the host/path allow-list checks. Validate that any redirect stays on the original host (or explicitly allow-list the final host) before consuming the response body.
$handler = [System.Net.Http.HttpClientHandler]::new()
$handler.AllowAutoRedirect = $true
$handler.MaxAutomaticRedirections = 5
$client = [System.Net.Http.HttpClient]::new($handler)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a280b482-e102-4ca0-9ff9-1cfe1946e21f
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a280b482-e102-4ca0-9ff9-1cfe1946e21f
PureWeen
left a comment
There was a problem hiding this comment.
Adversarial code review
Two warnings and two non-blocking operational concerns survived adversarial consensus:
-
⚠️ Regression —.github/scripts/Review-Tests.ps1:259recognizes the standard agent report marker and returns the report before recomputing failure, baseline, regression, platform, commit, and author metadata fromcontext.json. A normal local report can therefore publish agent-transcribed values that disagree with the deterministic context. Preserve the detailed report body, but regenerate its badge and attribution metadata from the context.
Flagged by: 2/3 reviewers after dispute -
⚠️ Logic —.github/skills/review-test-failures/scripts/Merge-TestVisualsIntoComment.ps1:227treats any changed file named<ClassName>.csas the exact visual test, without the platform/path constraint used for snapshot files. Duplicate class filenames or a different-platform edit can override decisive base attribution and label a pre-existing failure as likely PR-caused. Match the expected test path/platform and add a duplicate-basename negative test.
Flagged by: 2/3 reviewers -
💡 Error Handling —
.github/skills/review-test-failures/scripts/Publish-TestVisualAssets.ps1:576overwritescontext.jsondirectly. An I/O failure duringSet-Contentcan destroy the valid context while the continue-on-error workflow proceeds. Write to a same-directory temporary file, validate it, and atomically replace the destination.
Flagged by: 2/3 reviewers after dispute -
💡 Config Impact —
.github/skills/review-test-failures/scripts/Publish-TestVisualAssets.ps1:464chains every publication ontoreview-tests-assets, but no retention or cumulative repository-size policy is wired in. Add an explicit lifecycle policy or use storage designed for retained visual assets.
Flagged by: 2/3 reviewers
Test coverage: The focused Pester coverage is substantial, but it does not cover deterministic badge preservation or duplicate class basenames across platform paths.
Prior review status: Four existing Copilot inline findings were not duplicated and remain visible on the PR.
PR metadata: The title is accurate and the description is strong. The description claim that JSON updates are atomic does not currently match Save-Context.
What looks right: The trusted merger is sealed before PR checkout, and the post-step removes GitHub tokens before mutating the agent output.
Methodology: 3 independent reviewers with adversarial consensus.
|
Validated all four findings against HEAD (
@PureWeen — thanks for the adversarial pass; findings 1 and 2 are the ones worth prioritizing.
|
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 478d195b-20f3-4bc6-aeed-f6aa88b55fda
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 478d195b-20f3-4bc6-aeed-f6aa88b55fda
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 478d195b-20f3-4bc6-aeed-f6aa88b55fda
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 478d195b-20f3-4bc6-aeed-f6aa88b55fda
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 478d195b-20f3-4bc6-aeed-f6aa88b55fda
Test-AzDoAttachmentUrl is the allowlist gate before downloading an untrusted CI attachment. It validated scheme, host, and the exact result path but still accepted a URL carrying userinfo, a non-default port, a query, or a fragment. Reject all of those so only an unadorned https://dev.azure.com/<expected path> passes -- nothing that could smuggle credentials, redirect to another port, or tack on a query/fragment that changes what is fetched (Copilot finding). An explicit default :443 stays accepted. Added rejection + default-port tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d00747b7-96f3-4e7a-8dfb-e3a48db04b2d
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 478d195b-20f3-4bc6-aeed-f6aa88b55fda
… into review-tests-visual-comparisons
|
@PureWeen addressed the latest Copilot finding in |
| # prompt's pre-flight below) — is skipped entirely and the run goes silent. | ||
| # The whole downstream already tolerates a missing context.json (the artifact | ||
| # download is continue-on-error, the seal/merge steps exit 0 when it's absent). | ||
| continue-on-error: true |
There was a problem hiding this comment.
Good catch, and the suggested approach is right. For workflow_dispatch the pre-activation gates resolve true (is_team_member/command_position_ok), so Gather test-failure context runs; with an empty PR_NUMBER the set -euo pipefail + exit 1 ("PR number is required") is masked by continue-on-error: true, and the activated output still gates true → the agent job proceeds into a confusing no-op.
Concrete fix on the source step in copilot-review-tests.md (keeps resilience for /review tests, fails fast for manual dispatch):
- name: Gather test-failure context
...
continue-on-error: ${{ github.event_name != 'workflow_dispatch' }}I'm intentionally not hand-applying this here: this .md is the gh-aw source and the runtime is the generated copilot-review-tests.lock.yml (138 KB), which was compiled with gh-aw v0.82.14. Editing only the .md would drift from the lock, and hand-editing the generated lock would drift from the compiler. My environment's gh aw is v0.80.9 and can't reproduce the v0.82.14 lock (it rejects the newer model: frontmatter), so this needs a gh aw compile copilot-review-tests on v0.82.14 to regenerate the lock cleanly. Flagging for that recompile rather than introducing source/lock drift. (Leaving the thread open for the owner to apply + compile.)
PureWeen
left a comment
There was a problem hiding this comment.
Adversarial review: no issues found
Three independent reviewers completed a full current-head review with adversarial consensus. No correctness, security, regression, data-loss, race, resource, timeout, error-handling, or shared-config findings remain.
What looks right: trusted visual inputs are sealed before untrusted checkout, post-processing runs without GitHub tokens, asset URLs and PNG payloads are tightly validated, all network/process work is bounded, and the gh-aw source/lock pair is synchronized.
Test coverage: focused Pester coverage exercises gathering, publication, merging, local posting, timeout/failure paths, environment provenance, marker neutralization, and comment limits; 155 tests pass.
Prior review status: earlier findings were addressed across the current commit series; this pass found no duplicate or unresolved code-review issue.
Methodology: 3 independent reviewers with adversarial consensus. Review event: COMMENT.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a280b482-e102-4ca0-9ff9-1cfe1946e21f
|
Deployment validation found a repository-configuration blocker unrelated to the workflow implementation. Both the #36756 rerun and the #36507 run were rejected before a runner started:
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
.github/workflows/copilot-review-tests.md:126
Gather test-failure contextiscontinue-on-error: true, butworkflow_dispatchallowsinputs.pr_numberto be omitted. In that case this step’s “PR number is required” failure is masked and the workflow continues into the agent job with an empty PR number, leading to confusing/no-op behavior (and potentially invalidadd_commenttargeting). Consider failing fast forworkflow_dispatchwhile keeping resilience for/review testscomment-triggered runs (e.g., makecontinue-on-errorconditional or requirepr_numberfor dispatch).
- name: Gather test-failure context
if: >-
steps.exact_command.outputs.should_run == 'true' &&
steps.check_membership.outputs.is_team_member == 'true' &&
steps.check_command_position.outputs.command_position_ok == 'true'
# Resilience: a transient failure gathering context (AzDO/Helix/network) must
# NOT fail the pre-activation job, otherwise the agent job — which is designed
# to post a short failure report when the context files are missing (see the
# prompt's pre-flight below) — is skipped entirely and the run goes silent.
# The whole downstream already tolerates a missing context.json (the artifact
# download is continue-on-error, the seal/merge steps exit 0 when it's absent).
continue-on-error: true
Note
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!
Description of Change
Automates visual snapshot evidence in
/review tests. When public AzDO results contain failed screenshot comparisons, the command now emits exactly one test-failure analysis comment containing bounded, expandable baseline/actual/diff panels.Visual evidence remains supplementary: it does not change
gate.verdictCeiling, deterministic attribution, or the merge-readiness verdict.One-comment flow
resultsbybuildAPI, including retry-suffixed attachments such asSnapshot[1].pngandSnapshot-diff[1].png.ios-26,android-notch-36,mac, orwindows).review-tests-assetsusing immutable commit-pinnedraw.githubusercontent.comURLs.add_commentanalysis payload with a trusted insertion marker.Likely PR-causedfor an exact test/platform base regression or directly changed snapshot/test;Likely unrelatedfor an exact base/known-issue match without direct visual scope;Needs human investigationfor unmatched or mixed evidence.The local
.github/scripts/Review-Tests.ps1 -PostCommentpath uses the same merger. It also recognizes complete reports returned in Copilot's final response, preserving nested evidence code fences without wrapping a second title or badge section.Security and Failure Safety
COPILOT_GITHUB_TOKEN,GH_TOKEN, orGITHUB_TOKEN.@is neutralized before insertion.add_commentpayload is mutated.What NOT to Do
Validation
gh aw compile copilot-review-tests --approvecompletes without errors or warnings.agent_output.jsonfrom gh-aw run 29674953402 was replayed through the post-step:add_commentitem remained one item;Live Single-Comment Examples
The exact local
/review testspath from this branch posted or repaired these merged comments after the PRs'/azp runpipelines completed:Each result contains one
Tests Failure Analysistitle and one merged review marker. Across 114 rendered panels, all 336 embedded image URLs returned HTTP 200. Seven panels were safely classified as likely PR-caused; no panel in this sample had enough exact evidence to be safely classified as likely unrelated, so the remaining 107 stayed atNeeds human investigation. Another 260 comparisons were omitted safely by the comment limits.The latest eight-example batch was regenerated concurrently, and #36672, #31755, #34637, #35156, #35885, #36577, and #36212 were added afterward. The current
review-tests-assetshead retains the full asset history.The protected
copilot-pat-poolenvironment rejects feature-branchworkflow_dispatchruns before job execution. The live local-runner examples validate comment generation and asset publication, while the real gh-aw output replay validates the workflow post-step mutation without weakening that branch protection.Issues Fixed
N/A - reviewer workflow enhancement.