Skip to content

Add smart UI test category detection to skip irrelevant test jobs - #33176

Closed
jfversluis wants to merge 9 commits into
mainfrom
feature/detect-uitest-categories
Closed

jfversluis wants to merge 9 commits into
mainfrom
feature/detect-uitest-categories

Conversation

@jfversluis

@jfversluis jfversluis commented Dec 16, 2025 •

Copy link
Copy Markdown
Member

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

What this does

Before: Every PR runs ~24,000 UI tests across all categories (~2+ hours).

After: The pipeline detects which UI test categories a PR actually touches and only runs those. A PR that changes Button code only runs Button tests (~400 tests, ~30 min).

How it works

When maui-pr-uitests runs for a PR:

  1. A new Discover stage analyzes the PR diff
  2. It detects relevant categories using three methods:
    • From test files — finds [Category(UITestCategories.X)] in changed test files
    • From source paths — maps product code to categories (e.g. Shell/ → Shell, Button* → Button)
    • From AI — the PR review agent can suggest categories during pre-flight
  3. Each test job checks if its category group matches. If not, it skips (completes in seconds instead of ~30 min)
  4. If nothing matches (e.g. docs-only PR), all UI tests are skipped

Escape hatches

  • Add run-all-uitests label to force the full matrix
  • Queue manually with specific categories via the categories pipeline parameter

Files changed

File What it does
eng/scripts/detect-ui-test-categories.ps1 The detection script — 3-tier logic with 60+ path-to-category mappings
eng/pipelines/common/ui-tests.yml Adds Discover stage before test stages
eng/pipelines/common/ui-tests-steps.yml Adds per-job filter gate that skips non-matching categories
eng/pipelines/ci-uitests.yml Adds prNumber and categories parameters for manual queue
eng/pipelines/ci-copilot.yml Passes DNCENG_PUBLIC_PAT for cross-org build queuing
.github/scripts/post-uitest-categories-comment.ps1 Posts test results with platform table and failure details
.github/scripts/trigger-uitest-pipeline.ps1 Orchestrator for detect → queue → monitor flow
.github/scripts/Review-PR.ps1 Detects categories during review, gate retry on env errors
.github/scripts/post-ai-summary-comment.ps1 Adds UI Tests section to the unified AI summary
.github/pr-review/pr-preflight.md Adds step for AI to identify impacted categories

Tested on

Validated on 30+ PRs. Examples of targeted runs vs full matrix:

PR Detected Tests run vs Full matrix
#35009 ToolbarItem 467 24,000
#34997 RadioButton 496 24,000
#35079 SearchBar,Shell 2,218 24,000
#34637 Shape 505 24,000
#35072 Navigation,Shell 2,540 24,000

@jfversluis
jfversluis force-pushed the feature/detect-uitest-categories branch from 6a983cf to f060cc5 Compare December 17, 2025 13:52
@jfversluis jfversluis added this to the .NET 10.0 SR3 milestone Dec 18, 2025
@jfversluis jfversluis added the p/0 Current heighest priority issues that we are targeting for a release. label Dec 18, 2025
@jfversluis
jfversluis marked this pull request as ready for review December 18, 2025 12:54
Copilot AI review requested due to automatic review settings December 18, 2025 12:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces automatic UI test category detection for pull requests to optimize test execution time. When a PR adds new UI tests with [Category(...)] attributes, the pipeline detects these categories and runs only the relevant test matrix jobs, while non-matching jobs complete quickly with 0 tests.

Key Changes:

  • Adds a discovery stage that parses git diffs to detect new test categories in PRs
  • Implements early filtering logic to skip provisioning and test execution for non-matching category groups
  • Updates all test stages to depend on category discovery and use detected categories for filtering
  • Includes temporary dummy tests in ButtonUITests and LabelUITests to validate the detection mechanism

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
eng/scripts/detect-ui-test-categories.ps1 New PowerShell script that parses git diffs to extract Category attributes from added test code
eng/pipelines/common/ui-tests.yml Adds discovery stage and updates test stage dependencies to use detected categories
eng/pipelines/common/ui-tests-steps.yml Implements early category check and dynamic test filter calculation based on detected categories
eng/pipelines/common/provision.yml Adds skip logic to bypass provisioning when no tests will run for a category group
src/Controls/tests/TestCases.Shared.Tests/Tests/ButtonUITests.cs Adds dummy test to validate Button category detection
src/Controls/tests/TestCases.Shared.Tests/Tests/LabelUITests.cs Adds dummy test to validate Label category detection

