Learnly

Пример создания простого теста

Создание реального теста: setup, codegen, debug, trace

В этом уроке, полный путь от чистой папки до работающего теста с использованием современного API Playwright. Реалистичный e-commerce-сценарий, codegen, инструменты дебага, trace viewer.

Шаг 1. Инициализация проекта

# Можно с нуля
npm init playwright@latest

# Или вручную в существующем
npm install -D @playwright/test
npx playwright install              # скачать Chromium / Firefox / WebKit
npx playwright install --with-deps  # на Linux: + системные зависимости

После init структура:

my-project/
├── playwright.config.ts
├── tests/
│   └── example.spec.ts
├── tests-examples/
│   └── demo-todo-app.spec.ts
└── package.json

package.json обычно дополняют скриптами:

{
  "scripts": {
    "test": "playwright test",
    "test:ui": "playwright test --ui",
    "test:headed": "playwright test --headed",
    "test:debug": "PWDEBUG=1 playwright test",
    "report": "playwright show-report",
    "codegen": "playwright codegen"
  }
}

Шаг 2. Базовый playwright.config.ts

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 4 : undefined,
  reporter: [['html', { open: 'never' }], ['list']],

  use: {
    baseURL: process.env.BASE_URL ?? 'https://demo.playwright.dev',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },

  projects: [
    { name: 'chromium', use: devices['Desktop Chrome'] },
    { name: 'firefox',  use: devices['Desktop Firefox'] },
    { name: 'webkit',   use: devices['Desktop Safari'] },
  ],
});

Шаг 3. Codegen, записать первый тест кликами

npx playwright codegen https://demo.playwright.dev/todomvc

Откроется браузер + Playwright Inspector. Все ваши клики, фоллы, Enter записываются в код. Inspector показывает варианты selector'ов в порядке предпочтения (role > label > test-id > css).

Полученный код, копируете в tests/todo.spec.ts и доводите до ума:

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

test('add and complete todo', async ({ page }) => {
  await page.goto('/todomvc');

  await page.getByPlaceholder('What needs to be done?').fill('Buy milk');
  await page.getByPlaceholder('What needs to be done?').press('Enter');

  await page.getByPlaceholder('What needs to be done?').fill('Walk dog');
  await page.getByPlaceholder('What needs to be done?').press('Enter');

  const items = page.getByTestId('todo-item');
  await expect(items).toHaveCount(2);

  await items.first().getByRole('checkbox').check();
  await expect(items.first()).toHaveClass(/completed/);
});

Шаг 4. Реалистичный e-commerce тест

// tests/checkout.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Checkout flow', () => {

  test.beforeEach(async ({ page }) => {
    await page.goto('/');
  });

  test('add product → cart → checkout → confirmation', async ({ page }) => {
    // 1. Поиск товара
    await page.getByRole('searchbox').fill('headphones');
    await page.getByRole('searchbox').press('Enter');
    await expect(page).toHaveURL(/\/search/);

    // 2. Открыть карточку
    const firstResult = page.getByRole('article').first();
    await expect(firstResult).toBeVisible();
    const productName = await firstResult.getByRole('heading').textContent();
    await firstResult.getByRole('link', { name: /view/i }).click();

    // 3. Добавить в корзину
    await page.getByRole('button', { name: 'Add to cart' }).click();
    const cartBadge = page.getByTestId('cart-count');
    await expect(cartBadge).toHaveText('1');

    // 4. Перейти в корзину
    await page.getByRole('link', { name: /cart/i }).click();
    await expect(page.getByRole('heading', { name: 'Your cart' })).toBeVisible();
    await expect(page.getByText(productName!)).toBeVisible();

    // 5. Чекаут
    await page.getByRole('button', { name: 'Checkout' }).click();
    await page.getByLabel('Email').fill('test@example.com');
    await page.getByLabel('Address').fill('123 Test St');
    await page.getByLabel('City').fill('Springfield');
    await page.getByLabel('ZIP').fill('12345');

    // 6. Оплата (мокаем backend)
    await page.route('**/api/payment', route =>
      route.fulfill({ status: 200, json: { orderId: 'TEST-42' } })
    );

    await page.getByRole('button', { name: 'Pay now' }).click();

    // 7. Подтверждение
    await expect(page.getByRole('heading')).toContainText('Order confirmed');
    await expect(page.getByText(/TEST-42/)).toBeVisible();
  });

  test('cart persists after refresh', async ({ page }) => {
    await page.getByRole('article').first().getByRole('button', { name: 'Add to cart' }).click();
    await expect(page.getByTestId('cart-count')).toHaveText('1');
    await page.reload();
    await expect(page.getByTestId('cart-count')).toHaveText('1');
  });

  test('empty cart shows message', async ({ page }) => {
    await page.getByRole('link', { name: /cart/i }).click();
    await expect(page.getByText('Your cart is empty')).toBeVisible();
  });
});

Шаг 5. UI mode, главный инструмент разработки тестов

npx playwright test --ui

В UI mode:

  • Watch mode: тесты ре-запускаются при изменении файлов.
  • Time-travel debugger: для каждого шага видно DOM snapshot.
  • Live picker locator'ов: наводите мышью на UI → копируется готовый locator.
  • Network panel.
  • Re-run одного теста, debug-step, скрин-tail.

UI mode сильно сокращает цикл «написал → запустил → понял где сломалось → правлю».

