Всем привет, меня зовут Сергей Прощаев и в этой статье расскажу про то, что происходит, когда команда переезжает с Selenium на Playwright и по инерции тащит за собой старые привычки. Я Tech Lead и руководитель направления Java | Kotlin разработки в FinTech & E‑commerce, преподаю на курсах разработки и архитектуры в OTUS.

Ситуация, которую я вижу из раза в раз. Команда решает, что Selenium уже тесный: тесты медленные, флакают, поддержка съедает половину спринта. Смотрят на Playwright, читают про авто‑ожидания и параллелизм, заводят зависимость в pom.xml и переписывают первый десяток тестов. А через месяц открывают отчёт CI и видят ровно то же самое: красные прогоны через раз, Thread.sleep расползся по коду, тесты то проходят, то падают в зависимости от нагрузки на раннер.

Playwright тут не виноват. Виновата миграция один в один: люди переносят не тесты, а способ мышления, который сложился за годы работы с WebDriver. Фреймворк поменялся, а модель в голове осталась старая.

Ниже разберу семь таких переносов — тех, что чаще всего прячутся до первого честного прогона на CI. На каждый покажу симптом, причину, что он ломает и как чинится, с кодом, который я реально встречал в проектах. Отдельно скажу: часть примеров специфична именно для Java, потому что здесь у Playwright нет встроенного тест‑раннера с фикстурами, как в JS/TS, и это отдельный источник граблей.

Что вы вынесете из статьи:

  • почему переход на Playwright сам по себе не делает тесты быстрее и стабильнее;

  • какие ошибки чаще всего совершают команды сразу после миграции с Selenium;

  • как выглядят те же сценарии по‑playwright'овски — с кодом на Java до и после;

  • какие практики E2E‑автоматизации действительно рекомендует команда Playwright на 2026 год;

  • что конкретно проверить в своём проекте — в виде чек‑листа в конце.

Рис. 1. Миграция, при которой старые привычки переезжают вместе с тестами
Рис. 1. Миграция, при которой старые привычки переезжают вместе с тестами

Ошибка 1. Ручные ожидания поверх авто‑ожиданий

Симптом. В коде живёт Thread.sleep, а рядом — самописные ожидания в духе старого WebDriverWait. Причём часто и то, и другое одновременно, «на всякий случай».

Причина. В Selenium ожидание было ответственностью автора теста. Не подождал — поймал StaleElementReferenceException или клик по ещё не отрисованному элементу. Рефлекс «перед действием подожди» вбивается годами, и инженер тащит его в Playwright, не проверив, нужен ли он тут вообще.

А он не нужен. Playwright перед каждым действием сам проверяет, что элемент есть в DOM, видим, стабилен (не анимируется), доступен для взаимодействия и не перекрыт другим элементом. Это встроено в click, fill, check и в web‑first assertions. Ручное ожидание сверху в лучшем случае бесполезно, в худшем — маскирует реальную гонку и добавляет секунды к каждому тесту.

Последствия. Тест на 40 шагов с Thread.sleep(2000) на каждом — это лишние 80 секунд впустую. На suite из пятисот тестов набегают часы машинного времени в месяц. И это не считая того, что фиксированный сон не спасает от медленного ответа: если сегодня бэкенд ответил за 2.1 секунды, а вы спите 2 — тест упал, хотя приложение исправно.

Как надо. Убрать все фиксированные ожидания и довериться web‑first assertions, которые сами перепроверяют условие до таймаута.

// (Java) Плохо: перенос привычки из Selenium
page.click("#submit");
Thread.sleep(2000); // ждём "пока прогрузится"
assertEquals("Успешно", page.textContent(".status"));

// (Java) Хорошо: авто-ожидание внутри действия и внутри проверки
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Отправить")).click();
assertThat(page.locator(".status")).hasText("Успешно"); // ретраит до таймаута

