Playwright — новое поколение Puppeteer

Последние 2 года я занимаюсь автоматизацией UI, API. И в основном работал с Puppeteer. Но в мае вышел релиз версии Playwright 1.0 и теперь можно задуматься о полноценном использовании этой библиотеки.

В этой статье описаны основные изменения, улучшения и различия в синтаксисе Playwright по сравнению с Puppeteer. В Playwright есть всё, что и в Puppeteer, плюс намного больше. Здесь будут рассмотрены только отличия. Так что если вам нужен полноценный обзор Playwright со всеми его возможностями — вам не сюда.

Этот материал будет полезен тем, кто использует или ранее использовал Puppeteer. После прочтения вы будете знать как перевести свои тесты на Playwright и зачем это делать. Так же здесь будут перечислены некоторые фичи этой библиотеки.

Puppeteer

Я начал использовать Puppeteer примерно через пол года после релиза версии 1.0. Он был не очень удобным для автоматизации, так как приходилось писать вокруг свой фреймворк для ожиданий и работы с элементами. Но в то же время позволял делать то, что не могли другие инструменты. Я поверил в силу Google и начал автоматизировать UI, используя эту библиотеку.

Будни автоматизатора на Puppeteer были (и остаются) нелегкими. Но привыкнуть можно и позже даже удивляешься зачем все эти дополнительные надстройки, если все можно написать самому. Так автоматизатор на Puppeteer слегка напоминает консервативного пользователя Vim, не видящего превосходства современных IDE над редактором с явно ограниченным функционалом.

Одно из самых больших преимуществ Puppeteer — возможность эмулировать мобильные устройства. Это, конечно, не реальные девайсы и даже не симуляция (а эмуляция). Но это работает. Большинство (из личного опыта это 95+ процентов) функциональных багов можно отловить при эмуляции. А это достаточно много. Но с UI (расположение, вид, поведение элементов) все не так хорошо. Хотя, я могу смело предположить, что это около 70-80%.

И это без затрат на физические либо облачные фермы, стоимость которых варьируется от тысяч до десятков тысяч долларов. Здесь же мы получаем отличный эффект всего лишь инвестировав немного больше времени. Часто внеся несколько правок в десктопный тест, его можно запустить на мобилке.

И все работало отлично! До февраля этого года (2020). Тогда в репозитории Puppeteer на github начали появляться failed билды в мастере. Конечно, ребята исправились. Но это начало зарождать подозрения...

Playwright

И тут на сцену выходит Playwright. Первый коммит был сделан осенью предыдущего года (2019), а об инструменте начали говорить в началу текущего (2020), и в мае был релиз версии 1.0.

Теперь процесс тестирования стал немного проще (об этом ниже), количество поддерживаемых браузеров увеличилось (об этом тоже ниже) и Playwright начинает привлекать к себе больше внимания, чем его предшественник. Конечно, это все еще не полноценный инструмент для написания автотестов и ему есть куда расти. Работает Playwright по тому же протоколу, что и предшественник — по CDP.

Playwright сейчас находится в репозитории Microsoft microsoft/playwright. А Puppeteer разрабатывался компанией Google. Что вообще произошло?
Конечно же, Microsoft не купила Google. Основные разработчики теперь перешли ̶н̶а̶ ̶т̶е̶м̶н̶у̶ю̶ ̶с̶т̶о̶р̶о̶н̶у̶ в новую компанию и допиливают тот же инструмент (ну почти) под другим брендом.

Playwright уже смелее можно брать для реальных проектов в качестве основного инструмента для UI автоматизации. А стоит ли? Чем он лучше Puppeteer? Давайте разбираться.

Браузеры

Puppeteer поддерживает только Chromium и с недавних пор Firefox. Но последний все еще находится на стадии экспериментальной фичи.
Playwright полноценно поддерживает Chromium, Firefox и Webkit (движок Safari).

Установка

