TL;DR

  • Playwright — фреймворк автоматизации браузеров от Microsoft с auto-wait и встроенными assertions
  • Поддерживает Chromium, Firefox и WebKit единым API
  • TypeScript-first с отличной поддержкой IDE и генерацией кода
  • Параллельное выполнение из коробки — запускает тесты быстрее Selenium или Cypress
  • Trace viewer и запись видео для отладки упавших тестов

Подходит для: Команд, желающих современный инструментарий, поддержку TypeScript и быстрое параллельное выполнение Пропусти, если: Нужен Safari на реальных устройствах или есть большая существующая инфраструктура Selenium Время чтения: 15 минут

Твои Selenium-тесты выполняются 45 минут. Cypress-тесты не могут запускаться параллельно без оплаты облака. Тестировщики тратят часы на отладку flaky ожиданий.

Playwright решает эти проблемы. Auto-wait устраняет проблемы тайминга. Параллельное выполнение бесплатное и встроенное. Trace viewer показывает, что именно произошло, когда тест упал.

Что такое Playwright?

Playwright — open-source фреймворк автоматизации браузеров от Microsoft. Он управляет Chromium, Firefox и WebKit через единый API.

Ключевые возможности:

  • Auto-wait — ждет, пока элементы станут доступными перед взаимодействием
  • Web-first assertions — встроенная логика повторов для assertions
  • Параллельное выполнение — запускает тесты на нескольких воркерах по умолчанию
  • Trace viewer — отладка с путешествием во времени для упавших тестов
  • Codegen — генерирует тесты записывая действия в браузере

Установка и настройка

Создание нового проекта

# Создать новый Playwright проект
npm init playwright@latest

# Ответь на вопросы:
# - TypeScript или JavaScript? → TypeScript
# - Куда положить тесты? → tests
# - Добавить GitHub Actions? → Yes
# - Установить браузеры? → Yes

Структура проекта

my-project/
├── tests/
│   └── example.spec.ts
├── playwright.config.ts
├── package.json
└── .github/
    └── workflows/
        └── playwright.yml

Конфигурация

// 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', { open: 'never' }],
    ['list']
  ],

  use: {
    baseURL: 'http://localhost:3000',
    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'] } },
    { name: 'mobile', use: { ...devices['iPhone 14'] } },
  ],
});

Написание первого теста

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

test('user can login with valid credentials', async ({ page }) => {
  // Навигация
  await page.goto('/login');

  // Заполнение формы
  await page.getByLabel('Email').fill('user@example.com');
  await page.getByLabel('Password').fill('password123');

  // Отправка
  await page.getByRole('button', { name: 'Sign In' }).click();

  // Assertions
  await expect(page).toHaveURL('/dashboard');
  await expect(page.getByText('Welcome back')).toBeVisible();
});

test('shows error for invalid credentials', async ({ page }) => {
  await page.goto('/login');

  await page.getByLabel('Email').fill('wrong@example.com');
  await page.getByLabel('Password').fill('wrongpass');
  await page.getByRole('button', { name: 'Sign In' }).click();

  await expect(page.getByText('Invalid credentials')).toBeVisible();
  await expect(page).toHaveURL('/login');
});

Запуск тестов

# Запустить все тесты
npx playwright test

# Запустить конкретный файл
npx playwright test tests/login.spec.ts

# Запустить в headed режиме (видеть браузер)
npx playwright test --headed

# Запустить конкретный браузер
npx playwright test --project=chromium

# Режим отладки
npx playwright test --debug

# UI режим (интерактивный)
npx playwright test --ui

Локаторы: Поиск элементов

Playwright рекомендует user-facing локаторы вместо CSS/XPath селекторов.

Рекомендуемые локаторы

// По роли (основанные на accessibility)
page.getByRole('button', { name: 'Submit' })
page.getByRole('link', { name: 'Home' })
page.getByRole('textbox', { name: 'Email' })
page.getByRole('checkbox', { name: 'Remember me' })

// По label (поля форм)
page.getByLabel('Email')
page.getByLabel('Password')

// По placeholder
page.getByPlaceholder('Search...')

// По тексту
page.getByText('Welcome')
page.getByText('Welcome', { exact: true })

// По test ID (когда другие варианты не работают)
page.getByTestId('submit-button')

CSS и XPath (когда нужны)

// CSS селектор
page.locator('button.primary')
page.locator('[data-testid="submit"]')
page.locator('#login-form input[type="email"]')

// XPath
page.locator('xpath=//button[contains(text(), "Submit")]')

// Цепочка локаторов
page.locator('.card').filter({ hasText: 'Premium' }).getByRole('button')

Assertions

Assertions в Playwright автоматически повторяются до таймаута.

Распространенные assertions

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

