GuideUtveckling

Skärmbildstest i Playwright: fånga layoutfel från AI-ändringar

Senast granskad:

Skärmbildstest i Playwright med toHaveScreenshot: maska föränderliga ytor, stäng av animationer och se testet falla när en AI-ändring flyttar en knapp.

Publicerad

Mörk panel i borstad metall med två mörka gränssnittsvyer sida vid sida; mellan dem står en genomskinlig glasskiva med lysande grön kant, och en liten grönt inramad ruta i den vänstra vyn är dragen med en linje till kanten. Under panelen lyser ett vitt ljus.
Ett skärmbildstest lägger den nya bilden mot referensen och pekar ut var pixlarna skiljer sig.
Kort sagt

Ett funktionstest frågar om knappen finns och går att klicka. Ett skärmbildstest frågar om sidan ser ut som förra gången. Med toHaveScreenshot sparar Playwright en referensbild och fäller testet när bilden ändrats, till exempel när en AI-ändring flyttat Betala-knappen till andra kanten. Testet är ett komplement: det säger inget om tangentbord, skärmläsare eller om knappen gör rätt sak.

1. Se vad funktionstestet missar

Be en kodassistent städa i en kassavy, så kan knapparna hamna i andra änden av raden, under vikningen eller bakom en banderoll. toBeVisible går ändå igenom, eftersom knappen finns, syns och har rätt namn. I vårt prov passerade just den raden, medan skärmbildsjämförelsen på raden efter föll.

Testet vet däremot inte varför något ändrats. Det fäller en beställd ny design lika gärna som ett fel, och det säger inget om tangentbordsnavigering eller tillgängliga namn. Behåll därför funktionstesterna med rollocatorer och provet i guiden om tangentbordstest av en AI-byggd webbapp. Skärmbilden läggs ovanpå.

2. Förbered övningsmappen

Skapa en tom mapp. Du behöver Node.js med npm och en installerad Google Chrome. Sidan laddas med page.setContent, så ingen server behövs. Versionen är låst till den vi körde.

npm init -y
npm install --save-dev @playwright/test@1.64.1

Spara konfigurationen som playwright.config.ts. Projektet heter chromium, och det namnet blir en del av referensbildens filnamn. Fönstret är låst till 480 × 320 pixlar, eftersom en annan fönsterstorlek ger en annan bild.

import { defineConfig } from '@playwright/test';
export default defineConfig({
  testDir: './tests',
  retries: 0,
  expect: { toHaveScreenshot: { animations: 'disabled', caret: 'hide' } },
  projects: [{
    name: 'chromium',
    use: { browserName: 'chromium', channel: 'chrome', viewport: { width: 480, height: 320 } }
  }]
});

Raden under expect gäller alla skärmbilder. Enligt API-referensen för toHaveScreenshot stoppar animations: 'disabled' CSS-animationer, övergångar och Web Animations: ändliga animationer spolas fram till slutet och oändliga återställs till startläget. caret: 'hide' döljer textmarkören. Båda är redan standard för toHaveScreenshot; vi skriver ut dem så att en ändring syns i kodgranskningen. När vi bytte till animations: 'allow' föll testet i tre körningar av tre med Failed to take two consecutive stable screenshots efter 5 sekunder, eftersom snurran på sidan aldrig stod still.

3. Skriv sidan och testet

Skapa mappen tests och spara blocket nedan som tests/kassa.spec.ts. Sidan har en rubrik, en tidsstämpel som ändras vid varje laddning, en snurra som roterar hela tiden och en knapprad. Miljövariabeln FLYTTA lägger knapparna i högra kanten, som om en AI-ändring bytt justify-content.

import { test, expect } from '@playwright/test';

function sida(flyttad: boolean) {
  return `<!doctype html><html lang="sv"><meta charset="utf-8"><title>Kassa</title>
<style>
  body { font-family: Arial, sans-serif; margin: 24px; }
  .rad { display: flex; gap: 12px; justify-content: ${flyttad ? 'flex-end' : 'flex-start'}; }
  .snurra { width: 16px; height: 16px; border: 3px solid #bbb; border-top-color: #222;
            border-radius: 50%; animation: snurr 1s linear infinite; }
  @keyframes snurr { to { transform: rotate(360deg); } }
</style>
<h1>Kassa</h1>
<p>Uppdaterad <time id="tid"></time></p>
<div class="snurra"></div>
<div class="rad"><button>Betala</button><button>Avbryt</button></div>
<script>document.getElementById('tid').textContent = new Date().toISOString();</script>
</html>`;
}

test('kassan ser ut som referensen', async ({ page }) => {
  await page.setContent(sida(process.env.FLYTTA === '1'));
  await expect(page.getByRole('button', { name: 'Betala' })).toBeVisible();
  await expect(page).toHaveScreenshot('kassa.png', {
    mask: [page.locator('#tid')]
  });
});

Testet kontrollerar först att Betala syns, som ett vanligt funktionstest. Sedan väntar toHaveScreenshot tills två skärmbilder i rad blir likadana och jämför den sista med referensen. mask täcker tidsstämpeln med en ruta i färgen #FF00FF, så att klockan inte fäller testet. Utan masken föll vårt test vid nästa körning med 135 olika pixlar, bara för att tiden hade gått.

Masken passar för klockslag, slumpade id:n och annonser. Kommer innehållet från ett API är det ofta bättre att låsa svaret med API-mockning i Playwright, så att bilden visar riktig rendering av fast data. Sidor bakom inloggning tar guiden om inloggning i Playwright upp.

4. Skapa referensbilden

npx playwright test --reporter=line