Шаг 6. Дебаг падающего теста

A. Через UI mode

Откройте, найдите красный, кликните последний шаг, увидите DOM-снимок ровно перед падением.

B. Через PWDEBUG

PWDEBUG=1 npx playwright test checkout.spec.ts -g "add product"

Запускает тест с открытым Playwright Inspector + браузер останавливается перед каждым action. Видно selector, который сейчас выполнится; можно поправить и продолжить.

C. Через --debug

npx playwright test --debug

D. page.pause() в коде

test('flaky test', async ({ page }) => {
  await page.goto('/');
  await page.click('text=Open');
  await page.pause();      // ←  тест остановится; откроется Inspector
  await page.click('text=Save');
});

E. Trace viewer (для CI-падений)

# Скачали trace.zip из CI artifacts
npx playwright show-trace trace.zip

В trace viewer видно весь timeline: каждый step + DOM-snapshot до/после, network, console, source code теста с подсветкой текущей строки. Это самый эффективный способ дебага CI-only падений.

Шаг 7. Locator best practices в реальном коде

// ❌ Хрупко, ломается на любом CSS-рефакторинге
await page.locator('.MuiButton-root.MuiButton-containedPrimary').click();

// ❌ Хрупко, ломается, если поменяют структуру
await page.locator('div > section:nth-child(3) > button').click();

// ✅ Стабильно
await page.getByRole('button', { name: 'Submit' }).click();

// ✅ Если нет accessible name
// 1. Договориться с фронтом добавить `data-testid`
await page.getByTestId('submit-button').click();

// ✅ Уникальное сочетание role + текст рядом
await page.getByRole('listitem').filter({ hasText: 'iPhone 15' })
  .getByRole('button', { name: 'Add' })
  .click();

// ✅ Для формы
await page.getByLabel('Email').fill('a@b.com');
await page.getByPlaceholder('Search').press('Enter');

// ✅ Утверждения структуры через Aria snapshot (Playwright 1.50+)
await expect(page.getByRole('main')).toMatchAriaSnapshot(`
  - heading "Cart" [level=1]
  - list:
    - listitem: "iPhone 15"
    - listitem: "Headphones"
  - button "Checkout"
`);

Шаг 8. Web-first assertions vs JavaScript-if

Антипаттерн:

// Хрупко: ассерт может сработать раньше, чем UI обновится
const text = await page.locator('.message').textContent();
expect(text).toBe('Saved');

Правильно:

await expect(page.locator('.message')).toHaveText('Saved');
// retry до 5s, пока текст не совпадёт

Помнить: expect(locator).toX(), web-first с retry. expect(value).toBe(...), обычный sync ассерт.

Шаг 9. Группировка через describe, beforeEach, afterEach

test.describe('Auth', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/login');
  });

  test.afterEach(async ({ page }, info) => {
    if (info.status !== info.expectedStatus) {
      await page.screenshot({ path: `failures/${info.title}.png` });
    }
  });

  test('valid login', /*...*/);
  test('invalid password', /*...*/);
});

test.describe.serial('Order pipeline', () => {
  // тесты выполняются последовательно, делят state
});

Шаг 10. Snapshots / Visual regression

test('homepage looks correct', async ({ page }) => {
  await page.goto('/');
  await expect(page).toHaveScreenshot('home.png', { maxDiffPixels: 100 });
});

Первый запуск создаст baseline в __screenshots__/, последующие сравнят. Для богатого workflow (preview, approve in PR, cross-browser), Argos CI, Lost Pixel, Percy, Chromatic.

Шаг 11. Fixtures для проекта

tests/fixtures.ts (расширение test):

import { test as base, expect } from '@playwright/test';
import { LoginPage } from './pages/LoginPage';
import { CartPage } from './pages/CartPage';

type Fixtures = {
  loginPage: LoginPage;
  cartPage: CartPage;
};

export const test = base.extend<Fixtures>({
  loginPage: async ({ page }, use) => use(new LoginPage(page)),
  cartPage: async ({ page }, use) => use(new CartPage(page)),
});
export { expect };
// tests/checkout.spec.ts
import { test, expect } from './fixtures';

test('checkout', async ({ loginPage, cartPage, page }) => {
  await loginPage.loginAs('alice');
  await cartPage.goto();
  await cartPage.checkout();
  await expect(page).toHaveURL(/order-confirmed/);
});

Шаг 12. Запуск

npx playwright test                           # все тесты, headless, всех браузеров
npx playwright test --headed --project=chromium
npx playwright test checkout.spec.ts          # один файл
npx playwright test -g "empty cart"           # фильтр по имени
npx playwright test --workers=1               # последовательно (для дебага)
npx playwright test --update-snapshots        # обновить baseline для visual
npx playwright show-report                    # открыть HTML-отчёт

Главное

Цикл написания теста на Playwright в 2026:

  1. Codegen, наклацать каркас теста.
  2. Заменить хрупкие locator'ы на getByRole/Label/TestId.
  3. Доковать assertions через web-first expect(...).
  4. Закрыть бизнес-логику через POM, повторяющиеся setup'ы, через fixtures.
  5. UI mode для итераций.
  6. Trace viewer для дебага CI-падений.

В финальном уроке, production: CI/CD, отчёты, parallel/sharding, Docker, BrowserStack, мониторинг flaky-тестов.

AI-тест
1 / 5

Что является основой автоматизации тестирования в Playwright?