Я бы вынес это в жёсткое правило на code review: Thread.sleep в тестовом коде почти всегда говорит о том, что решение стоит пересмотреть. Редкие исключения есть — дебаг, ожидание внешнего процесса, throttling, анимации на canvas, — но в обычном UI‑сценарии фиксированный сон почти наверняка лишний, и в девяти случаях из десяти внятного объяснения ему не найдётся.

Ошибка 2. Локаторы по XPath и CSS вместо ролей

Симптом. Тесты усыпаны селекторами вида //div[@class='btn‑primary'][2] или.container > div:nth‑child(3) > button. При любой правке вёрстки половина падает.

Причина. В экосистеме Selenium XPath десятилетиями был основным инструментом. Инженеры научились писать сложные выражения и гордятся этим навыком. Playwright такие селекторы поддерживает — и автор продолжает писать так же, потому что «работает же».

Работает, пока дизайнер не переставил блоки местами. Хрупкость селектора, завязанного на структуру DOM, никуда не делась. Playwright предлагает другой подход: искать элементы так, как их видит пользователь и как их читает скринридер — по роли и доступному имени.

Последствия. Классический анти‑паттерн «тесты падают при каждом релизе фронта, хотя функциональность цела». Команда начинает воспринимать красный прогон как норму, перестаёт вчитываться в падения — и в этот момент автотесты теряют смысл, потому что настоящий баг утонет в шуме ложных срабатываний.

Как надо. Приоритет — user‑facing локаторы: getByRole, getByLabel, getByText, getByTestId. Они переживают перестановку блоков и рефакторинг классов.

// (Java) Плохо: завязка на структуру DOM
page.locator("//form/div[2]/input").fill("user@company.com");
page.locator(".container > div:nth-child(3) > button").click();

// (Java) Хорошо: ищем как пользователь
page.getByLabel("Email").fill("user@company.com");
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Войти")).click();

Оговорюсь, чтобы не создавать иллюзию серебряной пули: локатор по роли не всегда лучший выбор. Для технических компонентов, динамических гридов, canvas, SVG или legacy enterprise‑UI без вменяемой разметки getByTestId часто стабильнее. Правило простое: если приложение не предоставляет качественную accessibility‑разметку, предпочтительнее опираться на стабильные data‑testid. И побочный бонус — если тест невозможно написать через роль, потому что у кнопки нет доступного имени, это сигнал о проблеме с доступностью самого приложения. Тест начинает работать ещё и как индикатор качества вёрстки.

И сразу отвечу на вопрос, который в этот момент задаёт почти каждый Java‑разработчик: а что с моими Page Object? Ничего страшного — переход на Playwright не означает отказ от этого паттерна. Меняется реализация методов внутри page object (вместо findElement и WebDriverWait — локаторы Playwright), но сама идея инкапсуляции UI в классы‑страницы остаётся полностью актуальной. Более того, чистые Page Object переезжают быстрее всего: рефакторить приходится тела методов, а не структуру.

Ошибка 3. Один общий драйвер на весь класс тестов

Это самая дорогая ошибка миграции в Java, и на ней остановлюсь подробнее.

Симптом. Тесты проходят по одному, но при запуске классом начинают влиять друг на друга: то один упадёт из‑за залогиненного пользователя от предыдущего, то в параллельном прогоне всё рассыпается непредсказуемо.

Причина. В Selenium была устоявшаяся практика: один WebDriver на класс, поднимаем в @BeforeAll, гасим в @AfterAll, между тестами чистим куки руками. Дорого создавать драйвер — вот и переиспользуем. Эту схему переносят в Playwright: один Browser, один BrowserContext на всех, и понеслось.

Проблема в том, что в Playwright единица изоляции — это BrowserContext, аналог отдельного инкогнито‑профиля: свой cookie jar, localStorage, sessionStorage, выданные разрешения и origins. Это достаточно лёгкий объект, значительно дешевле полноценного Browser (хотя тяжёлый storageState, запись видео или HAR могут заметно его утяжелить). Поэтому правильная модель — не «один контекст на всех», а свежий контекст на каждый тест (браузер при этом часто держат на воркер). Тогда тесты не делят состояние в принципе.

