Files
webstudio/.github/workflows/visual-regression.yml
Oleg Isonen e33a269802 fix: show unsupported browser warning in a dialog (#6332)
The unsupported-browser warning now uses the Builder’s regular modal
dialog. Users can acknowledge it and continue, while the small-window
state remains a blocking alert.

Closes #6331

## Implementation

- Render the existing browser guidance and links inside the
design-system `Dialog`.
- Set the dialog to modal and keep close, Escape, outside-click, and
**Continue** dismissal behavior.
- Keep unsupported-browser detection, preview-mode suppression, and
Builder-ready timing unchanged.
- Keep the small-window alert blocking and remove its now-unused
dismissible-alert path.
- Add a focused Storybook state and browser regression test.
- Increase Main and Visual Regression workflow job timeouts from five to
seven minutes after otherwise healthy jobs were cancelled during slow
dependency setup.
- Run failed E2E diagnostics and service teardown through the backend
shell script so cleanup does not depend on pnpm remaining on `PATH`
after cancellation.
- Route `safe-to-deploy` label events through one reusable gate that
starts both Main and Vercel workflows.
- Remove revision-specific deployment and visual approval labels in one
synchronization-only cleanup job.
- Reduce expected skipped jobs from 6 to 1 on a normal branch update and
from 17 to 3 on a visual approval label event.

No migrations or compatibility changes are required.

## Todo

- [x] Replace the unsupported-browser alert with a modal dialog.
- [x] Preserve the small-window blocking alert.
- [x] Add regression coverage.
- [x] Give CI enough time for slower dependency setup.
- [x] Keep E2E cleanup available after runner cancellation.
- [x] Simplify label-triggered Main and Vercel workflows.
- [x] Consolidate revision-specific approval cleanup.
- [x] Review documentation impact: no product documentation changes are
needed for this presentation-only warning update.
- [x] Review fixture impact: no fixture inputs or generated fixture
outputs are affected.

## Verification

- [x] Focused browser regression test
- [x] Builder typecheck
- [x] Oxlint on changed files
- [x] Oxfmt and `git diff --check`
- [x] Workflow YAML parsing and backend shell syntax checks
- [x] Workflow trigger, nesting, permissions, and reusable-job contract
assertions
- [x] Prettier checks for changed workflows
- [ ] Storybook screenshot review was not run locally because explicit
visual-test approval was not available.

## Known limitations

- The visual state was not screenshot-tested locally.
- The reduced `pull_request_target` fan-out takes effect after these
workflow files reach the default branch, because GitHub loads those
workflow definitions from that branch.
2026-09-04 15:06:34 +02:00

149 lines
5.8 KiB
YAML

name: Visual regression
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened, labeled]
pull_request_target:
types: [opened, synchronize, reopened, labeled]
concurrency:
# A synchronize action emits both pull_request and pull_request_target. Keep
# same-repository and fork runs separate.
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.number || github.sha }}
cancel-in-progress: true
permissions:
contents: read
jobs:
visual-regression:
# Same-repository PRs use the unprivileged event. Organization members'
# fork PRs are trusted; every other fork revision needs safe-to-deploy.
if: |
github.event_name == 'push' ||
(
github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name == github.repository &&
(
github.event.action != 'labeled' ||
github.event.label.name == 'visual-change-approved'
)
) ||
(
github.event_name == 'pull_request_target' &&
github.event.pull_request.head.repo.full_name != github.repository &&
(
(
contains(fromJSON('["OWNER", "MEMBER"]'), github.event.pull_request.author_association) &&
(
github.event.action != 'labeled' ||
github.event.label.name == 'visual-change-approved'
)
) ||
(
github.event.action == 'labeled' &&
(
github.event.label.name == 'safe-to-deploy' ||
(
github.event.label.name == 'visual-change-approved' &&
contains(github.event.pull_request.labels.*.name, 'safe-to-deploy')
)
)
)
)
)
timeout-minutes: 7
runs-on: ubuntu-latest
environment:
name: development
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha || github.sha }}
allow-unsafe-pr-checkout: ${{ github.event_name == 'pull_request_target' }}
- uses: ./.github/actions/submodules-checkout
with:
submodules-ssh-key: ${{ secrets.PRIVATE_GITHUB_DEPLOY_TOKEN }}
- uses: ./.github/actions/ci-setup
- name: Cache Playwright Chromium
uses: actions/cache@v5
with:
path: |
~/.cache/ms-playwright/chromium-*
~/.cache/ms-playwright/chromium_headless_shell-*
~/.cache/ms-playwright/ffmpeg-*
key: playwright-chromium-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
- name: Install pinned visual browser
run: |
pnpm --filter @webstudio-is/sdk-components-animation exec playwright install chromium
playwright_browser_path="$(pnpm --filter @webstudio-is/sdk-components-animation exec node -e 'console.log(require("playwright").chromium.executablePath())')"
test -x "$playwright_browser_path"
echo "WEBSTUDIO_BROWSER_PATH=$playwright_browser_path" >> "$GITHUB_ENV"
- name: Install visual comparison dependencies
run: sudo apt-get install --no-install-recommends -y tesseract-ocr
- name: Resolve visual baseline cache key
id: visual-cache
env:
BASE_REF: ${{ github.event.pull_request.base.sha || github.event.before || 'origin/main' }}
run: |
echo "base=$(git merge-base HEAD "$BASE_REF")" >> "$GITHUB_OUTPUT"
echo "head=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
browser_version="$("$WEBSTUDIO_BROWSER_PATH" --version)"
runtime_hash="$(pnpm tsx scripts/visual-regression/runtime-hash.ts "$browser_version")"
echo "runtime=$runtime_hash" >> "$GITHUB_OUTPUT"
- name: Restore visual baseline screenshots
uses: actions/cache/restore@v5
with:
path: .visual-regression-cache/${{ steps.visual-cache.outputs.base }}-*
key: visual-baseline-v1-${{ runner.os }}-${{ steps.visual-cache.outputs.base }}-${{ steps.visual-cache.outputs.runtime }}
- name: Compare portable stories
env:
BASE_REF: ${{ github.event.pull_request.base.sha || github.event.before || 'origin/main' }}
APPROVE_VISUAL_CHANGES: ${{ github.event_name == 'push' || (github.event.action == 'labeled' && github.event.label.name == 'visual-change-approved') }}
run: |
args=(--base "$BASE_REF")
if [[ "$APPROVE_VISUAL_CHANGES" == "true" ]]; then
args+=(--approve-visual-changes)
fi
pnpm visual-regression -- "${args[@]}"
- name: Validate visual comparison report
if: always()
run: |
node -e 'const report = require("./.visual-regression/report/report.json"); if (!Array.isArray(report.comparisons) || report.comparisons.length === 0) throw new Error("Visual comparison report has no story results.")'
- name: Upload visual comparison report
if: always()
uses: actions/upload-artifact@v7
with:
name: visual-regression-report
path: .visual-regression/report
if-no-files-found: error
retention-days: 14
- name: Cache visual screenshots for dependent revisions
if: always()
uses: actions/cache/save@v5
with:
path: .visual-regression-cache/${{ steps.visual-cache.outputs.head }}-*
key: visual-baseline-v1-${{ runner.os }}-${{ steps.visual-cache.outputs.head }}-${{ steps.visual-cache.outputs.runtime }}
- name: Cache generated baseline screenshots
if: always()
uses: actions/cache/save@v5
with:
path: .visual-regression-cache/${{ steps.visual-cache.outputs.base }}-*
key: visual-baseline-v1-${{ runner.os }}-${{ steps.visual-cache.outputs.base }}-${{ steps.visual-cache.outputs.runtime }}