fix(ci): align visual selectors and nix hashes (#2471)

* fix(ci): align visual selectors and nix hashes

* fix(ci): add strict PR visual verification

* fix(ci): repair visual-home captures

Generated-By: looper 0.8.1 (runner=fixer, agent=opencode)
This commit is contained in:
Marc Chan 2026-05-21 10:45:37 +08:00 committed by GitHub
parent 7b1cc16988
commit c45c5c9764
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 82 additions and 8 deletions

66
.github/workflows/visual-pr-verify.yml vendored Normal file
View file

@ -0,0 +1,66 @@
name: visual-pr-verify
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
permissions:
contents: read
concurrency:
group: visual-pr-verify-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
verify:
name: Strict PR visual tests
if: ${{ !github.event.pull_request.draft }}
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
with:
fetch-depth: 0
- name: Detect visual-relevant changes
id: changes
shell: bash
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
changed_files="$(git diff --name-only "$BASE_SHA" "$HEAD_SHA")"
if printf '%s\n' "$changed_files" | grep -Eq '^(apps/web/|\.github/actions/visual-screenshot/|\.github/workflows/visual-.*\.yml$|e2e/package\.json$|e2e/playwright\.visual\.config\.ts$|e2e/lib/playwright/|e2e/scripts/playwright\.ts$|e2e/scripts/visual-report\.ts$|e2e/ui/visual-.*\.test\.ts$|pnpm-lock\.yaml$)'; then
echo "should_run=true" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "No visual-relevant file changes; skipping strict visual suite."
echo "should_run=false" >> "$GITHUB_OUTPUT"
- name: Prepare visual screenshot environment
if: ${{ steps.changes.outputs.should_run == 'true' }}
uses: ./.github/actions/visual-screenshot
- name: Run strict visual Playwright suite
if: ${{ steps.changes.outputs.should_run == 'true' }}
env:
OD_VISUAL_OUTPUT_DIR: ui/reports/visual-screenshots
run: |
pnpm -C e2e exec tsx scripts/playwright.ts clean
pnpm -C e2e exec playwright test -c playwright.visual.config.ts
- name: Upload visual debug artifact
if: ${{ always() && steps.changes.outputs.should_run == 'true' }}
uses: actions/upload-artifact@v7
with:
name: visual-pr-verify-${{ github.event.pull_request.number }}-${{ github.run_id }}
path: |
e2e/ui/reports/visual-screenshots
e2e/ui/reports/visual-results.json
if-no-files-found: ignore
retention-days: 7

View file

@ -260,7 +260,7 @@ export async function waitForVisualReady(page: Page): Promise<void> {
export async function waitForVisualProjects(page: Page, projects: readonly VisualProject[]): Promise<void> {
if (projects.length === 0) {
await expect(page.getByText('No projects yet — type a prompt to start one.')).toBeVisible();
await expect(page.getByTestId('recent-projects-strip')).toHaveCount(0);
return;
}

View file

@ -34,8 +34,8 @@ test('captures the home plugin filtered surface', async ({ page }) => {
await gotoVisualHome(page);
await page.getByTestId('plugins-home-pill-category-import').click();
await expect(page.getByTestId('plugins-home-pill-category-import')).toHaveAttribute('aria-selected', 'true');
await expect(page.locator('[data-plugin-id="visual-figma-importer"]')).toBeVisible();
await expect(page.getByTestId('plugins-home-clear')).toBeVisible();
await captureVisual(page, 'visual-home-plugin-filter');
});
@ -185,10 +185,10 @@ test('captures the topbar execution switcher surface', async ({ page }) => {
test('captures the avatar menu surface', async ({ page }) => {
await configureVisualPage(page);
await gotoVisualHome(page);
await gotoVisualWorkspace(page);
const menu = await openAvatarMenu(page);
await menu.getByTestId('entry-avatar-language').click();
await expect(menu.getByRole('group', { name: /Language/i })).toBeVisible();
await expect(menu.getByRole('button', { name: /^Settings\b/i })).toBeVisible();
await captureVisual(page, 'visual-avatar-menu');
});
@ -196,9 +196,10 @@ test('captures the avatar menu surface', async ({ page }) => {
test('captures the settings execution surface', async ({ page }) => {
await configureVisualPage(page);
await gotoVisualHome(page);
await gotoVisualWorkspace(page);
const menu = await openAvatarMenu(page);
await menu.getByRole('button', { name: /^Settings$/i }).click();
await menu.getByRole('button', { name: /^Settings\b/i }).click();
const dialog = page.getByRole('dialog');
await expect(dialog).toBeVisible();
await expect(dialog.getByRole('tab', { name: /Local CLI/i })).toBeVisible();
@ -211,9 +212,10 @@ test('captures the settings execution surface', async ({ page }) => {
test('captures the settings BYOK surface', async ({ page }) => {
await configureVisualPage(page);
await gotoVisualHome(page);
await gotoVisualWorkspace(page);
const menu = await openAvatarMenu(page);
await menu.getByRole('button', { name: /^Settings$/i }).click();
await menu.getByRole('button', { name: /^Settings\b/i }).click();
const dialog = page.getByRole('dialog');
await expect(dialog).toBeVisible();
await dialog.getByRole('tab', { name: 'BYOK' }).click();
@ -230,3 +232,9 @@ async function openAvatarMenu(page: Parameters<typeof configureVisualPage>[0]) {
await expect(menu).toBeVisible();
return menu;
}
async function gotoVisualWorkspace(page: Parameters<typeof configureVisualPage>[0]) {
await page.getByTestId('recent-projects-strip').locator('[data-project-id]').first().click();
await expect(page).toHaveURL(/\/projects\//);
await expect(page.getByTestId('chat-composer')).toBeVisible();
}

View file

@ -43,7 +43,7 @@ let
# `nix build .#daemon` will fail with the expected hash printed; copy
# that into `pnpmDepsHash` below. Bump it whenever pnpm-lock.yaml
# changes.
pnpmDepsHash = "sha256-BqnA3aBPHiy+o04atLF6RCZGJKA24qneuqPzV0WH2G8=";
pnpmDepsHash = "sha256-TI7gjjF47YIkLblWjG9flG3E1mg310AI5S4uZ+9B2kI=";
# pnpmDepsHash = lib.fakeHash;
in
stdenv.mkDerivation (finalAttrs: {

View file

@ -30,7 +30,7 @@ let
# `nix build .#web` will fail with the expected hash printed; copy
# that into `pnpmDepsHash` below. Bump it whenever pnpm-lock.yaml
# changes.
pnpmDepsHash = "sha256-BqnA3aBPHiy+o04atLF6RCZGJKA24qneuqPzV0WH2G8=";
pnpmDepsHash = "sha256-TI7gjjF47YIkLblWjG9flG3E1mg310AI5S4uZ+9B2kI=";
# pnpmDepsHash = lib.fakeHash;
in
stdenv.mkDerivation (finalAttrs: {