GuideTestning

Mocka API i Playwright: testa fel och tomma svar

Tre lokala Playwright-test för tom lista, HTTP 500 och nätverksfel. Registrera routen före navigering och kontrollera synlig återhämtning.

Publicerad

Tre genomskinliga cylindrar på en mörk, blank yta; den högra innehåller ett grönt nätverk och till vänster syns ett ansikte av tunna linjer.
Illustration till guidens tre testfall: tom lista, HTTP-fel och avbrutet nätverksanrop.
Kort sagt

Välj ett API-anrop från webbläsaren. Ge det tre bestämda utfall och kontrollera vad användaren ser: tom lista, serverfel och nätverksfel. Ett grönt test ska också visa att laddningen avslutas och att ett nytt försök fungerar efter felet.

1. Välj ett anrop som webbläsaren gör

En AI-byggd app kan fungera när utvecklingsservern svarar med en färdig lista, men fastna i ”Laddar…” när listan är tom. Börja därför med ett litet krav: sidan hämtar projekt från GET /api/projects. Ett lyckat svar är en JSON-array med objekt som har fältet name. Den tomma arrayen betyder att inga projekt finns. Ett HTTP-fel och ett misslyckat nätverksanrop ska få olika meddelanden.

Playwright kan hantera sidans nätverkstrafik, inklusive fetch-anrop. Det är omfattningen i dokumentationen om nätverk. Om din app hämtar data på servern och skickar färdig HTML till webbläsaren fångar denna browser-route inte serverns egna API-anrop. Hitta först anropet i webbläsarens nätverkspanel. Saknas det där behöver du ett prov på den nivå som faktiskt gör hämtningen.

Här bygger du en separat lokal övning med syntetiska projektnamn. Den har ingen fungerande backend: servern svarar 404 på andra sökvägar än startsidan. Det gör en missad route synlig som ett fel i uppställningen. De tre testerna kontrollerar gränssnittets behandling av bestämda svar, inte databas, autentisering eller backendintegration. Läs också guiden om testning av AI-genererad kod för att välja vilka riktiga flöden du behöver behålla.

2. Bygg den lokala sidan och servern

Skapa en tom övningsmapp. Du behöver Node.js med npm samt Google Chrome installerat. Konfigurationen nedan väljer Chrome genom Playwrights Chromium-stöd, enligt dokumentationen om browser channels. Testverktyget är versionslåst för att göra just övningens uppställning lättare att upprepa. Installera beroendet i övningsmappen och lägg sedan in filerna.

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

Spara följande som app.html. HTTP-fel kontrolleras genom svarets ok-fält före JSON-läsningen. Ett avbrutet fetch-anrop hamnar i catch-grenen. Slutligen tar finally bort laddtexten och öppnar knappen igen. Det här är övningens avsiktliga uppdelning; den hanterar inte alla möjliga felaktiga JSON-format och ska inte kopieras som en fullständig produktionsklient.

<!doctype html>
<html lang="sv"><meta charset="utf-8"><title>Projektprov</title>
<h1>Projekt</h1>
<p role="status"></p><p role="alert"></p><ul aria-label="Projektlista"></ul>
<button type="button">Försök igen</button>
<script>
const status = document.querySelector('[role="status"]');
const alert = document.querySelector('[role="alert"]');
const list = document.querySelector('ul');
const button = document.querySelector('button');
async function load() {
  status.textContent = 'Laddar…';
  alert.textContent = '';
  list.replaceChildren();
  button.disabled = true;
  try {
    const response = await fetch('/api/projects');
    if (!response.ok) {
      alert.textContent = 'Serverfel. Försök igen.';
      return;
    }
    const projects = await response.json();
    status.textContent = projects.length === 0 ? 'Inga projekt ännu.' : '';
    for (const project of projects) {
      const item = document.createElement('li');
      item.textContent = project.name;
      list.append(item);
    }
  } catch {
    alert.textContent = 'Nätverksfel. Försök igen.';
  } finally {
    if (status.textContent === 'Laddar…') status.textContent = '';
    button.disabled = false;
  }
}
button.addEventListener('click', load);
load();
</script></html>

