Ugrás a fő tartalomra
50% kedvezmény minden csomagra, korlátozott ideig. Már $2.48/mo
13 min left
Fejlesztői eszközök és DevOps

Vizuális regressziós tesztelés saját üzemeltetésben a CI-folyamatban

S Szerző: Sajjad 13 perc olvasás
Self-hosted visual regression testing running in a CI pipeline with BackstopJS and Docker

A vizuális regressziós tesztjeid lefutnak a laptopodon. Pusholsz, elindul a pipeline, és ugyanezek a tesztek vagy némán összeomlasztják a böngészőt, vagy ötven „változást” jeleznek, amelyek valójában csak élsimítási eltérések. A kódodban semmi nem változott, a CI mégis mást mond.

A „helyben működik” és a „CI-ben működik” közötti szakadék szinte mindig infrastruktúra-probléma, nem teszthiba. Ahhoz, hogy a vizuális regressziós tesztelést megbízhatóan üzemeltesd saját CI-folyamatban, négy elemet kell helyesen összekötnöd: egy CI runnert Docker executorral, egy úgy konfigurált konténert, amelyben a headless Chromiumnak nem fogy el az osztott memória, egy diffelő eszközt, amely CI által olvasható riportokat állít elő, és egy VPS-t, amelyet a headless böngészők tényleges memóriaigényére méreteztél.

Ez az útmutató végigviszi ezt a stacket az elejétől a végéig. A végére lesz egy működő .gitlab-ci.yml fájlod (vagy egy Gitea-megfelelője), egy Docker-környezeted, amelyben a Chromium nem omlik össze, egy BackstopJS, amely a pipeline számára olvasható JUnit riportokat készít, és egy megfelelően méretezett VPS, amelyen mindez elfut.

Röviden

  • Futtasd a böngészőt ugyanabban a Docker-image-ben helyben és a CI-ben is. A hamis pozitív eltérések többsége a környezetek közötti betűtípus- és renderelési különbségekből fakad. Rögzítsd az image-et, és eltűnnek.
  • Javítsd a 64MB-os /dev/shm alapértéket. A Docker alapértelmezett megosztott memóriamérete kiéhezteti a Chromium renderelőjét, ami némán összeomlik a CI-ben. Állítsd be a shm_size: '2gb' értéket a docker-compose.yml fájlban, vagy add át a --disable-dev-shm-usage kapcsolót a Chromiumnak.
  • A BackstopJS legyen az elsődleges, saját üzemeltetésre alkalmas eszköz. MIT licencű (v6.3.25), a --docker kapcsolóval Docker-központú, és JUnit riportokat állít elő. Ha már használsz Playwrightot, a beépített toHaveScreenshot() jó, telepítés nélküli kiindulópont.
  • 4GB RAM legyen a méretezés alapja. Számolj bőkezűen a memóriával. Valós CI-futtatásokban egy Chromium képernyőkép-job teljes oldalas rögzítés közben sokkal több memóriát fogyaszthat, mint amennyit üresjáratban igényelni látszik, ezért egy-két egymás utáni vagy enyhén párhuzamos jobhoz a 4GB RAM a biztonságosabb alap.

Mit nem fed le ez az útmutató

Ez egy építési útmutató olyan infrastruktúrához, amelynek felállításáról már döntöttél. Néhány dolog szándékosan kimarad belőle:

  • Ez nem teljes eszköz-összehasonlítás. Az útmutató arra a stackre koncentrál, amelyet saját üzemeltetésű CI-környezetben ténylegesen működtetni tudsz: BackstopJS, Docker és a saját runnered.
  • Nem foglalkozik az Argos saját üzemeltetésével. Az Argos nyílt forráskódú, de a nyilvános termékfolyamata és dokumentációja a felhőben futó Argos alkalmazásra és a CI-integrációkra összpontosít, nem pedig egy egyszerű, éles környezetben is használható saját üzemeltetési útra. Az útmutató céljához a BackstopJS a biztonságosabb kidolgozott példa.
  • Nem ajánlja a Lost Pixelt. Azt a projektet 2026 áprilisában archiválták; új telepítésekhez használd a BackstopJS-t.
  • Nem tárgyalja újra a saját üzemeltetés és a menedzselt SaaS közötti választást. Ha idáig eljutottál, már kizártad a Percyt vagy a Chromaticet.

Előfeltételek

Néhány dolognak már készen kell állnia az első parancs előtt:

  • Egy saját üzemeltetésű CI runner, vagy terv a telepítésére (lásd lentebb).
  • Telepített Docker a runner gazdagépén.
  • Egy Node.js projekt meglévő teszt-célponttal (egy futó alkalmazás URL-je vagy rögzítendő komponens-útvonalak halmaza).
  • Shell-hozzáférés ahhoz a VPS-hez vagy gazdagéphez, ahol a runner fut.

Miért mennek át a vizuális tesztek helyben, és miért buknak el a CI-ben

Az a böngésző, amely macOS-en rendereli az alapképeidet, nem ugyanaz, amely egy Linuxos CI-konténerben rendereli az összehasonlítást. A Playwright saját dokumentációja nyíltan kimondja: a böngésző renderelése függhet a gazdagép operációs rendszerétől, verziójától, beállításaitól, hardverétől, áramforrásától, a headless módtól és további tényezőktől. Az ajánlása ugyanilyen egyértelmű: a következetes képernyőképekhez ugyanabban a környezetben futtasd a teszteket, ahol az alapképernyőképek készültek.

Ez az egyetlen ajánlás az oka annak, hogy a beállítás többi része létezik. Három eltérés magyarázza szinte az összes hamis hibajelzést:

  • Betűtípus- és élsimítási különbségek. A fejlesztői géped és a CI-konténer más betűtípus-csomagokat és más szubpixeles renderelést használ. A szöveges oldalak minden futásnál eltérést mutatnak, akkor is, ha semmi nem változott.
  • Headless és headed renderelés. Egy headed és egy headless böngésző kissé eltérően tördelheti ugyanazt az oldalt.
  • Az osztott memória kimerülése. Ez a néma gyilkos. A Docker alapértelmezett /dev/shm mérete 64MB, a headless Chromium pedig osztott memóriát használ a renderelő folyamataihoz. Amikor ez elfogy, a Chromium használható hibaüzenet nélkül omlik össze. A job egyszerűen leáll vagy befagy.

Az első kettőt egyetlen rögzített Docker-image használata oldja meg mind az alapképek készítéséhez, mind az összehasonlításhoz. A harmadikat az alábbi Docker-konfigurációs szakasz orvosolja. Javítsd mind a hármat, és a korábban „CI-ben megbízhatatlan” tesztek determinisztikussá válnak.

Three reasons visual regression tests pass locally but fail in CI: font and anti-aliasing differences between the dev machine and the CI container, headless versus headed rendering differences, and shared-memory exhaustion from Docker's 64MB /dev/shm default crashing headless Chromium

Eszközválasztás: BackstopJS vagy a Playwright beépített diffelése

Ha már használsz Playwrightot végpontok közötti tesztekhez, a leggyorsabb út a beépített toHaveScreenshot() assertion: nulla extra függőség, pixelszintű diffelés és platformutótaggal ellátott alapképek, dobozból. Egy kisebb tesztkészlethez ez teljesen jogos megállási pont, és nagyjából ötven képernyő alatt, közös komponenskönyvtár nélkül dolgozó csapatoknak elég is lehet.

Ahol elfogy alóla az út, az az átnézési folyamat. A Playwright diffelése csak pixelalapú, és nincs PR-átnézési UI-ja, így egy jogos vizuális változáscsomag jóváhagyása kézi pillanatkép-újragenerálást jelent. Ahogy nő az alapképek száma, ez a kézi jóváhagyási kör egyre fájdalmasabb lesz.

BackstopJS az a célszerszám, amelyet pontosan erre a körre építettek. MIT licencű (jelenleg v6.3.25), a --docker kapcsolóval Docker-központú, és olyan JUnit XML-t állít elő, amelyet a CI runner natívan olvas. Az alapkép-folyamata (referencia készítése, tesztelés vele szemben, az elfogadott eltérések jóváhagyása új alapképként) az a működési modell, amelyet a legtöbb saját üzemeltetésű VRT-beállítás végül szeretne. Az útmutató hátralévő részében a BackstopJS a kidolgozott példa.

Két eszköz, amelyet máshol ajánlva láthatsz, ebből a végigvezetésből kimarad: az Argosszal azért nem foglalkozunk, mert a nyilvános termékfolyamata a felhőben futó alkalmazásra és a CI-integrációkra épül, a Lost Pixel pedig archivált. Egy gyakorlatias, saját üzemeltetésű beállításhoz a BackstopJS a biztonságosabb kidolgozott példa.

TengelyBackstopJSPlaywright toHaveScreenshot()
PR-átnézési folyamatBeépített riport és approve parancsKézi pillanatkép-újragenerálás
Alapképek kezeléseReference / test / approve parancsokTesztenkénti pillanatkép-fájlok
Dockerrel normalizált renderelés--docker kapcsolóHozza a saját képét
Telepítési többletmunkaKülön függőségMár adott, ha Playwrightot használsz

Ha több eszközre és szempontra kiterjedő, alaposabb összevetésre vágysz, a BackstopJS, Argos és Lost Pixel összehasonlításunk végigveszi a teljes döntési folyamatot.

Saját üzemeltetésű CI runner telepítése VPS-re

A renderelési következetességet valójában a Docker executor hozza el: a runner minden jobot egy általad definiált konténerben futtat, vagyis a böngészőkörnyezet mindig azonos. A runner-platformot aszerint válaszd, hogy mi mindent szeretnél még rábízni.

A saját üzemeltetésű GitLab CE a nehezebb változat: repók, CI/CD, registry, issue-k és merge requestek egyetlen példányban. Ebben az útmutatóban az a lényeges, hogy a GitLab Runner Docker executoraminden vizuális regressziós jobot ugyanabban a rögzített konténer-image-ben futtat. A GitLab, a Gitea, a Jenkins, a Forgejo, a Portainer és a Docker mind elérhető egykattintásos telepítésként a a Cloudzy marketplacehelyen, ami jelentősen lerövidíti a beállítást.

A Gitea act-runnerrel a könnyűsúlyú alternatíva. A Gitea egy Go alapú Git-szolgáltatás GitHub Actions-kompatibilis munkafolyamat-motorral (az act-runneren keresztül), így ha a csapatod otthonosan mozog az Actions szintaxisában, ez egy kicsi, gyorsan felállítható runner.

Bármelyiket választod, a konfiguráció célja ugyanaz: egy runner, amely jobokat fogad, és Docker executorral futtatja őket. Minden további lépés (a konténerkonfiguráció, a BackstopJS, a mintapipeline) feltételezi, hogy ez az executor a helyén van.

A Docker beállítása, hogy a Chromium ne omoljon össze

Íme a hiba, amely a legtöbb időt viszi el az emberektől. A Docker alapértelmezés szerint 64MB-tal csatolja a /dev/shm partíciót. A headless Chromium ezt az osztottmemória-partíciót használja a renderelő folyamataihoz, és egy teljes oldalas képernyőképnél jóval többre van szüksége 64MB-nál. Amikor elfogy, a renderelő elhal, gyakran úgy, hogy egyetlen hibaüzenet sem utal az osztott memóriára. A job befagy, időtúllépéssel leáll, vagy általános böngészőösszeomlást jelez.

Két megoldás létezik, és bármelyik működik.

A megoldás: emeld meg az osztott memória méretét a Compose-ban. A shm_size kulcs a konténer /dev/shm partíciójának méretét állítja be. A Docker Compose fájlreferencia megerősíti, hogy a shm_size a szolgáltatáskonténer számára engedélyezett osztott memória méretét állítja be. Add meg a szolgáltatás blokkjában:

# docker-compose.yml
services:
  vrt:
    image: backstopjs/backstopjs:6.3.25
    shm_size: '2gb'          # override the 64MB default
    volumes:
      - ./:/src
    working_dir: /src

B megoldás: mondd meg a Chromiumnak, hogy egyáltalán ne használja a /dev/shm partíciót. A --disable-dev-shm-usage kapcsoló hatására a Chromium az osztottmemória-partíció helyett a /tmp könyvtárba írja az osztott memória fájljait. Add át a böngésző indítási argumentumai között. Egy backstop.json konfigurációban ez az engineOptions; a Playwrightban a launchOptions:

// backstop.json (fragment)
{
  "engine": "puppeteer",
  "engineOptions": {
    "args": ["--disable-dev-shm-usage", "--no-sandbox"]
  }
}

Az A megoldás tisztább, ha kezedben van a Compose-fájl; a B megoldás a hordozható választás, ha csak a böngésző indítási kapcsolóihoz férsz hozzá. Mindkettőt alkalmazni sem árt.

Profi tipp: Pontosan ugyanazt a Docker-image-et használd helyben és a CI-ben. A BackstopJS ezt ingyen adja a --docker kapcsolóval, amely a rögzítést egy kikötött referencia-image-en belül futtatja, így a géped és a runner azonosan renderel. Ez szünteti meg a betűtípus- és élsimítási hamis pozitívokat: rögzítsd egyszer az image-et, és nem kell többé fantomeltérések után kajtatnod.

Two fixes for headless Chromium crashing in Docker CI: raising the container's shared memory with shm_size set to 2gb in docker-compose.yml, or passing the --disable-dev-shm-usage flag so Chromium writes shared-memory files to /tmp instead of the 64MB /dev/shm partition

A BackstopJS beállítása CI-hez

A BackstopJS olyan JUnit XML riportot állít elő, amelyből a CI runner eldönti, hogy átment-e a build, és pontosan ezért érdemes pipeline-ba kötni ahelyett, hogy kézzel futtatnád. Telepítsd, és hozz létre egy konfigurációt:

npm install --save-dev backstopjs
npx backstop init

Ezután irányítsd a backstop.json fájlt a rögzíteni kívánt oldalakra, és kapcsold be a CI-riportot:

// backstop.json (fragment)
{
  "id": "my_app_vrt",
  "viewports": [
    { "label": "desktop", "width": 1440, "height": 900 }
  ],
  "scenarios": [
    {
      "label": "Homepage",
      "url": "http://app:3000/",
      "selectors": ["document"]
    }
  ],
  "report": ["CI"],
  "engine": "puppeteer",
  "engineOptions": {
    "args": ["--disable-dev-shm-usage", "--no-sandbox"]
  }
}

A JUnit XML-t a CI-riport beállítása állítja elő. Használd a --docker kapcsolót, amikor ezeket a parancsokat helyben, a saját gépedről futtatod. Olyan CI-jobon belül, amely már a backstopjs/backstopjs image-et használja, helyette közvetlenül a backstop test parancsot futtasd. Az alapkép-folyamat három parancsból áll:

npx backstop reference --docker   # capture the approved baseline
npx backstop test --docker        # run the comparison, emit the report
npx backstop approve              # accept the current diffs as the new baseline

Egy megjegyzés az alapképek kezeléséhez: minden jogos UI-változás azt jelenti, hogy az approve parancsot kell futtatnod az új képernyőképek jóváhagyásához. Nagyjából száz forgatókönyv fölött ez a jóváhagyási lépés valódi üzemeltetési költség: valakinek végig kell néznie az eltéréseket, és el kell döntenie, melyik szándékos, a teher pedig együtt nő a bevezető csapattal. Ez a karbantartás a nagy léptékű vizuális regressziós tesztelés tényleges ára, és érdemes rá előre tervezni, mielőtt nagy tesztkészletet vállalsz be.

Egy működő CI-pipeline konfiguráció

A pipeline két logikai részből áll: az alapképek egyszeri (vagy igény szerinti) elkészítése, majd minden változásnál az ezekkel való összehasonlítás. Az alábbi minta egy Docker executort használó GitLab CI konfiguráció, amelyben az osztottmemória-javítás már alkalmazva van a backstop.json indítási kapcsolóin keresztül.

# .gitlab-ci.yml
stages:
  - visual-regression

visual_regression:
  stage: visual-regression
  image:
    name: backstopjs/backstopjs:6.3.25
    entrypoint: [""]
  script:
    - backstop test
  artifacts:
    when: always
    reports:
      junit: backstop_data/ci_report/xunit.xml
    paths:
      - backstop_data/bitmaps_test
      - backstop_data/html_report
    expire_in: 1 week

A junit sor átadja a riportot a GitLabnak, hogy a hibák megjelenjenek a merge request felületén; a paths sor megőrzi az eltérés-bitképeket, hogy megnézhesd, mi változott valójában. Giteán, act-runnerrel ugyanezek a lépések Actions-stílusú munkafolyamattá alakulnak:

# .gitea/workflows/visual-regression.yaml
name: Visual Regression
on: [push, pull_request]

jobs:
  vrt:
    runs-on: ubuntu-latest
    container:
      image: backstopjs/backstopjs:6.3.25
      options: --shm-size=2gb          # raise /dev/shm for the job container
    steps:
      - uses: actions/checkout@v4
      - run: backstop test

Figyeld meg a --shm-size=2gb konténeropciót a Gitea-munkafolyamatban. Ez ugyanaz az osztottmemória-javítás, mint az A megoldás, csak a job konténerének szintjén alkalmazva, ahol nincs külön szerkeszthető Compose-fájl.

A VPS méretezése headless Chromiumhoz

Itt a RAM a szűk keresztmetszet, nem a CPU. A headless Chromium processzorhasználata lökésszerű (rögzítés közben megugrik, közte üresjáratban van), így egy szerény magszám is bőven bírja. A memória az a plafon, amely eldönti, hány jobot futtathatsz egyszerre. Egy headless Chromium folyamat üresjáratban nagyjából 300–500MB körül van, és 1–2GB-ig kúszik fel egy teljes oldalas képernyőkép rögzítése közben, tehát a méretezés vezérlő tényezője az egyidejű jobonkénti RAM.

RAMMire alkalmas
2GBSzoros minimum: egyetlen, egymás utáni job
4GBAjánlott alap: 1–2 egyidejű job
8GB3–4 egyidejű job
16GBNagy tesztkészletek és párhuzamos pipeline-ok

Ezek a számok valós CI-futtatásokból származó gyakorlati iránymutatások, nem gyártói benchmark, ezért kezeld őket hozzávetőleges kiindulópontként, és figyeld a saját csúcsmemória-használatodat.

A szakasz lényege: a 4GB RAM a megbízható alap; minden további egyidejű vizuális regressziós jobhoz számolj nagyjából 2GB tartalékkal.

A saját üzemeltetésű VRT buktatója az, hogy a kevés memóriával futó runner nem hangosan hibázik, hanem csendben összeomlasztja a Chromiumot, vagyis pontosan azt a tünetet produkálja, amelynek elkerülésére az egész felállás létrejött. Elegendő tartalék biztosítása a legolcsóbb biztosítás, amit vehetsz. Ha nem szeretnél a nulláról runner-hosztot építeni, a Cloudzy marketplace-én egykattintásos telepítés érhető el a következőkhöz: saját üzemeltetésű GitLab és saját üzemeltetésű Gitea számára, amellyel percek alatt futó CI-példányod lesz, olyan VPS-en, amelyet pontosan a tesztkészleted headless Chromium tartalékigényére méretezhetsz.

VPS memory sizing guide for headless Chromium visual regression jobs: 2GB for a single sequential job, 4GB as the recommended baseline for one to two concurrent jobs, 8GB for three to four concurrent jobs, and 16GB for large suites and parallel pipelines

Gyakran ismételt kérdések

Hogyan állítsak be vizuális regressziós tesztelést CI/CD-folyamatban?

Telepítsd a BackstopJS-t, irányítsd a backstop.json fájlt a célzott oldalakra, és állítsd be a CI-riport opciót JUnit XML kiadására. Helyi gépről futtatva használd a backstop reference --docker és a backstop test --docker parancsot, hogy a képernyőképek a rögzített Docker-image-en belül készüljenek. A CI-ben futtasd a jobot a backstopjs/backstopjs image-ben, és hívd közvetlenül a backstop test parancsot, majd publikáld a JUnit riportot a backstop_data/ci_report/xunit.xml fájlból. Használj saját üzemeltetésű runnert Docker executorral, headless Chromiumhoz legalább 4GB RAM-ra méretezve.

Mennyi RAM kell a headless Chromiumnak egy CI-konténerben?

Egy headless Chromium folyamat üresjáratban nagyjából 300–500MB-ot használ, és 1–2GB-ig ugrik fel teljes oldalas képernyőkép rögzítése közben. Megbízható CI-hez számolj körülbelül 4GB RAM-mal egy-két egyidejű jobra, és minden további párhuzamos jobhoz adj hozzá nagyjából 2GB-ot. A CPU inkább lökésszerű, mint korlátozó tényező; a RAM dönti el, hány jobot futtathatsz egyszerre.

Miért mennek át helyben a vizuális regressziós tesztjeim, és miért buknak el a CI-ben?

A böngésző renderelése környezetenként eltér: a betűtípusok, az élsimítás, a headless vagy headed mód és az OS mind megváltoztatja a kimenetet. A megoldás az, hogy az alapképek készítését és az összehasonlítást ugyanabban a Docker-image-ben futtatod, így mindkettő azonosan renderel. A BackstopJS --docker kapcsolója pontosan ezt teszi: egy rögzített referencia-image-en belül végzi a rögzítéseket.

Hogyan javítsam a /dev/shm hibát Chromium Docker CI-ben?

A Docker alapértelmezett /dev/shm mérete 64MB, ami túl kicsi a headless Chromium renderelőjének, és néma összeomlásokat okoz. Vagy emeld meg a shm_size: '2gb' beállítással a docker-compose.yml fájlban (vagy a --shm-size=2gb opcióval a job konténerén), vagy add át a --disable-dev-shm-usage kapcsolót a Chromium indítási kapcsolói között, hogy helyette a /tmp könyvtárba írja az osztott memória fájljait.

A Playwright toHaveScreenshot megoldását vagy egy célzott vizuális regressziós eszközt használjak?

A Playwright beépített toHaveScreenshot() megoldása elég a kis tesztkészletekhez: nagyjából ötven képernyő alatt, közös komponenskönyvtár nélkül és PR-átnézési UI igénye nélkül. Válts célzott eszközre, például BackstopJS-re, amikor rendes alapkép-jóváhagyási folyamatra, átnézési riportra és Dockerrel normalizált renderelésre van szükséged, ami az alapképek számának növekedésével válik fontossá.

Karbantartják még a Lost Pixelt?

Nem. A Lost Pixelt 2026 áprilisában archiválták, így új telepítéseknél nem jöhet szóba. Saját üzemeltetésű vizuális regressziós teszteléshez használd a BackstopJS-t.

Megosztás

Több a blogról

Folytassa az olvasást.

Készen áll a telepítésre? Már 2,48 $/hó-tól.

Független felhő 2008 óta. AMD EPYC, NVMe, 40 Gbps. 14 napos pénzvisszafizetési garancia.