From 6717d1cb84a039ca6c3563f28b2df2879ceb0b7e Mon Sep 17 00:00:00 2001 From: Shane Neuville <5375137+PureWeen@users.noreply.github.com> Date: Wed, 25 Mar 2026 09:44:35 -0500 Subject: [PATCH 1/9] Add GitHub Actions workflow to run evaluate-pr-tests via Copilot CLI (#34548) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit > [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ## Description Adds a [gh-aw (GitHub Agentic Workflows)](https://github.github.com/gh-aw/introduction/overview/) workflow that automatically evaluates test quality on PRs using the `evaluate-pr-tests` skill. ### What it does When a PR adds or modifies test files, this workflow: 1. **Checks out the PR branch** (including fork PRs) in a pre-agent step 2. **Runs the `evaluate-pr-tests` skill** via Copilot CLI in a sandboxed container 3. **Posts the evaluation report** as a PR comment using gh-aw safe-outputs ### Triggers | Trigger | When | Fork PR support | |---------|------|-----------------| | `pull_request` | Automatic on test file changes (`src/**/tests/**`) | ❌ Blocked by `pre_activation` gate | | `workflow_dispatch` | Manual — enter PR number | ✅ Works for all PRs | | `issue_comment` (`/evaluate-tests`) | Comment on PR | ⚠️ Same-repo only (see Known Limitations) | ### Security model | Layer | Implementation | |-------|---------------| | **gh-aw sandbox** | Agent runs in container with scrubbed credentials, network firewall | | **Safe outputs** | Max 1 PR comment per run, content-limited | | **Checkout without execution** | `steps:` checks out PR code but never executes workspace scripts | | **Base branch restoration** | `.github/skills/`, `.github/instructions/`, `.github/copilot-instructions.md` restored from base branch after checkout | | **Fork PR activation gate** | `pull_request` events blocked for forks via `head.repo.id == repository_id` | | **Pinned actions** | SHA-pinned `actions/checkout`, `actions/github-script`, etc. | | **Minimal permissions** | Each job declares only what it needs | | **Concurrency** | One evaluation per PR, cancels in-progress | | **Threat detection** | gh-aw built-in threat detection analyzes agent output | ### Files added/modified - `.github/workflows/copilot-evaluate-tests.md` — gh-aw workflow source - `.github/workflows/copilot-evaluate-tests.lock.yml` — Compiled workflow (auto-generated by `gh aw compile`) - `.github/skills/evaluate-pr-tests/scripts/Gather-TestContext.ps1` — Test context gathering script (binary-safe file download, path traversal protection) - `.github/instructions/gh-aw-workflows.instructions.md` — Copilot instructions for gh-aw development ### Known Limitations **Fork PR evaluation via `/evaluate-tests` comment is not supported in v1.** The gh-aw platform inserts a `checkout_pr_branch.cjs` step after all user steps, which may overwrite base-branch skill files restored for fork PRs. This is a known gh-aw platform limitation — user steps always run before platform-generated steps, with no way to insert steps after. **Workaround:** Use `workflow_dispatch` (Actions UI → "Run workflow" → enter PR number) to evaluate fork PRs. This trigger bypasses the platform checkout step entirely and works correctly. **Related upstream issues:** - [github/gh-aw#18481](https://github.com/github/gh-aw/issues/18481) — "Using gh-aw in forks of repositories" - [github/gh-aw#18518](https://github.com/github/gh-aw/issues/18518) — Fork detection and warning in `gh aw init` - [github/gh-aw#18520](https://github.com/github/gh-aw/issues/18520) — Fork context hint in failure messages - [github/gh-aw#18521](https://github.com/github/gh-aw/issues/18521) — Fork support documentation ### Fixes - Fixes #34602 --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Jakub Florkowski --- .github/aw/actions-lock.json | 10 + .../gh-aw-workflows.instructions.md | 223 ++++ .github/scripts/Checkout-GhAwPr.ps1 | 100 ++ .../scripts/Gather-TestContext.ps1 | 130 +- .../workflows/copilot-evaluate-tests.lock.yml | 1089 +++++++++++++++++ .github/workflows/copilot-evaluate-tests.md | 139 +++ 6 files changed, 1677 insertions(+), 14 deletions(-) create mode 100644 .github/instructions/gh-aw-workflows.instructions.md create mode 100644 .github/scripts/Checkout-GhAwPr.ps1 create mode 100644 .github/workflows/copilot-evaluate-tests.lock.yml create mode 100644 .github/workflows/copilot-evaluate-tests.md diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json index 774742dfa79e..4a1a1a9ed381 100644 --- a/.github/aw/actions-lock.json +++ b/.github/aw/actions-lock.json @@ -5,6 +5,16 @@ "version": "v8", "sha": "ed597411d8f924073f98dfc5c65a23a2325f34cd" }, + "github/gh-aw-actions/setup@v0.62.1": { + "repo": "github/gh-aw-actions/setup", + "version": "v0.62.1", + "sha": "95c4e2aa6adbdf63ff0b0fbf09945ad4f4716fea" + }, + "github/gh-aw-actions/setup@v0.62.2": { + "repo": "github/gh-aw-actions/setup", + "version": "v0.62.2", + "sha": "20045bbd5ad2632b9809856c389708eab1bd16ef" + }, "github/gh-aw/actions/setup@v0.43.19": { "repo": "github/gh-aw/actions/setup", "version": "v0.43.19", diff --git a/.github/instructions/gh-aw-workflows.instructions.md b/.github/instructions/gh-aw-workflows.instructions.md new file mode 100644 index 000000000000..30d02675e639 --- /dev/null +++ b/.github/instructions/gh-aw-workflows.instructions.md @@ -0,0 +1,223 @@ +--- +applyTo: + - ".github/workflows/*.md" + - ".github/workflows/*.lock.yml" +--- + +# gh-aw (GitHub Agentic Workflows) Guidelines + +## Architecture + +gh-aw workflows are authored as `.md` files with YAML frontmatter, compiled to `.lock.yml` via `gh aw compile`. The lock file is auto-generated — **never edit it manually**. + +### Execution Model + +``` +activation job (renders prompt from base branch .md via runtime-import) + ↓ +agent job: + user steps: (pre-agent, OUTSIDE firewall, has GITHUB_TOKEN) + ↓ + platform steps: (configure git → checkout_pr_branch.cjs → install CLI) + ↓ + agent: (INSIDE sandboxed container, NO credentials) +``` + +| Context | Has GITHUB_TOKEN | Has gh CLI | Has git creds | Can execute scripts | +|---------|-----------------|-----------|---------------|-------------------| +| `steps:` (user) | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes — **be careful** | +| Platform steps | ✅ Yes | ✅ Yes | ✅ Yes | Platform-controlled | +| Agent container | ❌ Scrubbed | ❌ Scrubbed | ❌ Scrubbed | ✅ But sandboxed | + +### Step Ordering (Critical) + +User `steps:` **always run before** platform-generated steps. You cannot insert user steps after platform steps. + +The platform's `checkout_pr_branch.cjs` runs with `if: (github.event.pull_request) || (github.event.issue.pull_request)` — it is **skipped** for `workflow_dispatch` triggers. + +### Prompt Rendering + +The prompt is built in the **activation job** via `{{#runtime-import .github/workflows/.md}}`. This reads the `.md` file from the **base branch** workspace (before any PR checkout). The rendered prompt is uploaded as an artifact and downloaded by the agent job. + +- The agent prompt is always the base branch version — fork PRs cannot alter it +- The prompt references files on disk (e.g., `SKILL.md`) — those files must exist in the agent's workspace + +### Fork PR Activation Gate + +`gh aw compile` automatically injects a fork guard into the activation job's `if:` condition: `head.repo.id == repository_id`. This blocks fork PRs on `pull_request` events. This is **platform behavior** — do not add it manually. + +## Fork PR Handling + +### The "pwn-request" Threat Model + +The classic attack requires **checkout + execution** of fork code with elevated credentials. Checkout alone is not dangerous — the vulnerability is executing workspace scripts with `GITHUB_TOKEN`. + +Reference: https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/ + +### Fork PR Behavior by Trigger + +| Trigger | `checkout_pr_branch.cjs` runs? | Fork handling | +|---------|-------------------------------|---------------| +| `pull_request` | ✅ Yes | Blocked by auto-generated activation gate | +| `workflow_dispatch` | ❌ Skipped | ✅ Works — user steps handle checkout and restore is final | +| `issue_comment` (same-repo) | ✅ Yes | ✅ Works — files already on PR branch | +| `issue_comment` (fork) | N/A | ❌ Blocked by fail-closed fork guard in `Checkout-GhAwPr.ps1` | + +### The `issue_comment` + Fork Problem + +For `/slash-command` triggers on fork PRs, `checkout_pr_branch.cjs` runs AFTER all user steps and re-checks out the fork branch. This overwrites any files restored by user steps (e.g., `.github/skills/`). There is no way to run user steps after platform steps. A fork could include a crafted `SKILL.md` that alters the agent's evaluation behavior. + +**Current approach (fail-closed fork guard):** `Checkout-GhAwPr.ps1` checks `isCrossRepository` via `gh pr view` for `issue_comment` triggers. If the PR is from a fork or the API call fails, the script exits with code 1. Fork PRs should use `workflow_dispatch` instead, where `checkout_pr_branch.cjs` is skipped and the user step restore is the final workspace state. + +**Upstream issue:** [github/gh-aw#18481](https://github.com/github/gh-aw/issues/18481) — "Using gh-aw in forks of repositories" + +### Safe Pattern: Checkout + Restore + +Use the shared `.github/scripts/Checkout-GhAwPr.ps1` script, which implements checkout + restore in a single reusable step: + +```yaml +steps: + - name: Checkout PR and restore agent infrastructure + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }} + run: pwsh .github/scripts/Checkout-GhAwPr.ps1 +``` + +The script: +1. Captures the base branch SHA before checkout +2. **Fork guard** (`issue_comment` only): checks `isCrossRepository` — exits 1 if fork or API failure +3. Checks out the PR branch via `gh pr checkout` +4. Deletes `.github/skills/` and `.github/instructions/` (prevents fork-added files) +5. Restores them from the base branch SHA (best-effort, non-fatal) + +**Behavior by trigger:** +- **`workflow_dispatch`**: Fork guard skipped. Platform checkout is skipped, so the restore IS the final workspace state (trusted files from base branch) +- **`issue_comment`** (same-repo): Fork guard passes. Platform re-checks out PR branch — files already match, effectively a no-op +- **`issue_comment`** (fork): Fork guard rejects — exits 1 with actionable notice to use `workflow_dispatch` +- **`pull_request`** (same-repo): Fork guard skipped. Files already exist, restore is a no-op + +### Anti-Patterns + +**Do NOT skip checkout for fork PRs:** + +```bash +# ❌ ANTI-PATTERN: Makes fork PRs unevaluable +if [ "$HEAD_OWNER" != "$BASE_OWNER" ]; then + echo "Skipping checkout for fork PR" + exit 0 # Agent evaluates workflow branch instead of PR +fi +``` + +Skipping checkout means the agent evaluates the wrong files. The correct approach is: always check out the PR, then restore agent infrastructure from the base branch. + +**Do NOT execute workspace code after fork checkout:** + +```yaml +# ❌ DANGEROUS: runs fork code with GITHUB_TOKEN +- name: Checkout PR + run: gh pr checkout "$PR_NUMBER" ... +- name: Run analysis + run: pwsh .github/skills/some-script.ps1 +``` + +If you need to run scripts, either: +1. Run them **before** the checkout (from the base branch) +2. Run them **inside the agent container** (sandboxed, no tokens) + +## Compilation + +```bash +# Compile after every change to the .md source +gh aw compile .github/workflows/.md + +# This updates: +# - .github/workflows/.lock.yml (auto-generated) +# - .github/aw/actions-lock.json +``` + +**Always commit the compiled lock file alongside the source `.md`.** + +## Common Patterns + +### Pre-Agent Data Prep (the `steps:` pattern) + +Use `steps:` for any operation requiring GitHub API access that the agent needs: + +```yaml +steps: + - name: Fetch PR data + env: + GH_TOKEN: ${{ github.token }} + run: | + gh pr view "$PR_NUMBER" --json title,body > pr-metadata.json + gh pr diff "$PR_NUMBER" --name-only > changed-files.txt +``` + +### Safe Outputs (Posting Comments) + +```yaml +safe-outputs: + add-comment: + max: 1 + target: "*" # Required for workflow_dispatch (no triggering PR context) +``` + +### Concurrency + +Include all trigger-specific PR number sources: + +```yaml +concurrency: + group: "my-workflow-${{ github.event.issue.number || github.event.pull_request.number || inputs.pr_number || github.run_id }}" + cancel-in-progress: true +``` + +### Noise Reduction + +Filter `pull_request` triggers to relevant paths and add a gate step: + +```yaml +on: + pull_request: + paths: + - 'src/**/tests/**' + +steps: + - name: Gate — skip if no relevant files + if: github.event_name == 'pull_request' + run: | + FILES=$(gh pr diff "$PR_NUMBER" --name-only | grep -E '\.cs$' || true) + if [ -z "$FILES" ]; then exit 1; fi +``` + +Manual triggers (`workflow_dispatch`, `issue_comment`) should bypass the gate. Note: `exit 1` causes a red ❌ on non-matching PRs — this is intentional (no built-in "skip" mechanism in gh-aw steps). + +## Limitations + +| What | Behavior | Workaround | +|------|----------|------------| +| User steps always before platform steps | Cannot run user code after `checkout_pr_branch.cjs` | Use `workflow_dispatch` for fork PRs; see [gh-aw#18481](https://github.com/github/gh-aw/issues/18481) | +| `--allow-all-tools` in lock.yml | Emitted by `gh aw compile` | Cannot override from `.md` source | +| MCP integrity filtering | Fork PRs blocked as "unapproved" | Use `steps:` checkout instead of MCP | +| `gh` CLI inside agent | Credentials scrubbed | Use `steps:` for API calls, or MCP tools | +| `issue_comment` trigger | Requires workflow on default branch | Must merge to `main` before `/slash-commands` work | +| Duplicate runs | gh-aw sometimes creates 2 runs per dispatch | Harmless, use concurrency groups | + +### Upstream References + +- [github/gh-aw#18481](https://github.com/github/gh-aw/issues/18481) — Fork support tracking issue +- [github/gh-aw#18518](https://github.com/github/gh-aw/issues/18518) — Fork detection in `gh aw init` +- [github/gh-aw#18521](https://github.com/github/gh-aw/issues/18521) — Fork support documentation + +## Troubleshooting + +| Symptom | Cause | Fix | +|---------|-------|-----| +| Agent evaluates wrong PR | `workflow_dispatch` checks out workflow branch | Add `gh pr checkout` in `steps:` | +| Agent can't find SKILL.md | Fork PR branch doesn't have `.github/skills/` | Agent posts "rebase or use `workflow_dispatch`" message; or rebase fork on `main` | +| Fork PR rejected on `/evaluate-tests` | Fail-closed fork guard in `Checkout-GhAwPr.ps1` | Use `workflow_dispatch` with `pr_number` input instead | +| `gh` commands fail in agent | Credentials scrubbed inside container | Move to `steps:` section | +| Lock file out of date | Forgot to recompile | Run `gh aw compile` | +| Integrity filtering warning | MCP reading fork PR data | Expected, non-blocking | +| `/slash-command` doesn't trigger | Workflow not on default branch | Merge to `main` first | diff --git a/.github/scripts/Checkout-GhAwPr.ps1 b/.github/scripts/Checkout-GhAwPr.ps1 new file mode 100644 index 000000000000..4a6380a50421 --- /dev/null +++ b/.github/scripts/Checkout-GhAwPr.ps1 @@ -0,0 +1,100 @@ +<# +.SYNOPSIS + Shared PR checkout for gh-aw (GitHub Agentic Workflows). + +.DESCRIPTION + Checks out a PR branch and restores trusted agent infrastructure (skills, + instructions) from the base branch. For issue_comment triggers, fork PRs + are rejected (fail-closed) because the platform's checkout_pr_branch.cjs + overwrites restored files after user steps. Fork PRs should use + workflow_dispatch instead. + + SECURITY NOTE: This script checks out PR code onto disk. This is safe + because NO subsequent user steps execute workspace code — the gh-aw + platform copies the workspace into a sandboxed container with scrubbed + credentials before starting the agent. The classic "pwn-request" attack + requires checkout + execution; we only do checkout. + + DO NOT add steps after this that run scripts from the workspace + (e.g., ./build.sh, pwsh ./script.ps1). That would create an actual + fork code execution vulnerability. See: + https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/ + +.NOTES + Required environment variables (set by the calling workflow step): + GH_TOKEN - GitHub token for API access + PR_NUMBER - PR number to check out + GITHUB_REPOSITORY - owner/repo (set by GitHub Actions) + GITHUB_ENV - path to env file (set by GitHub Actions) + GITHUB_EVENT_NAME - trigger type (set by GitHub Actions) +#> + +$ErrorActionPreference = 'Stop' + +# ── Validate inputs ────────────────────────────────────────────────────────── + +if (-not $env:PR_NUMBER -or $env:PR_NUMBER -eq '0') { + Write-Host "No PR number available, using default checkout" + exit 0 +} + +$PrNumber = $env:PR_NUMBER + +# ── Fork guard (issue_comment only) ───────────────────────────────────────── +# For issue_comment triggers, platform's checkout_pr_branch.cjs runs AFTER user +# steps and re-checks out the fork branch, overwriting any restored skill/instruction +# files. A fork could include a crafted SKILL.md that alters agent behavior. +# Fail closed: if we can't verify origin, exit 1 (not 0). +# Fork PRs can still be evaluated via workflow_dispatch (where platform checkout is skipped). + +if ($env:GITHUB_EVENT_NAME -eq 'issue_comment') { + $isFork = gh pr view $PrNumber --repo $env:GITHUB_REPOSITORY --json isCrossRepository --jq '.isCrossRepository' 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Host "❌ Could not verify PR origin — failing closed" + exit 1 + } + if ($isFork -eq 'true') { + Write-Host "::notice::Fork PR detected — /evaluate-tests via issue_comment is not supported for fork PRs. Use workflow_dispatch with pr_number=$PrNumber instead." + exit 1 + } +} + +# ── Save base branch SHA ───────────────────────────────────────────────────── +# Must be captured BEFORE checkout replaces HEAD. +# Exported for potential use by downstream platform steps (e.g., checkout_pr_branch.cjs) + +$BaseSha = git rev-parse HEAD +if ($LASTEXITCODE -ne 0) { + Write-Host "❌ Failed to get current HEAD SHA" + exit 1 +} +Add-Content -Path $env:GITHUB_ENV -Value "BASE_SHA=$BaseSha" + +# ── Checkout PR branch ────────────────────────────────────────────────────── + +Write-Host "Checking out PR #$PrNumber..." +gh pr checkout $PrNumber --repo $env:GITHUB_REPOSITORY +if ($LASTEXITCODE -ne 0) { + Write-Host "❌ Failed to checkout PR #$PrNumber" + exit 1 +} +Write-Host "✅ Checked out PR #$PrNumber" +git log --oneline -1 + +# ── Restore agent infrastructure from base branch ──────────────────────────── +# Best-effort restore of skill/instruction files from the base branch. +# - workflow_dispatch: platform checkout is skipped, so this IS the final state +# - issue_comment (same-repo): platform's checkout_pr_branch.cjs runs after and +# overwrites, but files already match (same repo). Fork PRs are blocked above. +# - pull_request (same-repo): files already exist, this is a no-op +# rm -rf first to prevent fork-added files from surviving the restore. + +if (Test-Path '.github/skills/') { Remove-Item -Recurse -Force '.github/skills/' } +if (Test-Path '.github/instructions/') { Remove-Item -Recurse -Force '.github/instructions/' } + +git checkout $BaseSha -- .github/skills/ .github/instructions/ .github/copilot-instructions.md 2>&1 +if ($LASTEXITCODE -eq 0) { + Write-Host "✅ Restored agent infrastructure from base branch ($BaseSha)" +} else { + Write-Host "⚠️ Could not restore agent infrastructure from base branch — files may come from the PR branch" +} diff --git a/.github/skills/evaluate-pr-tests/scripts/Gather-TestContext.ps1 b/.github/skills/evaluate-pr-tests/scripts/Gather-TestContext.ps1 index 8b8805b5596b..7fe0cdedba50 100644 --- a/.github/skills/evaluate-pr-tests/scripts/Gather-TestContext.ps1 +++ b/.github/skills/evaluate-pr-tests/scripts/Gather-TestContext.ps1 @@ -12,6 +12,12 @@ - Find existing similar tests - Assess platform scope +.PARAMETER PrNumber + Explicit PR number to evaluate. When provided, the script uses + `gh pr view ` to detect the base branch and `gh pr diff ` + to get the changed files. This avoids relying on the currently checked-out + branch, which is critical for workflow_dispatch triggers. + .PARAMETER BaseBranch Base branch to diff against. Auto-detected from PR if not specified. @@ -19,13 +25,16 @@ Directory to write the context report to. .EXAMPLE - ./Gather-TestContext.ps1 + ./Gather-TestContext.ps1 -PrNumber 31244 .EXAMPLE ./Gather-TestContext.ps1 -BaseBranch "origin/main" #> param( + [Parameter(Mandatory = $false)] + [int]$PrNumber, + [Parameter(Mandatory = $false)] [string]$BaseBranch, @@ -42,7 +51,22 @@ New-Item -ItemType Directory -Force -Path $OutputDir | Out-Null $reportPath = Join-Path $OutputDir "context.md" # --- 1. Detect base branch --- -if (-not $BaseBranch) { +$usePrDiff = $false +if ($PrNumber -gt 0) { + # Explicit PR number — use gh pr view/diff so we don't depend on local branch + Write-Host "📋 Evaluating PR #$PrNumber (explicit)" + if (-not $BaseBranch) { + try { + $prJson = gh pr view $PrNumber --json baseRefName 2>$null + if ($prJson) { + $prInfo = $prJson | ConvertFrom-Json + $BaseBranch = "origin/$($prInfo.baseRefName)" + } + } catch { } + if (-not $BaseBranch) { $BaseBranch = "origin/main" } + } + $usePrDiff = $true +} elseif (-not $BaseBranch) { try { $prJson = gh pr view --json baseRefName 2>$null if ($prJson) { @@ -61,15 +85,69 @@ git fetch origin --quiet 2>$null # --- 2. Get changed files --- $changedFiles = @() -$diffOutput = git diff --name-only "$BaseBranch...HEAD" 2>$null -if ($diffOutput) { - $changedFiles = $diffOutput -split "`r?`n" | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" } -} else { - $diffOutput = git diff --name-only "$BaseBranch" 2>$null + +if ($changedFiles.Count -eq 0 -and $usePrDiff) { + # Use gh pr diff to get file list directly from GitHub API — works regardless of local checkout + $diffOutput = gh pr diff $PrNumber --name-only 2>$null if ($diffOutput) { $changedFiles = $diffOutput -split "`r?`n" | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" } } } +if ($changedFiles.Count -eq 0) { + $diffOutput = git diff --name-only "$BaseBranch...HEAD" 2>$null + if ($diffOutput) { + $changedFiles = $diffOutput -split "`r?`n" | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" } + } else { + $diffOutput = git diff --name-only "$BaseBranch" 2>$null + if ($diffOutput) { + $changedFiles = $diffOutput -split "`r?`n" | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" } + } + } +} + +# --- 2b. Download missing files via GitHub API (needed when PR isn't checked out locally) --- +if ($usePrDiff -and $changedFiles.Count -gt 0) { + $headSha = $null + try { + $headSha = gh pr view $PrNumber --json headRefOid --jq '.headRefOid' 2>$null + } catch { } + + if ($headSha) { + $downloadCount = 0 + $repoRootFull = [System.IO.Path]::GetFullPath($RepoRoot) + $owner, $repo = ($env:GITHUB_REPOSITORY ?? "dotnet/maui") -split '/', 2 + foreach ($file in $changedFiles) { + # Path traversal guard: ensure resolved path stays within repo root + $targetPath = [System.IO.Path]::GetFullPath((Join-Path $RepoRoot $file)) + if (-not $targetPath.StartsWith($repoRootFull + [System.IO.Path]::DirectorySeparatorChar)) { + Write-Warning "Skipping out-of-root path: $file" + continue + } + + if (-not (Test-Path $targetPath)) { + try { + $dir = [System.IO.Path]::GetDirectoryName($targetPath) + if ($dir -and -not (Test-Path $dir)) { + New-Item -ItemType Directory -Force -Path $dir | Out-Null + } + $encodedFile = [Uri]::EscapeDataString($file) -replace '%2F', '/' + $apiPath = "repos/$owner/$repo/contents/$($encodedFile)?ref=$headSha" + $b64 = gh api $apiPath --jq '.content' 2>$null + if ($b64) { + $bytes = [System.Convert]::FromBase64String(($b64 -replace '\s', '')) + [System.IO.File]::WriteAllBytes($targetPath, $bytes) + $downloadCount++ + } + } catch { + Write-Host "⚠️ Could not download $file via API: $_" + } + } + } + if ($downloadCount -gt 0) { + Write-Host "📥 Downloaded $downloadCount file(s) from PR #$PrNumber head ($($headSha.Substring(0,7)))" + } + } +} if ($changedFiles.Count -eq 0) { Write-Host "⚠️ No changed files detected. Check your branch and base branch." @@ -128,7 +206,8 @@ function Test-UITestConventions { # --- Naming (only flag files in Issues/ directory that look like issue tests) --- $fileName = [System.IO.Path]::GetFileNameWithoutExtension($TestFile) if ($TestFile -match "Issues/" -and $fileName -match "^Issue" -and $fileName -notmatch "^Issue\d+$") { - $issues += "Issue test file name ``$fileName`` should follow ``IssueXXXXX`` pattern" + $safeName = Escape-ForCodeSpan $fileName + $issues += "Issue test file name ``$safeName`` should follow ``IssueXXXXX`` pattern" } # --- Inheritance --- @@ -225,7 +304,8 @@ function Test-UITestConventions { # For .xaml files, skip C# attribute checks (they live in code-behind) if ($hostFile -notmatch "\.xaml$") { if ($hostContent -notmatch "\[Issue\(") { - $issues += "HostApp page ``$([System.IO.Path]::GetFileName($hostFile))`` missing ``[Issue()]`` attribute" + $safeHost = Escape-ForCodeSpan ([System.IO.Path]::GetFileName($hostFile)) + $issues += "HostApp page ``$safeHost`` missing ``[Issue()]`` attribute" } } if ($hostContent -match "new\s+Frame\b") { @@ -334,7 +414,8 @@ function Test-XamlTestConventions { # File naming for issues $fileName = [System.IO.Path]::GetFileNameWithoutExtension($TestFile) if ($TestFile -match "Issues/" -and $fileName -notmatch "^Maui\d+$") { - $issues += "Issue test file name ``$fileName`` doesn't follow ``MauiXXXXX`` pattern" + $safeName = Escape-ForCodeSpan $fileName + $issues += "Issue test file name ``$safeName`` doesn't follow ``MauiXXXXX`` pattern" } return @{ Issues = $issues; Info = $info } @@ -353,7 +434,25 @@ $report += "" $report += "| Category | Count | Files |" $report += "|----------|-------|-------|" -function Format-FileList { param([string[]]$files) if ($files.Count -eq 0) { return "_none_" } return ($files | ForEach-Object { "``$_``" }) -join ", " } +function Escape-ForCodeSpan { + param([string]$Text) + # Neutralise characters that break markdown code spans or line structure. + # Backticks are replaced with a visually similar RIGHT SINGLE QUOTATION MARK (U+2019) + # so the surrounding `` delimiters stay balanced. Newlines / carriage-returns are + # stripped because they would break table rows or heading lines. + return ($Text -replace '`', [char]0x2019 -replace '[\r\n]', '') +} + +function Format-FileList { + param([string[]]$files) + if ($files.Count -eq 0) { return "_none_" } + return ($files | ForEach-Object { + # Escape markdown metacharacters to prevent injection via crafted filenames. + # Use double-backtick code spans (`` ... ``) so literal backticks render correctly. + $escaped = (Escape-ForCodeSpan $_) -replace '\|', '\|' -replace '<', '<' -replace '>', '>' + "````$escaped````" + }) -join ", " +} $report += "| **Fix files** | $($fixFiles.Count) | $(Format-FileList $fixFiles) |" $report += "| **UI Tests (NUnit)** | $($uiTestFiles.Count) | $(Format-FileList $uiTestFiles) |" @@ -396,7 +495,8 @@ if ($uiTestFiles.Count -gt 0) { $hostName -eq $baseName } $result = Test-UITestConventions -TestFile $testFile -HostAppFiles $matchingHostFiles - $report += "### ``$baseName``" + $safeBase = Escape-ForCodeSpan $baseName + $report += "### ``$safeBase``" if ($result.Info.Count -gt 0) { foreach ($i in $result.Info) { $report += "- ℹ️ $i" } } @@ -419,7 +519,8 @@ if ($unitTestFiles.Count -gt 0) { foreach ($testFile in $unitTestFiles) { $baseName = [System.IO.Path]::GetFileNameWithoutExtension($testFile) $result = Test-UnitTestConventions -TestFile $testFile - $report += "### ``$baseName``" + $safeBase = Escape-ForCodeSpan $baseName + $report += "### ``$safeBase``" if ($result.Info.Count -gt 0) { foreach ($i in $result.Info) { $report += "- ℹ️ $i" } } @@ -442,7 +543,8 @@ if ($xamlTestFiles.Count -gt 0) { foreach ($testFile in ($xamlTestFiles | Where-Object { $_ -match "\.cs$" })) { $baseName = [System.IO.Path]::GetFileNameWithoutExtension($testFile) $result = Test-XamlTestConventions -TestFile $testFile - $report += "### ``$baseName``" + $safeBase = Escape-ForCodeSpan $baseName + $report += "### ``$safeBase``" if ($result.Info.Count -gt 0) { foreach ($i in $result.Info) { $report += "- ℹ️ $i" } } diff --git a/.github/workflows/copilot-evaluate-tests.lock.yml b/.github/workflows/copilot-evaluate-tests.lock.yml new file mode 100644 index 000000000000..c1e75132b153 --- /dev/null +++ b/.github/workflows/copilot-evaluate-tests.lock.yml @@ -0,0 +1,1089 @@ +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# This file was automatically generated by gh-aw (v0.62.2). DO NOT EDIT. +# +# To update this file, edit the corresponding .md file and run: +# gh aw compile +# Not all edits will cause changes to this file. +# +# For more information: https://github.github.com/gh-aw/introduction/overview/ +# +# Evaluates test quality, coverage, and appropriateness on PRs that add or modify tests +# +# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"9a0e480927d61956be62f85669d2e599525442694d280126849fe87e727c31df","compiler_version":"v0.62.2","strict":true} + +name: "Evaluate PR Tests" +"on": + issue_comment: + types: + - created + pull_request: + paths: + - src/**/tests/** + - src/**/test/** + types: + - opened + - synchronize + - reopened + - ready_for_review + workflow_dispatch: + inputs: + pr_number: + description: PR number to evaluate + required: true + type: number + +permissions: {} + +concurrency: + cancel-in-progress: true + group: evaluate-pr-tests-${{ github.event.pull_request.number || github.event.issue.number || inputs.pr_number || github.run_id }} + +run-name: "Evaluate PR Tests" + +jobs: + activation: + needs: pre_activation + if: > + (needs.pre_activation.outputs.activated == 'true') && (((github.event_name == 'pull_request' && github.event.pull_request.draft == false) || github.event_name == 'workflow_dispatch' || (github.event_name == 'issue_comment' && + github.event.issue.pull_request && + startsWith(github.event.comment.body, '/evaluate-tests'))) && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.id == github.repository_id))) + runs-on: ubuntu-slim + permissions: + contents: read + outputs: + body: ${{ steps.sanitized.outputs.body }} + comment_id: "" + comment_repo: "" + lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} + model: ${{ steps.generate_aw_info.outputs.model }} + secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + text: ${{ steps.sanitized.outputs.text }} + title: ${{ steps.sanitized.outputs.title }} + steps: + - name: Setup Scripts + uses: github/gh-aw-actions/setup@20045bbd5ad2632b9809856c389708eab1bd16ef # v0.62.2 + with: + destination: ${{ runner.temp }}/gh-aw/actions + - name: Generate agentic run info + id: generate_aw_info + env: + GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" + GH_AW_INFO_MODEL: "claude-sonnet-4.6" + GH_AW_INFO_VERSION: "" + GH_AW_INFO_AGENT_VERSION: "latest" + GH_AW_INFO_CLI_VERSION: "v0.62.2" + GH_AW_INFO_WORKFLOW_NAME: "Evaluate PR Tests" + GH_AW_INFO_EXPERIMENTAL: "false" + GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" + GH_AW_INFO_STAGED: "false" + GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' + GH_AW_INFO_FIREWALL_ENABLED: "true" + GH_AW_INFO_AWF_VERSION: "v0.24.3" + GH_AW_INFO_AWMG_VERSION: "" + GH_AW_INFO_FIREWALL_TYPE: "squid" + GH_AW_COMPILED_STRICT: "true" + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); + await main(core, context); + - name: Validate COPILOT_GITHUB_TOKEN secret + id: validate-secret + run: ${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + - name: Checkout .github and .agents folders + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + sparse-checkout: | + .github + .agents + sparse-checkout-cone-mode: true + fetch-depth: 1 + - name: Check workflow file timestamps + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_WORKFLOW_FILE: "copilot-evaluate-tests.lock.yml" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs'); + await main(); + - name: Compute current body text + id: sanitized + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/compute_text.cjs'); + await main(); + - name: Create prompt with built-in context + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GH_AW_EXPR_93C755A4: ${{ github.event.pull_request.number || github.event.issue.number || inputs.pr_number }} + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_IS_PR_COMMENT: ${{ github.event.issue.pull_request && 'true' || '' }} + run: | + bash ${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh + { + cat << 'GH_AW_PROMPT_EOF' + + GH_AW_PROMPT_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" + cat << 'GH_AW_PROMPT_EOF' + + Tools: add_comment, missing_tool, missing_data, noop + + + The following GitHub context information is available for this workflow: + {{#if __GH_AW_GITHUB_ACTOR__ }} + - **actor**: __GH_AW_GITHUB_ACTOR__ + {{/if}} + {{#if __GH_AW_GITHUB_REPOSITORY__ }} + - **repository**: __GH_AW_GITHUB_REPOSITORY__ + {{/if}} + {{#if __GH_AW_GITHUB_WORKSPACE__ }} + - **workspace**: __GH_AW_GITHUB_WORKSPACE__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} + - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} + - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} + - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} + - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ + {{/if}} + {{#if __GH_AW_GITHUB_RUN_ID__ }} + - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ + {{/if}} + + + GH_AW_PROMPT_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" + if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then + cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" + fi + cat << 'GH_AW_PROMPT_EOF' + + GH_AW_PROMPT_EOF + cat << 'GH_AW_PROMPT_EOF' + {{#runtime-import .github/workflows/copilot-evaluate-tests.md}} + GH_AW_PROMPT_EOF + } > "$GH_AW_PROMPT" + - name: Interpolate variables and render templates + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_EXPR_93C755A4: ${{ github.event.pull_request.number || github.event.issue.number || inputs.pr_number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs'); + await main(); + - name: Substitute placeholders + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_EXPR_93C755A4: ${{ github.event.pull_request.number || github.event.issue.number || inputs.pr_number }} + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_IS_PR_COMMENT: ${{ github.event.issue.pull_request && 'true' || '' }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + + const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs'); + + // Call the substitution function + return await substitutePlaceholders({ + file: process.env.GH_AW_PROMPT, + substitutions: { + GH_AW_EXPR_93C755A4: process.env.GH_AW_EXPR_93C755A4, + GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, + GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, + GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, + GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, + GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, + GH_AW_IS_PR_COMMENT: process.env.GH_AW_IS_PR_COMMENT, + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED + } + }); + - name: Validate prompt placeholders + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + run: bash ${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh + - name: Print prompt + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + run: bash ${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh + - name: Upload activation artifact + if: success() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: activation + path: | + /tmp/gh-aw/aw_info.json + /tmp/gh-aw/aw-prompts/prompt.txt + retention-days: 1 + + agent: + needs: activation + runs-on: ubuntu-latest + permissions: + contents: read + issues: read + pull-requests: read + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + GH_AW_ASSETS_ALLOWED_EXTS: "" + GH_AW_ASSETS_BRANCH: "" + GH_AW_ASSETS_MAX_SIZE_KB: 0 + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_WORKFLOW_ID_SANITIZED: copilotevaluatetests + outputs: + checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} + detection_success: ${{ steps.detection_conclusion.outputs.success }} + has_patch: ${{ steps.collect_output.outputs.has_patch }} + inference_access_error: ${{ steps.detect-inference-error.outputs.inference_access_error || 'false' }} + model: ${{ needs.activation.outputs.model }} + output: ${{ steps.collect_output.outputs.output }} + output_types: ${{ steps.collect_output.outputs.output_types }} + steps: + - name: Setup Scripts + uses: github/gh-aw-actions/setup@20045bbd5ad2632b9809856c389708eab1bd16ef # v0.62.2 + with: + destination: ${{ runner.temp }}/gh-aw/actions + - name: Set runtime paths + run: | + echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" >> "$GITHUB_ENV" + echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" >> "$GITHUB_ENV" + echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" >> "$GITHUB_ENV" + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Create gh-aw temp directory + run: bash ${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh + - name: Configure gh CLI for GitHub Enterprise + run: bash ${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh + env: + GH_TOKEN: ${{ github.token }} + - env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + if: github.event_name == 'pull_request' + name: Gate — skip if no test source files in diff + run: "TEST_FILES=$(gh pr diff \"$PR_NUMBER\" --repo \"$GITHUB_REPOSITORY\" --name-only \\\n | grep -E '\\.(cs|xaml)$' \\\n | grep -iE '(tests?/|TestCases|UnitTests|DeviceTests)' \\\n || true)\nif [ -z \"$TEST_FILES\" ]; then\n echo \"⏭️ No test source files (.cs/.xaml) found in PR diff. Skipping evaluation.\"\n exit 1\nfi\necho \"✅ Found test files to evaluate:\"\necho \"$TEST_FILES\" | head -20\n" + - env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number || inputs.pr_number }} + name: Checkout PR and restore agent infrastructure + run: pwsh .github/scripts/Checkout-GhAwPr.ps1 + + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Checkout PR branch + id: checkout-pr + if: | + (github.event.pull_request) || (github.event.issue.pull_request) + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); + await main(); + - name: Install GitHub Copilot CLI + run: ${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh latest + env: + GH_HOST: github.com + - name: Install AWF binary + run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.24.3 + - name: Determine automatic lockdown mode for GitHub MCP Server + id: determine-automatic-lockdown + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + with: + script: | + const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs'); + await determineAutomaticLockdown(github, context, core); + - name: Download container images + run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.24.3 ghcr.io/github/gh-aw-firewall/api-proxy:0.24.3 ghcr.io/github/gh-aw-firewall/squid:0.24.3 ghcr.io/github/gh-aw-mcpg:v0.1.19 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine + - name: Write Safe Outputs Config + run: | + mkdir -p ${RUNNER_TEMP}/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs + cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' + {"add_comment":{"max":1,"target":"*"},"missing_data":{},"missing_tool":{},"noop":{"max":1}} + GH_AW_SAFE_OUTPUTS_CONFIG_EOF + - name: Write Safe Outputs Tools + run: | + cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_EOF' + { + "description_suffixes": { + "add_comment": " CONSTRAINTS: Maximum 1 comment(s) can be added. Target: *." + }, + "repo_params": {}, + "dynamic_tools": [] + } + GH_AW_SAFE_OUTPUTS_TOOLS_META_EOF + cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_EOF' + { + "add_comment": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "item_number": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + } + } + }, + "missing_data": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + } + } + GH_AW_SAFE_OUTPUTS_VALIDATION_EOF + node ${RUNNER_TEMP}/gh-aw/actions/generate_safe_outputs_tools.cjs + - name: Generate Safe Outputs MCP Server Config + id: safe-outputs-config + run: | + # Generate a secure random API key (360 bits of entropy, 40+ chars) + # Mask immediately to prevent timing vulnerabilities + API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${API_KEY}" + + PORT=3001 + + # Set outputs for next steps + { + echo "safe_outputs_api_key=${API_KEY}" + echo "safe_outputs_port=${PORT}" + } >> "$GITHUB_OUTPUT" + + echo "Safe Outputs MCP server will run on port ${PORT}" + + - name: Start Safe Outputs MCP HTTP Server + id: safe-outputs-start + env: + DEBUG: '*' + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + run: | + # Environment variables are set above to prevent template injection + export DEBUG + export GH_AW_SAFE_OUTPUTS_PORT + export GH_AW_SAFE_OUTPUTS_API_KEY + export GH_AW_SAFE_OUTPUTS_TOOLS_PATH + export GH_AW_SAFE_OUTPUTS_CONFIG_PATH + export GH_AW_MCP_LOG_DIR + + bash ${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh + + - name: Start MCP Gateway + id: start-mcp-gateway + env: + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GITHUB_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }} + GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + run: | + set -eo pipefail + mkdir -p /tmp/gh-aw/mcp-config + + # Export gateway environment variables for MCP config and gateway script + export MCP_GATEWAY_PORT="80" + export MCP_GATEWAY_DOMAIN="host.docker.internal" + MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${MCP_GATEWAY_API_KEY}" + export MCP_GATEWAY_API_KEY + export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" + mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" + export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288" + export DEBUG="*" + + export GH_AW_ENGINE="copilot" + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.1.19' + + mkdir -p /home/runner/.copilot + cat << GH_AW_MCP_CONFIG_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh + { + "mcpServers": { + "github": { + "type": "stdio", + "container": "ghcr.io/github/github-mcp-server:v0.32.0", + "env": { + "GITHUB_HOST": "\${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_READ_ONLY": "1", + "GITHUB_TOOLSETS": "context,repos,issues,pull_requests" + }, + "guard-policies": { + "allow-only": { + "min-integrity": "$GITHUB_MCP_GUARD_MIN_INTEGRITY", + "repos": "$GITHUB_MCP_GUARD_REPOS" + } + } + }, + "safeoutputs": { + "type": "http", + "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", + "headers": { + "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + }, + "guard-policies": { + "write-sink": { + "accept": [ + "*" + ] + } + } + } + }, + "gateway": { + "port": $MCP_GATEWAY_PORT, + "domain": "${MCP_GATEWAY_DOMAIN}", + "apiKey": "${MCP_GATEWAY_API_KEY}", + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + } + } + GH_AW_MCP_CONFIG_EOF + - name: Download activation artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: activation + path: /tmp/gh-aw + - name: Clean git credentials + continue-on-error: true + run: bash ${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh + - name: Execute GitHub Copilot CLI + id: agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 15 + run: | + set -o pipefail + touch /tmp/gh-aw/agent-step-summary.md + # shellcheck disable=SC1003 + sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --allow-domains "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.24.3 --skip-pull --enable-api-proxy \ + -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-all-tools --allow-all-paths --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_MODEL: claude-sonnet-4.6 + GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + GH_AW_PHASE: agent + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GH_AW_VERSION: v0.62.2 + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md + GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + XDG_CONFIG_HOME: /home/runner + - name: Detect inference access error + id: detect-inference-error + if: always() + continue-on-error: true + run: bash ${RUNNER_TEMP}/gh-aw/actions/detect_inference_access_error.sh + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Copy Copilot session state files to logs + if: always() + continue-on-error: true + run: | + # Copy Copilot session state files to logs folder for artifact collection + # This ensures they are in /tmp/gh-aw/ where secret redaction can scan them + SESSION_STATE_DIR="$HOME/.copilot/session-state" + LOGS_DIR="/tmp/gh-aw/sandbox/agent/logs" + + if [ -d "$SESSION_STATE_DIR" ]; then + echo "Copying Copilot session state files from $SESSION_STATE_DIR to $LOGS_DIR" + mkdir -p "$LOGS_DIR" + cp -v "$SESSION_STATE_DIR"/*.jsonl "$LOGS_DIR/" 2>/dev/null || true + echo "Session state files copied successfully" + else + echo "No session-state directory found at $SESSION_STATE_DIR" + fi + - name: Stop MCP Gateway + if: always() + continue-on-error: true + env: + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} + run: | + bash ${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID" + - name: Redact secrets in logs + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); + await main(); + env: + GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' + SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Append agent step summary + if: always() + run: bash ${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh + - name: Copy Safe Outputs + if: always() + run: | + mkdir -p /tmp/gh-aw + cp "$GH_AW_SAFE_OUTPUTS" /tmp/gh-aw/safeoutputs.jsonl 2>/dev/null || true + - name: Ingest agent output + id: collect_output + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs'); + await main(); + - name: Parse agent logs for step summary + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs'); + await main(); + - name: Parse MCP Gateway logs for step summary + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs'); + await main(); + - name: Print firewall logs + if: always() + continue-on-error: true + env: + AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs + run: | + # Fix permissions on firewall logs so they can be uploaded as artifacts + # AWF runs with sudo, creating files owned by root + sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true + # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step) + if command -v awf &> /dev/null; then + awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" + else + echo 'AWF binary not installed, skipping firewall log summary' + fi + - name: Upload agent artifacts + if: always() + continue-on-error: true + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: agent + path: | + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/sandbox/agent/logs/ + /tmp/gh-aw/redacted-urls.log + /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/sandbox/firewall/logs/ + /tmp/gh-aw/agent-stdio.log + /tmp/gh-aw/agent/ + /tmp/gh-aw/safeoutputs.jsonl + /tmp/gh-aw/agent_output.json + if-no-files-found: ignore + # --- Threat Detection (inline) --- + - name: Check if detection needed + id: detection_guard + if: always() + env: + OUTPUT_TYPES: ${{ steps.collect_output.outputs.output_types }} + HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }} + run: | + if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then + echo "run_detection=true" >> "$GITHUB_OUTPUT" + echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH" + else + echo "run_detection=false" >> "$GITHUB_OUTPUT" + echo "Detection skipped: no agent outputs or patches to analyze" + fi + - name: Clear MCP configuration for detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + rm -f /tmp/gh-aw/mcp-config/mcp-servers.json + rm -f /home/runner/.copilot/mcp-config.json + rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" + - name: Prepare threat detection files + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection/aw-prompts + cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true + cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true + for f in /tmp/gh-aw/aw-*.patch; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + echo "Prepared threat detection files:" + ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true + - name: Setup threat detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + WORKFLOW_NAME: "Evaluate PR Tests" + WORKFLOW_DESCRIPTION: "Evaluates test quality, coverage, and appropriateness on PRs that add or modify tests" + HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs'); + await main(); + - name: Ensure threat-detection directory and log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection + touch /tmp/gh-aw/threat-detection/detection.log + - name: Execute GitHub Copilot CLI + if: always() && steps.detection_guard.outputs.run_detection == 'true' + id: detection_agentic_execution + # Copilot CLI tool arguments (sorted): + # --allow-tool shell(cat) + # --allow-tool shell(grep) + # --allow-tool shell(head) + # --allow-tool shell(jq) + # --allow-tool shell(ls) + # --allow-tool shell(tail) + # --allow-tool shell(wc) + timeout-minutes: 20 + run: | + set -o pipefail + touch /tmp/gh-aw/agent-step-summary.md + # shellcheck disable=SC1003 + sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --allow-domains "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,raw.githubusercontent.com,registry.npmjs.org,telemetry.enterprise.githubcopilot.com" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.24.3 --skip-pull --enable-api-proxy \ + -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(jq)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(wc)'\'' --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_MODEL: claude-sonnet-4.6 + GH_AW_PHASE: detection + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_VERSION: v0.62.2 + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md + GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + XDG_CONFIG_HOME: /home/runner + - name: Parse threat detection results + id: parse_detection_results + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + - name: Upload threat detection log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: detection + path: /tmp/gh-aw/threat-detection/detection.log + if-no-files-found: ignore + - name: Set detection conclusion + id: detection_conclusion + if: always() + env: + RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + DETECTION_SUCCESS: ${{ steps.parse_detection_results.outputs.success }} + run: | + if [[ "$RUN_DETECTION" != "true" ]]; then + echo "conclusion=skipped" >> "$GITHUB_OUTPUT" + echo "success=true" >> "$GITHUB_OUTPUT" + echo "Detection was not needed, marking as skipped" + elif [[ "$DETECTION_SUCCESS" == "true" ]]; then + echo "conclusion=success" >> "$GITHUB_OUTPUT" + echo "success=true" >> "$GITHUB_OUTPUT" + echo "Detection passed successfully" + else + echo "conclusion=failure" >> "$GITHUB_OUTPUT" + echo "success=false" >> "$GITHUB_OUTPUT" + echo "Detection found issues" + fi + + conclusion: + needs: + - activation + - agent + - safe_outputs + if: (always()) && ((needs.agent.result != 'skipped') || (needs.activation.outputs.lockdown_check_failed == 'true')) + runs-on: ubuntu-slim + permissions: + contents: read + discussions: write + issues: write + pull-requests: write + concurrency: + group: "gh-aw-conclusion-copilot-evaluate-tests" + cancel-in-progress: false + outputs: + noop_message: ${{ steps.noop.outputs.noop_message }} + tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} + total_count: ${{ steps.missing_tool.outputs.total_count }} + steps: + - name: Setup Scripts + uses: github/gh-aw-actions/setup@20045bbd5ad2632b9809856c389708eab1bd16ef # v0.62.2 + with: + destination: ${{ runner.temp }}/gh-aw/actions + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_ENV" + - name: Process No-Op Messages + id: noop + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_NOOP_MAX: "1" + GH_AW_WORKFLOW_NAME: "Evaluate PR Tests" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/noop.cjs'); + await main(); + - name: Record Missing Tool + id: missing_tool + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Evaluate PR Tests" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); + await main(); + - name: Handle Agent Failure + id: handle_agent_failure + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Evaluate PR Tests" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_WORKFLOW_ID: "copilot-evaluate-tests" + GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} + GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} + GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🧪 *Test evaluation by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔬 Evaluating tests on this PR… [{workflow_name}]({run_url})\",\"runSuccess\":\"✅ Test evaluation complete! [{workflow_name}]({run_url})\",\"runFailure\":\"❌ Test evaluation failed. [{workflow_name}]({run_url}) {status}\"}" + GH_AW_GROUP_REPORTS: "false" + GH_AW_FAILURE_REPORT_AS_ISSUE: "true" + GH_AW_TIMEOUT_MINUTES: "15" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); + await main(); + - name: Handle No-Op Message + id: handle_noop_message + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Evaluate PR Tests" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); + await main(); + + pre_activation: + if: > + ((github.event_name == 'pull_request' && github.event.pull_request.draft == false) || github.event_name == 'workflow_dispatch' || (github.event_name == 'issue_comment' && + github.event.issue.pull_request && + startsWith(github.event.comment.body, '/evaluate-tests'))) && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.id == github.repository_id)) + runs-on: ubuntu-slim + outputs: + activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} + matched_command: '' + steps: + - name: Setup Scripts + uses: github/gh-aw-actions/setup@20045bbd5ad2632b9809856c389708eab1bd16ef # v0.62.2 + with: + destination: ${{ runner.temp }}/gh-aw/actions + - name: Check team membership for workflow + id: check_membership + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_REQUIRED_ROLES: admin,maintainer,write + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs'); + await main(); + + safe_outputs: + needs: agent + if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (needs.agent.outputs.detection_success == 'true') + runs-on: ubuntu-slim + permissions: + contents: read + discussions: write + issues: write + pull-requests: write + timeout-minutes: 15 + env: + GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/copilot-evaluate-tests" + GH_AW_ENGINE_ID: "copilot" + GH_AW_ENGINE_MODEL: "claude-sonnet-4.6" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🧪 *Test evaluation by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔬 Evaluating tests on this PR… [{workflow_name}]({run_url})\",\"runSuccess\":\"✅ Test evaluation complete! [{workflow_name}]({run_url})\",\"runFailure\":\"❌ Test evaluation failed. [{workflow_name}]({run_url}) {status}\"}" + GH_AW_WORKFLOW_ID: "copilot-evaluate-tests" + GH_AW_WORKFLOW_NAME: "Evaluate PR Tests" + outputs: + code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} + code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} + comment_id: ${{ steps.process_safe_outputs.outputs.comment_id }} + comment_url: ${{ steps.process_safe_outputs.outputs.comment_url }} + create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} + create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} + process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} + process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} + steps: + - name: Setup Scripts + uses: github/gh-aw-actions/setup@20045bbd5ad2632b9809856c389708eab1bd16ef # v0.62.2 + with: + destination: ${{ runner.temp }}/gh-aw/actions + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_ENV" + - name: Configure GH_HOST for enterprise compatibility + shell: bash + run: | + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - name: Process Safe Outputs + id: process_safe_outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1,\"target\":\"*\"},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"}}" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); + await main(); + - name: Upload safe output items + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: safe-output-items + path: /tmp/gh-aw/safe-output-items.jsonl + if-no-files-found: ignore + diff --git a/.github/workflows/copilot-evaluate-tests.md b/.github/workflows/copilot-evaluate-tests.md new file mode 100644 index 000000000000..c84f83f8855e --- /dev/null +++ b/.github/workflows/copilot-evaluate-tests.md @@ -0,0 +1,139 @@ +--- +description: Evaluates test quality, coverage, and appropriateness on PRs that add or modify tests +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + paths: + - 'src/**/tests/**' + - 'src/**/test/**' + issue_comment: + types: [created] + workflow_dispatch: + inputs: + pr_number: + description: 'PR number to evaluate' + required: true + type: number + +if: >- + (github.event_name == 'pull_request' && github.event.pull_request.draft == false) || + github.event_name == 'workflow_dispatch' || + (github.event_name == 'issue_comment' && + github.event.issue.pull_request && + startsWith(github.event.comment.body, '/evaluate-tests')) + +permissions: + contents: read + issues: read + pull-requests: read + +engine: + id: copilot + model: claude-sonnet-4.6 + +safe-outputs: + add-comment: + max: 1 + target: "*" + noop: + messages: + footer: "> 🧪 *Test evaluation by [{workflow_name}]({run_url})*" + run-started: "🔬 Evaluating tests on this PR… [{workflow_name}]({run_url})" + run-success: "✅ Test evaluation complete! [{workflow_name}]({run_url})" + run-failure: "❌ Test evaluation failed. [{workflow_name}]({run_url}) {status}" + +tools: + github: + toolsets: [default] + +network: defaults + +concurrency: + group: "evaluate-pr-tests-${{ github.event.pull_request.number || github.event.issue.number || inputs.pr_number || github.run_id }}" + cancel-in-progress: true + +timeout-minutes: 15 + +steps: + - name: Gate — skip if no test source files in diff + if: github.event_name == 'pull_request' + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + TEST_FILES=$(gh pr diff "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --name-only \ + | grep -E '\.(cs|xaml)$' \ + | grep -iE '(tests?/|TestCases|UnitTests|DeviceTests)' \ + || true) + if [ -z "$TEST_FILES" ]; then + echo "⏭️ No test source files (.cs/.xaml) found in PR diff. Skipping evaluation." + exit 1 + fi + echo "✅ Found test files to evaluate:" + echo "$TEST_FILES" | head -20 + + - name: Checkout PR and restore agent infrastructure + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number || inputs.pr_number }} + run: pwsh .github/scripts/Checkout-GhAwPr.ps1 +--- + +# Evaluate PR Tests + +Invoke the **evaluate-pr-tests** skill: read and follow `.github/skills/evaluate-pr-tests/SKILL.md`. + +## Context + +- **Repository**: ${{ github.repository }} +- **PR Number**: ${{ github.event.pull_request.number || github.event.issue.number || inputs.pr_number }} + +The PR branch has been checked out for you. All files from the PR are available locally. + +## Pre-flight check + +Before starting, verify the skill file exists: + +```bash +test -f .github/skills/evaluate-pr-tests/SKILL.md +``` + +If the file is **missing**, the fork PR branch is likely not rebased on the latest `main`. Post a comment using `add_comment`: + +```markdown +## 🧪 PR Test Evaluation + +❌ **Cannot evaluate**: this PR's branch does not include the evaluate-pr-tests skill (`.github/skills/evaluate-pr-tests/SKILL.md` is missing). + +**Fix**: rebase your fork on the latest `main` branch, or use the **workflow_dispatch** trigger (Actions tab → "Evaluate PR Tests" → "Run workflow" → enter PR number) which handles this automatically. +``` + +Then stop — do not proceed with the evaluation. + +## Running the skill + +1. Use `gh pr view ` to fetch PR metadata (title, body, labels, base branch). If `gh` CLI is unavailable, use the GitHub MCP tools instead. +2. Run `pwsh .github/skills/evaluate-pr-tests/scripts/Gather-TestContext.ps1` to gather automated context +3. Read the context report and the actual changed files, then evaluate per SKILL.md criteria +4. Post results using `add_comment` with `item_number` set to the PR number + +## Posting Results + +Call `add_comment` with `item_number` set to the PR number. Wrap the report in a collapsible `
` block: + +```markdown +## 🧪 PR Test Evaluation + +**Overall Verdict:** [✅ Tests are adequate | ⚠️ Tests need improvement | ❌ Tests are insufficient] + +[1-2 sentence summary] + +> 👍 / 👎 — Was this evaluation helpful? React to let us know! + +
+📊 Expand Full Evaluation + +[Full report from SKILL.md] + +
+``` From 720a9d4a935413c89e0748198549cf92694ee051 Mon Sep 17 00:00:00 2001 From: Shane Neuville <5375137+PureWeen@users.noreply.github.com> Date: Wed, 25 Mar 2026 16:05:33 -0500 Subject: [PATCH 2/9] Allow fork PRs to auto-trigger evaluate-pr-tests workflow (#34655) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Enables the copilot-evaluate-tests gh-aw workflow to run on fork PRs by adding `forks: ["*"]` to the `pull_request` trigger and removing the fork guard from `Checkout-GhAwPr.ps1`. ## Changes 1. **copilot-evaluate-tests.md**: Added `forks: ["*"]` to opt out of gh-aw auto-injected fork activation guard. Scoped `Checkout-GhAwPr.ps1` step to `workflow_dispatch` only (redundant for other triggers since platform handles checkout). 2. **copilot-evaluate-tests.lock.yml**: Recompiled via `gh aw compile` — fork guard removed from activation `if:` conditions. 3. **Checkout-GhAwPr.ps1**: Removed the `isCrossRepository` fork guard. Updated header docs and restore comments to accurately describe behavior for all trigger×fork combinations (including corrected step ordering). 4. **gh-aw-workflows.instructions.md**: Updated all stale references to the removed fork guard. Documented `forks: ["*"]` opt-in, clarified residual risk model for fork PRs, and updated troubleshooting table. ## Security Model Fork PRs are safe because: - Agent runs in **sandboxed container** with all credentials scrubbed - Output limited to **1 comment** via `safe-outputs: add-comment: max: 1` - Agent **prompt comes from base branch** (`runtime-import`) — forks cannot alter instructions - Pre-flight check catches missing `SKILL.md` if fork isn't rebased on `main` - No workspace code is executed with `GITHUB_TOKEN` (checkout without execution) ## Testing - ✅ `workflow_dispatch` tested against fork PR #34621 - ✅ Lock.yml statically verified — fork guard removed from `if:` conditions - ⏳ `pull_request` trigger on fork PRs can only be verified post-merge (GitHub Actions reads lock.yml from default branch) --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../gh-aw-workflows.instructions.md | 35 +++++++++-------- .github/scripts/Checkout-GhAwPr.ps1 | 39 +++++-------------- .../workflows/copilot-evaluate-tests.lock.yml | 15 ++++--- .github/workflows/copilot-evaluate-tests.md | 7 +++- 4 files changed, 44 insertions(+), 52 deletions(-) diff --git a/.github/instructions/gh-aw-workflows.instructions.md b/.github/instructions/gh-aw-workflows.instructions.md index 30d02675e639..c8cd70ecc3be 100644 --- a/.github/instructions/gh-aw-workflows.instructions.md +++ b/.github/instructions/gh-aw-workflows.instructions.md @@ -44,7 +44,9 @@ The prompt is built in the **activation job** via `{{#runtime-import .github/wor ### Fork PR Activation Gate -`gh aw compile` automatically injects a fork guard into the activation job's `if:` condition: `head.repo.id == repository_id`. This blocks fork PRs on `pull_request` events. This is **platform behavior** — do not add it manually. +By default, `gh aw compile` automatically injects a fork guard into the activation job's `if:` condition: `head.repo.id == repository_id`. This blocks fork PRs on `pull_request` events. + +To **allow fork PRs**, add `forks: ["*"]` to the `pull_request` trigger in the `.md` frontmatter. The compiler removes the auto-injected guard from the compiled `if:` conditions. This is safe when the workflow uses the `Checkout-GhAwPr.ps1` pattern (checkout + trusted-infra restore) and the agent is sandboxed. ## Fork PR Handling @@ -58,16 +60,17 @@ Reference: https://securitylab.github.com/resources/github-actions-preventing-pw | Trigger | `checkout_pr_branch.cjs` runs? | Fork handling | |---------|-------------------------------|---------------| -| `pull_request` | ✅ Yes | Blocked by auto-generated activation gate | +| `pull_request` (default) | ✅ Yes | Blocked by auto-generated activation gate unless `forks: ["*"]` is set | +| `pull_request` + `forks: ["*"]` | ✅ Yes | ✅ Works — user steps restore trusted infra before agent runs | | `workflow_dispatch` | ❌ Skipped | ✅ Works — user steps handle checkout and restore is final | | `issue_comment` (same-repo) | ✅ Yes | ✅ Works — files already on PR branch | -| `issue_comment` (fork) | N/A | ❌ Blocked by fail-closed fork guard in `Checkout-GhAwPr.ps1` | +| `issue_comment` (fork) | ✅ Yes | ⚠️ Works — `checkout_pr_branch.cjs` re-checks out fork branch after user steps, potentially overwriting restored infra. Acceptable because agent is sandboxed (no credentials, max 1 comment via safe-outputs). Pre-flight check catches missing `SKILL.md` if fork isn't rebased. | ### The `issue_comment` + Fork Problem -For `/slash-command` triggers on fork PRs, `checkout_pr_branch.cjs` runs AFTER all user steps and re-checks out the fork branch. This overwrites any files restored by user steps (e.g., `.github/skills/`). There is no way to run user steps after platform steps. A fork could include a crafted `SKILL.md` that alters the agent's evaluation behavior. +For `/slash-command` triggers on fork PRs, `checkout_pr_branch.cjs` runs AFTER all user steps and re-checks out the fork branch. This overwrites any files restored by user steps (e.g., `.github/skills/`). A fork could include a crafted `SKILL.md` that alters the agent's evaluation behavior. -**Current approach (fail-closed fork guard):** `Checkout-GhAwPr.ps1` checks `isCrossRepository` via `gh pr view` for `issue_comment` triggers. If the PR is from a fork or the API call fails, the script exits with code 1. Fork PRs should use `workflow_dispatch` instead, where `checkout_pr_branch.cjs` is skipped and the user step restore is the final workspace state. +**Accepted residual risk:** The agent runs in a sandboxed container with all credentials scrubbed. The worst outcome is a manipulated evaluation comment (`safe-outputs: add-comment: max: 1`). The agent has no ability to push code, access secrets, or exfiltrate data. The pre-flight check in the agent prompt catches the case where `SKILL.md` is missing entirely (fork not rebased on `main`). **Upstream issue:** [github/gh-aw#18481](https://github.com/github/gh-aw/issues/18481) — "Using gh-aw in forks of repositories" @@ -86,16 +89,16 @@ steps: The script: 1. Captures the base branch SHA before checkout -2. **Fork guard** (`issue_comment` only): checks `isCrossRepository` — exits 1 if fork or API failure -3. Checks out the PR branch via `gh pr checkout` -4. Deletes `.github/skills/` and `.github/instructions/` (prevents fork-added files) -5. Restores them from the base branch SHA (best-effort, non-fatal) +2. Checks out the PR branch via `gh pr checkout` +3. Deletes `.github/skills/` and `.github/instructions/` (prevents fork-added files) +4. Restores them from the base branch SHA (best-effort, non-fatal) **Behavior by trigger:** -- **`workflow_dispatch`**: Fork guard skipped. Platform checkout is skipped, so the restore IS the final workspace state (trusted files from base branch) -- **`issue_comment`** (same-repo): Fork guard passes. Platform re-checks out PR branch — files already match, effectively a no-op -- **`issue_comment`** (fork): Fork guard rejects — exits 1 with actionable notice to use `workflow_dispatch` -- **`pull_request`** (same-repo): Fork guard skipped. Files already exist, restore is a no-op +- **`workflow_dispatch`**: Platform checkout is skipped, so the restore IS the final workspace state (trusted files from base branch) +- **`pull_request`** (same-repo): User step restores trusted infra. `checkout_pr_branch.cjs` runs after and re-checks out PR branch — for same-repo PRs, skill files typically match main unless the PR modified them. +- **`pull_request`** (fork with `forks: ["*"]`): Same as above, but fork's skill files may differ. Same residual risk as `issue_comment` fork case — agent is sandboxed, pre-flight catches missing `SKILL.md`. +- **`issue_comment`** (same-repo): Platform re-checks out PR branch — files already match, effectively a no-op +- **`issue_comment`** (fork): Platform re-checks out fork branch after us, overwriting restored files. Agent is sandboxed; pre-flight in the prompt catches missing `SKILL.md` ### Anti-Patterns @@ -197,7 +200,7 @@ Manual triggers (`workflow_dispatch`, `issue_comment`) should bypass the gate. N | What | Behavior | Workaround | |------|----------|------------| -| User steps always before platform steps | Cannot run user code after `checkout_pr_branch.cjs` | Use `workflow_dispatch` for fork PRs; see [gh-aw#18481](https://github.com/github/gh-aw/issues/18481) | +| User steps always before platform steps | Cannot run user code after `checkout_pr_branch.cjs` | For `issue_comment` fork PRs, accept sandboxed residual risk; see [gh-aw#18481](https://github.com/github/gh-aw/issues/18481) | | `--allow-all-tools` in lock.yml | Emitted by `gh aw compile` | Cannot override from `.md` source | | MCP integrity filtering | Fork PRs blocked as "unapproved" | Use `steps:` checkout instead of MCP | | `gh` CLI inside agent | Credentials scrubbed | Use `steps:` for API calls, or MCP tools | @@ -215,8 +218,8 @@ Manual triggers (`workflow_dispatch`, `issue_comment`) should bypass the gate. N | Symptom | Cause | Fix | |---------|-------|-----| | Agent evaluates wrong PR | `workflow_dispatch` checks out workflow branch | Add `gh pr checkout` in `steps:` | -| Agent can't find SKILL.md | Fork PR branch doesn't have `.github/skills/` | Agent posts "rebase or use `workflow_dispatch`" message; or rebase fork on `main` | -| Fork PR rejected on `/evaluate-tests` | Fail-closed fork guard in `Checkout-GhAwPr.ps1` | Use `workflow_dispatch` with `pr_number` input instead | +| Agent can't find SKILL.md | Fork PR branch doesn't include `.github/skills/` | Rebase fork on `main`, or use `workflow_dispatch` with `pr_number` input | +| Fork PR skipped on `pull_request` | `forks: ["*"]` not in workflow frontmatter | Add `forks: ["*"]` under `pull_request:` in the `.md` source and recompile | | `gh` commands fail in agent | Credentials scrubbed inside container | Move to `steps:` section | | Lock file out of date | Forgot to recompile | Run `gh aw compile` | | Integrity filtering warning | MCP reading fork PR data | Expected, non-blocking | diff --git a/.github/scripts/Checkout-GhAwPr.ps1 b/.github/scripts/Checkout-GhAwPr.ps1 index 4a6380a50421..a2f9533bb7d2 100644 --- a/.github/scripts/Checkout-GhAwPr.ps1 +++ b/.github/scripts/Checkout-GhAwPr.ps1 @@ -4,10 +4,13 @@ .DESCRIPTION Checks out a PR branch and restores trusted agent infrastructure (skills, - instructions) from the base branch. For issue_comment triggers, fork PRs - are rejected (fail-closed) because the platform's checkout_pr_branch.cjs - overwrites restored files after user steps. Fork PRs should use - workflow_dispatch instead. + instructions) from the base branch. Works for both same-repo and fork PRs. + + This script is only invoked for workflow_dispatch triggers. For pull_request + and issue_comment, the gh-aw platform's checkout_pr_branch.cjs handles PR + checkout automatically (it runs as a platform step after all user steps). + workflow_dispatch skips the platform checkout entirely, so this script is + the only thing that gets the PR code onto disk. SECURITY NOTE: This script checks out PR code onto disk. This is safe because NO subsequent user steps execute workspace code — the gh-aw @@ -26,7 +29,6 @@ PR_NUMBER - PR number to check out GITHUB_REPOSITORY - owner/repo (set by GitHub Actions) GITHUB_ENV - path to env file (set by GitHub Actions) - GITHUB_EVENT_NAME - trigger type (set by GitHub Actions) #> $ErrorActionPreference = 'Stop' @@ -40,25 +42,6 @@ if (-not $env:PR_NUMBER -or $env:PR_NUMBER -eq '0') { $PrNumber = $env:PR_NUMBER -# ── Fork guard (issue_comment only) ───────────────────────────────────────── -# For issue_comment triggers, platform's checkout_pr_branch.cjs runs AFTER user -# steps and re-checks out the fork branch, overwriting any restored skill/instruction -# files. A fork could include a crafted SKILL.md that alters agent behavior. -# Fail closed: if we can't verify origin, exit 1 (not 0). -# Fork PRs can still be evaluated via workflow_dispatch (where platform checkout is skipped). - -if ($env:GITHUB_EVENT_NAME -eq 'issue_comment') { - $isFork = gh pr view $PrNumber --repo $env:GITHUB_REPOSITORY --json isCrossRepository --jq '.isCrossRepository' 2>&1 - if ($LASTEXITCODE -ne 0) { - Write-Host "❌ Could not verify PR origin — failing closed" - exit 1 - } - if ($isFork -eq 'true') { - Write-Host "::notice::Fork PR detected — /evaluate-tests via issue_comment is not supported for fork PRs. Use workflow_dispatch with pr_number=$PrNumber instead." - exit 1 - } -} - # ── Save base branch SHA ───────────────────────────────────────────────────── # Must be captured BEFORE checkout replaces HEAD. # Exported for potential use by downstream platform steps (e.g., checkout_pr_branch.cjs) @@ -82,11 +65,9 @@ Write-Host "✅ Checked out PR #$PrNumber" git log --oneline -1 # ── Restore agent infrastructure from base branch ──────────────────────────── -# Best-effort restore of skill/instruction files from the base branch. -# - workflow_dispatch: platform checkout is skipped, so this IS the final state -# - issue_comment (same-repo): platform's checkout_pr_branch.cjs runs after and -# overwrites, but files already match (same repo). Fork PRs are blocked above. -# - pull_request (same-repo): files already exist, this is a no-op +# This script only runs for workflow_dispatch (other triggers use the platform's +# checkout_pr_branch.cjs instead). For workflow_dispatch the platform checkout is +# skipped, so this restore IS the final workspace state. # rm -rf first to prevent fork-added files from surviving the restore. if (Test-Path '.github/skills/') { Remove-Item -Recurse -Force '.github/skills/' } diff --git a/.github/workflows/copilot-evaluate-tests.lock.yml b/.github/workflows/copilot-evaluate-tests.lock.yml index c1e75132b153..5b5dc7f9b37d 100644 --- a/.github/workflows/copilot-evaluate-tests.lock.yml +++ b/.github/workflows/copilot-evaluate-tests.lock.yml @@ -22,7 +22,7 @@ # # Evaluates test quality, coverage, and appropriateness on PRs that add or modify tests # -# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"9a0e480927d61956be62f85669d2e599525442694d280126849fe87e727c31df","compiler_version":"v0.62.2","strict":true} +# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"d671028235c1b911c7a816a257b07b02793a6b57747b4358f792af183e26ca07","compiler_version":"v0.62.2","strict":true} name: "Evaluate PR Tests" "on": @@ -30,6 +30,8 @@ name: "Evaluate PR Tests" types: - created pull_request: + # forks: # Fork filtering applied via job conditions + # - "*" # Fork filtering applied via job conditions paths: - src/**/tests/** - src/**/test/** @@ -57,9 +59,9 @@ jobs: activation: needs: pre_activation if: > - (needs.pre_activation.outputs.activated == 'true') && (((github.event_name == 'pull_request' && github.event.pull_request.draft == false) || github.event_name == 'workflow_dispatch' || (github.event_name == 'issue_comment' && + (needs.pre_activation.outputs.activated == 'true') && ((github.event_name == 'pull_request' && github.event.pull_request.draft == false) || github.event_name == 'workflow_dispatch' || (github.event_name == 'issue_comment' && github.event.issue.pull_request && - startsWith(github.event.comment.body, '/evaluate-tests'))) && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.id == github.repository_id))) + startsWith(github.event.comment.body, '/evaluate-tests'))) runs-on: ubuntu-slim permissions: contents: read @@ -324,7 +326,8 @@ jobs: run: "TEST_FILES=$(gh pr diff \"$PR_NUMBER\" --repo \"$GITHUB_REPOSITORY\" --name-only \\\n | grep -E '\\.(cs|xaml)$' \\\n | grep -iE '(tests?/|TestCases|UnitTests|DeviceTests)' \\\n || true)\nif [ -z \"$TEST_FILES\" ]; then\n echo \"⏭️ No test source files (.cs/.xaml) found in PR diff. Skipping evaluation.\"\n exit 1\nfi\necho \"✅ Found test files to evaluate:\"\necho \"$TEST_FILES\" | head -20\n" - env: GH_TOKEN: ${{ github.token }} - PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number || inputs.pr_number }} + PR_NUMBER: ${{ inputs.pr_number }} + if: github.event_name == 'workflow_dispatch' name: Checkout PR and restore agent infrastructure run: pwsh .github/scripts/Checkout-GhAwPr.ps1 @@ -986,9 +989,9 @@ jobs: pre_activation: if: > - ((github.event_name == 'pull_request' && github.event.pull_request.draft == false) || github.event_name == 'workflow_dispatch' || (github.event_name == 'issue_comment' && + (github.event_name == 'pull_request' && github.event.pull_request.draft == false) || github.event_name == 'workflow_dispatch' || (github.event_name == 'issue_comment' && github.event.issue.pull_request && - startsWith(github.event.comment.body, '/evaluate-tests'))) && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.id == github.repository_id)) + startsWith(github.event.comment.body, '/evaluate-tests')) runs-on: ubuntu-slim outputs: activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} diff --git a/.github/workflows/copilot-evaluate-tests.md b/.github/workflows/copilot-evaluate-tests.md index c84f83f8855e..854c2ec407d8 100644 --- a/.github/workflows/copilot-evaluate-tests.md +++ b/.github/workflows/copilot-evaluate-tests.md @@ -3,6 +3,7 @@ description: Evaluates test quality, coverage, and appropriateness on PRs that a on: pull_request: types: [opened, synchronize, reopened, ready_for_review] + forks: ["*"] paths: - 'src/**/tests/**' - 'src/**/test/**' @@ -72,10 +73,14 @@ steps: echo "✅ Found test files to evaluate:" echo "$TEST_FILES" | head -20 + # Only needed for workflow_dispatch — for pull_request and issue_comment, + # the gh-aw platform's checkout_pr_branch.cjs handles PR checkout automatically. + # workflow_dispatch skips the platform checkout entirely, so we must do it here. - name: Checkout PR and restore agent infrastructure + if: github.event_name == 'workflow_dispatch' env: GH_TOKEN: ${{ github.token }} - PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number || inputs.pr_number }} + PR_NUMBER: ${{ inputs.pr_number }} run: pwsh .github/scripts/Checkout-GhAwPr.ps1 --- From e18dcfecc63588a3efc7f7a75bfc49e81b45a382 Mon Sep 17 00:00:00 2001 From: BagavathiPerumal Date: Fri, 16 May 2025 13:53:50 +0530 Subject: [PATCH 3/9] fix-8716-Sync SearchView's SearchHandler with current page for consistent search behavior during navigation. --- .../Handlers/Shell/Android/ShellToolbarTracker.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellToolbarTracker.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellToolbarTracker.cs index feda1cdbb481..1a7deb2788c3 100644 --- a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellToolbarTracker.cs +++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellToolbarTracker.cs @@ -662,6 +662,11 @@ protected virtual void UpdateToolbarItems(AToolbar toolbar, Page page) _searchView.View.LayoutParameters = new LP(LP.MatchParent, LP.MatchParent); _searchView.SearchConfirmed += OnSearchConfirmed; } + else if (_searchView.SearchHandler != SearchHandler) + { + _searchView.SearchHandler = SearchHandler; + _searchView.LoadView(); + } if (SearchHandler.SearchBoxVisibility == SearchBoxVisibility.Collapsible) { From 439554efe1dcaf791c468d53179cd62f63f3794d Mon Sep 17 00:00:00 2001 From: BagavathiPerumal Date: Fri, 16 May 2025 14:10:44 +0530 Subject: [PATCH 4/9] fix-8716-Testcases Added. --- .../TestCases.HostApp/Issues/Issue8716.cs | 356 ++++++++++++++++++ .../Tests/Issues/Issue8716.cs | 24 ++ 2 files changed, 380 insertions(+) create mode 100644 src/Controls/tests/TestCases.HostApp/Issues/Issue8716.cs create mode 100644 src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue8716.cs diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue8716.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue8716.cs new file mode 100644 index 000000000000..16b522dbc779 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue8716.cs @@ -0,0 +1,356 @@ +namespace Maui.Controls.Sample.Issues; + +[Issue(IssueTracker.Github, 8716, "[Shell][Android] The truth is out there...but not on top tab search handlers", PlatformAffected.Android)] +public class Issue8716 : Shell +{ + public Issue8716() + { + this.FlyoutBehavior = FlyoutBehavior.Flyout; + this.FlyoutBackgroundImageAspect = Aspect.AspectFill; + this.FlyoutHeaderBehavior = FlyoutHeaderBehavior.CollapseOnScroll; + + var flyoutItem = new FlyoutItem + { + Route = "animals", + FlyoutDisplayOptions = FlyoutDisplayOptions.AsMultipleItems + }; + + var domesticTab = new Tab + { + Title = "Domestic", + Route = "domestic" + }; + + domesticTab.Items.Add(new ShellContent + { + Title = "CatsPage", + Route = "cats", + ContentTemplate = new DataTemplate(typeof(_8716CatsPage)) + }); + + domesticTab.Items.Add(new ShellContent + { + Title = "DogsPage", + Route = "dogs", + ContentTemplate = new DataTemplate(typeof(_8716DogsPage)) + }); + + flyoutItem.Items.Add(domesticTab); + + Items.Add(flyoutItem); + } + + public class _8716Animal + { + public string Name { get; set; } + public string Location { get; set; } + public string Details { get; set; } + } + + public class _8716CatsPage : ContentPage + { + _8716AnimalSearchHandler searchHandler; + public _8716CatsPage() + { + Title = "CatsPage"; + + searchHandler = new _8716AnimalSearchHandler + { + Placeholder = "Enter Cat name", + CancelButtonColor = Colors.Green, + ShowsResults = true, + Animals= _8716CatData.Cats, + ItemTemplate = new DataTemplate(() => + { + var grid = new Grid + { + Padding = 10 + }; + + var nameLabel = new Label + { + FontAttributes = FontAttributes.Bold, + VerticalOptions = LayoutOptions.Center + }; + nameLabel.SetBinding(Label.TextProperty, "Name"); + + grid.Children.Add(nameLabel); + + return grid; + }) + }; + + Shell.SetSearchHandler(this, searchHandler); + + var collectionView = new CollectionView + { + AutomationId = "MainPageCollectionView", + Margin = new Thickness(20), + ItemsSource = _8716CatData.Cats, + ItemTemplate = new DataTemplate(() => + { + var grid = new Grid + { + Padding = 10, + RowDefinitions = new RowDefinitionCollection + { + new RowDefinition { Height = GridLength.Auto }, + new RowDefinition { Height = GridLength.Auto } + } + }; + + var nameLabel = new Label { FontAttributes = FontAttributes.Bold }; + nameLabel.SetBinding(Label.TextProperty, "Name"); + + var locationLabel = new Label + { + FontAttributes = FontAttributes.Italic, + VerticalOptions = LayoutOptions.End + }; + locationLabel.SetBinding(Label.TextProperty, "Location"); + + grid.Children.Add(nameLabel); + Grid.SetRow(nameLabel, 0); + + grid.Children.Add(locationLabel); + Grid.SetRow(locationLabel, 1); + + return grid; + }), + SelectionMode = SelectionMode.Single + }; + + var button = new Button + { + Text = "Button", + AutomationId = "MainPageButton" + }; + + var layout = new StackLayout + { + Children = { button, collectionView } + }; + + Content = layout; + } + } + + public class _8716DogsPage : ContentPage + { + _8716AnimalSearchHandler searchHandler; + public _8716DogsPage() + { + Title = "DogsPage"; + + searchHandler = new _8716AnimalSearchHandler + { + Placeholder = "Enter Dog name", + ShowsResults = true, + AutomationId = "AnimalSearchHandler", + Animals = _8716DogData.Dogs, + ItemTemplate = new DataTemplate(() => + { + var grid = new Grid + { + Padding = 10 + }; + + var nameLabel = new Label + { + FontAttributes = FontAttributes.Bold, + VerticalOptions = LayoutOptions.Center + }; + nameLabel.SetBinding(Label.TextProperty, "Name"); + + grid.Children.Add(nameLabel); + + return grid; + }) + }; + + Shell.SetSearchHandler(this, searchHandler); + + var collectionView = new CollectionView + { + Margin = new Thickness(20), + ItemsSource = _8716DogData.Dogs, + ItemTemplate = new DataTemplate(() => + { + var grid = new Grid + { + Padding = 10, + RowDefinitions = new RowDefinitionCollection + { + new RowDefinition { Height = GridLength.Auto }, + new RowDefinition { Height = GridLength.Auto } + } + }; + + var nameLabel = new Label { FontAttributes = FontAttributes.Bold }; + nameLabel.SetBinding(Label.TextProperty, "Name"); + + var locationLabel = new Label + { + FontAttributes = FontAttributes.Italic, + VerticalOptions = LayoutOptions.End + }; + locationLabel.SetBinding(Label.TextProperty, "Location"); + + grid.Children.Add(nameLabel); + Grid.SetRow(nameLabel, 0); + + grid.Children.Add(locationLabel); + Grid.SetRow(locationLabel, 1); + + return grid; + }), + SelectionMode = SelectionMode.Single + }; + + var layout = new StackLayout + { + Children = { collectionView } + }; + + Content = layout; + } + } + + public static class _8716DogData + { + public static IList<_8716Animal> Dogs { get; set; } + + static _8716DogData() + { + Dogs = new List<_8716Animal>(); + + Dogs.Add(new _8716Animal + { + Name = "Afghan Hound", + Location = "Afghanistan", + Details = "The Afghan Hound is a hound that is distinguished by its thick, fine, silky coat and its tail with a ring curl at the end. The breed is selectively bred for its unique features in the cold mountains of Afghanistan. Other names for this breed are Kuchi Hound, Tāzī, Balkh Hound, Baluchi Hound, Barakzai Hound, Shalgar Hound, Kabul Hound, Galanday Hound or sometimes incorrectly African Hound.", + }); + + Dogs.Add(new _8716Animal + { + Name = "Alpine Dachsbracke", + Location = "Austria", + Details = "The Alpine Dachsbracke is a small breed of dog of the scent hound type originating in Austria. The Alpine Dachsbracke was bred to track wounded deer as well as boar, hare, and fox. It is highly efficient at following a trail even after it has gone cold. The Alpine Dachsbracke is very sturdy, and Austria is said to be the country of origin.", + }); + } + } + + public static class _8716CatData + { + public static IList<_8716Animal> Cats { get; set; } + + static _8716CatData() + { + Cats = new List<_8716Animal>(); + + Cats.Add(new _8716Animal + { + Name = "Abyssinian", + Location = "Ethiopia", + Details = "The Abyssinian is a breed of domestic short-haired cat." + }); + + Cats.Add(new _8716Animal + { + Name = "Arabian Mau", + Location = "Arabian Peninsula", + Details = "The Arabian Mau is a formal breed of domestic cat." + }); + } + } + + public class _8716AnimalSearchHandler : SearchHandler + { + public IList<_8716Animal> Animals { get; set; } + + protected override void OnQueryChanged(string oldValue, string newValue) + { + base.OnQueryChanged(oldValue, newValue); + + if (string.IsNullOrWhiteSpace(newValue)) + { + ItemsSource = null; + } + else + { + var filteredAnimals = new List<_8716Animal>(); + if (Animals != null) + { + foreach (var animal in Animals) + { + if (animal.Name != null && animal.Name.IndexOf(newValue, StringComparison.CurrentCultureIgnoreCase) >= 0) + { + filteredAnimals.Add(animal); + } + } + } + ItemsSource = filteredAnimals; + } + } + } + + [QueryProperty(nameof(Name), "name")] + public class _8716CatDetailsPage : ContentPage + { + public string Name + { + set + { + LoadAnimal(value); + } + } + + public _8716CatDetailsPage() + { + var scrollView = new ScrollView(); + var stackLayout = new StackLayout { Margin = new Thickness(20) }; + + var nameLabel = new Label + { + HorizontalOptions = LayoutOptions.Center, + }; + nameLabel.SetBinding(Label.TextProperty, "Name"); + + var locationLabel = new Label + { + FontAttributes = FontAttributes.Italic, + HorizontalOptions = LayoutOptions.Center + }; + locationLabel.SetBinding(Label.TextProperty, "Location"); + + var detailsLabel = new Label + { + HorizontalOptions = LayoutOptions.Center, + LineBreakMode = LineBreakMode.WordWrap, + VerticalOptions = LayoutOptions.Start + }; + detailsLabel.SetBinding(Label.TextProperty, "Details"); + + stackLayout.Children.Add(nameLabel); + stackLayout.Children.Add(locationLabel); + stackLayout.Children.Add(detailsLabel); + + scrollView.Content = stackLayout; + Content = scrollView; + } + + void LoadAnimal(string name) + { + foreach (var animal in _8716CatData.Cats) + { + if (animal.Name == name) + { + BindingContext = animal; + return; + } + } + + throw new Exception("Animal not found."); + } + } +} \ No newline at end of file diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue8716.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue8716.cs new file mode 100644 index 000000000000..0c1f6b22666c --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue8716.cs @@ -0,0 +1,24 @@ +#if TEST_FAILS_ON_WINDOWS // A fix for this issue is already available for Windows platform in an open PR (https://github.com/dotnet/maui/pull/29441), so the test is restricted on Windows for now. +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues; + +public class Issue8716 : _IssuesUITest +{ + public Issue8716(TestDevice testDevice) : base(testDevice) { } + + public override string Issue => "[Shell][Android] The truth is out there...but not on top tab search handlers"; + + [Test] + [Category(UITestCategories.Shell)] + public void ShouldUpdateSearchViewOnPageNavigation() + { + App.WaitForElement("MainPageButton"); + App.TapTab("DogsPage"); + + VerifyScreenshot(); + } +} +#endif \ No newline at end of file From ffdee8719c98120b17f287495f792da30e8c649a Mon Sep 17 00:00:00 2001 From: BagavathiPerumal Date: Fri, 16 May 2025 15:45:41 +0530 Subject: [PATCH 5/9] fix-8716-Updated testcase and added snapshot for Android and iOS. --- ...ShouldUpdateSearchViewOnPageNavigation.png | Bin 0 -> 44247 bytes .../TestCases.HostApp/Issues/Issue8716.cs | 496 ++++++------------ ...ShouldUpdateSearchViewOnPageNavigation.png | Bin 0 -> 53110 bytes 3 files changed, 165 insertions(+), 331 deletions(-) create mode 100644 src/Controls/tests/TestCases.Android.Tests/snapshots/android/ShouldUpdateSearchViewOnPageNavigation.png create mode 100644 src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ShouldUpdateSearchViewOnPageNavigation.png diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/ShouldUpdateSearchViewOnPageNavigation.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/ShouldUpdateSearchViewOnPageNavigation.png new file mode 100644 index 0000000000000000000000000000000000000000..30ad0dc524acc3fc34579eebe43fcedf735ba99b GIT binary patch literal 44247 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 literal 0 HcmV?d00001 diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue8716.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue8716.cs index 16b522dbc779..f68605f0e8e7 100644 --- a/src/Controls/tests/TestCases.HostApp/Issues/Issue8716.cs +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue8716.cs @@ -3,354 +3,188 @@ [Issue(IssueTracker.Github, 8716, "[Shell][Android] The truth is out there...but not on top tab search handlers", PlatformAffected.Android)] public class Issue8716 : Shell { - public Issue8716() - { - this.FlyoutBehavior = FlyoutBehavior.Flyout; - this.FlyoutBackgroundImageAspect = Aspect.AspectFill; - this.FlyoutHeaderBehavior = FlyoutHeaderBehavior.CollapseOnScroll; - - var flyoutItem = new FlyoutItem - { - Route = "animals", - FlyoutDisplayOptions = FlyoutDisplayOptions.AsMultipleItems - }; - - var domesticTab = new Tab - { - Title = "Domestic", - Route = "domestic" - }; - - domesticTab.Items.Add(new ShellContent - { - Title = "CatsPage", - Route = "cats", - ContentTemplate = new DataTemplate(typeof(_8716CatsPage)) - }); - - domesticTab.Items.Add(new ShellContent - { - Title = "DogsPage", - Route = "dogs", - ContentTemplate = new DataTemplate(typeof(_8716DogsPage)) - }); - - flyoutItem.Items.Add(domesticTab); - - Items.Add(flyoutItem); - } - - public class _8716Animal - { - public string Name { get; set; } - public string Location { get; set; } - public string Details { get; set; } - } - - public class _8716CatsPage : ContentPage - { - _8716AnimalSearchHandler searchHandler; - public _8716CatsPage() - { - Title = "CatsPage"; - - searchHandler = new _8716AnimalSearchHandler - { - Placeholder = "Enter Cat name", - CancelButtonColor = Colors.Green, - ShowsResults = true, - Animals= _8716CatData.Cats, - ItemTemplate = new DataTemplate(() => - { - var grid = new Grid - { - Padding = 10 - }; - - var nameLabel = new Label - { - FontAttributes = FontAttributes.Bold, - VerticalOptions = LayoutOptions.Center - }; - nameLabel.SetBinding(Label.TextProperty, "Name"); - - grid.Children.Add(nameLabel); - - return grid; - }) - }; - - Shell.SetSearchHandler(this, searchHandler); - - var collectionView = new CollectionView - { - AutomationId = "MainPageCollectionView", - Margin = new Thickness(20), - ItemsSource = _8716CatData.Cats, - ItemTemplate = new DataTemplate(() => - { - var grid = new Grid - { - Padding = 10, - RowDefinitions = new RowDefinitionCollection - { - new RowDefinition { Height = GridLength.Auto }, - new RowDefinition { Height = GridLength.Auto } - } - }; - - var nameLabel = new Label { FontAttributes = FontAttributes.Bold }; - nameLabel.SetBinding(Label.TextProperty, "Name"); - - var locationLabel = new Label - { - FontAttributes = FontAttributes.Italic, - VerticalOptions = LayoutOptions.End - }; - locationLabel.SetBinding(Label.TextProperty, "Location"); - - grid.Children.Add(nameLabel); - Grid.SetRow(nameLabel, 0); - - grid.Children.Add(locationLabel); - Grid.SetRow(locationLabel, 1); - - return grid; - }), - SelectionMode = SelectionMode.Single - }; - - var button = new Button - { - Text = "Button", - AutomationId = "MainPageButton" - }; - - var layout = new StackLayout - { - Children = { button, collectionView } - }; - - Content = layout; - } - } - - public class _8716DogsPage : ContentPage - { - _8716AnimalSearchHandler searchHandler; - public _8716DogsPage() - { - Title = "DogsPage"; - - searchHandler = new _8716AnimalSearchHandler - { - Placeholder = "Enter Dog name", - ShowsResults = true, - AutomationId = "AnimalSearchHandler", - Animals = _8716DogData.Dogs, - ItemTemplate = new DataTemplate(() => - { - var grid = new Grid + public Issue8716() + { + this.FlyoutBehavior = FlyoutBehavior.Flyout; + this.FlyoutBackgroundImageAspect = Aspect.AspectFill; + this.FlyoutHeaderBehavior = FlyoutHeaderBehavior.CollapseOnScroll; + + var flyoutItem = new FlyoutItem + { + Route = "animals", + FlyoutDisplayOptions = FlyoutDisplayOptions.AsMultipleItems + }; + + var domesticTab = new Tab + { + Title = "Domestic", + Route = "domestic" + }; + + domesticTab.Items.Add(new ShellContent + { + Title = "CatsPage", + Route = "cats", + ContentTemplate = new DataTemplate(typeof(_8716CatsPage)) + }); + + domesticTab.Items.Add(new ShellContent + { + Title = "DogsPage", + Route = "dogs", + ContentTemplate = new DataTemplate(typeof(_8716DogsPage)) + }); + + flyoutItem.Items.Add(domesticTab); + + Items.Add(flyoutItem); + } + + public class _8716CatsPage : ContentPage + { + _8716AnimalSearchHandler searchHandler; + public _8716CatsPage() + { + Title = "CatsPage"; + + searchHandler = new _8716AnimalSearchHandler + { + Placeholder = "Enter cat's name", + CancelButtonColor = Colors.Green, + ShowsResults = true, + Animals = new List() { "Abyssinian", "Arabian Mau" }, + ItemTemplate = new DataTemplate(() => + { + var grid = new Grid { Padding = 10 }; - var nameLabel = new Label - { - FontAttributes = FontAttributes.Bold, - VerticalOptions = LayoutOptions.Center - }; - nameLabel.SetBinding(Label.TextProperty, "Name"); - + var nameLabel = new Label + { + FontAttributes = FontAttributes.Bold, + VerticalOptions = LayoutOptions.Center + }; + nameLabel.SetBinding(Label.TextProperty, "."); grid.Children.Add(nameLabel); return grid; - }) - }; - - Shell.SetSearchHandler(this, searchHandler); - - var collectionView = new CollectionView - { - Margin = new Thickness(20), - ItemsSource = _8716DogData.Dogs, - ItemTemplate = new DataTemplate(() => - { - var grid = new Grid - { - Padding = 10, - RowDefinitions = new RowDefinitionCollection - { - new RowDefinition { Height = GridLength.Auto }, - new RowDefinition { Height = GridLength.Auto } - } - }; - - var nameLabel = new Label { FontAttributes = FontAttributes.Bold }; - nameLabel.SetBinding(Label.TextProperty, "Name"); - - var locationLabel = new Label - { - FontAttributes = FontAttributes.Italic, - VerticalOptions = LayoutOptions.End - }; - locationLabel.SetBinding(Label.TextProperty, "Location"); - - grid.Children.Add(nameLabel); - Grid.SetRow(nameLabel, 0); - - grid.Children.Add(locationLabel); - Grid.SetRow(locationLabel, 1); - - return grid; - }), - SelectionMode = SelectionMode.Single - }; - - var layout = new StackLayout - { - Children = { collectionView } - }; - - Content = layout; - } - } + }) + }; - public static class _8716DogData - { - public static IList<_8716Animal> Dogs { get; set; } + Shell.SetSearchHandler(this, searchHandler); - static _8716DogData() - { - Dogs = new List<_8716Animal>(); - - Dogs.Add(new _8716Animal + var collectionView = new CollectionView { - Name = "Afghan Hound", - Location = "Afghanistan", - Details = "The Afghan Hound is a hound that is distinguished by its thick, fine, silky coat and its tail with a ring curl at the end. The breed is selectively bred for its unique features in the cold mountains of Afghanistan. Other names for this breed are Kuchi Hound, Tāzī, Balkh Hound, Baluchi Hound, Barakzai Hound, Shalgar Hound, Kabul Hound, Galanday Hound or sometimes incorrectly African Hound.", - }); - - Dogs.Add(new _8716Animal + AutomationId = "MainPageCollectionView", + Margin = new Thickness(20), + ItemsSource = new List() { "Abyssinian", "Arabian Mau" }, + ItemTemplate = new DataTemplate(() => + { + var nameLabel = new Label { FontAttributes = FontAttributes.Bold }; + nameLabel.SetBinding(Label.TextProperty, "."); + return nameLabel; + }), + SelectionMode = SelectionMode.Single + }; + + var button = new Button { - Name = "Alpine Dachsbracke", - Location = "Austria", - Details = "The Alpine Dachsbracke is a small breed of dog of the scent hound type originating in Austria. The Alpine Dachsbracke was bred to track wounded deer as well as boar, hare, and fox. It is highly efficient at following a trail even after it has gone cold. The Alpine Dachsbracke is very sturdy, and Austria is said to be the country of origin.", - }); - } - } - - public static class _8716CatData - { - public static IList<_8716Animal> Cats { get; set; } - - static _8716CatData() - { - Cats = new List<_8716Animal>(); - - Cats.Add(new _8716Animal - { - Name = "Abyssinian", - Location = "Ethiopia", - Details = "The Abyssinian is a breed of domestic short-haired cat." - }); - - Cats.Add(new _8716Animal - { - Name = "Arabian Mau", - Location = "Arabian Peninsula", - Details = "The Arabian Mau is a formal breed of domestic cat." - }); - } - } - - public class _8716AnimalSearchHandler : SearchHandler - { - public IList<_8716Animal> Animals { get; set; } - - protected override void OnQueryChanged(string oldValue, string newValue) - { - base.OnQueryChanged(oldValue, newValue); - - if (string.IsNullOrWhiteSpace(newValue)) - { - ItemsSource = null; - } - else - { - var filteredAnimals = new List<_8716Animal>(); - if (Animals != null) - { - foreach (var animal in Animals) - { - if (animal.Name != null && animal.Name.IndexOf(newValue, StringComparison.CurrentCultureIgnoreCase) >= 0) - { - filteredAnimals.Add(animal); - } - } - } - ItemsSource = filteredAnimals; - } - } - } - - [QueryProperty(nameof(Name), "name")] - public class _8716CatDetailsPage : ContentPage - { - public string Name - { - set - { - LoadAnimal(value); - } - } + Text = "Button", + AutomationId = "MainPageButton" + }; - public _8716CatDetailsPage() - { - var scrollView = new ScrollView(); - var stackLayout = new StackLayout { Margin = new Thickness(20) }; + var layout = new StackLayout + { + Children = { button, collectionView } + }; - var nameLabel = new Label - { - HorizontalOptions = LayoutOptions.Center, - }; - nameLabel.SetBinding(Label.TextProperty, "Name"); + Content = layout; + } + } - var locationLabel = new Label - { - FontAttributes = FontAttributes.Italic, - HorizontalOptions = LayoutOptions.Center - }; - locationLabel.SetBinding(Label.TextProperty, "Location"); + public class _8716DogsPage : ContentPage + { + _8716AnimalSearchHandler searchHandler; + public _8716DogsPage() + { + Title = "DogsPage"; - var detailsLabel = new Label - { - HorizontalOptions = LayoutOptions.Center, - LineBreakMode = LineBreakMode.WordWrap, - VerticalOptions = LayoutOptions.Start - }; - detailsLabel.SetBinding(Label.TextProperty, "Details"); + searchHandler = new _8716AnimalSearchHandler + { + Placeholder = "Enter dog's name", + ShowsResults = true, + AutomationId = "AnimalSearchHandler", + Animals = new List() { "Afghan Hound", "Alpine Dachsbracke" }, + ItemTemplate = new DataTemplate(() => + { + var nameLabel = new Label + { + FontAttributes = FontAttributes.Bold, + VerticalOptions = LayoutOptions.Center + }; + nameLabel.SetBinding(Label.TextProperty, "."); + return nameLabel; + }) + }; + + Shell.SetSearchHandler(this, searchHandler); + + var collectionView = new CollectionView + { + Margin = new Thickness(20), + ItemsSource = new List() { "Afghan Hound", "Alpine Dachsbracke" }, + ItemTemplate = new DataTemplate(() => + { + var nameLabel = new Label { FontAttributes = FontAttributes.Bold }; + nameLabel.SetBinding(Label.TextProperty, "."); + return nameLabel; + }), + SelectionMode = SelectionMode.Single + }; + + var button = new Button + { + Text = "DogPageButton", + AutomationId = "DogPageButton" + }; - stackLayout.Children.Add(nameLabel); - stackLayout.Children.Add(locationLabel); - stackLayout.Children.Add(detailsLabel); + var layout = new StackLayout + { + Children = { button, collectionView } + }; - scrollView.Content = stackLayout; - Content = scrollView; - } + Content = layout; + } + } - void LoadAnimal(string name) - { - foreach (var animal in _8716CatData.Cats) - { - if (animal.Name == name) - { - BindingContext = animal; - return; - } - } + public class _8716AnimalSearchHandler : SearchHandler + { + public IList Animals { get; set; } - throw new Exception("Animal not found."); - } - } + protected override void OnQueryChanged(string oldValue, string newValue) + { + base.OnQueryChanged(oldValue, newValue); + if (string.IsNullOrWhiteSpace(newValue)) + { + ItemsSource = null; + } + else + { + var filteredAnimals = new List(); + if (Animals != null) + { + foreach (var animal in Animals) + { + if (animal.IndexOf(newValue, StringComparison.CurrentCultureIgnoreCase) >= 0) + { + filteredAnimals.Add(animal); + } + } + } + + ItemsSource = filteredAnimals; + } + } + } } \ No newline at end of file diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ShouldUpdateSearchViewOnPageNavigation.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ShouldUpdateSearchViewOnPageNavigation.png new file mode 100644 index 0000000000000000000000000000000000000000..38d4aa7029cd3b7f95e45398a1de77b7345b1a3f GIT binary patch literal 53110 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 Date: Mon, 19 May 2025 11:15:09 +0530 Subject: [PATCH 6/9] fix-8716- Added Mac snapshot. --- .../ShouldUpdateSearchViewOnPageNavigation.png | Bin 0 -> 19054 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/ShouldUpdateSearchViewOnPageNavigation.png diff --git a/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/ShouldUpdateSearchViewOnPageNavigation.png b/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/ShouldUpdateSearchViewOnPageNavigation.png new file mode 100644 index 0000000000000000000000000000000000000000..44b6975a7c8d4a0e392090ede21774c3c4bdd98c GIT binary patch literal 19054 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 literal 0 HcmV?d00001 From efc44eab929679f88c52e75ac9f8a1aff8d630ae Mon Sep 17 00:00:00 2001 From: BagavathiPerumal Date: Tue, 17 Jun 2025 16:26:34 +0530 Subject: [PATCH 7/9] fix-8716-Added comment for code changes. --- .../Compatibility/Handlers/Shell/Android/ShellToolbarTracker.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellToolbarTracker.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellToolbarTracker.cs index 1a7deb2788c3..4b57ff6c7ed5 100644 --- a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellToolbarTracker.cs +++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellToolbarTracker.cs @@ -662,6 +662,8 @@ protected virtual void UpdateToolbarItems(AToolbar toolbar, Page page) _searchView.View.LayoutParameters = new LP(LP.MatchParent, LP.MatchParent); _searchView.SearchConfirmed += OnSearchConfirmed; } + // If the existing _searchView is present but its SearchHandler doesn't match the current page SearchHandler, + // update it to ensure the correct handler is used for search operations. else if (_searchView.SearchHandler != SearchHandler) { _searchView.SearchHandler = SearchHandler; From dd95ab310de0dae1c2a5c49f0263aec3546e7ebe Mon Sep 17 00:00:00 2001 From: BagavathiPerumal Date: Mon, 23 Mar 2026 19:07:31 +0530 Subject: [PATCH 8/9] fix-8716-Changes committed. --- .../Shell/Android/ShellToolbarTracker.cs | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellToolbarTracker.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellToolbarTracker.cs index 4b57ff6c7ed5..1443432631e6 100644 --- a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellToolbarTracker.cs +++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellToolbarTracker.cs @@ -663,9 +663,12 @@ protected virtual void UpdateToolbarItems(AToolbar toolbar, Page page) _searchView.SearchConfirmed += OnSearchConfirmed; } // If the existing _searchView is present but its SearchHandler doesn't match the current page SearchHandler, - // update it to ensure the correct handler is used for search operations. + // first collapse and reset the active search UI state, then apply the existing handler swap + reload + // so Shell tab navigation does not carry the previous page's interaction state into the next page. else if (_searchView.SearchHandler != SearchHandler) { + menu.FindItem(_placeholderMenuItemId)?.CollapseActionView(); + ClearSearchViewState(_searchView.View); _searchView.SearchHandler = SearchHandler; _searchView.LoadView(); } @@ -711,6 +714,27 @@ protected virtual void UpdateToolbarItems(AToolbar toolbar, Page page) menu.Dispose(); } + static void ClearSearchViewState(AView view) + { + view.ClearFocus(); + + if (view is AppCompatAutoCompleteTextView autoCompleteTextView) + { + autoCompleteTextView.DismissDropDown(); + autoCompleteTextView.ClearComposingText(); + + var text = autoCompleteTextView.Text; + if (!string.IsNullOrEmpty(text)) + autoCompleteTextView.SetSelection(text.Length); + } + + if (view is ViewGroup viewGroup) + { + for (int i = 0; i < viewGroup.ChildCount; i++) + ClearSearchViewState(viewGroup.GetChildAt(i)); + } + } + void OnSearchViewAttachedToWindow(object sender, AView.ViewAttachedToWindowEventArgs e) { // We only need to do this tint hack when using collapsed search handlers From 0749c29982474681f50705046343fdb1abd9743b Mon Sep 17 00:00:00 2001 From: BagavathiPerumal Date: Fri, 27 Mar 2026 18:57:06 +0530 Subject: [PATCH 9/9] fix-8716-Updated test conditions to run only on Android; restrict Windows, iOS, and macCatalyst until respective fixes are available --- .../tests/TestCases.Shared.Tests/Tests/Issues/Issue8716.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue8716.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue8716.cs index 0c1f6b22666c..587bc434a3c2 100644 --- a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue8716.cs +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue8716.cs @@ -1,4 +1,4 @@ -#if TEST_FAILS_ON_WINDOWS // A fix for this issue is already available for Windows platform in an open PR (https://github.com/dotnet/maui/pull/29441), so the test is restricted on Windows for now. +#if ANDROID // The fix is available on Android only. iOS and macCatalyst are tracked separately in https://github.com/dotnet/maui/issues/34693. A fix for Windows is available in an open PR (https://github.com/dotnet/maui/pull/29441), so the test is restricted on Windows, iOS and macCatalyst for now. using NUnit.Framework; using UITest.Appium; using UITest.Core;