Merge pull request #380 from trails-cool/stigi/visual-tests-ci

Add visual-tests job to CI
This commit is contained in:
Ullrich Schäfer 2026-05-10 19:27:04 +02:00 committed by GitHub
commit e1e9fc6527
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 79 additions and 21 deletions

View file

@ -110,6 +110,44 @@ jobs:
- run: pnpm install --frozen-lockfile
- run: pnpm build
visual-tests:
name: Visual Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
- run: pnpm install --frozen-lockfile
- name: Cache Playwright browsers
id: playwright-cache
uses: actions/cache@v5
with:
path: ~/.cache/ms-playwright
key: playwright-${{ hashFiles('pnpm-lock.yaml') }}
- name: Install Playwright Chromium
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: pnpm exec playwright install --with-deps chromium
- name: Install Playwright deps only
if: steps.playwright-cache.outputs.cache-hit == 'true'
run: pnpm exec playwright install-deps chromium
- name: Run visual regression tests
run: pnpm --filter @trails-cool/planner test:visual
- name: Upload screenshots on failure
if: failure()
uses: actions/upload-artifact@v7
with:
name: visual-snapshots-diff
path: apps/planner/app/**/__screenshots__/
retention-days: 7
e2e:
name: E2E Tests
needs: build

View file

@ -62,19 +62,19 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.ref || github.event.inputs.branch || github.ref }}
# Use a token with push rights so the commit-back step can push
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v6
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version-file: ".nvmrc"
node-version: 24
cache: "pnpm"
- name: Install dependencies
@ -96,7 +96,7 @@ jobs:
- name: Upload snapshots as artifact
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: visual-snapshots
path: apps/planner/app/**/__screenshots__/

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View file