И вот ключевой момент, о котором спотыкаются мигранты с опытом в JS. В JavaScript‑раннере Playwright свежий контекст на тест даётся автоматически через фикстуры. В Java такого раннера нет — Playwright работает поверх JUnit или TestNG, и жизненным циклом контекста вы управляете руками. Никто за вас не создаст изоляцию. Если написать @BeforeAll по привычке из Selenium, вы получите общий контекст и все вытекающие гонки.

Последствия. Флаки, которые невозможно воспроизвести локально. Тест падает только на CI, только при определённом порядке, только под нагрузкой. Команда неделями списывает это на «инфраструктуру», хотя корень — общее состояние между тестами. Помню, как однажды разбирали именно такой кейс: тест логина падал раз в двадцать прогонов, и только когда выписали порядок выполнения на бумагу, стало видно, что предыдущий тест оставлял залогиненную сессию в общем контексте.

Как надо. Браузер должен жить дольше контекста — создавать его на каждый тест действительно дорого. В примере ниже он поднимается один раз на класс, а контекст и страница — заново на каждый тест. Оговорюсь: конкретный жизненный цикл браузера зависит от раннера и модели параллелизма. В некоторых конфигурациях JUnit 5, TestNG или Gradle с параллельным прогоном браузер держат на воркер, а не на класс — именно это чаще и рекомендует команда Playwright. Схема «браузер на класс» ниже — это упрощение для наглядности, а не догма.

// (Java) Хорошо: браузер общий, контекст и страница — на каждый тест
static Playwright playwright;
static Browser browser;
BrowserContext context;
Page page;

@BeforeAll
static void launchBrowser() {
    playwright = Playwright.create();
    browser = playwright.chromium().launch(
            new BrowserType.LaunchOptions().setHeadless(true));
}

@BeforeEach
void createContextAndPage() {
    context = browser.newContext(); // свежая изоляция под каждый тест
    page = context.newPage();
}

@AfterEach
void closeContext() {
    context.close(); // закрытие контекста закрывает и все его страницы
}

@AfterAll
static void closeBrowser() {
    browser.close();
    playwright.close();
}

Чтобы разница была нагляднее, ниже я развёл две модели изоляции на одной схеме: слева — общий контекст, к которому тянутся все тесты, справа — свежий контекст под каждый тест (Рис. 2).

Рис. 2. Изоляция тестов через BrowserContext
Рис. 2. Изоляция тестов через BrowserContext

Главное, что стоит вынести из этой схемы: в Playwright изоляция достигается не чисткой кук между тестами, а созданием нового контекста. Это дёшево, и именно поэтому в JS‑раннере так делают по умолчанию. В Java тот же результат надо обеспечить самому через @BeforeEach — иначе вы воспроизводите ровно ту модель общего состояния, от которой уходили из Selenium.

Ошибка 4. Логин через UI в каждом тесте

Симптом. В @BeforeEach каждый тест открывает страницу входа, вбивает логин и пароль, ждёт редиректа. На suite из сотен тестов это заметная часть общего времени прогона.

Причина. В Selenium переиспользовать состояние авторизации было неудобно, и логин через форму стал стандартом де‑факто. Люди привыкли, что «залогиниться = прокликать вход», и переносят это в Playwright, не зная про storageState.

Последствия. Медленный suite и хрупкость: если ломается форма логина, разом краснеют все тесты, хотя проверяют они совсем другое. Плюс каждый лишний UI‑логин — это ещё одна точка, где может стрельнуть флака.

Как надо. Залогиниться один раз, сохранить состояние сессии (куки и storage) в storageState, а затем поднимать контексты уже авторизованными. Отдельно тестировать сам сценарий логина — одним‑двумя тестами, а не пятьюстами.