Конечно же, Playwright является npm модулем. Устанавливается он соответственно npm i playwright.
И аналогично Puppeteer, скачает нужные браузерные движки (все 3).

Но есть одно отличие. Playwright устанавливает эти движки глобально. Что значит, их скачивание будет происходить на вашей машине ровно один раз — при первой установке. Независимо от того, в какой из директорий вы ее выполняете.

Puppeteer же будет скачивать chromium в каждом проекте/директории, где вы выполните установку.

Плюс в карму получает Playwright. Экономим и̶н̶т̶е̶р̶н̶е̶т время.

Если все браузеры вам не нужны, можно установить playwright-core.
При этом установятся только нужные зависимости, но не будут скачаны движки (а вместе они весят почти пол гигабайта).
Есть модули, каждый из которых скачает один, нужный вам, браузер
playwright-chromium
playwright-firefox
playwright-webkit

Как видим, все очень гибко.

Документация

(Кликните на картинку, чтобы открыть в полном размере).

Сходства в документации очевидны. Говорит это о том, что проект был скопирован и в дальнейшем модифицирован.
Обсуждать здесь нечего, кроме одного момента — структуры.
У обоих проектов есть отдельные сайты (этот и этот) с доками.
И хочу обратить ваше внимание на её структуру (оглавление).

Во втором случае мы видим Installation, Usage, First script — то, что поможет начать работать с библиотекой. Text input, Checkboxes, Select options и так далее — все то, посредством осуществляется взаимодействие с элементами на странице. В Puppeteer же мы не видим ничего подобного, большинство таких нужных пунктов скрыто где-то в недрах документации.

Контекст


Рассмотрим так называемую пирамиду браузера. В основании у нас, собственно, сам браузер. В браузере мы можем создавать контекст, и в контексте мы можем открывать страницы.

Контекст по своей сути очень похож на инкогнито окно Chrome.

Контекст присутствует в обеих библиотеках. Но в Playwright он является одной из базовых сущностей (на уровне с браузером и страницей), так что взаимодействовать с ним придется чаще, например, задавать в нем так называемые permissions:

'*'
'geolocation'
'notifications'
'push'
'camera'
'microphone'
'ambient-light-sensor'
'accelerometer'
'gyroscope'
  ...

Кстати, в Puppeteer это был метод overridePermissions() в Playwright он переименован в grantPermissions().

Что вообще нам дает использование контекста(ов)?
Например, с помощью них можно выполнять multi-page сценарии. Когда нужно сделать что-то на двух страницах, но при этом чтобы данные между ними не шерились (например, куки). Сделать это очень просто с помощью двух контекстов. К тому же создание контекста намного быстрее (как минимум в несколько раз) запуска нового браузера. Так что профит очевидный.

Давайте посмотрим, как, собственно, изменилось взаимодействия с браузером, контекстом и страницей в Playwright в сравнении с Puppeteer.

Puppeteer
// импортируем puppeteer
import puppeteer from 'puppeteer';
// запускаем браузер
const browser = await puppeteer.launch();
// в браузере открываем страницу
const page =  await browser.newPage();
Playwright
// импортируем playwright
import playwright from 'playwright';
// запускем барузер
// обратите внимание, нужно указать имя браузера, который будем запускать (chromium, firefox  или webkit)
const browser = await playwright.chromium.launch();
// в браузере создаем контекст
const context = await browser.newContext();
// в контексте открываем страницу
const page =  await context.newPage();

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

import playwright from 'playwright';
const browser = await playwright.chromium.launch();
const page =  await browser.newPage();

Еще на несколько слов сократить код можно, импортировав не playwright, а сразу нужный браузер

import {chromium} from 'playwright';
const browser = await chromium.launch();

Чекбоксы

В Playwright добавлена «нормальная» работа с чекбоксами.

page.check(selector)
page.uncheck(selector)

В библиотеке от Google приходилось либо делать клик на чекбокс, либо внедрять на страницу JS код, которые меняет свойство checked. Не очень удобно :\