@ -1,6 +1,6 @@
import { describe, it, expect } from "vitest";
import { render } from "@testing-library/react";
import { page } from "@vitest/browser/context";
import { describe, it, expect, afterEach } from "vitest";
import { render, cleanup } from "@testing-library/react";
import { page } from "vitest/browser";
import { drawElevationChart } from "./elevation-chart-draw";
import type { DrawChartParams } from "./elevation-chart-draw";
@ -46,6 +46,7 @@ function ChartFixture({ params }: { params: DrawChartParams }) {
}
describe("drawElevationChart visual regression", () => {
afterEach(() => cleanup());
it("renders plain color mode", async () => {
render(<ChartFixture params={BASE_PARAMS} />);
await expect(page.getByTestId("chart")).toMatchScreenshot("plain.png");

View file

@ -11,7 +11,7 @@
"lint": "eslint .",
"test": "vitest run",
"test:visual": "vitest run --config vitest.browser.config.ts",
"test:visual:update": "vitest run --config vitest.browser.config.ts --update-snapshots"
"test:visual:update": "vitest run --config vitest.browser.config.ts -u"
},
"dependencies": {
"@codemirror/commands": "^6.10.3",
@ -55,6 +55,7 @@
"@types/react-dom": "catalog:",
"@types/ws": "^8.18.1",
"@vitest/browser": "^4.1.5",
"@vitest/browser-playwright": "^4.1.5",
"leaflet.markercluster": "^1.5.3",
"pino-pretty": "^13.1.3",
"tailwindcss": "catalog:",

View file

@ -1,5 +1,6 @@
import { defineConfig } from "vitest/config";
import { resolve } from "node:path";
import { playwright } from "@vitest/browser-playwright";
// Separate config for Vitest browser visual regression tests.
// Run with: pnpm --filter @trails-cool/planner test:visual
@ -10,9 +11,6 @@ import { resolve } from "node:path";
// (.github/workflows/update-visual-snapshots.yml), triggered manually or
// by adding the `update-snapshots` label to a PR.
export default defineConfig({
esbuild: {
jsx: "automatic",
},
resolve: {
alias: {
"~": resolve(import.meta.dirname, "app"),
@ -23,10 +21,7 @@ export default defineConfig({
include: ["app/**/*.browser.test.{ts,tsx}"],
browser: {
enabled: true,
// Provider is resolved from whichever @vitest/browser peer is installed.
// playwright is the default when @playwright/test is available.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
provider: "playwright" as any,
provider: playwright(),
instances: [{ browser: "chromium" }],
},
},

33
pnpm-lock.yaml generated
View file

@ -196,7 +196,7 @@ importers:
version: 8.0.10(@types/node@25.6.2)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.8.4)
vitest:
specifier: ^4.1.5
version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.6.2)(jsdom@29.1.1(canvas@3.2.3))(vite@8.0.10(@types/node@25.6.2)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.8.4))
version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.6.2)(@vitest/browser-playwright@4.1.5)(jsdom@29.1.1(canvas@3.2.3))(vite@8.0.10(@types/node@25.6.2)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.8.4))
apps/journal:
dependencies:
@ -568,7 +568,10 @@ importers:
version: 8.18.1
'@vitest/browser':
specifier: ^4.1.5
version: 4.1.5(vite@8.0.10(@types/node@25.6.2)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.8.4))(vitest@4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.6.2)(jsdom@29.1.1(canvas@3.2.3))(vite@8.0.10(@types/node@25.6.2)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.8.4)))
version: 4.1.5(vite@8.0.10(@types/node@25.6.2)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.8.4))(vitest@4.1.5)
'@vitest/browser-playwright':
specifier: ^4.1.5
version: 4.1.5(playwright@1.59.1)(vite@8.0.10(@types/node@25.6.2)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.8.4))(vitest@4.1.5)
leaflet.markercluster:
specifier: ^1.5.3
version: 1.5.3(leaflet@1.9.4)
@ -3936,6 +3939,12 @@ packages:
peerDependencies:
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
'@vitest/browser-playwright@4.1.5':
resolution: {integrity: sha512-CWy0lBQJq97nionyJJdnaU4961IXTl43a7UCu5nHy51IoKxAt6PVIJLo+76rVl7KOOgcWHNkG4kbJu/pW7knvA==}
peerDependencies:
playwright: '*'
vitest: 4.1.5
'@vitest/browser@4.1.5':
resolution: {integrity: sha512-iCDGI8c4yg+xmjUg2VsygdAUSIIB4x5Rht/P68OXy1hPELKXHDkzh87lkuTcdYmemRChDkEpB426MmDjzC0ziA==}
peerDependencies:
@ -11741,7 +11750,20 @@ snapshots:
dependencies:
vite: 8.0.10(@types/node@25.6.2)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.8.4)
'@vitest/browser@4.1.5(vite@8.0.10(@types/node@25.6.2)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.8.4))(vitest@4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.6.2)(jsdom@29.1.1(canvas@3.2.3))(vite@8.0.10(@types/node@25.6.2)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.8.4)))':
'@vitest/browser-playwright@4.1.5(playwright@1.59.1)(vite@8.0.10(@types/node@25.6.2)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.8.4))(vitest@4.1.5)':
dependencies:
'@vitest/browser': 4.1.5(vite@8.0.10(@types/node@25.6.2)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.8.4))(vitest@4.1.5)
'@vitest/mocker': 4.1.5(vite@8.0.10(@types/node@25.6.2)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.8.4))
playwright: 1.59.1
tinyrainbow: 3.1.0
vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.6.2)(@vitest/browser-playwright@4.1.5)(jsdom@29.1.1(canvas@3.2.3))(vite@8.0.10(@types/node@25.6.2)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.8.4))
transitivePeerDependencies:
- bufferutil
- msw
- utf-8-validate
- vite
'@vitest/browser@4.1.5(vite@8.0.10(@types/node@25.6.2)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.8.4))(vitest@4.1.5)':
dependencies:
'@blazediff/core': 1.9.1
'@vitest/mocker': 4.1.5(vite@8.0.10(@types/node@25.6.2)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.8.4))
@ -11750,7 +11772,7 @@ snapshots:
pngjs: 7.0.0
sirv: 3.0.2
tinyrainbow: 3.1.0
vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.6.2)(jsdom@29.1.1(canvas@3.2.3))(vite@8.0.10(@types/node@25.6.2)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.8.4))
vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.6.2)(@vitest/browser-playwright@4.1.5)(jsdom@29.1.1(canvas@3.2.3))(vite@8.0.10(@types/node@25.6.2)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.8.4))
ws: 8.20.0
transitivePeerDependencies:
- bufferutil
@ -16125,7 +16147,7 @@ snapshots:
tsx: 4.21.0
yaml: 2.8.4
vitest@4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.6.2)(jsdom@29.1.1(canvas@3.2.3))(vite@8.0.10(@types/node@25.6.2)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.8.4)):
vitest@4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.6.2)(@vitest/browser-playwright@4.1.5)(jsdom@29.1.1(canvas@3.2.3))(vite@8.0.10(@types/node@25.6.2)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.8.4)):
dependencies:
'@vitest/expect': 4.1.5
'@vitest/mocker': 4.1.5(vite@8.0.10(@types/node@25.6.2)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.8.4))
@ -16150,6 +16172,7 @@ snapshots:
optionalDependencies:
'@opentelemetry/api': 1.9.1
'@types/node': 25.6.2
'@vitest/browser-playwright': 4.1.5(playwright@1.59.1)(vite@8.0.10(@types/node@25.6.2)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.8.4))(vitest@4.1.5)
jsdom: 29.1.1(canvas@3.2.3)
transitivePeerDependencies:
- msw