// (Java) Один раз: логинимся и сохраняем состояние
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate("https://app.company.com/login");
page.getByLabel("Email").fill("user@company.com");
page.getByLabel("Пароль").fill("secret");
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Войти")).click();
assertThat(page).hasURL("https://app.company.com/dashboard");
context.storageState(new BrowserContext.StorageStateOptions()
        .setPath(Paths.get("auth/state.json")));

// (Java) В тестах: контекст сразу авторизован, форму входа не трогаем
BrowserContext ctx = browser.newContext(new Browser.NewContextOptions()
        .setStorageStatePath(Paths.get("auth/state.json")));

Мой вариант, который я обычно использую: делать это на нескольких аккаунтах под разные роли — отдельный state.json для админа, отдельный для обычного пользователя. Тогда тесты под разные права стартуют мгновенно и не мешают друг другу.

Пара важных оговорок. Первая — про безопасность: state.json как правило содержит живую сессию (куки, токены, а иногда и данные из storage), поэтому он не должен попадать в систему контроля версий. Обычно его генерируют непосредственно перед прогоном тестов, а не хранят в репозитории. Вторая — про границы применимости: подход отлично работает для классической cookie‑ или токен‑сессии, но плохо ложится на SSO, MFA, короткоживущие JWT и OAuth Device Flow. Если авторизация завязана на внешнего провайдера или второй фактор, сохранённое состояние протухает быстро, и логин приходится гонять по расписанию или продумывать отдельно.

Ошибка 5. Смешивание UI и API там, где нужен только один слой

Симптом. Чтобы проверить реакцию интерфейса на данные, тест руками прокликивает создание десяти сущностей через формы. Или наоборот: подготовку состояния гонят через UI, потому что «так привычнее».

Причина. Selenium — про браузер, и точка. Отдельного HTTP‑клиента в нём нет, поэтому подготовка тестовых данных исторически шла либо через тот же UI, либо через сторонний REST Assured в параллельной инфраструктуре. Мысль «данные можно готовить прямо тут же, по HTTP» в голове мигранта просто не возникает.

А в Playwright это встроено. Есть APIRequestContext — полноценный HTTP‑клиент, который умеет слать любые запросы без браузера. И что важнее — состояние сессии между API и браузером можно синхронизировать: page.request() делит куки с контекстом страницы, а storageState переносится между BrowserContext и APIRequestContext. Тут есть нюансы с куками, хранилищем и авторизацией, но в основе можно залогиниться по API, а дальше ходить в браузере уже авторизованным.

Последствия. Подготовка данных через UI — это медленно и хрупко: чтобы проверить отображение заказа, тест сначала десять раз кликает по форме создания заказа, и любая из этих прокликиваний может упасть, обрушив тест, который вообще не про создание. Логика теста тонет в служебных шагах.

Как надо. Разделить слои. Готовим состояние и проверяем бэкенд — через API. Проверяем интерфейс — через UI. В одном тесте это спокойно уживается.

// (Java) Готовим данные по API — быстро и надёжно
APIRequestContext request = playwright.request().newContext(
        new APIRequest.NewContextOptions().setBaseURL("https://api.company.com"));
try {
    Map<String, Object> payload = new HashMap<>();
    payload.put("title", "Тестовый заказ");
    APIResponse created = request.post("/orders", RequestOptions.create().setData(payload));
    assertThat(created).isOK();

    // (Java) А отображение проверяем уже через браузер
    page.navigate("https://app.company.com/orders");
    assertThat(page.getByText("Тестовый заказ")).isVisible();
} finally {
    request.dispose(); // освобождаем ресурс даже если проверка упала
}

В этой ситуации я бы сформулировал так: UI‑тест должен кликать ровно то, что проверяет, а всё остальное состояние приезжает через API. Это и быстрее, и на порядок стабильнее.

Ошибка 6. Тесты зависят от порядка и делят данные

Симптом. Тест «создать заказ» должен отработать раньше теста «удалить заказ», иначе второй падает. При включении параллелизма всё рушится.

