Skip to content

Commit f8ec2ee

Browse files
feat: audit vrt tools (#9592)
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: arii <342438+arii@users.noreply.github.com>
1 parent 828aeb8 commit f8ec2ee

6 files changed

Lines changed: 160 additions & 0 deletions

File tree

.github/copilot-instructions.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,11 @@ This project prioritizes a lean and maintainable codebase. AI assistants should
294294
- Simplifying complex functions into smaller, more manageable units.
295295
- Removing boilerplate or over-specified types that can be inferred.
296296

297+
### 8. Visual Regression Testing (VRT) Stabilization Standards
298+
299+
Strictly adhere to the standards defined in:
300+
[.github/instructions/vrt-stability.instructions.md](.github/instructions/vrt-stability.instructions.md)
301+
297302
## Quick Reference: Anti-Patterns to Avoid
298303

299304
| ❌ AI Slop | ✅ Correct Approach |
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
---
2+
applyTo: 'tests/playwright/**/*.spec.ts,tests/playwright/lib/visual.ts'
3+
description: 'Use when editing Playwright visual regression tests; enforce deterministic VRT setup, strict thresholds, and no sleep-based stabilization.'
4+
---
5+
6+
# VRT Stability Guardrails
7+
8+
When changing Playwright visual regression tests, follow these standards.
9+
10+
## Threshold policy
11+
12+
- Default to `maxDiffPixelRatio: 0.1`.
13+
- Allow up to `0.15` only for clearly documented dynamic captures.
14+
- Do not introduce `maxDiffPixelRatio > 0.15`.
15+
- Keep `threshold: 0.2` and `scale: 'css'` in shared screenshot behavior.
16+
17+
## Synchronization policy
18+
19+
- Do not use `waitForTimeout(...)` for stabilization.
20+
- Use deterministic checks instead:
21+
- `await page.evaluateHandle(() => document.fonts.ready)`
22+
- `await page.waitForLoadState('networkidle')`
23+
- explicit assertions on target UI state (`toBeVisible`, `toHaveCSS`, `toHaveText`)
24+
- layout read/reflow (`await page.evaluate(() => document.body.offsetHeight)`)
25+
26+
## Responsive screenshot policy
27+
28+
- Always set viewport explicitly before responsive snapshots.
29+
- Wait for viewport convergence before capture:
30+
- `await page.waitForFunction((w) => document.body.clientWidth === w, width)`
31+
32+
## Masking policy
33+
34+
- Mask only truly dynamic elements (timers, live HR values, websocket-driven changing text).
35+
- Use shared mask helpers (`getDynamicContentMasks`, `getHrMasks`) to keep masks consistent.
36+
- Do not mask large stable layout containers.
37+
38+
## Portal and locator policy
39+
40+
- For menu/popover/modal locator screenshots, avoid full-page scroll side effects that can detach portals.
41+
- `skipA11y: true` is allowed for portal snapshots only when accompanied by a brief rationale comment.
42+
43+
## Stateful suite policy
44+
45+
- Use `resetServerState(request)` in `beforeEach` when server state can leak between tests.
46+
- Add deterministic teardown in `afterEach` for long-running state (for example `stopTimer(...)`).

.github/prompts/AGENTS.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,23 @@ Security and Performance: Ensure the refactored code is performant, especially f
3737
- Use data factories or mock data generators for all tests to ensure consistency and readability.
3838
- Use ARIA labels and `data-testid` attributes for easier and more reliable testing of UI components.
3939

40+
- **Playwright VRT Review Standards (Critical):**
41+
- Treat `maxDiffPixelRatio: 0.1` as the baseline for standard snapshots.
42+
- Allow up to `maxDiffPixelRatio: 0.15` only for clearly justified dynamic/complex captures (for example dense chart + HR overlays).
43+
- Do not approve or suggest `maxDiffPixelRatio > 0.15` as a stabilization tactic.
44+
- Keep `threshold: 0.2` and `scale: 'css'` in shared screenshot defaults.
45+
- Reject arbitrary sleep-based stabilization (`waitForTimeout(...)`); require deterministic checks instead:
46+
- `await page.evaluateHandle(() => document.fonts.ready)`
47+
- `await page.waitForLoadState('networkidle')`
48+
- explicit state assertions (`toBeVisible`, `toHaveCSS`, `toHaveText`)
49+
- layout read/reflow (`await page.evaluate(() => document.body.offsetHeight)`)
50+
- For responsive snapshots, require explicit viewport convergence checks:
51+
- `await page.setViewportSize({ width, height })`
52+
- `await page.waitForFunction((w) => document.body.clientWidth === w, width)`
53+
- Prefer dynamic masking helpers (`getDynamicContentMasks`, `getHrMasks`) over raising thresholds.
54+
- For portal-based targets (MUI menus/popovers/modals), avoid full-page scroll side effects on locator screenshots and allow `skipA11y: true` only with a brief rationale.
55+
- In stateful VRT suites, require deterministic lifecycle handling (`resetServerState(request)` in setup and teardown hooks such as `stopTimer(...)` in `afterEach`).
56+
4057
Output Format:
4158

4259
Explanation: Start with a section titled Improvements: containing a concise, technical explanation of what was improved and why, with specific references to the modern features used (e.g., "Improved data transformation using array spread for guaranteed immutability and used optional chaining for safe access to the nested session.data object.").

prompts/fix-mode.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,24 @@ The CI pipeline has failed. Your ONLY goal is to diagnose the failure and provid
2424
2. **Diagnose**: Why did it fail? (e.g., type error, test timeout, missing mock).
2525
3. **Fix**: Provide a concrete code fix.
2626

27+
### VRT Failure Guardrails (Apply when failure is visual regression related)
28+
29+
1. Do not raise `maxDiffPixelRatio` as the first fix.
30+
2. Keep baseline `maxDiffPixelRatio: 0.1`; allow up to `0.15` only with explicit dynamic-capture justification.
31+
3. Treat `maxDiffPixelRatio > 0.15` as a policy violation unless exceptional rationale is provided.
32+
4. Keep screenshot defaults aligned with `threshold: 0.2` and `scale: 'css'`.
33+
5. Reject sleep-based stabilization (`waitForTimeout(...)`); prefer deterministic readiness checks:
34+
35+
- `await page.evaluateHandle(() => document.fonts.ready)`
36+
- `await page.waitForLoadState('networkidle')`
37+
- explicit UI assertions (`toBeVisible`, `toHaveCSS`, `toHaveText`)
38+
- layout read (`await page.evaluate(() => document.body.offsetHeight)`)
39+
40+
6. For responsive captures, require explicit viewport lock and width convergence checks.
41+
7. Prefer dynamic masking (`getDynamicContentMasks`, `getHrMasks`) over tolerance inflation.
42+
8. For portal-based locator captures, avoid full-page scroll side effects and use `skipA11y: true` only with rationale.
43+
9. Ensure deterministic lifecycle handling in stateful suites (`resetServerState(request)` and teardown hooks such as `stopTimer(...)`).
44+
2745
### Output Format
2846

2947
Return a JSON object:

prompts/standard-review.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,22 @@ You are a senior software engineer. Your goal is to provide a high-signal, low-n
115115
3. **Performance**: Look for N+1 queries, unnecessary re-renders, or memory leaks.
116116
4. **Maintainability**: Is the code readable? D.R.Y.? suitably typed?
117117

118+
### 2.1 VRT-Specific Review Policy (When Playwright visual tests are touched)
119+
120+
- Enforce baseline `maxDiffPixelRatio: 0.1`.
121+
- Allow at most `maxDiffPixelRatio: 0.15` only for documented dynamic/complex captures.
122+
- Treat `maxDiffPixelRatio > 0.15` as a review issue unless there is exceptional, explicit rationale.
123+
- Ensure shared screenshot behavior keeps `threshold: 0.2` and `scale: 'css'`.
124+
- Reject sleep-based stabilization (`waitForTimeout(...)`) and require deterministic checks:
125+
- `document.fonts.ready`
126+
- `page.waitForLoadState('networkidle')`
127+
- explicit `toBeVisible` / `toHaveCSS` / `toHaveText` assertions
128+
- layout read (`document.body.offsetHeight`)
129+
- For responsive snapshots, require explicit viewport setup and width convergence checks (`document.body.clientWidth`).
130+
- Prefer dynamic masks (`getDynamicContentMasks`, `getHrMasks`) over threshold increases.
131+
- For portal captures (MUI menu/popover/modal), avoid full-page scroll side effects on locator screenshots and allow `skipA11y: true` only with rationale.
132+
- In stateful suites, require deterministic setup/teardown (for example `resetServerState(request)` and timer teardown hooks).
133+
118134
### 3. Feedback Style
119135

120136
- **Actionable**: Suggest specific code changes with examples.

scripts/audit-vrt-changes.sh

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#!/bin/bash
2+
3+
for cmd in gh jq; do
4+
if ! command -v "$cmd" &> /dev/null; then
5+
echo "Error: $cmd is not installed." >&2
6+
exit 1
7+
fi
8+
done
9+
10+
VRT_PATTERN="tests/playwright/.*(spec\.ts|visual\.ts|test-helpers\.ts)"
11+
OUTPUT_DIR="vrt-file-audits"
12+
13+
rm -rf "$OUTPUT_DIR"
14+
mkdir -p "$OUTPUT_DIR"
15+
16+
prs=$(gh pr list --state open --json number,headRefName,title)
17+
18+
echo "$prs" | jq -c '.[]' | while read -r pr; do
19+
pr_num=$(echo "$pr" | jq -r '.number')
20+
branch=$(echo "$pr" | jq -r '.headRefName')
21+
title=$(echo "$pr" | jq -r '.title')
22+
23+
changed_files=$(gh pr diff "$pr_num" --name-only | grep -E "$VRT_PATTERN")
24+
25+
if [ -n "$changed_files" ]; then
26+
audit_file="$OUTPUT_DIR/pr-${pr_num}.md"
27+
28+
{
29+
echo "# PR #$pr_num: $title"
30+
echo "**Branch:** \`$branch\`"
31+
echo ""
32+
echo "#### [[TODO]] Global Feedback"
33+
echo "- [ ] Reconcile conflicting \`maxDiffPixelRatio\` changes"
34+
echo "- [ ] Ensure consistent masking strategy across all PRs"
35+
echo ""
36+
echo "---"
37+
} > "$audit_file"
38+
39+
pr_diff=$(gh pr diff "$pr_num" --patch)
40+
for file_path in $changed_files; do
41+
{
42+
echo "### \`$file_path\`"
43+
echo "\`\`\`diff"
44+
echo "$pr_diff" | awk -v path="$file_path" '
45+
$0 ~ "diff --git a/"path" " {hunk=1; print; next}
46+
$0 ~ "diff --git a/" {hunk=0}
47+
hunk {print}
48+
'
49+
echo "\`\`\`"
50+
echo ""
51+
} >> "$audit_file"
52+
done
53+
54+
echo " ✅ Logged PR #$pr_num changes to pr-${pr_num}.md"
55+
fi
56+
done
57+
58+
echo "🎉 File-based audit complete. View reports in /$OUTPUT_DIR"

0 commit comments

Comments
 (0)