Интервью по Playwright проверяет суждение, а не словарный запас. Интервьюер который слышит «getByRole лучше CSS-селекторов» спросит почему, и «читабельнее» уже не засчитывается. Знание того что локаторы getByRole ломаются когда меняется доступное имя, а не когда меняется CSS-класс. Вот глубина которая разделяет джуниорские и сениорские ответы. Это руководство разбирает вопросы которые чаще всего встречаются на интервью, сгруппированные по темам, с разбором что входит в хороший ответ и где большинство кандидатов проваливаются.

Основные концепции

Что такое Playwright и чем он отличается от Selenium?

Playwright: фреймворк автоматизации тестирования веб-приложений от Microsoft. Ключевые архитектурные отличия от Selenium:

Автоожидание: Playwright ждёт пока элементы станут готовы к взаимодействию, никаких WebDriverWait или ExpectedConditions не нужно. Selenium требует явного кода ожидания.

Всё из коробки: Playwright поставляет тест-раннер, библиотеку ассёртов, trace viewer и HTML-репортер. Selenium: библиотека автоматизации браузера, остальное собираешь сам.

Браузерный протокол: Playwright использует CDP (Chrome DevTools Protocol) для Chromium и аналогичные протоколы для Firefox и WebKit. Selenium использует WebDriver-протокол, который медленнее из-за большего числа сетевых round-trip'ов.

Дизайн под язык: JavaScript/TypeScript API Playwright использует нативный async/await. JS-биндинги Selenium добавили позже Java-реализации, и это чувствуется.

Как работает автоожидание в Playwright?

До выполнения действия Playwright проверяет что целевой элемент соответствует набору условий «действительности» (actionability). Для click(): элемент должен быть видимым, в зоне видимости, не перекрыт другим элементом, включён и стабилен (не движется из-за анимации). Для fill(): элемент должен быть видимым и редактируемым.

Эти проверки автоматически запускаются в цикле поллинга до выполнения условий или истечения таймаута. Проверки действительности видны в сообщении об ошибке когда тест падает: там написано точно какое условие не выполнено.

В чём разница между page.locator() и методами getBy*?

page.locator() принимает CSS-селектор, XPath или строку текста. Это универсальный локатор.

Методы getBy* (getByRole, getByLabel, getByText, getByTestId и т.д.) являются семантическими локаторами. Они находят элементы так как пользователь их идентифицирует: через атрибуты доступности, а не CSS-структуру.

Методы getBy* предпочтительны потому что устойчивее к UI-изменениям: переименование CSS-класса их не ломает, но изменение доступного имени кнопки ломает. Именно такое изменение затрагивает реальных пользователей.

Объясни фикстуры Playwright

Фикстуры: система dependency injection в Playwright. Они предоставляют готовые объекты тестам без ручного setup в каждом тесте. Встроенные фикстуры: page (страница браузера), context (браузерный контекст), browser (экземпляр браузера), request (HTTP-клиент) и browserName.

Кастомные фикстуры расширяют встроенные:

const test = base.extend({
  loggedInPage: async ({ page }, use) => {
    await page.goto('https://lab.becomeqa.com');
    await page.getByRole('button', { name: 'Login' }).click();
    await page.getByLabel('Username').fill('admin@becomeqa.com');
    await page.getByLabel('Password').fill('testpass123');
    await page.getByRole('button', { name: 'Submit' }).click();
    await use(page);  // передаём залогиненную страницу тесту
  }
});

test('dashboard shows items', async ({ loggedInPage }) => {
  await expect(loggedInPage.getByText('My Travel Items')).toBeVisible();
});

Что такое изоляция тестов в Playwright?

По умолчанию каждый тест Playwright запускается в собственном браузерном контексте: чистый slate без куков, без localStorage, без кэшированных данных других тестов. Тесты не мешают друг другу даже при параллельном запуске.

Можно шарить контекст между тестами внутри блока test.describe через test.describe.configure({ mode: 'serial' }), но это снижает изоляцию и должно использоваться редко.

Локаторы и ассёрты

Когда использовать getByTestId вместо getByRole?

getByRole предпочтителен когда у элемента есть осмысленная ARIA-роль и доступное имя. getByTestId полезен когда:

У элемента нет чёткой семантической роли (кастомный компонент который рендерится как обычный div).

Доступное имя динамическое или недетерминированное.

Разработчики явно добавили атрибуты data-testid как стабильные хуки для тестов.

Практическое правило: сначала getByRole, getByTestId как запасной вариант когда getByRole не работает чисто.

Что реально проверяет expect(locator).toBeVisible()?

Проверяет что элемент присутствует в DOM, не скрыт через CSS (display: none, visibility: hidden, opacity: 0) и имеет ненулевые размеры. Элемент с display: none проверку не проходит. Элемент прокрученный за пределы вьюпорта но не скрытый проходит.

toBeVisible() также автоматически повторяется. Продолжает проверку до выполнения условия или истечения таймаута. Это не простая булева проверка.

Как проверять несколько элементов сразу?

// проверяем количество
await expect(page.getByRole('row')).toHaveCount(6);

// проверяем что все элементы списка содержат текст
const items = page.getByRole('listitem');
await expect(items).toContainText(['Tokyo', 'Paris', 'Oslo']);

// проверяем текст каждого элемента
await expect(items).toHaveText(['Tokyo', 'Paris', 'Oslo']); // точное совпадение

В чём разница между page.waitForSelector() и ассёртом через локатор?

Метод page.waitForSelector() относится к низкоуровневому API: ждёт появления элемента в DOM и появился до системы локаторов Playwright.

