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.

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.
| Osa | BackstopJS | Playwright toHaveScreenshot() |
|---|---|---|
| Proces revize v PR | Vestavěný report a příkaz approve | Ruční regenerace snapshotů |
| Správa referenčních snímků | Příkazy reference / test / approve | Soubory snapshotů podle jednotlivých testů |
| Vykreslování normalizované Dockerem | Přepínač --docker | Přines si vlastní image |
| Náročnost instalace | Samostatná závislost | Už 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.

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.
| RAM | Vhodné pro |
|---|---|
| 2GB | Těsné minimum: jedna sekvenční úloha |
| 4GB | Doporučený základ: 1–2 souběžné úlohy |
| 8GB | 3–4 souběžné úlohy |
| 16GB | Velké 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.

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