Comment thread eng/scripts/detect-ui-test-categories.ps1
Comment thread eng/scripts/detect-ui-test-categories.ps1 Outdated
Comment thread eng/scripts/detect-ui-test-categories.ps1 Outdated
Comment thread eng/pipelines/common/ui-tests-steps.yml Outdated
Comment thread eng/pipelines/common/ui-tests.yml Outdated
Comment on lines +75 to +79
condition: |
or(
ne(variables['Build.Reason'], 'PullRequest'),
in(dependencies.discover_ui_test_categories.result, 'Succeeded', 'Skipped')
)

Copilot AI Dec 18, 2025

Copy link

Choose a reason for hiding this comment

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

The condition uses in(dependencies.discover_ui_test_categories.result, 'Succeeded', 'Skipped') which allows the build to proceed even if discovery was skipped. However, for non-PR builds, the discovery stage has condition: eq(variables['Build.Reason'], 'PullRequest') which means it will be skipped, not produce any output variables. The test stages depend on these output variables (DETECTED_CATEGORIES), so they need to handle the case where these variables are empty or undefined. Verify that the downstream logic correctly handles undefined DETECTED_CATEGORIES for non-PR builds.

Suggested change
condition: |
or(
ne(variables['Build.Reason'], 'PullRequest'),
in(dependencies.discover_ui_test_categories.result, 'Succeeded', 'Skipped')
)
condition: succeeded()

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +59
foreach ($cat in $categoryList) {
$cat = $cat.Trim()
foreach ($det in $detectedList) {
$det = $det.Trim()
if ($cat -eq $det) {
$hasMatch = $true
Write-Host "Match found: '$cat'"
break
}
}
if ($hasMatch) { break }
}

Copilot AI Dec 18, 2025

Copy link

Choose a reason for hiding this comment

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

The category comparison at lines 52 and 224 uses case-sensitive string equality ($cat -eq $det). However, the HashSet used for collecting categories in the PowerShell detection script uses case-insensitive comparison (line 57: [System.StringComparer]::OrdinalIgnoreCase). This inconsistency could cause matching failures if category names differ in case between the detection script output and the matrix CATEGORYGROUP values. Use case-insensitive comparison here as well with -ieq or .ToLower() for consistency.

Copilot uses AI. Check for mistakes.
Comment on lines 196 to 243

$testFilter = ""
$testConfigrationArgs = "${{ parameters.testConfigurationArgs }}"