Причина. Из Selenium‑практики, где тесты часто гоняли последовательно одним драйвером, приходит скрытое допущение о порядке. Данные, созданные одним тестом, используются другим. Пока прогон линейный, это незаметно.

Playwright запускает тесты параллельно по умолчанию (в JS — из коробки, в Java — через конфигурацию JUnit/TestNG). И параллелизм мгновенно вскрывает связанность: два теста лезут в одну запись, ловят гонку, падают через раз. Причём падают неповторяемо, что превращает разбор в мучение.

Последствия. Либо команда отключает параллелизм и живёт с медленным suite, теряя главное преимущество Playwright. Либо мирится с флаками. Оба варианта — поражение.

Как надо. Каждый тест сам создаёт нужные данные и сам за собой убирает. Никаких общих изменяемых переменных на уровне класса, никаких предположений о том, кто отработал раньше. Идеал — тест, который можно запустить в одиночку, в любом порядке, и он пройдёт.

Практический приём: генерировать уникальные данные на каждый тест, чтобы прогоны физически не пересекались.

// (Java) Плохо: общее поле, тесты зависят друг от друга
static String orderId; // заполняется в одном тесте, читается в другом

// (Java) Хорошо: у каждого теста свои уникальные данные
String email = "user-" + UUID.randomUUID().toString() + "@company.com";
// ...создаём, проверяем, в конце удаляем — всё в рамках одного теста

Как и предполагал в своё время один мой коллега: «если тест нельзя запустить отдельной кнопкой и получить зелёный — это не тест, а часть сценария». Звучит жёстко, но за годы я не нашёл, что возразить.

Ошибка 7. Ловля исключений вместо использования трейсов

Симптом. Тело теста обёрнуто в try/catch, внутри — логирование и скриншот руками. При падении в CI на руках только строчка стектрейса и непонимание, что реально произошло на странице.

Причина. В Selenium диагностика падений была ручной работой: сам поймай исключение, сам сделай скриншот, сам сложи в артефакты. Playwright умеет это из коробки через trace viewer — записывает полную трассировку с DOM‑снапшотами каждого шага, сетевыми запросами и консолью. Но мигрант по привычке продолжает обкладывать тест try/catch, потому что не знает про трейсы.

Последствия. Флака в CI, которая не воспроизводится локально, становится нерешаемой: по стектрейсу непонятно, был ли элемент на странице, что вернул бэкенд, что творилось в консоли. Команда часами гадает вместо того, чтобы за минуту посмотреть запись.

Как надо. Включить трейсы вместо ручного отлова. Хорошая стратегия — писать трассировку при падении и повторах, чтобы можно было сравнить упавший прогон с успешным.

// (Java) Плохо: диагностика вручную
try {
    page.click("#submit");
} catch (Exception e) {
    page.screenshot(/* ... */);
    log.error("Упало", e);
    throw e;
}

// (Java) Хорошо: включаем трейс, дальше пишем тест без обёрток
context.tracing().start(new Tracing.StartOptions()
        .setScreenshots(true).setSnapshots(true).setSources(true));
// ... тело теста без try/catch ...
context.tracing().stop(new Tracing.StopOptions()
        .setPath(Paths.get("trace.zip")));

Открываете trace.zip в trace viewer и видите каждый шаг: что было в DOM, какие запросы ушли, что в консоли. В JS‑раннере включение трейсов обычно настраивается один раз на уровне конфигурации и дальше работает само. В Java своего механизма фикстур нет, поэтому включение tracing стоит вынести в общий базовый класс или хелпер, а не расставлять start/stop руками в каждом тесте. Мне как‑то попалась флака, которую неделю не могли поймать по логам, — в трейсе за полминуты стало видно, что бэкенд иногда отвечал пустым телом, и падал вообще не тот шаг, на который думали.

Реальный кейс: как это выглядит вживую