test('примеры assertions', async ({ page }) => {
  await page.goto('/dashboard');

  // Видимость
  await expect(page.getByText('Welcome')).toBeVisible();
  await expect(page.getByText('Loading')).toBeHidden();

  // Текстовое содержимое
  await expect(page.getByRole('heading')).toHaveText('Dashboard');
  await expect(page.getByRole('heading')).toContainText('Dash');

  // Атрибуты
  await expect(page.getByRole('button')).toBeEnabled();
  await expect(page.getByRole('button')).toBeDisabled();
  await expect(page.getByRole('checkbox')).toBeChecked();

  // URL и title
  await expect(page).toHaveURL('/dashboard');
  await expect(page).toHaveURL(/dashboard/);
  await expect(page).toHaveTitle('My App - Dashboard');

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

  // Значение
  await expect(page.getByLabel('Email')).toHaveValue('user@example.com');
});

Page Object Model

// pages/LoginPage.ts
import { Page, Locator, expect } from '@playwright/test';

export class LoginPage {
  readonly page: Page;
  readonly emailInput: Locator;
  readonly passwordInput: Locator;
  readonly submitButton: Locator;
  readonly errorMessage: Locator;

  constructor(page: Page) {
    this.page = page;
    this.emailInput = page.getByLabel('Email');
    this.passwordInput = page.getByLabel('Password');
    this.submitButton = page.getByRole('button', { name: 'Sign In' });
    this.errorMessage = page.getByRole('alert');
  }

  async goto() {
    await this.page.goto('/login');
  }

  async login(email: string, password: string) {
    await this.emailInput.fill(email);
    await this.passwordInput.fill(password);
    await this.submitButton.click();
  }

  async expectError(message: string) {
    await expect(this.errorMessage).toContainText(message);
  }
}
// tests/login.spec.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';

test.describe('Login', () => {
  test('successful login', async ({ page }) => {
    const loginPage = new LoginPage(page);
    await loginPage.goto();
    await loginPage.login('user@example.com', 'password123');
    await expect(page).toHaveURL('/dashboard');
  });

  test('invalid credentials', async ({ page }) => {
    const loginPage = new LoginPage(page);
    await loginPage.goto();
    await loginPage.login('wrong@example.com', 'wrong');
    await loginPage.expectError('Invalid credentials');
  });
});

Перехват сети

test('мокаем API ответ', async ({ page }) => {
  // Мокаем API
  await page.route('/api/users', async (route) => {
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify([
        { id: 1, name: 'Mock User', email: 'mock@example.com' }
      ])
    });
  });

  await page.goto('/users');
  await expect(page.getByText('Mock User')).toBeVisible();
});

test('блокируем запросы', async ({ page }) => {
  // Блокируем аналитику
  await page.route('**/*google-analytics*', route => route.abort());
  await page.route('**/*.{png,jpg,jpeg}', route => route.abort());

  await page.goto('/');
});

Отладка

Trace Viewer

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

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

Режим отладки

# Пошаговое выполнение теста
npx playwright test --debug

# Пауза в конкретной точке
await page.pause();

Интеграция CI/CD

# .github/workflows/playwright.yml
name: Playwright Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright browsers
        run: npx playwright install --with-deps

      - name: Run tests
        run: npx playwright test

      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 30

AI-Assisted разработка Playwright

AI-инструменты хорошо интегрируются с читаемым API Playwright.

Что AI делает хорошо:

  • Генерация тестов из user stories или требований
  • Конвертация Selenium/Cypress тестов в Playwright
  • Написание Page Object классов из HTML-структуры
  • Создание assertions для сложной валидации данных
  • Объяснение методов и паттернов API Playwright

Что всё ещё требует людей:

  • Решения о стратегии и покрытии тестов
  • Отладка визуальных или timing-связанных сбоев
  • Выбор между стратегиями локаторов
  • Оптимизация производительности для больших test suite

FAQ

Playwright лучше Selenium?

Playwright предлагает несколько преимуществ: auto-wait устраняет большинство проблем тайминга, выполнение быстрее благодаря коммуникации через протокол браузера, API более современный. Selenium имеет более широкую поддержку браузеров и большее сообщество. Для новых проектов Playwright обычно лучший выбор.

Playwright бесплатный?

Да, полностью. Playwright open-source под лицензией Apache 2.0. В отличие от Cypress, нет платных тарифов. Параллельное выполнение, trace viewer, запись видео — всё бесплатно. Единственная стоимость — твоя собственная CI-инфраструктура.

Может ли Playwright тестировать мобильные приложения?

Playwright тестирует мобильные веб-браузеры через эмуляцию устройств — симулирует viewport iPhone, Android и планшетов. Для нативных мобильных приложений (iOS/Android из app store) нужен Appium или платформенно-специфичные инструменты.

Какие языки поддерживает Playwright?

Playwright официально поддерживает TypeScript, JavaScript, Python, Java и C#. TypeScript/JavaScript имеют больше функций и лучшую документацию. Python отлично подходит для команд, уже использующих pytest.

Когда выбирать Playwright

Выбирай Playwright когда:

  • Начинаешь новый проект автоматизации
  • Команда использует TypeScript/JavaScript
  • Нужно быстрое параллельное выполнение
  • Хочешь современные инструменты отладки (trace viewer)
  • Тестирование на Chromium, Firefox, WebKit

Рассмотри альтернативы когда:

  • Нужен реальный Safari на macOS (Selenium + Safari)
  • Большая существующая Selenium-инфраструктура
  • Команда предпочитает Python/Java-first инструменты

Официальные ресурсы

Смотрите также