Ta strona to tłumaczenie maszynowe, które nie zostało jeszcze zweryfikowane. Wersja angielska jest wiążąca.
Pierwsze kroki
Instalacja
Plugin wymaga Node.js 20.9 lub nowszego. Bibliotekę zainstalujesz swoim ulubionym menedżerem pakietów:
# npm
npm install --save-dev @pixsame/cypress-plugin-visual-regression-diff
# yarn
yarn add -D @pixsame/cypress-plugin-visual-regression-diff
# pnpm
pnpm add -D @pixsame/cypress-plugin-visual-regression-diff
Następnie musisz zaimportować bibliotekę:
- najpierw w pliku support (domyślnie znajduje się w
cypress/support/index.js):
// typescript / ES6
import '@pixsame/cypress-plugin-visual-regression-diff';
// javascript
require('@pixsame/cypress-plugin-visual-regression-diff');
- a potem w
cypress.config.js(lubcypress.config.ts):
// typescript / ES6
import { defineConfig } from 'cypress';
import { initPlugin as initVisualRegressionPlugin } from '@pixsame/cypress-plugin-visual-regression-diff/plugins';
export default defineConfig({
// initPlugin trzeba wywołać w tej sekcji, w której jest używany: e2e lub component
e2e: {
setupNodeEvents(on, config) {
initVisualRegressionPlugin(on, config);
},
},
component: {
setupNodeEvents(on, config) {
initVisualRegressionPlugin(on, config);
},
},
});
To wszystko - teraz zobaczmy, jak korzystać z biblioteki, w sekcji o użyciu.
Użycie
Po instalacji wystarczy, że w teście napiszesz:
cy.get('.an-element-of-your-choice').matchImage();
Albo, jeśli chcesz zrobić zrzut ekranu całego dokumentu:
cy.matchImage();
Komenda matchImage robi zrzut ekranu i porównuje go z obrazem z poprzedniego uruchomienia. W razie regresji test nie przejdzie, a ty dostaniesz przycisk "See comparison", który pokaże, gdzie leży problem.
Przykład
Nadal masz problemy z instalacją? Zajrzyj do katalogu examples w tym repozytorium, żeby zobaczyć, jak użyć tego pluginu w środowisku Cypress przy testach e2e lub komponentowych w twoim projekcie.
Automatyczne usuwanie nieużywanych obrazów
Warto usuwać zrzuty ekranu wygenerowane przez plugin, z których nie korzysta już żaden test. Włącz tę funkcję przez zmienną expose i ciesz się odzyskanym miejscem na dysku 🚀:
# Cypress 15.10+
npx cypress run --expose "pluginVisualRegressionCleanupUnusedImages=true"
# Cypress <15.10 (przestarzałe od 15.10, usunięte w 16)
npx cypress run --env "pluginVisualRegressionCleanupUnusedImages=true"
Manifest uruchomienia (integracja z CI)
Od wersji 1.1 każde uruchomienie zapisuje manifest JSON z listą wszystkich porównań matchImage: z którego testu pochodzi dane porównanie, czy przeszło, oblało, utworzyło lub zaktualizowało swój baseline, jaki był współczynnik diffa oraz ścieżki (względem projektu) do plików baseline, .actual.png i .diff.png. Obok wpisów zapisuje też, gdzie odbyło się uruchomienie (dostawca CI, repozytorium, commit, pull request, id uruchomienia) i z czym zostało uruchomione (przeglądarka, pliki spec, plik konfiguracyjny, opcje pluginu), dzięki czemu narzędzie czytające plik może zaraportować wynik do właściwego PR-a, pobrać właściwy artefakt albo ponownie uruchomić te same porównania. Manifest jest przeznaczony dla narzędzi CI (komentarze w PR-ach, dashboardy do przeglądu, boty zatwierdzające), które działają po zakończeniu Cypressa.
Format to otwarty, niezależny od runnera standard oparty na CTRF i udokumentowany na visual-regression.dev/manifest. Pakiet npm visual-regression-manifest dostarcza JSON Schema, typy TypeScript, walidujący reader, merger dla manifestów z całego uruchomienia oraz konwertery dla narzędzi, które nie piszą własnego manifestu (toHaveScreenshot z Playwrighta, Vitest, jest-image-snapshot, BackstopJS, raporty CTRF, zwykłe pliki obrazów). Swoje narzędzia buduj na tym pakiecie, a nie na tym pluginie.
Domyślnie plik ląduje obok pozostałych artefaktów Cypressa, w <screenshotsFolder>/visual-regression-manifest.<testingType>.json (np. cypress/screenshots/visual-regression-manifest.e2e.json). Wgraj go razem z katalogiem snapshotów jako artefakt CI.
{
"reportFormat": "visual-regression-manifest",
"specVersion": "1.0.0-beta.1",
"createdAt": "2026-09-22T10:00:00.000Z",
"updatedAt": "2026-09-22T10:03:12.481Z",
"projectRoot": "/home/runner/work/app/app",
"platform": {
"osPlatform": "linux",
"arch": "x64",
"osRelease": "6.8.0-1021-azure",
},
"ci": {
"provider": "github",
"repositoryName": "acme/app",
"repositoryUrl": "https://github.com/acme/app",
"commit": "0f1e2d…", // przy zdarzeniach pull_request: commit merge
"ref": "refs/pull/42/merge",
"branchName": "feat/new-header",
"pullRequest": {
"number": 42,
"headSha": "9a8b7c…",
"headRef": "feat/new-header",
"baseRef": "main",
},
"event": "pull_request",
"buildId": "1234567890",
"buildName": "CI",
"buildUrl": "https://github.com/acme/app/actions/runs/1234567890/attempts/1",
"runAttempt": "1",
"job": "e2e",
"workspace": "/home/runner/work/app/app",
},
"tool": {
"name": "@pixsame/cypress-plugin-visual-regression-diff",
"version": "1.1.0",
}, // co zapisało plik
"options": { "updateImages": "failures-only" }, // globalne opcje pluginVisualRegression*, bez prefiksu
"runner": {
"name": "cypress",
"version": "16.1.0",
"testingType": "e2e",
"mode": "run",
"configFile": "cypress.config.ts",
"browser": {
"name": "chrome",
"version": "130.0.0.0",
"family": "chromium",
"headless": true,
},
"specs": ["cypress/e2e/home.cy.ts"],
"specPattern": "cypress/e2e/**/*.cy.{js,jsx,ts,tsx}",
"baseUrl": "http://localhost:3000",
"viewport": { "width": 1280, "height": 720 },
},
"summary": {
"entries": 1,
"passed": 0,
"failed": 1,
"missingBaseline": 0,
"created": 0,
"updated": 0,
"approved": 0,
"needsHuman": 1,
},
"entries": [
{
"name": "home page renders_#0",
"test": {
"filePath": "cypress/e2e/home.cy.ts",
"titlePath": ["home page", "renders"],
"retry": 0,
},
"status": "failed",
"comparison": { "diffRatio": 0.0231, "threshold": 0.01 },
"images": {
"baseline": {
"path": "cypress/e2e/__image_snapshots__/home page renders_#0.png",
"width": 1280,
"height": 720,
},
"actual": {
"path": "cypress/e2e/__image_snapshots__/home page renders_#0.actual.png",
"width": 1280,
"height": 720,
},
"diff": {
"path": "cypress/e2e/__image_snapshots__/home page renders_#0.diff.png",
},
},
"baselineWritten": false,
"recordedAt": "2026-09-22T10:03:12.480Z",
"platform": {
"osPlatform": "linux",
"arch": "x64",
"browser": {
"name": "chrome",
"version": "130.0.0.0",
"family": "chromium",
"headless": true,
},
},
"viewport": { "width": 1280, "height": 720 },
"options": {
"imagesPath": "{spec_path}/__image_snapshots__",
"maxDiffThreshold": 0.01,
"diffConfig": {},
"createMissingImages": true,
"updateImages": false,
"forceDeviceScaleFactor": true,
"screenshotConfig": {},
},
"renderer": {
"backend": "native",
"browser": "chrome",
"browserVersion": "130.0.0.0",
},
"hashes": {
"baseline": "3b7e…", // sha256 plików wymienionych w `images`, tylko dla plików, które istnieją
"actual": "9d21…",
"diff": "c0ff…",
},
"message": "2310 pixels (2.31%) differ; the limit is 1%.",
},
],
}
status | znaczenie | images.actual.path | images.diff.path |
|---|---|---|---|
passed | w granicach progu | null | null |
failed | powyżej progu, .actual.png i .diff.png zostają do przeglądu | ścieżka | ścieżka |
missing-baseline | brak baseline'u i createMissingImages: false | ścieżka | null |
created | brak baseline'u, .actual.png stał się baseline'em | null | null |
updated | baseline nadpisany (updateImages: true lub 'failures-only') | null | null |
approved | baseline zastąpiony z UI przeglądu w trybie headed ("Replace image") | null | null |
Pola na poziomie uruchomienia:
| pole | znaczenie |
|---|---|
createdAt, updatedAt | kiedy uruchomienie się zaczęło i kiedy plik został ostatnio przepisany (ISO 8601) |
platform | osPlatform (linux, darwin, win32), arch, osRelease jądra oraz, w trybie run, czytelna dla człowieka osVersion maszyny, która uruchomiła testy |
tool | co zapisało plik: ten plugin i jego wersja |
ci | wykrywane ze zmiennych środowiskowych na GitHub Actions i GitLab CI; { "provider": null } na innych systemach CI (ustawione jest tylko CI), null poza CI. Ze środowiska kopiowane są wyłącznie wymienione klucze |
options | każda opcja pluginVisualRegression* z expose/env, bez prefiksu, wartości takie, jak skonfigurowano (wartości z CLI pozostają stringami) |
runner | co uruchomiło testy. mode to run lub open; browser to uruchomiona przeglądarka; specs jest znane tylko w trybie run; configFile i specs są względne wobec projectRoot. Uruchomienia w Cypress Cloud dostają też cloud.runUrl, group, tag, parallel |
summary | liczba wpisów dla każdego statusu; needsHuman to failed + missingBaseline, więc konsument może podjąć decyzję bez czytania wpisów |
Uwagi dla konsumentów:
- Wszystkie ścieżki są względne wobec
projectRooti używają separatora/; mogą wskazywać poza projekt (../…), gdyimagesPathjest bezwzględna.projectRootto projekt Cypressa, niekoniecznie katalog główny repozytorium:ci.workspaceto katalog checkoutu, więcrelative(ci.workspace, projectRoot)daje prefiks ścieżki wewnątrz repozytorium. failedimissing-baselineto wpisy, które wymagają człowieka: skopiowanieimages.actual.pathw miejsceimages.baseline.pathje zatwierdza.baselineWrittenmówi, czy drzewo robocze zmieniło się dla danego zrzutu ekranu, niezależnie odstatus.- Wpis, którego porównanie wykryło różnice, zawiera
extra.pixsame.analysis(o ile opcjaanalysisnie jest wyłączona):label(content-change,moved,noise-likelubsize-change), jednozdaniowydescription, zmienione obszaryregionswraz z ich ramkami, wykryte przesunięcieshiftorazbundleKey, który jest taki sam dla wpisów zmienionych w ten sam sposób. Funkcjabundle()z@pixsame/analysisgrupuje wpisy według niego. - Każdy wpis niesie własne
platformiviewport, więc po sklejeniu kilku manifestów da się odróżnić wpisy z macierzy maszyn.optionswe wpisie to dokładne wejściematchImage(imagesPathzachowuje nierozwinięte tokeny);comparisonto wynik. platformto miejsce, gdzie uruchomił się test,rendererto miejsce, skąd pochodzą piksele. Dziś każdy zrzut ekranu robi przeglądarka sterowana przez Cypressa, więcrenderer.backendtonative, arenderer.browserpowtarzaplatform.browser.name. Pole istnieje po to, żeby renderer inny niż lokalna przeglądarka (przypięty obraz Dockera, hostowany renderer) mógł się zidentyfikować przezrendererVersioniimageDigest, a uruchomienie, które musiało wrócić do lokalnej przeglądarki, mogło zostać oznaczonerenderer.fallback: true. Wpisy z różnymi rendererami traktuj jako różne baseline'y.hasheszawiera sha256 każdego pliku zimages, który istnieje na dysku. Użyj ich, żeby pominąć ponowne wgrywanie niezmienionych baseline'ów, sprawdzić, czy artefakt zgadza się z manifestem, albo wykryć dwa wpisy, które dały identyczne piksele.- Mapowanie na pull requesta: użyj
ci.repositoryNameici.pullRequest.number. Przy zdarzeniachpull_requestna GitHubieci.committo tymczasowy commit merge, więc wszystko, co pushuje do gałęzi PR-a, musi używaćci.pullRequest.headSha/headRef. Artefakt workflow znajdziesz przezci.buildIdici.runAttempt. - Odtworzenie uruchomienia:
npx cypress run --<runner.testingType> --browser <runner.browser.name> --config-file <runner.configFile> --spec <runner.specs joined with ,>, plus jedno--expose "pluginVisualRegression<Key>=<value>"na każdy wpis zoptions. Manifest przechowuje je jako osobne części, a nie jako string polecenia, więc cytowanie w shellu i menedżer pakietów pozostają twoim wyborem. - Plik jest przepisywany po każdym porównaniu, więc jest kompletny nawet wtedy, gdy uruchomienie zostanie przerwane. Gdy pliki spec działają równolegle na kilku maszynach, każda maszyna zapisuje własny manifest;
**/visual-regression-manifest{,.*}.json(MANIFEST_FILE_GLOBw pakiecie) znajduje je wszystkie w artefakcie, areadManifestFiles+mergeManifestszvisual-regression-manifestłączą je w jedną listę, w której późniejsza próba CI wygrywa z wcześniejszą. - Tylko blok
runneri kluczeoptionssą specyficzne dla Cypressa; reszta formatu jest wspólna dla wszystkich pozostałych narzędzi zapisujących manifest.specVersiontrzyma się semver, a jego major jest podbijany wyłącznie przy łamiących zmianach formatu; konsumenci ignorują klucze, których nie znają. Ponieważ nazwy pól są zgodne z CTRF,toCtrfz pakietu zamienia manifest w raport CTRF dla narzędzi, które czytają ten format. - Typy
Manifest*są dla wygody re-eksportowane z@pixsame/cypress-plugin-visual-regression-diff/plugins; to te same typy, co wvisual-regression-manifest.
Manifest jest domyślnie włączony. Żeby zapisać go gdzie indziej (ścieżki są rozwiązywane względem katalogu głównego projektu) albo go wyłączyć, użyj klucza pluginVisualRegressionManifestPath (false go wyłącza, nic innego się nie zmienia):
# Cypress 15.10+
npx cypress run --expose "pluginVisualRegressionManifestPath=reports/visual-regression.json"
npx cypress run --expose "pluginVisualRegressionManifestPath=false"
# Cypress <15.10 (przestarzałe od 15.10, usunięte w 16)
npx cypress run --env "pluginVisualRegressionManifestPath=false"
Gdy z własną ścieżką uruchamiasz zarówno testy e2e, jak i komponentowe, sam dodaj do niej typ testów; w przeciwnym razie uruchomienia nadpisywałyby sobie nawzajem plik.
Konfiguracja
Plugin skonfigurujesz:
- przekazując konfigurację jako argument komendy
matchImage:
cy.matchImage({
// konfiguracja zrzutu ekranu, przekazywana bezpośrednio do metody screenshot w Cypress: https://docs.cypress.io/api/cypress-api/screenshot-api#Arguments
// domyślnie: { }
screenshotConfig: {
blackout: ['.element-to-be-blackouted'],
},
// opcje pixelmatch, zobacz: https://www.npmjs.com/package/pixelmatch#pixelmatchimg1-img2-output-width-height-options
// domyślnie: {} - domyślne ustawienia pixelmatch, w tym includeAA: false (piksele krawędzi z anti-aliasingiem nie są liczone jako różnice)
diffConfig: {
threshold: 0.01,
},
// czy automatycznie tworzyć brakujące obrazy baseline
// domyślnie: true
createMissingImages: false,
// czy automatycznie aktualizować obrazy - przydatne na CI
// - true: nadpisuj wszystkie baseline'y bez porównywania (najszybsze)
// - 'failures-only': najpierw porównaj, aktualizuj baseline tylko wtedy, gdy diff przekroczy próg
// - false: nigdy nie aktualizuj baseline'ów automatycznie
// domyślnie: false
updateImages: true,
// ścieżka katalogu, w którym będą zapisywane zrzuty ekranu
// ścieżki względne są rozwiązywane względem katalogu głównego projektu
// ścieżki bezwzględne (zarówno na unixie, jak i na Windowsie) są obsługiwane
// separatory ścieżek plugin znormalizuje zależnie od systemu; zawsze używaj / jako separatora, np.: C:/my-directory/nested dla notacji dyskowej w stylu Windows
// w ścieżce można użyć tych zmiennych:
// - {spec_path} - ścieżka względna od katalogu głównego projektu do katalogu bieżącego pliku spec (np. `/src/components/my-tested-component`); musi stanowić cały segment ścieżki
// - {platform} - `{os}-{browser}`, np. `linux-chrome`, `darwin-electron`, `win32-firefox`
// - {os} - Cypress.platform: `linux`, `darwin` albo `win32`
// - {browser} - przeglądarka, w której Cypress uruchamia spec (Cypress.browser.name): `electron`, o ile `--browser` nie mówi inaczej, `chrome`, `firefox`, `edge`, ...
// {platform}, {os} i {browser} mogą też być częścią segmentu (`shots-{platform}`); `matchAgainstPath` nigdy nie jest rozwijana
// używaj ich, żeby trzymać osobno baseline'y z różnych systemów/przeglądarek, zobacz "Osobne baseline'y dla każdej platformy" poniżej
// domyślnie: '{spec_path}/__image_snapshots__'
imagesPath: 'this-might-be-your-custom/maybe-nested-directory',
// maksymalny próg, powyżej którego test ma nie przejść
// domyślnie: 0.01
maxDiffThreshold: 0.1,
// opisuje, co się zmieniło, gdy porównanie wykryje różnice, np. "Everything below y=340 moved down by 12 px."
// zdanie kończy komunikat błędu, jest widoczne w karuzeli przeglądu i trafia do manifestu przebiegu
// nigdy nie wpływa na to, czy porównanie przechodzi
// domyślnie: true
analysis: false,
// wymusza ustawienie współczynnika skalowania na wartość "1"
// pomaga, gdy zrzuty ekranu są skalowane 2x na ekranach o wysokiej gęstości, np. Mac Retina
// domyślnie: true
forceDeviceScaleFactor: false,
// włącza preset deterministycznego renderowania, zobacz "Reducing cross-OS rendering noise" poniżej
// dla pojedynczego wywołania kontroluje tylko CSS wstrzykiwany do strony na czas zrzutu ekranu (kursor, animacje, paski przewijania);
// przełączniki przeglądarki działają na poziomie całego procesu i zależą od globalnej opcji pluginVisualRegressionDeterministicRendering
// domyślnie: true
deterministicRendering: false,
// tytuł używany do nazwania pliku obrazu
// domyślnie: Cypress.currentTest.titlePath (tytuł twojego testu)
title: `${Cypress.currentTest.titlePath.join(' ')} (${Cypress.browser.displayName})`,
// ścieżka do własnego obrazu, z którym ma być robione porównanie
// zamiast porównywania z obrazem z poprzedniego uruchomienia
// domyślnie: undefined
matchAgainstPath: '/path/to/reference-image.png',
});
- przez globalną konfigurację expose (Cypress 15.10+). Nazwy kluczy konfiguracji są takie same jak klucze obiektu konfiguracji powyżej, tylko z dodanym prefiksem
pluginVisualRegression, np.:
# Cypress 15.10+
npx cypress run --expose "pluginVisualRegressionUpdateImages=true,pluginVisualRegressionDiffConfig={\"threshold\":0.01}"
# Cypress <15.10 (przestarzałe od 15.10, usunięte w 16)
npx cypress run --env "pluginVisualRegressionUpdateImages=true,pluginVisualRegressionDiffConfig={\"threshold\":0.01}"
Kilka opcji przekazuj w jednej fladze, rozdzielone przecinkami. Gdy --expose (lub --env) powtórzy się, Cypress weźmie pod uwagę tylko ostatnie wystąpienie.
// cypress.config.ts (Cypress 15.10+)
import { defineConfig } from 'cypress';
export default defineConfig({
expose: {
pluginVisualRegressionUpdateImages: true,
pluginVisualRegressionDiffConfig: { threshold: 0.01 },
},
});
// cypress.config.ts (Cypress <15.10, przestarzałe w nowszych wersjach)
import { defineConfig } from 'cypress';
export default defineConfig({
env: {
pluginVisualRegressionUpdateImages: true,
pluginVisualRegressionDiffConfig: { threshold: 0.01 },
},
});
Więcej sposobów ustawiania zmiennych expose znajdziesz tutaj.
Osobne baseline'y dla każdej platformy
Chrome zostawia rasteryzację tekstu systemowi operacyjnemu, więc ta sama strona nigdy nie renderuje się bajt w bajt identycznie na macOS, Linuksie i Windowsie, a do tego przeglądarki różnią się jeszcze między sobą. Baseline nagrany na Macu developera rzadko więc pasuje do zrzutu ekranu tej samej strony zrobionego przez linuksowy job CI. imagesPath rozumie trzy tokeny, które trzymają baseline'y różnych platform osobno, zamiast pozwalać im się wzajemnie nadpisywać:
| token | rozwija się do |
|---|---|
{os} | Cypress.platform: linux, darwin albo win32 |
{browser} | Cypress.browser.name, czyli przeglądarka, w której Cypress uruchamia spec: electron, o ile nie przekażesz --browser, w przeciwnym razie chrome, firefox, edge, ... |
{platform} | {os}-{browser}, np. linux-chrome, darwin-electron |
Każdy zrzut ekranu robi przeglądarka sterowana przez Cypressa, więc {browser} to właśnie ta przeglądarka; cypress run bez --browser używa Electrona, dlatego katalog nazywa się darwin-electron, a nie darwin-chrome, chyba że CI przekaże --browser chrome.
Tokeny są opcjonalne: domyślny imagesPath to nadal {spec_path}/__image_snapshots__, czyli jeden zestaw baseline'ów dla wszystkich. To właściwy układ, gdy każde uruchomienie odbywa się na tym samym systemie i w tej samej przeglądarce (na przykład w kontenerze, zobacz FAQ) albo gdy pozostałe różnice mieszczą się w twoim maxDiffThreshold. Żeby podzielić baseline'y według platformy, dodaj token globalnie:
# Cypress 15.10+
npx cypress run --expose "pluginVisualRegressionImagesPath={spec_path}/__image_snapshots__/{platform}"
# Cypress <15.10 (przestarzałe od 15.10, usunięte w 16)
npx cypress run --env "pluginVisualRegressionImagesPath={spec_path}/__image_snapshots__/{platform}"
// cypress.config.ts (Cypress 15.10+; na Cypress <15.10 użyj `env` zamiast `expose`)
export default defineConfig({
expose: {
pluginVisualRegressionImagesPath:
'{spec_path}/__image_snapshots__/{platform}',
},
});
cypress/e2e/__image_snapshots__/
├── darwin-electron/ # to, co `cypress open` zapisuje na Macu
│ └── home page renders_#0.png
└── linux-chrome/ # to, co zapisuje twój job CI
└── home page renders_#0.png
Co trzeba ustalić po podziale:
- Które baseline'y commitować. Większość zespołów commituje tylko platformę CI, a lokalne baseline'y trzyma poza gitem, dzięki czemu każdy developer ma własny zestaw bez zaśmiecania repozytorium:
# ignoruj wszystkie katalogi platform poza tym, który produkuje CI **/__image_snapshots__/*/ !**/__image_snapshots__/linux-chrome/
Nowe baseline'y CI trzeba wtedy wygenerować na CI i stamtąd je zacommitować (albo zatwierdzić z poziomu PR-a, zobacz Manifest uruchomienia). - Podział tylko według przeglądarki albo tylko według systemu. Użyj samego
{browser}albo samego{os}, np.{spec_path}/__image_snapshots__/{browser}. - Czyszczenie usuwa baseline'y pozostałych platform.
pluginVisualRegressionCleanupUnusedImagesprzeszukuje cały projekt w poszukiwaniu PNG-ów stworzonych przez plugin i nic nie wie o platformach: uruchomienie nadarwin-electrontraktuje każdy plik wlinux-chrome/jako nieużywany i go usuwa. Trzymaj tę opcję wyłączoną na maszynach, które mają katalogi więcej niż jednej platformy (checkout developera z zacommitowanymi baseline'ami CI to dokładnie taki przypadek), a włączaj ją tylko tam, gdzie leżą baseline'y jednej platformy, zwykle w jobie CI, który je produkuje. Jeśli jednak coś zostanie usunięte, skasowane katalogi wrócą pogit checkout -- <folder>.
Dokąd to zmierza: plan dla #212 to renderer, który produkuje piksele w przypiętym obrazie Dockera niezależnie od tego, gdzie działa Cypress, więc obrazy lokalne i z CI przestają się rozjeżdżać z samej konstrukcji. {browser} nazywa wtedy przeglądarkę renderera, a nie tę sterowaną przez Cypressa, a {platform} nadal przydaje się do trzymania osobno kilku renderowanych przeglądarek, a nie do różnic lokalne-vs-CI. Manifest już zapisuje obie strony: platform to miejsce, gdzie uruchomiono test, renderer to miejsce, skąd pochodzą piksele.
Tryb Batch Review
Tryb Batch Review jest domyślnie włączony. Pozwala uruchomić cały zestaw testów bez zatrzymywania się na pierwszym nieudanym porównaniu wizualnym. Zamiast tego wszystkie nieudane snapshoty są zbierane i po zakończeniu testów prezentowane w interaktywnym UI do przeglądu, dzięki czemu zmiany możesz zatwierdzać lub pomijać hurtowo.
Dodatki w trybie headed (środowiska poza CI)
W prawym dolnym rogu runnera Cypress pojawia się pływający przycisk akcji (FAB). W trakcie działania testów:
- Za każdym razem, gdy wywołanie
matchImage()przekroczy próg diffa, licznik na FAB-ie się zwiększa, zamiast od razu rzucać błąd. - Gdy wszystkie testy się skończą, rzucany jest błąd z łączną liczbą niepowodzeń.
- Kliknięcie FAB-a otwiera karuzelę, w której przejrzysz każdy nieudany snapshot obok siebie (nowy vs. stary) i albo zastąpisz baseline, albo pominiesz zmianę.

Uwaga: przed wersją 2 pakietu
@pixsame/cypress-plugin-visual-regression-difftryb Batch Review był opt-in. Zobacz przewodnik migracji, jeśli aktualizujesz z 4.x.
Jak wyłączyć
Tryb Batch Review to opcja globalna (nie da się jej przekazać bezpośrednio do matchImage). Żeby wrócić do natychmiastowego oblewania matchImage() przy pierwszej różnicy, ustaw klucz pluginVisualRegressionBatchReviewMode na false:
# Cypress 15.10+
npx cypress open --expose "pluginVisualRegressionBatchReviewMode=false"
# Cypress <15.10 (przestarzałe od 15.10, usunięte w 16)
npx cypress open --env "pluginVisualRegressionBatchReviewMode=false"
// cypress.config.ts (Cypress 15.10+)
import { defineConfig } from 'cypress';
export default defineConfig({
expose: {
pluginVisualRegressionBatchReviewMode: false,
},
});
// cypress.config.ts (Cypress <15.10, przestarzałe w nowszych wersjach)
import { defineConfig } from 'cypress';
export default defineConfig({
env: {
pluginVisualRegressionBatchReviewMode: false,
},
});
Przeglądanie obrazów, które przeszły
Obrazy, które różnią się od baseline'u, ale mieszczą się w maxDiffThreshold, nie oblewają testu, a mimo to są zbierane. Karuzela ma checkbox Show passing, który pozwala je przejrzeć (każdy ma odznakę passed) i również zastąpić ich baseline, np. żeby pozbyć się drobnego, nawarstwiającego się dryfu.
Checkbox jest domyślnie odznaczony. Użyj klucza pluginVisualRegressionBatchReviewModeShowPassingImages, żeby był domyślnie zaznaczony:
# Cypress 15.10+
npx cypress open --expose "pluginVisualRegressionBatchReviewModeShowPassingImages=true"
# Cypress <15.10 (przestarzałe od 15.10, usunięte w 16)
npx cypress open --env "pluginVisualRegressionBatchReviewModeShowPassingImages=true"
// cypress.config.ts (Cypress 15.10+)
import { defineConfig } from 'cypress';
export default defineConfig({
expose: {
pluginVisualRegressionBatchReviewModeShowPassingImages: true,
},
});
Przełączenie checkboxa w runnerze jest zapamiętywane w local storage przeglądarki i przy kolejnych uruchomieniach ma pierwszeństwo przed wartością z konfiguracji.
Reducing cross-OS rendering noise
Przeglądarki oddają rasteryzację tekstu systemowi operacyjnemu (CoreText na macOS, DirectWrite na Windows, FreeType na Linuksie), więc ta sama strona nigdy nie wyrenderuje się bajt w bajt identycznie na laptopie developera i na linuksowym runnerze CI. Plugin nie jest w stanie całkowicie tego wyeliminować, ale preset deterministicRendering (domyślnie włączony) usuwa tę część szumu, która leży po stronie samej przeglądarki:
- Chrome, Chromium, Edge są uruchamiane z
--font-render-hinting=none --disable-font-subpixel-positioning --disable-lcd-text --force-color-profile=srgb --disable-gpu(tekst bez hintingu systemowego i sztuczek subpikselowych, bez zarządzania kolorem, rasteryzacja na CPU), a w trybie headless dodatkowo--hide-scrollbars. Lista jest eksportowana jakoDETERMINISTIC_RENDERING_CHROMIUM_ARGSz@pixsame/cypress-plugin-visual-regression-diff/constants. - Firefox dostaje preferencję
gfx.webrender.software(renderowanie na CPU). Firefox nie ma międzyplatformowego przełącznika hintingu fontów, więc tekst może nadal różnić się między systemami operacyjnymi. - Każda przeglądarka: na czas zrzutu ekranu z
matchImagedo testowanej strony wstrzykiwany jest element<style>, który ukrywa kursor tekstowy, wyłącza przejścia CSS, animacje i płynne przewijanie oraz ukrywa paski przewijania. Zaraz po zrzucie jest usuwany. - Porównanie uruchamia
pixelmatchzincludeAA: false, więc piksele krawędzi z anti-aliasingiem są wykrywane i nie są liczone jako różnice.
Czego preset nie rozwiązuje: różnych plików fontów i fallbacków na poszczególnych systemach (klasyczny przypadek Arial vs. Liberation Sans), fontów emoji, rasteryzacji tekstu w WebKit i Firefoxie oraz obrazów rysowanych przez WebGL. W takich przypadkach trzymaj osobny baseline dla każdej platformy (zobacz opcje title i imagesPath oraz wpis w FAQ o nazwach przeglądarek) albo generuj baseline'y w tym samym środowisku, w którym działa twoje CI.
Electron ignoruje przełączniki ustawiane przez pluginy. Przekaż je zamiast tego przez zmienną środowiskową:
ELECTRON_EXTRA_LAUNCH_ARGS="--font-render-hinting=none --disable-font-subpixel-positioning --disable-lcd-text --force-color-profile=srgb --disable-gpu --hide-scrollbars" npx cypress run
WebGL: --disable-gpu może sprawić, że strony zależne od WebGL zostaną bez kontekstu renderowania. Albo wyłącz preset, albo dodaj --use-gl=angle --use-angle=swiftshader (nowsze Chrome wymaga też --enable-unsafe-swiftshader) we własnym handlerze before:browser:launch, połączonym z handlerem pluginu przez cypress-on-fix.
Hooki zrzutów ekranu: plugin przekazuje własne onBeforeScreenshot i onAfterScreenshot do każdego zrzutu, który robi. Hooki podane przez screenshotConfig są nadal wywoływane, a hooki ustawione globalnie przez Cypress.Screenshot.defaults() nie.
Żeby wyłączyć preset globalnie (przełączniki przeglądarki i CSS):
# Cypress 15.10+
npx cypress run --expose "pluginVisualRegressionDeterministicRendering=false"
# Cypress <15.10 (przestarzałe od 15.10, usunięte w 16)
npx cypress run --env "pluginVisualRegressionDeterministicRendering=false"
// cypress.config.ts (Cypress 15.10+)
import { defineConfig } from 'cypress';
export default defineConfig({
expose: {
pluginVisualRegressionDeterministicRendering: false,
},
});
Żeby zachować przełączniki przeglądarki, ale pominąć CSS dla pojedynczego zrzutu (np. gdy chcesz złapać stan końcowy animacji):
cy.matchImage({ deterministicRendering: false });
FAQ
Dlaczego zrzuty ekranu nie trzymają się viewport ustawionego w mojej konfiguracji Cypress?
Zrzuty ekranu w Cypress domyślnie nie skalują się do rozmiaru viewportu. Możesz to zmienić:
- globalnie, zmieniając domyślną konfigurację zrzutów ekranu:
Cypress.Screenshot.defaults({ capture: 'viewport' }); - lokalnie, przekazując konfigurację zrzutu ekranu bezpośrednio do komendy
.matchImage:cy.matchImage({ screenshotConfig: { capture: 'viewport' } });
Zaktualizowałem wersję pluginu i wszystkie moje obrazy baseline zostały automatycznie podmienione. Dlaczego?
Czasem musimy wprowadzić zmianę łamiącą kompatybilność w algorytmach porównywania lub generowania obrazów. Żeby aktualizacja była dla ciebie jak najprostsza, plugin sam aktualizuje twoje baseline'y. Po aktualizacji pluginu po prostu zacommituj je do repozytorium i gotowe!
Zrzuty ekranu wyglądają inaczej w cypress run i cypress open. Jak to naprawić?
Zwykle wynika to z różnic w device pixel ratio między trybem headless a headed. Użyj opcji forceDeviceScaleFactor, żeby znormalizować współczynnik skalowania do 1:
cy.matchImage({ forceDeviceScaleFactor: true });
Albo ustaw to globalnie przez zmienną expose:
# Cypress 15.10+
npx cypress run --expose "pluginVisualRegressionForceDeviceScaleFactor=true"
# Cypress <15.10 (przestarzałe od 15.10, usunięte w 16)
npx cypress run --env "pluginVisualRegressionForceDeviceScaleFactor=true"
Jeśli różnice w rozmiarze viewportu nie znikają, rozważ jawne ustawienie rozmiaru okna przeglądarki w setupNodeEvents (połącz to z własnym handlerem before:browser:launch pluginu przez cypress-on-fix):
on('before:browser:launch', (browser, launchOptions) => {
if (browser.family === 'chromium' && browser.isHeadless) {
launchOptions.args.push('--window-size=1280,720');
}
return launchOptions;
});
Jeśli oba uruchomienia odbywają się na różnych systemach operacyjnych (laptop z macOS vs. linuksowy runner CI), pozostałe różnice wynikają z renderowania fontów. Zobacz Reducing cross-OS rendering noise.
Jak używać tego pluginu razem z innymi pluginami Cypress, które też rejestrują zdarzenia w setupNodeEvents?
Cypress obsługuje tylko jeden handler na zdarzenie. Jeśli kilka pluginów zarejestruje to samo zdarzenie (np. after:screenshot), wywołany zostanie tylko ostatni z nich. Żeby bezpiecznie połączyć kilka pluginów, użyj cypress-on-fix:
import { defineConfig } from 'cypress';
import { initPlugin as initVisualRegressionPlugin } from '@pixsame/cypress-plugin-visual-regression-diff/plugins';
import fix from 'cypress-on-fix';
export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
const fixedOn = fix(on);
initVisualRegressionPlugin(fixedOn, config);
// tutaj zarejestruj pozostałe pluginy, używając fixedOn
},
},
});
Jak zintegrować plugin z mochawesome lub innymi reporterami, które osadzają zrzuty ekranu?
Plugin przechowuje obrazy baseline poza domyślnym katalogiem cypress/screenshots/. Niektóre reportery (np. cypress-mochawesome-reporter) oczekują, że zrzuty ekranu będą właśnie w tym folderze.
Użyj hooka taska processImgPath, żeby kontrolować, gdzie trafia plik zrzutu ekranu. Nadpisz go w setupNodeEvents po wywołaniu initPlugin:
setupNodeEvents(on, config) {
initVisualRegressionPlugin(on, config);
// nadpisz przetwarzanie ścieżki, żeby zrzuty ekranu pozostały dostępne dla reportera
on('task', {
'cp-visual-regression-diff-processImgPath': ({ path }: { path: string }) => {
// zwróć nową ścieżkę albo tę samą, żeby zachować domyślne zachowanie
return path;
},
});
}
Jak oblać test przy każdej różnicy wizualnej, nawet najmniejszej?
Ustaw maxDiffThreshold na 0:
cy.matchImage({ maxDiffThreshold: 0 });
Albo globalnie przez zmienną expose:
# Cypress 15.10+
npx cypress run --expose "pluginVisualRegressionMaxDiffThreshold=0"
# Cypress <15.10 (przestarzałe od 15.10, usunięte w 16)
npx cypress run --env "pluginVisualRegressionMaxDiffThreshold=0"
Dlaczego aktualny zrzut ekranu jest usuwany po udanym porównaniu obrazów?
Gdy obrazy się zgadzają, plik .actual.png jest tymczasowy i po porównaniu jest sprzątany. Zostaje tylko obraz baseline (bez sufiksu). Dzięki temu nie trzymamy zbędnych plików.
Gdy obrazy się nie zgadzają, plik .actual.png zostaje i niesie te same metadane pluginu co obraz baseline. Możesz go bezpiecznie przemianować lub skopiować w miejsce baseline'u (np. w jobie CI, który otwiera pull requesta ze zaktualizowanymi baseline'ami) - plugin potraktuje go jako aktualny baseline i nie nadpisze go przy następnym uruchomieniu.
Jeśli potrzebujesz, żeby aktualne zrzuty ekranu pozostały dostępne dla reportera (np. cypress-mochawesome-reporter), użyj hooka taska processImgPath, żeby kontrolować, gdzie są zapisywane. Zobacz Jak zintegrować plugin z mochawesome lub innymi reporterami, które osadzają zrzuty ekranu?
Jak usunąć spacje z nazw plików zrzutów ekranu?
Nazwy plików zrzutów ekranu pochodzą od tytułów testów Cypress, które mogą zawierać spacje. Żeby użyć nazwy pliku bez spacji, przekaż własną opcję title do każdego wywołania matchImage:
cy.matchImage({ title: 'my-screenshot-without-spaces' });
Żeby zastosować to globalnie dla wszystkich wywołań matchImage, nadpisz komendę w pliku support (cypress/support/commands.ts):
Cypress.Commands.overwrite('matchImage', (originalFn, subject, options = {}) =>
originalFn(subject, {
title: Cypress.currentTest.titlePath.join(' ').replace(/\s+/g, '-'),
...options,
}),
);
Jak dodać nazwę przeglądarki do nazw plików obrazów (testy cross-browser)?
Użyj tokenu {browser} w imagesPath; rozwija się on do nazwy przeglądarki, która wyrenderowała zrzut ekranu (chrome, firefox, electron, ...):
cy.matchImage({ imagesPath: '{spec_path}/__image_snapshots__/{browser}' });
Albo globalnie:
# Cypress 15.10+
npx cypress run --expose "pluginVisualRegressionImagesPath={spec_path}/__image_snapshots__/{browser}"
# Cypress <15.10 (przestarzałe od 15.10, usunięte w 16)
npx cypress run --env "pluginVisualRegressionImagesPath={spec_path}/__image_snapshots__/{browser}"
W ten sposób powstają osobne katalogi obrazów dla każdej przeglądarki (__image_snapshots__/chrome/, __image_snapshots__/firefox/). W sekcji Osobne baseline'y dla każdej platformy znajdziesz {platform} i {os}, które dzielą baseline'y także według systemu operacyjnego, informację o tym, które katalogi commitować, oraz wyjaśnienie, dlaczego pluginVisualRegressionCleanupUnusedImages musi pozostać wyłączone na maszynie, która ma katalogi więcej niż jednej przeglądarki (usunęłoby baseline'y pozostałych przeglądarek).
Jak wygenerować raport HTML z obrazami baseline, diff i aktualnym?
Plugin ma wbudowaną nakładkę porównania wizualnego w UI Cypress: kliknięcie "See comparison" przy nieudanym teście pokazuje obok siebie obraz baseline, diff i aktualny.
Na CI albo do raportów HTML, którymi chcesz się dzielić, zintegruj plugin z reporterem takim jak cypress-mochawesome-reporter. Szczegóły konfiguracji znajdziesz w Jak zintegrować plugin z mochawesome lub innymi reporterami, które osadzają zrzuty ekranu?.