Playwright Basics

Playwright — фреймворк для E2E-тестирования от Microsoft. Поддерживает Chromium, Firefox, WebKit в одном API. Быстрый, надёжный, с автогенерацией тестов.

Зачем нужно

Playwright решает проблемы Cypress: кросс-браузерность (не только Chrome), работа с несколькими вкладками, iframe, загрузка файлов, мобильные устройства. Автоматическое ожидание, параллельный запуск, trace viewer для отладки.

Где используется

E2E и integration тесты веб-приложений, визуальные сравнения, тестирование на мобильных viewport, автоматизация браузера, scraping.

Предпосылки

Виды тестов, Основы тестирования, async/await

Установка

# Создание нового проекта
npm init playwright@latest

# Или добавление в существующий
npm install -D @playwright/test
npx playwright install  # Скачивает браузеры

playwright.config.ts

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

export default defineConfig({
  testDir: './tests',
  timeout: 30000,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: [['html'], ['list']],

  use: {
    baseURL: 'http://localhost:3000',
    screenshot: 'only-on-failure',
    trace: 'on-first-retry',
    video: 'retain-on-failure',
  },

  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
    { name: 'webkit', use: { ...devices['Desktop Safari'] } },
    { name: 'mobile', use: { ...devices['iPhone 14'] } },
  ],

  webServer: {
    command: 'npm run dev',
    port: 3000,
    reuseExistingServer: !process.env.CI,
  },
});

Скрипты

{
  "scripts": {
    "test:e2e": "playwright test",
    "test:e2e:ui": "playwright test --ui",
    "test:e2e:headed": "playwright test --headed",
    "test:e2e:debug": "playwright test --debug",
    "test:e2e:codegen": "playwright codegen http://localhost:3000"
  }
}

Основные команды

Навигация

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

test('навигация', async ({ page }) => {
  await page.goto('/');
  await page.goto('/login');
  await page.goBack;
  await page.goForward;
  await page.reload;
});

Locators — поиск элементов

test('locators', async ({ page }) => {
  // По роли (рекомендуется! Доступность!)
  page.getByRole('button', { name: 'Войти' });
  page.getByRole('heading', { name: 'Главная' });
  page.getByRole('link', { name: 'О нас' });
  page.getByRole('textbox', { name: 'Email' });

  // По тексту
  page.getByText('Добро пожаловать');
  page.getByText(/пожаловать/i); // RegExp

  // По label (для форм)
  page.getByLabel('Email');
  page.getByLabel('Пароль');

  // По placeholder
  page.getByPlaceholder('Введите email');

  // По test-id
  page.getByTestId('login-button');

  // CSS-селектор (последний вариант)
  page.locator('.btn-primary');
  page.locator('#submit');
  page.locator('[data-testid="card"]');

  // Цепочки
  page.getByRole('list').getByRole('listitem');
  page.locator('.card').filter({ hasText: 'Алиса' });
});

Действия

test('действия', async ({ page }) => {
  // Клик
  await page.getByRole('button', { name: 'Войти' }).click();
  await page.getByRole('link', { name: 'Ссылка' }).click();

  // Ввод текста
  await page.getByLabel('Email').fill('alice@test.com');
  await page.getByLabel('Email').clear();

  // Клавиатура
  await page.getByLabel('Поиск').press('Enter');
  await page.keyboard.press('Escape');

  // Чекбокс
  await page.getByLabel('Запомнить').check;
  await page.getByLabel('Запомнить').uncheck;

  // Select
  await page.getByLabel('Страна').selectOption('RU');

  // Hover
  await page.getByRole('button').hover;

  // Загрузка файла
  await page.getByLabel('Файл').setInputFiles('test.pdf');
});

Assertions

test('assertions', async ({ page }) => {
  // Видимость
  await expect(page.getByText('Привет')).toBeVisible;
  await expect(page.getByTestId('modal')).toBeHidden;

  // Текст
  await expect(page.getByRole('heading')).toHaveText('Заголовок');
  await expect(page.getByRole('heading')).toContainText('Заголов');

  // URL
  await expect(page).toHaveURL('/dashboard');
  await expect(page).toHaveURL(/dashboard/);

  // Title
  await expect(page).toHaveTitle('Мой сайт');

  // Значение input
  await expect(page.getByLabel('Email')).toHaveValue('alice@test.com');

  // CSS-класс
  await expect(page.locator('.btn')).toHaveClass(/active/);

  // Количество элементов
  await expect(page.getByRole('listitem')).toHaveCount(5);

  // Атрибут
  await expect(page.getByRole('link')).toHaveAttribute('href', '/about');

  // Доступен/недоступен
  await expect(page.getByRole('button')).toBeEnabled;
  await expect(page.getByRole('button')).toBeDisabled;
});

