Logo Light
Published on

Playwright - proč se v testu změní hodnota v konstantě?

Problém: proč mám pokaždé jiný e-mail?

Při psaní API testů si často potřebujeme vygenerovat unikátní hodnotu nějaké proměnné - např. e-mail pro uživatele.
V prvním testu ho chceme úspěšně použít jako přihlašovací jméno pro registraci.
V druhém testu pak stejný e-mail použijeme znovu, abychom ověřili, že duplicitní registrace selže. Na začátku, ještě před definicí testů, si tedy vygenerujeme jedinečný e-mail:

const validUser = {
  email: `testuser_${Date.now()}@example.com`,
  password: 'MyPassword',
}

test.describe('Authentication API', () => {
  // ...
  test('1.1 Should register successfully with valid email and password', async () => {
    console.log('Test 1.1 user: ' + validUser)
    // ...
  })
  test.only('1.2 Should fail registration with an already existing email', async () => {
    console.log('Test 1.2 user: ' + validUser)
    // ...
  })
})

Na první pohled to vypadá v pořádku - vytvoříme unikátní e-mail, který pak použijeme v každém jednotlivém testu.

Jenže při spuštění sady testů se ukáže, že v každém testu se použil jiný e-mail. Výsledek:

Test 1.1 user: testuser_1753241517705@example.com
Test 1.2 user: testuser_1753241517730@example.com

Druhý test pak není skutečně duplicitní registrace – ve skutečnosti testujete úplně jiného uživatele a registrace naproti očekávání proběhne úspěšně.

Proč se to děje?

Playwright spouští každý test(...) izolovaně. Soubor s testy se znovu načte a proměnné na top-levelu se znovu vyhodnotí.

Jinými slovy, tento kód:

const validUser = {
  email: `testuser_${Date.now()}@example.com`,
  password: 'MyPassword',
}

neběží jen jednou, ale při každém testu znovu. Proto máme pokaždé jiný e-mail.

Řešení:

Horší varianta: použít beforeAll

Chceme, aby se validUser vygeneroval jen jednou před celou sadou testů - to zajistí test.beforeAll.

Správné řešení:

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

const BASE_URL = 'http://localhost:3000'
const REGISTER_ENDPOINT = `${BASE_URL}/register`

let validUser

test.describe('Authentication API', () => {
  let apiContext

  test.beforeAll(async ({ playwright }) => {
    apiContext = await request.newContext()

    // vygenerujeme JEDNOU pro celou sadu testů
    validUser = {
      email: `testuser_${Date.now()}@example.com`,
      password: 'MyPassword',
    }

    console.log('unique test user:', validUser)
  })

  test.afterAll(async () => {
    await apiContext.dispose()
  })

  test('1.1 registrace nového uživatele', async () => {
    console.log('Test 1.1 user:', validUser)
    const res = await apiContext.post(REGISTER_ENDPOINT, { data: validUser })
    expect(res.status()).toBe(201)
  })

  test('1.2 registrace duplicitního uživatele', async () => {
    console.log('Test 1.2 user:', validUser)
    const res = await apiContext.post(REGISTER_ENDPOINT, { data: validUser })
    expect(res.status()).toBe(400)
  })
})

Toto řešení samo o sobě může opět skončit neúspěchem kvůli paralelnímu spouštění testů. Je třeba testy pustit sekvenčně:

npx playwright test --workers=1

Lepší varianta: psát testy, které jsou na sobě nezávislé

Psát testy závislé na jiných testech je obecně antipattern.

Důvody:

  • Izolovanost testů Každý test by měl být spustitelný samostatně, bez ohledu na pořadí. Pokud je test závislý na výsledku jiného, ztrácíme možnost spouštět je selektivně (test.only, --grep, paralelně apod.).

  • Paralelní běh Playwright (i jiné frameworky) běžně spouští testy paralelně. Pokud mají závislosti, mohou selhat jen proto, že běžely ve špatném pořadí nebo ve více vláknech současně.

  • Snadnější ladění a údržba Když test selže, má být jasné, že problém je buď v testu samotném, nebo v aplikaci. Pokud závisí na jiném testu, může selhat i kvůli němu.

Shrnutí

  • Proměnné na top-levelu v Playwright testech se vyhodnocují pro každý test zvlášť.
  • Pokud chcete sdílená data pro celou describe skupinu, použijte beforeAll.
  • Jednoduše lze problému předejít psaním na sobě nezávislých testů