Современный вариант: ассёрты через локаторы вроде await expect(locator).toBeVisible(). Они выразительнее, автоматически повторяются и согласованно работают с системой локаторов Playwright. В тестах используй ассёрты, а не waitForSelector.

Сеть и API

Как перехватить сетевой запрос в Playwright?

// перехватываем и подменяем ответ
await page.route('**/api/items', route => {
  route.fulfill({
    status: 200,
    contentType: 'application/json',
    body: JSON.stringify([{ id: 1, destination: 'Mocked City' }])
  });
});

// блокируем конкретные запросы
await page.route('**/*.png', route => route.abort());

// модифицируем заголовки запроса
await page.route('**/api/**', route => {
  route.continue({
    headers: { ...route.request().headers(), 'X-Custom-Header': 'test' }
  });
});

В чём разница между page.request и фикстурой request?

page.request шарит браузерный контекст. Отправляет запросы с теми же куками и состоянием аутентификации что и текущая браузерная страница.

Фикстура request: самостоятельный HTTP-клиент без браузерной сессии. Используй request для чистых API-тестов которым не нужно состояние браузера. page.request используй когда нужно делать API-вызовы с сессией текущего залогиненного пользователя.

Как дождаться завершения сетевого запроса перед продолжением?

// ждём конкретный запрос и ответ
const responsePromise = page.waitForResponse('**/api/items');
await page.getByRole('button', { name: 'Refresh' }).click();
const response = await responsePromise;
expect(response.status()).toBe(200);

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

Как запустить тесты Playwright в GitHub Actions?

# .github/workflows/playwright.yml
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
  with:
    node-version: 20
    cache: 'npm'
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
- uses: actions/upload-artifact@v4
  if: always()
  with:
    name: playwright-report
    path: playwright-report/

Ключевые моменты: npm ci вместо npm install для воспроизводимости. npx playwright install --with-deps устанавливает браузеры и их системные зависимости. if: always() на загрузке отчёта гарантирует что получишь отчёт даже когда тесты падают.

Что такое шардинг и когда его использовать?

Шардинг делит тест-сьют между несколькими машинами. Каждый шард выполняет подмножество тестов:

npx playwright test --shard=1/4  # эта машина выполняет 25% тестов
npx playwright test --shard=2/4
npx playwright test --shard=3/4
npx playwright test --shard=4/4

Используй когда сьют занимает больше 5 минут на одной машине и нужна более быстрая обратная связь от CI. Тесты должны быть изолированы (никакого общего состояния) чтобы шардинг работал корректно.

Что делает trace: 'on-first-retry' в playwright.config.ts?

Когда тест падает и запускается повторно, Playwright записывает трейс: полную запись выполнения теста включая снимки DOM, сетевые запросы, скриншоты на каждом шаге и консольные логи. Трейс сохраняется в папку результатов тестов и просматривается через npx playwright show-report или загрузкой на trace.playwright.dev.

Архитектура и паттерны

Когда использовать Page Object Model, а когда обычные тесты?

Обычные тесты: когда тестов мало (меньше 20), тесты исследовательские, или только начинаешь.

POM: когда несколько тестов взаимодействуют с одной страницей, локаторы повторяются в разных тест-файлах, или UI часто меняется и хочется обновлять локаторы в одном месте.

Практический триггер: когда ловишь себя на копипасте одних и тех же 5+ строк в разных тестах: пора делать page object.

Как обрабатывать аутентификацию в тестах без входа для каждого теста?

storageState сохраняет и восстанавливает куки и localStorage браузера. Запусти вход один раз в файле глобального setup, сохрани состояние и настрой тесты на его загрузку:

// global-setup.ts
import { chromium } from '@playwright/test';

export default async function globalSetup() {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.goto('https://lab.becomeqa.com');
  await page.getByRole('button', { name: 'Login' }).click();
  await page.getByLabel('Username').fill('admin@becomeqa.com');
  await page.getByLabel('Password').fill('testpass123');
  await page.getByRole('button', { name: 'Submit' }).click();
  await page.context().storageState({ path: 'auth.json' });
  await browser.close();
}

// playwright.config.ts
export default defineConfig({
  globalSetup: './global-setup.ts',
  use: { storageState: 'auth.json' }
});

Тест проходит локально но падает в CI. Что проверяешь?

По порядку: переменные окружения (секреты могут быть не добавлены в CI), base URL (захардкоженный localhost), таймминг (CI-раннеры медленнее, добавь retries: 1), пропущенные await, различия в версиях браузера. Скачай артефакт трейса из CI и сравни пошаговое выполнение с локальным трейсом.

PWDEBUG=1 npx playwright test открывает инспектор с пошаговым выполнением. Используй чтобы понять что Playwright реально делает, до того как предполагать баг.

FAQ

Нужно ли знать внутренности Playwright для сениорского интервью?

Понимание CDP-протокола, как реализовано автоожидание и как фикстуры работают под капотом показывает глубину. Тебя не попросят это реализовать, но объяснение «автоожидание поллит проверки действительности по таймеру с экспоненциальным backoff» производит впечатление сильнее чем «оно ждёт автоматически».

Как показать опыт с Playwright если не использовал его профессионально?

GitHub-репозиторий с 20+ тестами покрывающими вход, CRUD-операции и API-вызовы реального приложения: конкретное доказательство. lab.becomeqa.com создан именно для этого. Проведи интервьюера по структуре тестов и объясни почему принял те или иные архитектурные решения.

→ See also: Page Object Model в Playwright: от хаоса к поддерживаемым тестам | Топ-50 вопросов на собеседовании QA-инженера в 2026 году (с ответами) | Assertions в Playwright: полное руководство | Фикстуры Playwright: от встроенных до кастомных