Viewport

В Playwright стандартное значение было изменено с 800×600 на 1280×720, и это более приближено к реальности.
И сам метод изменения размеров вьюпорта сменил название setViewport() > setViewportSize()

Input

В Puppeteer заполнить текстовое поле можно, используя метод page.type(), в Playwright это page.fill().
В них есть одно принципиальное отличие, которое проявляется в случае использования опции slowMo при запуске браузера.

.launch({
    headless: false,
    slowMo: 100
});

(Наличие этой опции добавляет задержку после каждого действия, исполняемого Puppeteer или Playwright. Например, поиска элемента, клика и т.д. В примере выше это 100 мс).

При отработке метода type указанная задержка будет происходить после ввода каждого символа. То есть если мы вводим строку из 100 символов, нам придется подождать лишних 10 секунд. Метод fill(), используемый в Playwright, вводит текст мгновенно. Возможно, это менее натуральная имитация поведения юзера, но экономит много времени.

А если в метод передать пустую строку в качестве второго аргумента, то поле для ввода очищается (но не настолько, чтобы в него вернулись дельфины)

page.fill(selector, '')

Selectors

На этой теме остановимся побольше. Изменений много и они достаточно важные. Для начала рассмотрим как мы ожидаем элемент по XPath селектору в Puppeteer:

await page.waitForXPath(XPathSelector);
а так мы можем получить/сохранить элемент
const element = await page.$x(XPathSelector);

В Playwright это немного проще

const element = await page.$(selector);

А как же при этом Playwright различает CSS и XPath селекторы? Всё, что начинается с двух слешей // считается XPath.

await page.$('//html/body/div');

При желании можно строго указать тип селектора

await page.$('xpath=//html/body/div');

Или использовать оба типа селекторов при поиске элемента

await page.$('xpath=//html/body/div >> css=span');

Удобно, согласитесь. А иногда может быть и крайне полезно.
Если тип селектора не указан и он не начинается с двух слешей, то считается, что это CSS селектор.

Еще одно приятное нововведение в Playwright — поиск по тексту.

await page.$('text="some element text"');

Можно вместо указания ключевого слова text заключить селектор в дополнительные кавычки.

await page.$('"some element text"');

К сожалению, такой роскоши в Puppeteer нет.

Итого, метода waitForXPath() в Playwright нет. Вместо него используется универсальный waitForSelector() для обоих типов селекторов.
Также данный метод получил небольшое изменение-улучшение: в Puppeteer в качестве необязательного аргумента options можно было передавать параметры visible/hidden со значениями true/false.

waitForSelector(selector[, options])
visible <boolean>
hidden <boolean>

В Playwright это заменено на один параметр state, который может принимать одно из следующих значений

<"attached"|"detached"|"visible"|"hidden">

Wait...

Теперь поговорим про ожидания. Первое, с чего начну — автоматический waitForNavigation(). Этот метод позволяет дождаться загрузки после:

  • открытия страницы.
  • перехода по ссылке.
  • перезагрузки страницы.

Теперь не нужно вызывать этот метод намеренно, он отработает сам когда необходимо.

Метод page.waitFor() переименован на page.waitForTimeout(). И это изменение вполне логично. waitFor звучит как метод с загадкой, не совсем понятно чего мы ждем. В Playwright все очевидно — мы ожидаем таймаута.

Click

Метод click() в Playwright дожидается появления элемента, на который, собственно, вы кликаете. А если с элементом что-то пошло не так, то произойдет повторная попытка клика.

Вообще, в Playwright достаточно много методов получили ожидания до и после их выполнения, что позволяет создавать более линейный user flow.

Press

Метод press() в Playwright поддерживает сочетания клавиш. Например:

press('Control+Shift+T')
Puppeteer при этом ограничен поддержкой нажатий только одиночных клавиш (Enter, Backspace и др).

Logger

В Playwright есть логгер из коробки. Подключить его можно так