Spara servern som server.cjs. Den lyssnar på loopback-adressen och port 4184. Om porten redan används ska du välja en annan och ändra både server, konfiguration och testernas URL:er. Starta inte två kopior av övningsservern samtidigt.

const http = require('node:http');
const fs = require('node:fs');
http.createServer((req, res) => {
  if (req.url === '/') {
    res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
    res.end(fs.readFileSync('app.html'));
    return;
  }
  res.writeHead(404);
  res.end('Ingen backend i detta prov');
}).listen(4184, '127.0.0.1');

Spara konfigurationen som playwright.config.ts. Testkörningen startar och stoppar servern enligt Playwrights webServer-konfiguration. Vi blockerar Service Workers i det här provet: Playwrights nätverksguide beskriver hur de annars kan ta över trafik så att route-interception missar den. Om din produkt bygger på en Service Worker behöver du även ett separat prov med dess verkliga beteende.

import { defineConfig } from '@playwright/test';
export default defineConfig({
  testDir: './tests',
  retries: 0,
  workers: 1,
  use: { browserName: 'chromium', channel: 'chrome', serviceWorkers: 'block' },
  webServer: {
    command: 'node server.cjs',
    url: 'http://127.0.0.1:4184',
    reuseExistingServer: false
  }
});

3. Registrera routen före navigeringen

Skapa mappen tests och filen tests/projects.spec.ts med hela blocket nedan. De första två konstanterna avgränsar anropet till en exakt lokal URL. Registreringen med await page.route kommer före page.goto, eftersom sidan börjar hämta data direkt när skriptet körs. Samma ordning används i Playwrights guide för API-mockning.

Det första testet levererar status 200 och en tom array med route.fulfill. Det andra levererar HTTP 500 vid första anropet och en projektlista vid nästa. Det tredje avbryter första anropet med route.abort('failed') och lämnar därefter en lista. Alternativen status och json samt avbrottet beskrivs i referensen för Route. Den syntetiska texten ”Testfel” används inte som en faktisk serverdiagnos.

import { test, expect } from '@playwright/test';
const endpoint = 'http://127.0.0.1:4184/api/projects';
const pageUrl = 'http://127.0.0.1:4184/';

test('tom lista lämnar laddningen', async ({ page }) => {
  let calls = 0;
  await page.route(endpoint, async route => {
    calls++;
    await route.fulfill({ status: 200, json: [] });
  });
  await page.goto(pageUrl);
  await expect(page.getByRole('status')).toHaveText('Inga projekt ännu.');
  await expect(page.getByRole('alert')).toBeEmpty();
  await expect(page.getByRole('listitem')).toHaveCount(0);
  await expect(page.getByRole('button', { name: 'Försök igen' })).toBeEnabled();
  expect(calls).toBe(1);
});

test('HTTP 500 visar serverfel och kan återhämta sig', async ({ page }) => {
  let calls = 0;
  await page.route(endpoint, async route => {
    calls++;
    await route.fulfill(calls === 1
      ? { status: 500, json: { message: 'Testfel' } }
      : { status: 200, json: [{ name: 'Återställt projekt' }] });
  });
  await page.goto(pageUrl);
  await expect(page.getByRole('alert')).toHaveText('Serverfel. Försök igen.');
  await expect(page.getByRole('status')).toBeEmpty();
  await expect(page.getByRole('listitem')).toHaveCount(0);
  const retry = page.getByRole('button', { name: 'Försök igen' });
  await expect(retry).toBeEnabled();
  await retry.click();
  await expect(page.getByRole('listitem')).toHaveText(['Återställt projekt']);
  await expect(page.getByRole('alert')).toBeEmpty();
  await expect(page.getByRole('status')).toBeEmpty();
  await expect(retry).toBeEnabled();
  expect(calls).toBe(2);
});