Чтобы не звучало теоретически, приведу пару разобранных публично историй. Runa — лондонский финтех, работает в 30+ странах и 18 валютах. Они сидели на связке Selenium плюс REST Assured и упёрлись в знакомые стены: ручные боттлнеки на релизах, слабая связка между разработкой и QA, тяжёлая диагностика падений. После перехода на Playwright — снижение флакаемости, более быстрые релизы и рост скорости прогона за счёт встроенного параллелизма (кейс описан в блоге currents.dev). Ровно те вещи, о которых шла речь выше: изоляция контекстами, авто‑ожидание, единый инструмент под UI и API.

Где есть и конкретные цифры — история Zenjob: около 100 Selenium‑тестов, восемь параллельных Jenkins‑джобов. После миграции на Playwright время прогона E2E‑сьюта упало примерно с 35 минут до 7 (по данным инженерного блога Zenjob). Порядок величины здесь важнее точной цифры: основной выигрыш дало не переписывание тестов как таковое, а уход от explicit waits и накладных расходов драйвера в пользу авто‑ожидания и дешёвого параллелизма.

И сразу честная оговорка, без которой картинка была бы предвзятой. Часть практик из этой статьи — изоляция тестов, независимость данных, единый логин, подготовка состояния через API — это хорошие практики автоматизации вообще, а не эксклюзивные возможности Playwright. Многое из этого можно реализовать и на Selenium, просто руками и с бОльшим количеством обвязки. Playwright не изобрёл эти принципы — он делает их значительно проще и естественнее, встраивая в сам инструмент то, что в Selenium приходилось собирать самому.

Обобщённо практика сильных команд на 2026 год сводится к одному принципу: не бороться с флакой заплатками, а убирать её причину архитектурой. Web‑first assertions вместо ручных ожиданий, локаторы по роли вместо XPath, изоляция через контекст вместо чистки кук, единый логин через storageState, разделение UI и API‑слоёв, независимые тесты и трейсы для диагностики. Ни один из этих пунктов не про синтаксис Playwright — все они про то, чтобы перестать писать тесты по‑selenium'овски. Всё перечисленное я проверял на актуальной для Java версии Playwright — на июнь 2026 это ветка 1.61 в Maven Central; если API в вашем проекте называется иначе, скорее всего, зависимость просто сильно устарела.

Сводная таблица: что проверить в своих тестах

Ошибка

Признак в коде

Что проверить

Ручные ожидания

Thread.sleep, самописные wait

Убрать; довериться web‑first assertions

Хрупкие локаторы

XPath, nth‑child, завязка на классы

Перейти на getByRole / getByLabel / getByTestId

Общий драйвер

Контекст в @BeforeAll, чистка кук руками

Контекст и страница в @BeforeEach

Логин через UI

Форма входа в каждом тесте

Один логин → storageState

Смешение слоёв

Подготовка данных кликами

Данные через APIRequestContext, UI — только проверка

Зависимость от порядка

Общие статические поля, порядок тестов

Уникальные данные на тест, независимость

Ручная диагностика

try/catch со скриншотами

Включить tracing, смотреть trace viewer

Почему Playwright вообще смог отказаться от этих костылей

Стоит на секунду объяснить, откуда берётся разница, — иначе список выглядит как набор придирок. Дело в архитектуре. Selenium взаимодействует с браузером через стандарт WebDriver: каждое действие проходит через отдельный драйвер, который транслирует команды в браузер. Из‑за этого WebDriver сознательно остаётся относительно «тонким» слоем и не берёт на себя синхронизацию пользовательских сценариев с состоянием страницы — ожидания приходится строить поверх него руками. Отсюда и WebDriverWait, и вся защитная обвязка.

Playwright использует нативные протоколы управления браузерами (CDP для Chromium и отдельные протоколы для Firefox и WebKit) вместо WebDriver, поэтому имеет более тесную интеграцию с движком браузера и видит его внутреннее состояние — загрузку, сетевые запросы, готовность элементов. Именно это позволяет реализовать автоматические ожидания перед каждым действием, дешёвые изолированные контексты вместо отдельных процессов и обращение к API из того же клиента — всё встроено в сам инструмент. Так что семь ошибок выше — это, по сути, семь мест, где старая модель мешает воспользоваться новой архитектурой и получить стабильный End‑to‑End‑прогон.