"${{ parameters.testFilter }}".Split(",") | ForEach {
$testFilter += "TestCategory=" + $_ + "|"

# Get test filter from environment variable (passed from matrix via env block)
$testFilterParam = $env:CATEGORY_GROUP
$detectedCategories = $env:DETECTED_CATEGORIES
$isPR = $env:BUILD_REASON -eq "PullRequest"

Write-Host "Category Group from matrix: '$testFilterParam'"
Write-Host "Detected Categories: '$detectedCategories'"

# Fallback to parameter for non-matrix builds
if (-not $testFilterParam) {
$testFilterParam = "${{ parameters.testFilter }}"
Write-Host "Using testFilter parameter: '$testFilterParam'"
}

# For PRs with detected categories, use only matching categories
if ($isPR -and -not [string]::IsNullOrWhiteSpace($detectedCategories) -and -not [string]::IsNullOrWhiteSpace($testFilterParam)) {
$categoryList = $testFilterParam -split ","
$detectedList = $detectedCategories -split ","
$matchingCategories = @()

foreach ($cat in $categoryList) {
$cat = $cat.Trim()
foreach ($det in $detectedList) {
$det = $det.Trim()
if ($cat -eq $det) {
$matchingCategories += $cat
break
}
}
}

$testFilterParam = $matchingCategories -join ","
Write-Host "Running matching categories: $testFilterParam"
}

Write-Host "Final test filter: '$testFilterParam'"

if ($testFilterParam) {
$testFilterParam.Split(",") | ForEach {
$testFilter += "TestCategory=" + $_ + "|"
}
$testFilter = $testFilter.TrimEnd("|")
}

$testFilter = $testFilter.TrimEnd("|")

Copilot AI Dec 18, 2025

Copy link

Choose a reason for hiding this comment

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

There is duplicated category matching logic between the early check step (lines 20-69) and the test filter calculation step (lines 200-233). Both steps perform the same category matching operation with identical logic. Consider extracting this into a shared function or removing one of the checks. Since the early check already determines whether tests should run and sets SHOULD_RUN_TESTS, the test filter calculation step could be simplified to only handle the filter string construction when SHOULD_RUN_TESTS is True.

Copilot uses AI. Check for mistakes.
Comment on lines +20 to +69
- pwsh: |
$testFilterParam = $env:CATEGORY_GROUP
$testFilterFallback = $env:TEST_FILTER_PARAM
$detectedCategories = $env:DETECTED_CATEGORIES
$isPR = $env:BUILD_REASON -eq "PullRequest"

Write-Host "=== Early Category Check ==="
Write-Host "Build Reason: $env:BUILD_REASON"
Write-Host "Category Group (from matrix): '$testFilterParam'"
Write-Host "Test Filter (from parameter): '$testFilterFallback'"
Write-Host "Detected Categories: '$detectedCategories'"

# Use testFilter parameter as fallback when CATEGORYGROUP is not set or not expanded
# When CATEGORYGROUP variable doesn't exist, it shows as literal '$(CATEGORYGROUP)'
$categoryGroupNotSet = [string]::IsNullOrWhiteSpace($testFilterParam) -or $testFilterParam -eq '$(CATEGORYGROUP)'
if ($categoryGroupNotSet -and -not [string]::IsNullOrWhiteSpace($testFilterFallback)) {
$testFilterParam = $testFilterFallback
Write-Host "Using testFilter parameter as category: '$testFilterParam'"
}

$shouldRun = $true

# For PRs with detected categories, check if this category group has any matches
if ($isPR -and -not [string]::IsNullOrWhiteSpace($detectedCategories) -and -not [string]::IsNullOrWhiteSpace($testFilterParam)) {
$categoryList = $testFilterParam -split ","
$detectedList = $detectedCategories -split ","
$hasMatch = $false

foreach ($cat in $categoryList) {
$cat = $cat.Trim()
foreach ($det in $detectedList) {
$det = $det.Trim()
if ($cat -eq $det) {
$hasMatch = $true
Write-Host "Match found: '$cat'"
break
}
}
if ($hasMatch) { break }
}

if (-not $hasMatch) {
$shouldRun = $false
Write-Host "##[warning]No matching categories - SKIPPING all steps for this job"
Write-Host "Category group '$testFilterParam' does not contain any detected categories: $detectedCategories"
}
}

Write-Host "Should run tests: $shouldRun"
Write-Host "##vso[task.setvariable variable=SHOULD_RUN_TESTS]$shouldRun"

Copilot AI Dec 18, 2025

Copy link

Choose a reason for hiding this comment

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

The early check step could skip the job entirely if SHOULD_RUN_TESTS is False, but instead it sets a variable and forces all subsequent steps to check this condition. Azure Pipelines supports job-level conditions that could terminate the job earlier. Consider using a job-level condition expression or setting the job result to "Skipped" when no matching categories are found, rather than requiring every subsequent step to check SHOULD_RUN_TESTS.

Copilot uses AI. Check for mistakes.
@jfversluis

Copy link
Copy Markdown
Member Author

/azp run maui-pr-uitests

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@PureWeen PureWeen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Chatted with @jfversluis about some changes to be made here

@github-project-automation github-project-automation Bot moved this from Todo to Changes Requested in MAUI SDK Ongoing Jan 26, 2026
@PureWeen
PureWeen marked this pull request as draft January 26, 2026 15:29
@kubaflo

kubaflo commented Apr 19, 2026

Copy link
Copy Markdown
Contributor

Added a run-all-uitests label escape hatch (commit 25518eb99d):

  • The discovery script now queries the GitHub API for the PR's labels before doing diff analysis
  • If run-all-uitests is present, the script returns early and the full UI test matrix runs (same as a non-PR build)
  • Falls back gracefully if the API call fails — detection continues as normal

This lets reviewers force full coverage on a PR without having to push a no-op commit or hand-edit pipeline parameters. We'll need to create the run-all-uitests label on the repo (or whoever has admin can do that as part of merging).

@kubaflo

kubaflo commented Apr 19, 2026

Copy link
Copy Markdown
Contributor

Added a prNumber queue-time parameter (commit c9b9ff5101) so you can validate this end-to-end before merging.

How to test:

  1. Queue maui-pr-uitests from the AzDO UI against this branch (feature/detect-uitest-categories)
  2. In the parameter list you'll now see PR number (for testing category detection from a manual queue) — set it to any open PR number that adds [Category(...)] tests (e.g., a recent UI test PR)
  3. The discovery stage will:
    • Hit the GitHub API for that PR's metadata (handles forks)
    • Fetch the PR's base + head refs and check out the head commit
    • Diff base..head for added [Category(...)] lines
    • Honor the run-all-uitests label if present
  4. Watch downstream stages get skipped for non-matching category groups (or run with the per-category MATCHING_CATEGORIES filter)

Leaving the parameter empty/whitespace falls back to the normal PR-build behavior, so it's a no-op for real PR validation.

@github-actions

github-actions Bot commented Apr 21, 2026 •

Copy link
Copy Markdown
Contributor

🔍 Skill Validation Results

❌ Static Checks Failed

Skills checked: 15 | Agents checked: 0

Full validator output
Found 15 skill(s)
[code-review] 📊 code-review: 2,354 BPE tokens [chars/4: 2,476] (detailed ✓), 28 sections, 6 code blocks
[evaluate-pr-tests] 📊 evaluate-pr-tests: 2,955 BPE tokens [chars/4: 2,949] (standard ~), 35 sections, 6 code blocks
[evaluate-pr-tests]    ⚠  Skill is 2,955 BPE tokens (chars/4 estimate: 2,949) — approaching "comprehensive" range where gains diminish.
[pr-review] 📊 pr-review: 3,269 BPE tokens [chars/4: 3,161] (standard ~), 22 sections, 7 code blocks
[pr-review]    ⚠  Skill is 3,269 BPE tokens (chars/4 estimate: 3,161) — approaching "comprehensive" range where gains diminish.
[write-xaml-tests] 📊 write-xaml-tests: 755 BPE tokens [chars/4: 742] (detailed ✓), 13 sections, 3 code blocks
[write-xaml-tests]    ⚠  No numbered workflow steps — agents follow sequenced procedures more reliably.
[learn-from-pr] 📊 learn-from-pr: 2,192 BPE tokens [chars/4: 2,463] (detailed ✓), 26 sections, 3 code blocks
[write-ui-tests] 📊 write-ui-tests: 2,877 BPE tokens [chars/4: 2,965] (standard ~), 27 sections, 13 code blocks
[write-ui-tests]    ⚠  Skill is 2,877 BPE tokens (chars/4 estimate: 2,965) — approaching "comprehensive" range where gains diminish.
[verify-tests-fail-without-fix] 📊 verify-tests-fail-without-fix: 2,271 BPE tokens [chars/4: 2,189] (detailed ✓), 26 sections, 7 code blocks
[run-helix-tests] 📊 run-helix-tests: 1,446 BPE tokens [chars/4: 1,362] (detailed ✓), 27 sections, 11 code blocks
[azdo-build-investigator] 📊 azdo-build-investigator: 1,060 BPE tokens [chars/4: 1,005] (detailed ✓), 7 sections, 1 code blocks
[azdo-build-investigator]    ⚠  No numbered workflow steps — agents follow sequenced procedures more reliably.
[pr-finalize] 📊 pr-finalize: 2,906 BPE tokens [chars/4: 3,073] (standard ~), 61 sections, 11 code blocks
[pr-finalize]    ⚠  Skill is 2,906 BPE tokens (chars/4 estimate: 3,073) — approaching "comprehensive" range where gains diminish.
[run-integration-tests] 📊 run-integration-tests: 2,028 BPE tokens [chars/4: 2,052] (detailed ✓), 35 sections, 7 code blocks
[run-device-tests] 📊 run-device-tests: 2,969 BPE tokens [chars/4: 2,992] (standard ~), 53 sections, 8 code blocks
[run-device-tests]    ⚠  Skill is 2,969 BPE tokens (chars/4 estimate: 2,992) — approaching "comprehensive" range where gains diminish.
[try-fix] 📊 try-fix: 3,860 BPE tokens [chars/4: 4,027] (standard ~), 37 sections, 12 code blocks
[try-fix]    ⚠  Skill is 3,860 BPE tokens (chars/4 estimate: 4,027) — approaching "comprehensive" range where gains diminish.
[issue-triage] 📊 issue-triage: 2,035 BPE tokens [chars/4: 1,932] (detailed ✓), 31 sections, 8 code blocks
[find-reviewable-pr] 📊 find-reviewable-pr: 1,778 BPE tokens [chars/4: 1,722] (detailed ✓), 22 sections, 3 code blocks
✅ All checks passed (15 skill(s))
No agents found in the specified paths: "/home/runner/work/maui/maui/.github/agents"

✅ LLM Evaluation Passed

🔍 Full results and investigation steps

@PureWeen

Copy link
Copy Markdown
Member

🔍 Multi-Model Code Review — PR #33176

PR: Add PR category detection for UI tests
Author: @jfversluis | Target: main | Milestone: .NET 10 SR7 | Label: p/0

Reviewed by 3 independent reviewers with adversarial consensus on disputed findings.


CI Status

⏳ PENDING — maui-pr builds (Windows/macOS Debug + Release, Pack) are still in progress. Helix unit tests ✅ passed.

❌ Skill Validation is failing — this appears related to the PR's changes to .github/skills/ files. The static check fails but the LLM evaluation passes. This should be investigated.

Prior Review Status

  • @PureWeen requested changes (Jan 26) — "Chatted with @jfversluis about some changes to be made here"
  • @kubaflo addressed prior Copilot review feedback (Apr 19) — regex simplification, fetchDepth, duplicated logic, and other items
  • No re-review from @PureWeen after fixes — outstanding CHANGES_REQUESTED status remains

🔴 CRITICAL

1. Hardcoded feature/detect-uitest-categories branch will break after merge (3/3 reviewers)

File: .github/scripts/trigger-uitest-pipeline.ps1, line ~41

[string]$SourceBranch = "feature/detect-uitest-categories",

After this PR merges and the feature branch is deleted, every agent-triggered UI test pipeline queue will fail because it tries to build from a non-existent branch. Review-PR.ps1 invokes this script, so the Copilot PR review workflow silently breaks.

Fix: Default to main or dynamically resolve:

[string]$SourceBranch = "main",

�� MODERATE

2. UITestCategoryMatrix output variable is emitted but never consumed (2/3 reviewers)

File: eng/scripts/detect-ui-test-categories.ps1, lines ~394, ~55

The script emits UITestCategoryMatrix as an AzDO output variable with a full matrix JSON, but no YAML template ever references it. The matrix in ui-tests.yml remains static via categoryGroupsToTest. Only UITestCategoryList is consumed. This is dead code that misleads readers into thinking dynamic matrix selection is wired up.

Fix: Either wire up UITestCategoryMatrix for true dynamic matrix filtering (which would avoid creating matrix jobs entirely), or remove it and add a # TODO comment if planned for future work.


3. AzDO PAT exposed to Copilot agent container (2/3 reviewers after adversarial)

File: eng/pipelines/ci-copilot.yml, line ~652

AZURE_DEVOPS_EXT_PAT: $(DNCENG_PUBLIC_PAT)

This passes the AzDO PAT as an environment variable to the Copilot agent step. Per gh-aw architecture, --env-all passes all env vars into the agent container. While AWF network firewalls, redact_secrets.cjs, and the threat detection agent mitigate exfiltration, the PAT is accessible to any subprocess the agent launches — expanding the sandbox's blast radius.

Fix: Move trigger-uitest-pipeline.ps1 invocation to a steps: block (pre-agent, trusted context) rather than passing the PAT into the sandboxed container.


4. Most test stages lack stage-level NONE guards — wasted CI resources (2/3 reviewers after adversarial)

File: eng/pipelines/common/ui-tests.yml

Only ios_ui_tests_mono_cv1 and ios_ui_tests_mono_carv1 have explicit stage-level conditions checking UITestCategoryList. All other stages (android_ui_tests, ios_ui_tests_mono, android_ui_tests_coreclr, winui_ui_tests, mac_ui_tests, ios_ui_tests_nativeaot) unconditionally launch when UITestCategoryList = 'NONE'. Each matrix entry spins up an agent, runs checkout, then discovers SHOULD_RUN_TESTS=False and exits. This wastes dozens of agent allocations and startup costs for docs-only or build-script-only PRs.

Similarly, build_ui_tests runs even when detection returns NONE — building the full test app unnecessarily.

Fix: Apply the same stage-level condition pattern from cv1/carv1 to all test stages:

condition: |
  and(
    succeeded('build_ui_tests'),
    or(
      ne(variables['Build.Reason'], 'PullRequest'),
      eq(dependencies.discover_ui_test_categories.result, 'Skipped'),
      ne(stageDependencies...UITestCategoryList, 'NONE')
    )
  )

🟢 MINOR

5. PAT prefix logged to CI output (2/3 reviewers)

File: .github/scripts/trigger-uitest-pipeline.ps1, lines ~192-193

$maskedPat = $azdoPat.Substring(0, [Math]::Min(4, $patLen)) + "****"
Write-Host "  📡 Queueing via AzDO REST API (Basic auth, token: $maskedPat, length: $patLen)..."

Logs the first 4 characters and exact length of the PAT. While AzDO PATs are long enough that 4 chars don't enable brute force, security best practice is to log zero characters of secrets.

Fix: Log only presence, not content:

Write-Host "  📡 Queueing via AzDO REST API (Basic auth, token: ****, length: $patLen)..." -ForegroundColor Gray

ℹ️ Informational — Single-Reviewer Findings (Not Corroborated)

The following were flagged by only one reviewer and either disputed during adversarial review or not corroborated. Noted here for awareness:

Finding Flagged By Adversarial Result Notes
throw on unrecognized [Category] blocks all tests 1 reviewer Disputed — intentional fail-fast design, zero triggering instances in codebase, failure is visible (not silent) Consider Write-Warning + fallback instead of throw for robustness
Regex ^\+\s*\[Category\( misses multi-attribute lines 1 reviewer Disputed — only 1 instance in entire test suite uses combined pattern, guidelines explicitly forbid it Theoretical edge case, not practical risk
contains() substring match for CV1/CarV1 stage conditions 1 reviewer Not verified Fragile if future category names overlap (e.g., CollectionViewLayout matches CollectionView)
provision.yml skip-check runs for ALL pipelines, not just UI tests 1 reviewer Not verified Harmless but adds noise to non-UI pipeline logs
Shallow clone depth=200 silently falls back on long-lived PRs 1 reviewer Not verified Fallback to run-all is correct behavior; could add ##[warning]
Unauthenticated polling loop in trigger-uitest-pipeline.ps1 1 reviewer Not verified Works for public project; would fail silently if project goes private
$matches shadows PowerShell automatic variable 1 reviewer Not verified Scoping prevents bugs; cosmetic concern
Missing newline at EOF in detect-ui-test-categories.ps1 1 reviewer Not verified POSIX compatibility nit
Space ' ' defaults instead of '' for pipeline parameters 1 reviewer Not verified Confusing UX in AzDO queue dialog
Empty testFilter edge case when MATCHING_CATEGORIES is empty 1 reviewer Not verified Falls back to running all categories in the group — correct but slower

Test Coverage Assessment

This PR is primarily CI/CD pipeline infrastructure (YAML templates + PowerShell scripts). There are no automated tests for the detection logic itself (detect-ui-test-categories.ps1). The PR previously included dummy tests in ButtonUITests.cs and LabelUITests.cs for manual validation, which appear to have been removed. The prNumber queue-time parameter was added for end-to-end validation.

Recommendation: Consider adding Pester tests for detect-ui-test-categories.ps1 to validate regex matching, tier fallback logic, and edge cases (empty diff, no test files, combined attributes).


Recommended Action

⚠️ Request Changes — with the following specific asks:

  1. [Must fix] Change $SourceBranch default from "feature/detect-uitest-categories" to "main" in trigger-uitest-pipeline.ps1 — this is a post-merge time bomb
  2. [Should fix] Add stage-level NONE guards to all test stages (not just cv1/carv1) to avoid wasting CI agent allocations on docs-only PRs
  3. [Should fix] Move PAT usage out of the Copilot agent container in ci-copilot.yml — run trigger-uitest-pipeline.ps1 in a pre-agent steps: block instead
  4. [Should fix] Remove or wire up UITestCategoryMatrix — dead output variables create confusion
  5. [Nice to have] Stop logging PAT prefix characters in trigger-uitest-pipeline.ps1

Items 1 is a blocking issue. Items 2-4 are strongly recommended before merge. Item 5 is a minor improvement.

@kubaflo
kubaflo marked this pull request as draft April 22, 2026 19:22
@kubaflo
kubaflo force-pushed the feature/detect-uitest-categories branch from 551b9b3 to 42a78ef Compare April 23, 2026 14:51
…orkflow

3-tier category detection (test attrs, source paths, AI reasoning),
AzDO pipeline filtering, result comments, and integration with
the PR review workflow (trigger early, collect after try-fix).

Includes:
- eng/scripts/detect-ui-test-categories.ps1 (3-tier detection)
- eng/pipelines/common/ui-tests-steps.yml (per-job filter gate)
- eng/pipelines/common/ui-tests.yml (discovery stage)
- eng/pipelines/ci-uitests.yml (prNumber + categories params)
- eng/pipelines/ci-copilot.yml (DNCENG_PUBLIC_PAT for cross-org)
- .github/scripts/trigger-uitest-pipeline.ps1 (orchestrator)
- .github/scripts/post-uitest-categories-comment.ps1 (results comment)
- .github/scripts/Review-PR.ps1 (Step 0.5 trigger, Step 2.5 collect)
- .github/scripts/post-ai-summary-comment.ps1 (UI Tests section)
- .github/pr-review/pr-preflight.md (AI category identification)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kubaflo
kubaflo force-pushed the feature/detect-uitest-categories branch from 42a78ef to 7437402 Compare April 23, 2026 15:00
@kubaflo kubaflo changed the title Add PR category detection for UI tests Add UI test category detection and targeted pipeline execution Apr 23, 2026
@kubaflo

kubaflo commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Addressing review feedback

The PR has been significantly rewritten since the original review. Here's how the current implementation addresses the feedback:

Copilot review suggestions — status:

Suggestion Status
Unrecognized category should throw (not silently fallback) ✅ Fixed — now throws with ##[error]
Case-insensitive comparison in category matching ✅ Fixed — uses -ieq
fetchDepth: 200 instead of 0 ✅ Fixed
Duplicated matching logic in ui-tests-steps.yml ✅ Consolidated — early check sets MATCHING_CATEGORIES variable, downstream steps reuse it
Silent fallback masking problems ✅ Improved — retries (3x with 10s delay) on GitHub API failures before falling back. Git failures log ##[warning] with specific error. Intentional fallback on network issues per @jfversluis
Job-level skip vs step-level conditions Kept step-level — AzDO doesn't support dynamically skipping matrix jobs mid-execution. The early check + SHOULD_RUN_TESTS variable is the standard pattern.
Non-PR builds handling undefined DETECTED_CATEGORIES ✅ Handled — StartsWith('$(') check detects unresolved AzDO placeholders

Major changes since original review:

  1. 3-tier detection — now detects categories from test attributes (Tier 1), source-path mapping with 60+ patterns (Tier 2), and AI reasoning during pre-flight (Tier 3)
  2. NONE signal — when no categories are relevant, outputs NONE to skip all tests (instead of running everything)
  3. PR review integration — Step 0.5 triggers UI tests early, Step 2.5 collects results, embedded in AI summary comment
  4. Rich results comment — platform table, failure classification (snapshot/timeout/crash/assertion), per-run breakdown
  5. prNumber + categories params — maintainers can manually trigger with specific categories
  6. GitHub API retry — 3 attempts with 10s delay to handle transient 504/timeout errors

@PureWeen @jfversluis — ready for re-review when you get a chance.

kubaflo and others added 8 commits April 23, 2026 23:40
Lightweight ubuntu job runs detect + queue independently from the
main CopilotReview job, making it easy to test the PAT and pipeline
integration without waiting for the full review.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tests now run the PR's actual code (refs/pull/N/merge) instead of
main or the feature branch. This ensures test results reflect the
PR's changes, including new tests and snapshot baselines.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ENV ERROR (emulator timeout, ADB failure, app crash) now triggers
automatic retry with 30s delay. Real failures (test logic) don't
retry. Checks verification-report.md for 'ENV ERROR' pattern.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Step 0.5 now only detects categories (no pipeline trigger/monitor).
Removed Step 2.5 (collect results) and -SkipUITests parameter.
The actual UI test pipeline runs separately via PR CI.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Check verification-report.md for proper format before using it.
If report has old/broken format (Passed: False, empty Total/Failed),
generate a clean fallback instead.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kubaflo kubaflo changed the title Add UI test category detection and targeted pipeline execution Add smart UI test category detection to skip irrelevant test jobs Apr 24, 2026
@kubaflo

kubaflo commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Closing this PR — superseded by a new clean PR with the same branch (coming next).

@kubaflo

kubaflo commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

This PR has been superseded by #35133 which has a clean commit history.

@github-actions github-actions Bot locked and limited conversation to collaborators May 25, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

p/0 Current heighest priority issues that we are targeting for a release.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants