Přejít na hlavní obsah
Sleva 50% všechny plány, omezený čas. Od $2.48/mo
13 min left
Vývojářské nástroje a DevOps

Jak si sami hostovat vizuální regresní testování ve své CI pipeline

S Autor: Sajjad 13 min čtení
Self-hosted visual regression testing running in a CI pipeline with BackstopJS and Docker

Vaše vizuální regresní testy na notebooku procházejí. Pushnete kód, pipeline se rozjede a tytéž testy buď tiše shodí prohlížeč, nebo nahlásí padesát „změn“, které jsou ve skutečnosti jen rozdíly v antialiasingu. V kódu se nezměnilo nic, ale CI tvrdí opak.

Ta propast mezi „funguje lokálně“ a „funguje v CI“ je skoro vždycky problém infrastruktury, ne testů. Aby vizuální regresní testování běželo ve vlastní CI pipeline spolehlivě, musíte správně propojit čtyři díly: CI runner s Docker executorem, kontejner nakonfigurovaný tak, aby headless Chromiu nedošla sdílená paměť, nástroj pro porovnávání nastavený tak, aby vydával reporty čitelné pro CI, a VPS nadimenzovaný na paměť, kterou headless prohlížeče reálně spotřebují.

Tento návod postaví celý stack od začátku do konce. Na konci budete mít funkční .gitlab-ci.yml (nebo ekvivalent pro Gitea), prostředí Dockeru, ve kterém Chromium nepadá, BackstopJS generující JUnit reporty, které vaše pipeline přečte, a správně nadimenzovaný VPS, na kterém to všechno poběží.

Stručně

  • Spouštějte prohlížeč lokálně i v CI ve stejném Docker image. Většina falešně pozitivních rozdílů pramení z odlišných fontů a renderingu mezi prostředími. Zafixujte image a zmizí.
  • Opravte výchozích 64MB u /dev/shm. Výchozí velikost sdílené paměti v nástroji Docker v Dockeru vyhladoví renderer Chromia, který pak v CI tiše spadne. Nastavte shm_size: '2gb' v docker-compose.yml, nebo předejte Chromiu --disable-dev-shm-usage.
  • Jako hlavní nástroj pro vlastní hosting použijte BackstopJS. Je pod licencí MIT (v6.3.25), staví na Dockeru přes přepínač --docker a generuje JUnit reporty. Pokud už používáte Playwright, jeho vestavěné toHaveScreenshot() je slušný začátek bez jakékoli instalace.
  • Za základ považujte 4GB RAM. S pamětí počítejte štědře. Při reálných bězích CI umí úloha se snímky obrazovky v Chromiu spotřebovat během zachytávání celé stránky mnohem víc paměti, než by se podle klidového stavu zdálo, takže 4GB RAM je bezpečnější základ pro jednu až dvě sekvenční nebo mírně paralelní úlohy.

Co tento průvodce nepokrývá

Tohle je návod na stavbu infrastruktury, kterou už jste se rozhodli postavit. Pár věcí je záměrně mimo záběr:

  • Není to úplné srovnání nástrojů. Tento návod se soustředí na stack, který reálně ubráníte ve vlastním CI prostředí: BackstopJS, Docker a váš vlastní runner.
  • Nepokrývá vlastní hosting nástroje Argos. Argos je open source, ale jeho veřejný produktový tok i dokumentace míří na hostovanou aplikaci Argos a integrace s CI, ne na jednoduchou cestu k vlastnímu produkčnímu hostingu. Pro cíl tohoto návodu je BackstopJS bezpečnější ukázkový příklad.
  • Nedoporučuje Lost Pixel. Ten projekt byl v dubnu 2026 archivován; pro nová nasazení použijte BackstopJS.
  • Neřeší znovu vlastní hosting versus spravovaný SaaS. Pokud jste tady, Percy nebo Chromatic už jste vyloučili.

Předpoklady

Než napíšete první příkaz, budete potřebovat pár věcí:

  • Vlastní CI runner, nebo aspoň plán, jak ho nasadit (popsáno níže).
  • Docker nainstalovaný na hostu s runnerem.
  • Projekt v Node.js s existujícím cílem testů (URL běžící aplikace nebo sada cest ke komponentám, které chcete zachytit).
  • Přístup do shellu na VPS nebo hostu, kde runner běží.

Proč vizuální testy procházejí lokálně, ale v CI padají

Prohlížeč, který vykreslí referenční snímek na macOS, není tentýž prohlížeč, který vykreslí porovnání v linuxovém CI kontejneru. Dokumentace samotného Playwrightu je v tom přímočará: vykreslování v prohlížeči se může lišit podle OS hosta, verze, nastavení, hardwaru, zdroje napájení, headless režimu a dalších faktorů. Její doporučení je stejně přímé: chcete-li konzistentní snímky, spouštějte testy ve stejném prostředí, ve kterém byly vygenerovány referenční snímky.

Právě kvůli tomu jedinému doporučení existuje celý zbytek téhle konfigurace. Za skoro každým falešným selháním stojí tři nesoulady:

  • Rozdíly ve fontech a antialiasingu. Váš vývojářský stroj a CI kontejner mají jiné balíčky fontů a jiné subpixelové vykreslování. Stránky s hodně textem se pak liší při každém běhu, i když se nic nezměnilo.
  • Headless versus headed vykreslování. Prohlížeč s grafickým rozhraním a headless prohlížeč můžou tutéž stránku vysázet trochu jinak.
  • Vyčerpání sdílené paměti. Tenhle je tichý. Výchozí /dev/shm v Dockeru má 64MB a headless Chromium používá sdílenou paměť pro své renderovací procesy. Když dojde, Chromium spadne bez použitelné chybové hlášky. Vaše úloha prostě umře nebo zamrzne.

První dva vyřešíte tím, že pro generování referenčních snímků i pro porovnání zafixujete jediný Docker image. Třetí se řeší v sekci o konfiguraci Dockeru níže. Opravte všechny tři a z testů, které byly „v CI nespolehlivé“, se stanou deterministické testy.

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

Volba nástroje: BackstopJS, nebo vestavěné porovnávání v Playwrightu

Pokud už Playwright používáte na end-to-end testy, nejrychlejší cesta je jeho vestavěná aserce toHaveScreenshot(): žádné závislosti navíc, porovnávání na úrovni pixelů a referenční snímky s příponou podle platformy rovnou v základu. Pro malou testovací sadu je to legitimní místo, kde se zastavit, a pro týmy s méně než zhruba padesáti obrazovkami a bez sdílené knihovny komponent to může být všechno, co kdy budete potřebovat.

Kde mu dojde dech, je proces revize. Porovnávání v Playwrightu je čistě pixelové a nemá žádné UI pro revizi v PR, takže schválení legitimní dávky vizuálních změn znamená ruční regeneraci snapshotů. Jakmile počet referenčních snímků naroste, začne tenhle ruční schvalovací kolotoč bolet.

BackstopJS je specializovaný nástroj postavený přesně na tenhle cyklus. Je pod licencí MIT (aktuálně v6.3.25), staví na Dockeru přes přepínač --docker a generuje JUnit XML, které CI runner přečte nativně. Jeho postup s referenčními snímky (vygenerovat referenci, otestovat proti ní, schválit přijaté rozdíly jako novou referenci) je provozní model, ke kterému většina vlastních VRT sestav nakonec dojde. Ve zbytku návodu bude BackstopJS naším ukázkovým příkladem.

Dva nástroje, které jinde uvidíte doporučovat, jsou v tomhle návodu mimo hru: Argos tu neřešíme, protože jeho veřejný produktový tok se točí kolem hostované aplikace a integrací s CI, a Lost Pixel je archivovaný. BackstopJS je pro praktickou vlastní sestavu bezpečnější ukázkový příklad.

OsaBackstopJSPlaywright toHaveScreenshot()
Proces revize v PRVestavěný report a příkaz approveRuční regenerace snapshotů
Správa referenčních snímkůPříkazy reference / test / approveSoubory snapshotů podle jednotlivých testů
Vykreslování normalizované DockeremPřepínač --dockerPřines si vlastní image
Náročnost instalaceSamostatná závislostUž máte, pokud používáte Playwright

Pokud chcete hlubší srovnání napříč více nástroji a kritérii, naše srovnání BackstopJS, Argos a Lost Pixel pokrývá celé rozhodování o výběru.

Nasazení vlastního CI runneru na VPS

Díl, který skutečně zajistí konzistentní vykreslování, je Docker executor: runner spouští každou úlohu uvnitř kontejneru, který si nadefinujete, takže prostředí prohlížeče je pokaždé stejné. Platformu runneru vybírejte podle toho, co všechno má kromě toho umět.

Vlastní instance GitLab CE je těžší varianta: repozitáře, CI/CD, registry, issues i merge requesty v jednom. Pro tento návod je podstatný Docker executor v GitLab Runneru, díky kterému běží každá úloha vizuální regrese pokaždé ve stejném zafixovaném image kontejneru. GitLab, Gitea, Jenkins, Forgejo, Portainer i Docker jsou dostupné jako nasazení na jedno kliknutí na marketplace Cloudzy, což nastavení výrazně zkrátí.

Gitea s act-runner je odlehčená alternativa. Gitea je gitová služba napsaná v Go s enginem workflow kompatibilním s GitHub Actions (právě přes act-runner), takže pokud je váš tým zvyklý na syntaxi Actions, rozjedete tenhle malý a rychlý runner během chvíle.

Ať zvolíte cokoli, cíl konfigurace je stejný: runner, který přijímá úlohy a spouští je přes Docker executor. Všechno navazující (konfigurace kontejneru, BackstopJS, ukázková pipeline) předpokládá, že tenhle executor máte.

Konfigurace Dockeru tak, aby Chromium nepadalo

Tady je chyba, která lidi stojí nejvíc času. Docker ve výchozím stavu připojuje /dev/shm o velikosti 64MB. Headless Chromium používá tenhle oddíl sdílené paměti pro renderovací procesy a při snímku celé stránky potřebuje výrazně víc než 64MB. Když mu dojde, renderer umře, často bez jediné chybové hlášky, která by ukazovala na sdílenou paměť. Úloha zamrzne, vyprší jí čas, nebo nahlásí obecný pád prohlížeče.

Existují dvě opravy a funguje kterákoli z nich.

Oprava A: zvyšte velikost sdílené paměti v Compose. Klíč shm_size nastavuje velikost oddílu /dev/shm v kontejneru. Referenční příručka souboru Docker Compose potvrzuje, že shm_size konfiguruje velikost sdílené paměti povolené pro kontejner dané služby. Nastavte jej v bloku služby:

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

Oprava B: řekněte Chromiu, ať /dev/shm vůbec nepoužívá. Přepínač --disable-dev-shm-usage přiměje Chromium zapisovat soubory sdílené paměti do /tmp místo do oddílu sdílené paměti. Předejte ho ve spouštěcích argumentech prohlížeče. V konfiguraci backstop.json je to engineOptions, v Playwrightu launchOptions:

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

Oprava A je čistší, když máte pod kontrolou Compose soubor. Oprava B je přenositelná volba, když můžete sahat jen na spouštěcí přepínače prohlížeče. Použít obě zároveň nic nezkazí.

Tip

Používejte lokálně i v CI přesně tentýž Docker image. BackstopJS vám to dá zadarmo přepínačem --docker, který spustí zachytávání uvnitř zafixovaného referenčního image, takže váš stroj i runner vykreslují stejně. Právě tohle odstraní falešně pozitivní nálezy kvůli fontům a antialiasingu: zafixujte image jednou a přestaňte honit fantomové rozdíly.

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

Nastavení BackstopJS pro CI

BackstopJS vytváří JUnit XML report, podle kterého váš CI runner rozhodne, jestli build projde, nebo ne. Přesně kvůli tomu ho zapojujete do pipeline místo ručního spouštění. Nainstalujte ho a inicializujte konfiguraci:

npm install --save-dev backstopjs
npx backstop init

Pak v backstop.json nasměrujte nástroj na stránky, které chcete zachytit, a zapněte CI report:

// 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"]
  }
}

JUnit XML vzniká právě díky nastavení CI reportu. Při spouštění těchto příkazů lokálně z vlastního stroje použijte přepínač --docker. Uvnitř CI úlohy, která už image backstopjs/backstopjs používá, spusťte místo toho rovnou backstop test. Práce s referenčními snímky stojí na třech příkazech:

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

Jedna výhrada ke správě referenčních snímků: každá legitimní změna UI znamená spustit approve a nové snímky posvětit. Nad zhruba sto scénáři je tenhle schvalovací krok reálný provozní náklad: někdo si musí rozdíly projít očima a rozhodnout, které jsou záměrné, a zátěž roste s tím, jak nástroj přebírá celý tým. Tahle údržba je skutečná cena vizuálního regresního testování ve větším měřítku a vyplatí se s ní počítat dřív, než nasadíte velkou testovací sadu.

Funkční konfigurace CI pipeline

Pipeline běží ve dvou logických částech: jednou (nebo na vyžádání) vygenerovat referenci a pak proti ní porovnávat při každé změně. Ukázka níže je konfigurace GitLab CI s Docker executorem, kde je oprava sdílené paměti už aplikovaná přes spouštěcí přepínače v backstop.json.

# .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

Řádek junit předá report GitLabu, takže se selhání objeví přímo v UI merge requestu. Řádek paths uchová bitmapy rozdílů, abyste se mohli podívat, co se doopravdy změnilo. Na Gitea s act-runner se stejné kroky mapují do workflow ve stylu Actions:

# .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

Všimněte si volby kontejneru --shm-size=2gb v Gitea workflow. Je to tatáž oprava sdílené paměti jako Oprava A, jen aplikovaná na úrovni kontejneru úlohy tam, kde není žádný samostatný Compose soubor k úpravě.

Dimenzování VPS pro headless Chromium

Omezením je tady RAM, ne CPU. Zatížení CPU u headless Chromia je nárazové (vyskočí během zachytávání a mezi jednotlivými snímky se nudí), takže i skromný počet jader stačí. Strop, který rozhoduje, kolik úloh utáhnete najednou, je paměť. Proces headless Chromia sedí v klidu na zhruba 300–500MB a při snímání celé stránky vyšplhá na 1–2GB, takže tím, co určuje dimenzování, je RAM na jednu souběžnou úlohu.

RAMVhodné pro
2GBTěsné minimum: jedna sekvenční úloha
4GBDoporučený základ: 1–2 souběžné úlohy
8GB3–4 souběžné úlohy
16GBVelké testovací sady a paralelní pipeline

Tato čísla jsou doporučení z praxe, vycházející z reálných běhů CI, ne dodavatelský benchmark. Berte je jako přibližné výchozí body a sledujte si vlastní špičkovou spotřebu paměti.

Hlavní poznatek této sekce: spolehlivý základ jsou 4GB RAM, na každou další souběžnou úlohu vizuální regrese přidejte zhruba 2GB rezervy.

Zádrhel u vlastního hostování VRT je v tom, že runner, kterému dochází paměť, neselže hlasitě, ale potichu shodí Chromium, tedy přesně ten příznak, kvůli kterému celé tohle nastavení vzniklo. Dostatečná rezerva paměti je ta nejlevnější pojistka, jakou si můžete koupit. Pokud nechcete stavět hostitele pro runner od nuly, marketplace Cloudzy nabízí nasazení jedním kliknutím pro GitLab na vlastním serveru a Gitea na vlastním serveru , se kterým máte běžící CI instanci během pár minut, a to na VPS, který nadimenzujete přesně na paměťovou rezervu pro headless Chromium, jakou vaše testovací sada potřebuje.

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

Časté dotazy

Jak nastavím vizuální regresní testování v CI/CD pipeline?

Nainstalujte BackstopJS, nasměrujte backstop.json na cílové stránky a zapněte volbu CI reportu, aby se generovalo JUnit XML. Při spouštění z vlastního stroje používejte backstop reference --docker a backstop test --docker, aby se snímky vykreslovaly uvnitř zafixovaného Docker image. V CI spusťte úlohu uvnitř image backstopjs/backstopjs a volejte rovnou backstop test, pak publikujte JUnit report z backstop_data/ci_report/xunit.xml. Použijte vlastní runner s Docker executorem, nadimenzovaný alespoň na 4GB RAM pro headless Chromium.

Kolik RAM potřebuje headless Chromium v CI kontejneru?

Proces headless Chromia spotřebuje v klidu zhruba 300–500MB a při snímání celé stránky vyskočí na 1–2GB. Pro spolehlivé CI počítejte s asi 4GB RAM na jednu až dvě souběžné úlohy a přidejte zhruba 2GB za každou další paralelní úlohu. CPU je spíš nárazové než limitující; kolik úloh utáhnete najednou, určuje RAM.

Proč moje vizuální regresní testy procházejí lokálně, ale v CI padají?

Vykreslování v prohlížeči se mezi prostředími liší: výstup mění fonty, antialiasing, headless versus headed režim i OS. Řešením je spouštět generování reference i porovnání ve stejném Docker image, aby se obojí vykreslilo identicky. Přepínač --docker v BackstopJS to zajistí tím, že zachytávání běží uvnitř zafixovaného referenčního image.

Jak opravím chybu /dev/shm u Chromia v Docker CI?

Výchozí /dev/shm v Dockeru má 64MB, což je pro renderer headless Chromia málo a vede to k tichým pádům. Buď ho zvětšete pomocí shm_size: '2gb' v docker-compose.yml (nebo --shm-size=2gb na kontejneru úlohy), nebo předejte ve spouštěcích přepínačích Chromia --disable-dev-shm-usage, aby soubory sdílené paměti zapisovalo do /tmp.

Mám použít Playwright toHaveScreenshot, nebo specializovaný nástroj na vizuální regrese?

Vestavěné toHaveScreenshot() v Playwrightu stačí na malé testovací sady: zhruba do padesáti obrazovek, bez sdílené knihovny komponent a bez potřeby UI pro revizi v PR. Na specializovaný nástroj jako BackstopJS přejděte, jakmile potřebujete pořádný proces schvalování referencí, report pro revizi a vykreslování normalizované Dockerem, což začne být důležité s rostoucím počtem referenčních snímků.

Je Lost Pixel stále udržovaný?

Ne. Lost Pixel byl v dubnu 2026 archivován a pro nová nasazení nepřipadá v úvahu. Pro vlastní sestavu vizuálního regresního testování použijte BackstopJS.

Sdílet

Další z blogu

Pokračuj ve čtení.

Hotov k nasazení? Od 2,48 $/měs.

Nezávislý cloud od roku 2008. AMD EPYC, NVMe, 40 Gbps. Vrácení peněz do 14 dnů.