test('avbrutet anrop visar nätverksfel och kan återhämta sig', async ({ page }) => {
  let calls = 0;
  await page.route(endpoint, async route => {
    calls++;
    if (calls === 1) await route.abort('failed');
    else await route.fulfill({ status: 200, json: [{ name: 'Återställt projekt' }] });
  });
  await page.goto(pageUrl);
  await expect(page.getByRole('alert')).toHaveText('Nätverksfel. Försök igen.');
  await expect(page.getByRole('status')).toBeEmpty();
  await expect(page.getByRole('listitem')).toHaveCount(0);
  const retry = page.getByRole('button', { name: 'Försök igen' });
  await expect(retry).toBeEnabled();
  await retry.click();
  await expect(page.getByRole('listitem')).toHaveText(['Återställt projekt']);
  await expect(page.getByRole('alert')).toBeEmpty();
  await expect(page.getByRole('status')).toBeEmpty();
  await expect(retry).toBeEnabled();
  expect(calls).toBe(2);
});

4. Läs de tre synliga faciten

Kör följande kommando från övningsmappen. Konfigurationen kör med en worker och utan omförsök av hela testet. Knappen ”Försök igen” är däremot en del av appens beteende och trycks uttryckligen i de två feltesterna. Blanda inte ihop en testomkörning med användarens återförsök.

npx playwright test --reporter=line
Bestämt utfallSynligt facitYtterligare kontroll
200 och tom arrayInga projekt ännu.Ingen feltext eller listpost; knappen är aktiverad.
HTTP 500Serverfel. Försök igen.Laddtexten försvinner; ett klick visar Återställt projekt och tar bort felet.
Avbrutet anropNätverksfel. Försök igen.Laddtexten försvinner; ett klick visar samma återställda projekt.

Räknaren kontrollerar att routen faktiskt användes: ett anrop i det tomma fallet och två i återhämtningsfallen. Testet nöjer sig alltså inte med att sidan redan innehåller en passande text. Kontrollen av en tom statusruta efter felet fångar också en kvarhängande laddindikator. HTTP 500 är ett levererat HTTP-svar i övningen; abort lämnar ingen sådan svarskropp till sidans kod. Därför ska de inte ersättas med samma mock när du granskar felhanteringen.

De tre publicerade testerna kördes lokalt den 4 oktober 2026 med Node.js 24.13.0, Playwright Test 1.58.2 och installerad Chrome på Windows. Samtliga passerade. Körningen använde exakt övningsfilerna här, inklusive återförsöken. Resultatet gäller denna fixture och denna körning; ingen verklig applikationsbackend testades.

5. Använd testet för att granska AI-rättningen

Om din app faller, ge kodassistenten endpoint, svarsformat, feltext och den kontroll som misslyckades. Be den rätta övergången från laddning till det aktuella tillståndet och behålla testets förväntan. Kontrollera diffen: en hårdkodad tomtext för alla svar kan göra ett enskilt test grönt men förstör lyckade listor. De separata felmeddelandena och återförsöken gör uppdragets facit tydligare.

Om routens räknare stannar på noll ska du kontrollera URL, port, eventuella query-parametrar och om hämtningen verkligen görs i webbläsaren. Vårt exakta URL-val matchar inte en variant med en extra query-sträng. Gör matchningen avsiktligt bredare när appens kontrakt kräver det, men undvik att mocka alla API-anrop bara för att få provet grönt. En bred route kan dölja att appen ropar på fel endpoint.

Behöver du se vad som hände mellan navigeringen och kontrollen kan du följa guiden om Playwright Trace Viewer. Behåll dessutom minst ett flöde som anropar den riktiga testbackendens endpoint utan dessa routes. Där kontrollerar du att verkligt svarsformat och integration passar appen. De tre bestämda UI-proven och ett riktigt integrationsprov svarar på olika frågor och kompletterar varandra.

Källor och avgränsning

Primärkällorna kontrollerades 4 oktober 2026. Fixture, projektnamn, feltexter och facit är AI-kreatörs egna exempel. Den lokala körningen verifierar UI-beteendet i övningen.