Что на самом деле проверяет этот список

Если присмотреться, все семь ошибок — об одном. Selenium приучил, что за корректность теста отвечает автор: сам жди, сам изолируй, сам готовь данные, сам собирай диагностику. Playwright значительную часть этой работы берёт на себя — но только если ему не мешать старыми привычками. Настоящий навык при переходе не в том, чтобы выучить новый синтаксис. Он в том, чтобы заметить, где вы по инерции делаете руками то, что инструмент уже делает за вас, и убрать лишнее.

Самая большая ошибка миграции — считать, что Playwright это «Selenium с другим API». На самом деле меняется не библиотека, а модель написания тестов: где раньше вы страховались вручную, теперь доверяете инструменту. И чем быстрее команда перестанет переносить старые привычки, тем быстрее получит ту стабильность и скорость, ради которых переход вообще затевался. Тесты после этого снова начинают что‑то значить, когда краснеют.

Чек‑лист: что проверить в своём проекте

Если вы уже переехали на Playwright или в процессе — пройдитесь по этому списку. Каждый пункт закрывает одну из разобранных ошибок.

  • В коде тестов нет Thread.sleep и самописных ожиданий поверх авто‑ожидания.

  • Проверки написаны через web‑first assertions (assertThat(...).hasText(...)), а не через getText() плюс сравнение.

  • Локаторы — по роли и доступному имени (getByRole, getByLabel), а не по XPath и позиционным CSS.

  • Там, где accessibility‑разметки нет, используются стабильные data‑testid, а не хрупкие цепочки классов.

  • Браузер не создаётся заново на каждый тест (обычно живёт на класс или на воркер).

  • Контекст и страница создаются заново на каждый тест — в @BeforeEach, а не в @BeforeAll.

  • Между тестами не чистятся куки руками — изоляция обеспечивается новым контекстом.

  • Логин выполняется один раз через storageState, а не прокликивается в каждом тесте.

  • Файл state.json не закоммичен в репозиторий и генерируется перед прогоном.

  • Для SSO, MFA и короткоживущих токенов продуман отдельный сценарий обновления сессии.

  • Подготовка тестовых данных идёт через APIRequestContext, а не через клики по формам.

  • APIRequestContext освобождается (dispose) в finally — нет утечки ресурсов.

  • Тесты не зависят от порядка выполнения и используют уникальные данные (UUID).

  • Параллельный прогон включён и тесты его переживают.

  • Диагностика падений идёт через trace viewer, а не через ручные try/catch со скриншотами; в Java включение tracing вынесено в общий хелпер.

Если по большинству пунктов у вас «да» — миграция прошла осмысленно, а не механически.

Если много «нет» — вы, скорее всего, переносите Selenium‑мышление, и часть выигрыша от Playwright пока не получена. Быстрее всего эти привычки уходят, когда разбираешь их не в одиночку, а на живых примерах с обратной связью.

Проверить себя шире можно, пройдя вступительный тест по автоматизации тестирования на Java продвинутого уровня. Он поможет понять, какие темы уже освоены, а где остались пробелы.

После перехода на Playwright тесты всё ещё остаются медленными и нестабильными? Стоит разбирать не только код, но и подход к автоматизации. На этих бесплатных уроках можно посмотреть, как связать проверку интерфейса и API в одном проекте и использовать управление сетевым трафиком для более точных сценариев.

  • 3 сентября, 20:00. «UI‑ и API‑тестирование с Java и Playwright». Записаться

  • 22 сентября, 20:00. «Автоматизация управления трафиком с mitmproxy». Записаться

Больше открытых уроков августа смотрите в дайджесте.

Комментарии (1)


  1. KonBone
    08.08.2026 12:41

    Нет, я всё понимаю, но почему миграция тогда уж не на Selenide?