const { chromium } = require('playwright');
(async () => {
 const browser = await chromium.launch({
   logger: {
     isEnabled: (name, severity) => name === 'browser',
     log: (name, severity, message, args) => console.log(`${name} ${message}`)
   }
 });
 ...
})();

В результате в консоли увидим

browser <launching> /Users/pelykh/Library/Caches/ms-playwright/webkit-1219/pw_run.sh --inspector-pipe --no-startup-window
 at Object.log (src/__tests__/playwright-input.ts:16:57)
browser <launched> pid=2617
 at Object.log (src/__tests__/playwright-input.ts:16:57)
browser <gracefully close end>
 at Object.log (src/__tests__/playwright-input.ts:16:57)
и прочую полезную (и не очень) информацию.

Get attribute

Странно, что этого метода все еще нет в Puppeteer и приходится выполнять на странице JS, который будет искать элемент, считывать у него атрибут и возвращать

page.$eval('#id1', (element, attribute) => {
 element.getAttribute('name'), attribute
});

В Playwright мы можем просто вызвать у элемента метод getAttribute()

const element = await page.$('#id1');
await element.getAttribute('name');

То же самое касается методов

element.innerText()
element.textContent()

Теперь их можно вызывать прямо в тесте, без никаких дополнительных танцев с б̶̶̶у̶̶̶б̶̶̶н̶̶̶а̶̶̶м̶̶̶и̶ инджектом JS.

Cookies

Касательно куки в Playwright есть несколько отличий в сравнении с Puppeteer. Рассмотрим на примере

page.setCookie(...cookies)

browserContext.addCookies(cookies)

в Puppeteer взаимодействие с cookie происходит на уровне страницы, в Playwright — на уровне контекста (насколько это удобнее покажет практика): изменено название метода на addCookies и изменился способ передачи аргументов.

Последнее актуально не только для данного метода, но и для многих других в Playwright.

Если в Puppeteer мы могли передать какое угодно количество аргументов через запятую, то в Playwright это один аргумент. И в случае необходимости передать несколько значений придется использовать массив (или объект).

page.evaluate(pageFunction[, ...args])
page.evaluate(x, y)

page.evaluate(pageFunction[, arg])
page.evaluate([x, y])

Crash

В Playwright если страница начнет потреблять слишком много памяти и зависнет — можно отловить это, используя соответствующее событие (page crash event).

Docker

Как видим, разработчики прекрасно о нас позаботились. И еще одним доказательством этого является Docker image. Конечно же, для Puppeteer тоже существует образ Docker и не один. Но пользоваться официальным удобнее (не нужно ничего искать) и безопаснее.

File upload

Здесь изменение небольшое и представляет собой лишь новое имя метода

const handle = await page.$("input[type=file]");
await handle.uploadFile('path/to/file');

const handle = await page.$('input[type="file"]');
await handle.setInputFiles('path/to/file');

Typescript

Оставил этот приятный бонус напоследок. Playwright поддерживает typescript из коробки и теперь не нужно прописывать типы в package.json, как это было в Puppeteer:

"dependencies": {
    @types/puppeteer,
    …
}

Заключение

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

Например, команда CodeceptJS добавила поддержку Playwright еще на версиях до 0.2 (увидели в нем перспективу и не ошиблись).
К слову, использование этого инструмента (CodeceptJS) позволяет вам запускать один и тот же тест на любом из тулов: WDIO, Protractor, WebDriver, TestCafe и других.

А тесты в стиле BDD выглядят очень лаконично.

I.amOnPage('/');
I.click('Login');
I.see('Please Login', 'h1');

Еще один, но набирающий популярность только сейчас, инструмент — QA Wolf. Ребята тоже посчитали Playwright перспективным, выбрав его в качестве базового для создания и запуска UI тестов.
Данный тул по своей сути очень похож на Selenium IDE. Но, как минимум, в несколько раз превосходит его. И это только начало.