Första körningen faller med A snapshot doesn't exist at …kassa-chromium-win32.png, writing actual. Playwright har då sparat bilden i mappen tests/kassa.spec.ts-snapshots. Öppna den och kontrollera att den visar sidan som den ska, innan du kör igen och ser testet gå igenom. Annars godkänner du ett fel som facit. Dokumentationen om visuella jämförelser säger att mappen ska checkas in i versionshanteringen.

Filnamnet följer dokumentationens mönster: namnet du gav (kassa), projektets namn (chromium) och plattformen (win32 hos oss, darwin i dokumentationens exempel). När vi först körde utan projects blev namnet kassa-win32.png.

5. Flytta knappen och läs facit

Kör samma test med den flyttade knappraden. Vi körde varianten i Git Bash; i PowerShell sätter du variabeln med $env:FLYTTA = '1' före kommandot och tar bort den med Remove-Item Env:FLYTTA efteråt.

FLYTTA=1 npx playwright test --reporter=line
KörningFacit
FörstaFaller: snapshot doesn't exist, writing actual.
Andra1 passed.
FLYTTA=1Faller: 918 pixels (ratio 0.01 of all image pixels) are different.
Utan maskFaller: 135 olika pixlar från tidsstämpeln.
animations: 'allow'Faller: Failed to take two consecutive stable screenshots.

918 pixlar är ungefär 0,6 procent av bildens 153 600 pixlar, avrundat till 0.01 i meddelandet. Under test-results ligger tre bilder: kassa-expected.png, kassa-actual.png och kassa-diff.png, där skillnaden är markerad. Vill du se hela körningen steg för steg kan du öppna den i Playwright Trace Viewer.

Provet kördes lokalt den 1 november 2026 med Node.js 24.13.0, Playwright Test 1.64.1 och installerad Chrome på Windows. Siffrorna gäller den här sidan och den maskinen.

6. Välj tröskel med avsikt

Två inställningar styr hur mycket som får skilja. threshold avgör när en enskild pixel räknas som olik: en upplevd färgskillnad i färgrymden YIQ, från 0 (strikt) till 1 (generös), med 0,2 som standard. maxDiffPixels och maxDiffPixelRatio anger hur många olika pixlar som får finnas, och maxDiffPixels är enligt dokumentationen inte satt som standard.

Avvägningen syns i provet. Sätter du maxDiffPixels: 1000 för att slippa falsklarm hade den flyttade knappraden på 918 pixlar gått igenom. Maska hellre den yta som ändras än höj toleransen för hela bilden.

7. Uppdatera referensbilden medvetet

npx playwright test --update-snapshots

Gör det bara när ändringen är beställd. I version 1.64.1 visar npx playwright test --help att flaggan utan värde betyder läget changed; övriga lägen är all, missing, none och default. När vi körde den flyttade varianten med flaggan skrev Playwright att referensbilden is re-generated, writing actual. Därefter gick den flyttade varianten igenom, och originalet föll med samma 918 pixlar.

Lägg den nya referensbilden i samma commit som designändringen, så att granskaren ser båda. Be inte kodassistenten uppdatera referensbilderna bara för att få testet grönt. Då har testet godkänt just det fel det skulle fånga.

8. Kör i CI på samma system som referensen

Dokumentationen om visuella jämförelser säger att rendering kan variera med operativsystem, version, inställningar, hårdvara och strömkälla, och att testerna ska köras i samma miljö som referensbilderna skapades i. En referens från din Windowsdator heter kassa-chromium-win32.png och används inte av en Linuxrunner, som har en annan plattform i filnamnet. Webbläsaren ska också vara densamma: vår referens är tagen med installerad Chrome via channel: 'chrome'.

CI-dokumentationen visar ett GitHub Actions-jobb i containern mcr.microsoft.com/playwright:v1.64.1-noble och nämner containern som ett sätt att få en konsekvent miljö för skärmbilder mellan operativsystem. Skapa då referensbilderna i samma container och version som CI kör. Sedan version 1.63 går en körning med --update-snapshots=missing igenom, så att CI kan skapa saknade referensbilder och kontrollera befintliga i samma körning, enligt versionsanteckningarna. Granska de nya bilderna innan de checkas in. CI-dokumentationen rekommenderar också en worker i CI för stabilitet och reproducerbarhet, och installationssteget beskrivs i guiden om npm ci. CI-delen har vi inte kört.

FAQ

Ersätter ett skärmbildstest funktionstester?

Nej. Det jämför bara hur sidan ser ut mot en referensbild. Det säger inget om att knappen gör rätt sak, om tangentbordet fungerar eller om sidan är tillgänglig, så det är ett komplement.

Varför heter referensbilden kassa-chromium-win32.png?

Playwright lägger till projektets namn och plattformen efter namnet du anger. Samma test får därför olika referensbilder på en Windowsdator och i en Linuxbaserad CI.

Hur stor skillnad tolererar toHaveScreenshot som standard?

En pixel räknas som olik när färgskillnaden överstiger threshold, som är 0,2 på en skala från 0 till 1. maxDiffPixels är inte satt som standard.

Varför får jag Failed to take two consecutive stable screenshots?

Playwright fick aldrig två likadana skärmbilder i rad innan tiden tog slut, i vårt prov för att en animation fortsatte. Låt animations stå på disabled.

Källor och prov

Primärkällor kontrollerade 1 november 2026. Övningsfilerna kördes samma dag i en isolerad mapp med Node.js 24.13.0, Playwright Test 1.64.1 och installerad Chrome på Windows; lägena för --update-snapshots är hämtade ur hjälptexten i den versionen. CI-delen kördes inte. Sidan och facit är AI-kreatörs egna exempel.