Полный пример: тест авторизации

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

test.describe('Авторизация', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/login');
  });

  test('успешный логин', async ({ page }) => {
    await page.getByLabel('Email').fill('alice@test.com');
    await page.getByLabel('Пароль').fill('secret123');
    await page.getByRole('button', { name: 'Войти' }).click();

    await expect(page).toHaveURL('/dashboard');
    await expect(page.getByRole('heading')).toContainText('Алиса');
  });

  test('ошибка при неверном пароле', async ({ page }) => {
    await page.getByLabel('Email').fill('alice@test.com');
    await page.getByLabel('Пароль').fill('wrong');
    await page.getByRole('button', { name: 'Войти' }).click();

    await expect(page.getByTestId('error')).toBeVisible;
    await expect(page.getByTestId('error')).toHaveText('Неверный пароль');
    await expect(page).toHaveURL('/login');
  });
});

Codegen — автогенерация тестов

npx playwright codegen http://localhost:3000

Открывается браузер. Кликаете, вводите — Playwright записывает код теста. Идеально для быстрого старта.

Visual Comparisons — визуальные сравнения

test('страница выглядит правильно', async ({ page }) => {
  await page.goto('/');

  // Скриншот всей страницы
  await expect(page).toHaveScreenshot('homepage.png');

  // Скриншот элемента
  await expect(page.getByTestId('header')).toHaveScreenshot('header.png');

  // С допуском
  await expect(page).toHaveScreenshot('page.png', {
    maxDiffPixels: 100,
  });
});
# Первый запуск: создаёт эталонные снапшоты
npx playwright test --update-snapshots

API-запросы в тестах

// Перехват запросов
test('мок API', async ({ page }) => {
  await page.route('/api/users', async (route) => {
    await route.fulfill({
      status: 200,
      body: JSON.stringify([{ id: 1, name: 'Алиса' }]),
    });
  });

  await page.goto('/users');
  await expect(page.getByTestId('user-card')).toHaveCount(1);
});

// Прямые API-запросы (без браузера)
test('API тест', async ({ request }) => {
  const response = await request.post('/api/login', {
    data: { email: 'alice@test.com', password: 'secret123' },
  });

  expect(response.ok).toBeTruthy();
  const body = await response.json();
  expect(body.token).toBeDefined();
});

Trace Viewer — отладка

# Включить tracing
npx playwright test --trace on

# Посмотреть trace
npx playwright show-trace trace.zip

Trace Viewer показывает: каждый шаг теста, скриншоты, DOM-снапшоты, network requests, console logs.

Частые ошибки

1. Не используют auto-waiting

// ПЛОХО: ручные ожидания
await page.waitForTimeout(3000);

// ХОРОШО: Playwright ждёт автоматически
await expect(page.getByText('Загружено')).toBeVisible;

2. Хрупкие селекторы

// ПЛОХО:
page.locator('div > div:nth-child(3) > button');

// ХОРОШО:
page.getByRole('button', { name: 'Отправить' });

3. Не используют page fixtures

// ПЛОХО: повторяющийся логин
test('тест 1', async ({ page }) => {
  await page.goto('/login');
  await page.fill('#email', 'a@t.com');
  // ...
});

// ХОРОШО: fixture для аутентификации
// auth.setup.ts
test('authenticate', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('Email').fill('alice@test.com');
  await page.getByLabel('Пароль').fill('secret123');
  await page.getByRole('button', { name: 'Войти' }).click();
  await page.context.storageState({ path: '.auth/user.json()' });
});

// Другие тесты используют сохранённое состояние
test.use({ storageState: '.auth/user.json()' });

Практика

  1. Установи Playwright и сгенерируй первый тест через codegen
  2. Напиши тест формы поиска: ввод запроса, нажатие Enter, проверка результатов
  3. Перехвати API-запрос через page.route и верни мок-данные
  4. Сделай визуальное сравнение (screenshot) для компонента
  5. Запусти тесты в трёх браузерах одновременно

Связанные темы

Ресурсы