Наверное, эра Puppeteer на этом закончится и мы перейдем к следующему звену этой эволюционной цепочки — Playwright 🎭.

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

👍ПодобаєтьсяСподобалось1
До обраногоВ обраному11
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Крутая статья, все хорошо скомбинировано и доступно написано! Ждем новых статей)

Автор, можно такую же статью сравнение с cypress?)

Хммм...)
Ну здесь больше по синтаксису.

Playwright сравнить с Cypress? Больше синтаксис или функционал?

Все) в интернетах больше хвалят сайпресс, у меня тоже был позитивный опыт внедрения, но так как не профессиональный qa инженер — не могу оценить по всем реал ворлд кейсам, может после сравнения переведете все на новый фреймворк)

дуже непогані зміни, дякую за огляд!
Стосовно press(), заради справедливості зазначу, що комбінації в Puppeteer доволі просто виконуються наступним чином:

async sendTwoKeysKombo(key1, key2) {
    await page.keyboard.down(key1)
    await page.keyboard.press(key2)
    await page.keyboard.up(key1)
  }
Хоча, звичайно в Playwrite це значно простіше.

Хороший лайфхак. Спасибо )

Фича с поиском ноды по тексту — огонь!
Спасибо, отличное ревью!

Дааа, без нее была печалька )

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

Выбор очевиден. Однозначно Playwright.
Вот если вы рассматриваете другие тулы — тут да, нужно уже думать.
Но среди этих двух ребят все понятно.

Використовую puppeteer для скрейпінгу сайтів із client side rendering. Чи натикалися на якісь порівняння стосовно performance puppeteer і playwright? Чи скоротився/збільшився час виконання тестів після переходу з puppeteer на playwright? Наперед вдячний за відповідь.

Хороший вопрос. Но не сталкивался с таким, чтоб замерять время. При этом уверен, что разницы нет.

Добрый день Oleksandr Pelykh, подскажите пожалуйста Playwright нормально работает с iFrame? и с iFrame в iFrame? Спасибо

Приветствую, Виталий.
Да, с помощью page.frames() можно получить список фреймов на странице и далее взаимодействовать с его элементами аналогичным образом, как и на самой странице. iFrame в iFrame не использовал, предлагаю вам исследовать этот вопрос. Но у меня есть подозрения, что проблем с этим не будет.

Puppeteer працює, особисто у мене був кейс з потрійною вкладеністю, тому думаю Playwrite теж працюватиме.

Главной фишкой для меня в Playwright стало отсутсвие жесткой привязки версии библиотеки к версии Chromium. С Puppeteer была боль, допустим версия 1.18.0 работает только с chromium@edge~77.0.3865.120-r0, а версия 1.20.0 — только с Chromium 78.0.3882.0 (r686378). Поскольку Chromium нужно было ставить в Docker image, тот еще гемор был найти правильную версию.

Мигрировал тесты в итоге с Puppeteer на Playwright буквально за час. Правда тестов было не мгого.

Спасибо, годный комент.

версия не привязана

что понимается под

Поскольку Chromium нужно было ставить в Docker image, тот еще гемор был найти правильную версию.

даже подумать боюсь

Senior Full-Stack Developer

лол

версия не привязана

Ну да, именно поэтому указывают поддерживаемую версию в чейнджлоге: github.com/...​peteer/puppeteer/releases

даже подумать боюсь
RUN apk add chromium@edge~77.0.3865.120-r0
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true
лол

ок аноним

не ну лол же просто. сенйор только чейнджлог осилил. Сорцы и доки почитать это для СТО наверн.

так разве вы не в курсе, что сейчас, если оканчиваешь вайтишные курсы по хтмл и цсс, сразу тимлида дают

Кинь плиз ссылку на статью

Рустам, речь про веб приложения. Но это даже не мобильный браузер, а его эмуляция. Она аналогична той, что в Google Chrome.

посмотри на testcafe. эта вся херня прибита гвоздями к хрому.

Підписатись на коментарі