Playwright: веб-тестирование без драмы

dea222131399aab088f469a450b1869f.png

Андрей Лушников выступил у нас на Heisenbug с докладом о Playwright ещё в 2020-м. Но похоже, что с тех пор тема доклада стала только актуальнее: 34 000 звёзд проекта на GitHub ясно показывают, что он пришёл к популярности. А Андрей по-прежнему остаётся одним из главных контрибьюторов Playwright.

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

Видео и расшифровка — под катом. Далее повествование будет вестись от лица спикера.

Прежде чем говорить про веб-тестирование, разберемся в терминологии.

Действие I: Браузеры и браузерные движки

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

В один прекрасный день Джоэл просыпается с утра, а игра стала знаменитой и приносит ему кучу денег. Что делает Джоэл? Во-первых, он, конечно же, очень рад. Но, во-вторых, у него развивается тревожность, потому что он боится всё это потерять. Поэтому он засучивает рукава, делает два шага назад и устанавливает на свой компьютер все браузеры, которые только есть. И, начиная с этого момента, каждый день в 9 часов утра Джоэл просыпается и проверяет, что его игра работает на всех этих браузерах. 

Проходит день, неделя, месяц, и Джоэл начинает замечать, что на самом деле поведение его сайта в разных браузерах совпадает. Если что-то работает в Chrome, оно точно так же работает в Edge. А если что-то не работает, то не работает в обоих браузерах. 

6d07fa1d83d20da6c2f93852e97b90f8.png

Посмотрим на окно браузера. Визуально оно состоит из двух частей. 

d13dce0db64d6065588b06a78a482e45.png

Сверху — шапка, в которой сосредоточены все органы управления браузером (URL-bar, стрелки вправо и влево, закладки). И для пользователей это как раз та часть браузера, которая отличает браузеры друг от друга. Браузеров существует очень много, я нашел 19, которые ходят по рукам пользователей. 

Но наибольшую часть экрана у нас занимает сама веб-страница сайта. И за показ этих пикселей на экране отвечает другая программа — браузерный движок. Он обрабатывает CSS, JavaScript, HTML, ходит в сеть. Движков не так много, всего пять.  Джоэл, как веб-разработчик, является клиентом именно браузерных движков, потому что на браузер он никак повлиять не может, а вот на пиксели внутри экрана еще как может, этим и занимается его программа.  

Если мы, например, возьмем браузер Safari, аккуратно начнем его поворачивать и откроем крышку сбоку, мы увидим, что внутри — движок WebKit. Похожая ситуация произойдет и с браузером Chrome, внутри окажется Chromium. А если мы откроем Firefox, там будет Gecko.

6e75f3abb46624b88e51a01769f02da1.png

Все браузерные движки, которые в 2020 году распространены в интернете, можно глобально поделить на две небольшие группы:  

  • движки, которые сегодня активно поддерживаются и разрабатываются (WebKit, Chromium, Gecko);  

  • движки, которые активно не поддерживаются и не разрабатываются, однако распространены в интернете (Trident, EdgeHTML).

3a8ade3898fcc1f8783c30a960dd2b4e.png

Trident пытался в 2014 году обрести вторую жизнь, его форкнули в EdgeHTML и попытались починить всё, что плохо работало. Эта инициатива закончилась в 2018 году, когда Microsoft полностью перешел на браузерную платформу Chromium. Новый Edge использует Chromium, а не свои проприетарные движки.

Как же эти движки распространены в интернете?  

7072856e9cd0fd544e35b4cf9fc7399c.png

Три четверти круга занимает Chromium. На втором месте — WebKit, а на третьем — Gecko. Напротив этих трех движков я поставил зеленую галочку — они поддерживаются, хорошо разрабатываются, в них добавляются новые фичи. А в Old IE я объединил два движка, они занимают 3,5% от всей браузерной аудитории [примечание: как и стоило ожидать, к 2022 году это значение стало ниже]. Напротив Old IE я поставил восклицательный знак, потому что эти движки не поддерживаются и активно не развиваются.

Давайте посмотрим, как доля браузерных движков в мире менялась со временем:

fd0f871d387a54a3eb555d9abca1bd2b.png

В «облаках» летает Chromium. Красной чертой отмечен WebKit, который набирает темп и немного растет. Зеленая черта внизу — объединенные старые IE, которые за год потеряли больше процента мировой аудитории. Это значит, что в следующие десять лет доля IE будет только падать и всё больше растворяться в общем количестве интернет-пользователей. Мы окажемся в мире, в котором доминируют три движка: Chromium, WebKit и Gecko.

Так что Джоэла не интересует, чтобы его программа работала на старых IE. Посмотрим, какие браузеры (и движки) он использует:  

66a9507f9186f06daa18d57771b752ea.png

Теперь ясно, почему поведение тестов Джоэла в Chrome, Opera и Edge было одинаковым. Чтобы протестировать свой сайт, Джоэлу достаточно взять по браузеру от каждого браузерного движка.

Действие II: Драма веб-тестирования

Веб-тестирование глобально разделяется на две части:

С ручным тестированием всё понятно, а к автоматическому есть много вопросов.

Автоматическое тестирование в нашем контексте — возможность написать программу, которая должна уметь следующие вещи:

  • «разговаривать» с тремя браузерными движками и иметь доступ к их богатым возможностям (геолокации, Service Workers и так далее);

  • работать на трех платформах (Mac, Windows, Linux);

  • выполнять тесты без окна браузера (тестов много, и они не должны мне мешать работать на компьютере);

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

Это идеальная картинка веб-тестирования:

8a8dc3be630f6346ca8bd0c7e964b710.png

Проблема в том, что она не существует* на сегодняшний день. Насчет звездочки поговорим позже. Почему?

Проблема №1: WebKit

e53929d64d331e67f66c7ed131e92854.png

Этот браузерный движок редко где можно встретить. В операционной системе Windows он отсутствует. На Linux есть браузер Epiphany, который использует WebKit, но какой версии — неизвестно, о существовании самого браузера толком никто не знает, как автоматизировать — непонятно. А headless-версию WebKit даже на Mac не найти.

Это приводит к тому, что на 2020 год в индустрии WebKit обычно тестируется руками.

Проблема №2: технология

На 2020 год в индустрии есть две технологии: WebDriver и Chrome DevTools Protocol.

Мы заинтересованы, чтобы технология, во-первых, работала на трех разных браузерных движках, а во-вторых, поддерживала автоматизацию продвинутых API.

PWA (progressive web app) — пакет современных технологий, предназначенный для того, чтобы приблизить веб-приложение к нативному приложению. 

Схема совместимости получается такой:

165c8e5ed2eb55c81256ab5d59bb265d.png

WebDriver всем хорош, работает на трех движках, но много чего не умеет. 

Chrome DevTools Protocol поддерживается его родоначальником Chrome. В Firefox есть инициатива по реализации Chrome DevTools Protocol и его подмножества, однако на сегодняшний день эта инициатива далека от промышленного использования. В Safari Chrome DevTools Protocol отсутствует.

Вообще хотелось бы видеть везде зелёные галочки, а на практике в индустрии часто используется комбинированный подход. Какие-то тесты гоняют на WebDriver, для каких-то используют, например, Puppeteer. 

Действие III: знакомство с Playwright

Представляю наш опенсорсный проект Playwright. Его амбициозная цель — «починить» веб-тестирование, сделать его приемлемым, дешевым и доступным для веб-разработчиков на следующие 10–20 лет.

Playwright — большая инициатива, которая объединяет несколько направлений. 

8a1064a25ec63871702044018cefc34c.png

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

Второе направление — это браузерные протоколы автоматизации. Мы уже видели Chrome DevTools Protocol — это протокол для автоматизации Chromium. В Safari есть похожий протокол по идеологии — WebInspector Protocol. К сожалению, на сегодняшний день он не обладает нужной функциональностью, поэтому мы  ее добавляем, чтобы он был пригоден для браузерной автоматизации. В Firefox используем протокол Juggler — это аддон, который реализует похожую вещь на Chrome DevTools Protocol, однако в терминах движка Gecko, чтобы он был естественным для браузерных разработчиков. 

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

[примечание: в 2022-м, кроме перечисленного, есть ещё и собственный тестраннер @playwright/test, также ставший важным направлением развития]

Как поставить Playwright? Это Node.js-приложение, поэтому в первую очередь я делаю npm i playwright:

7ba609b710fa2ac2f47607b748c75436.png

Когда пакет ставится, он скачивает три браузера, на которых мы занимаемся и закачиваем CDN. В частности, мы видим, что последним он скачал WebKit. 

Сам этот подход не нов, однако есть одно «но». Обратим внимание на пути, куда скачались браузеры. Playwright переиспользует браузеры, которые вы скачиваете. Это значит, что, когда я буду в последующие разы ставить Playwright, они не будут ставиться в мои node_modules каждый раз и аккумулировать сотни мегабайтов браузеров на жестком диске. 

Demo time!

У меня есть репозиторий, в который я сейчас буду периодически пушить.

Я проинициализирую git-репозиторий и добавлю сюда наш репозиторий для Heisenbug. 

d4f7671ff59ccf1ccc067d738c24f9ce.png

Начнем с базовых вещей, которые нужно делать всегда — сделаем файл .gitignore. Хочу игнорировать node_modules/ и все картинки .png, потому что мы будем делать скриншоты. Добавлю такой .gitignore и запушу первый коммит с ним. Теперь в репозитории появился один файл. 

Давайте поставим Playwright, но сначала мне надо проинициализировать npm-пакет: npm init -y

b771a564ca53583e35475728555e6495.png

И теперь npm i playwright. Это не первый раз, когда я ставлю Playwright на эту систему, поэтому браузеры не скачиваются, а переиспользуются, и установка получается моментальной. 

41fab4a99ac30c79c377ceaa5502a78f.png

После установки Playwright я готов сразу начать программировать. Создам файл demo.js. Первое, что я в нём делаю — это достаю три браузера из нашего Playwright. Объекты webkit, chromium и firefox предоставляют нам доступ, чтобы запускать дальнейшие браузеры и управлять ими.

const {webkit, chromium, firefox} = require(‘playwright’);

Нам еще не подвезли async/await, поэтому я делаю асинхронную функцию, которую сразу исполняю. Давайте сделаем здесь программу, которая зайдет на сайт и сделает три скриншота. 

(async () => {
  for (const browserType of [webkit, firefox, chromium]) {
    const browser = await browserType.launch();         // запуск каждого браузера
    const page = await browser.newPage();                 // откроем страницу
    await page.goto(‘https://github.com/microsoft/playwright’);  // данного сайта
    await page.screenshot({                                   
      path: `screenshot-${browserType.name()}.png`,  // сохраняем скриншот 
    });
    await browser.close();                                         // закрываем браузер 
    console.log(‘success: ’ + browserType.name()); // чтобы отследить прогресс
  };
} )();

В общем, это вся наша программа. Пробуем ее запустить:

e2f2e95051e7928ff4e54385d329c020.png

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

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

Для этого я покажу промежуточный сайт, который называется playwright.dev.

56a54fa6d1ff303b504071818c0fc3f3.png

Это наша интерактивная документация. Слева столбец с оглавлением. Если вы будете просто читать документацию сверху вниз, вы получите хорошее представление о технологии, это как книжка. Кроме того, здесь есть глоссарий и обзор всего API. 

Есть поиск, и в нем можно написать, например, travis:

00f32dfb2cba589a14b4e3fcbc787404.png

И видно, что мы предоставляем конфиг, чтобы запускать ваши скрипты на Travis CI. Есть конфиги и для других вещей, например, для Docker. А сейчас хочу показать GitHub Actions.

На нашем сайте можно найти статью про него; в частности, есть специальный action, который подготовили для Playwright, чтобы было удобно использовать на GitHub в облачной инфраструктуре. Использовать его очень просто. Заходим на его страницу, копируем с неё YAML-файл и кладём к себе в директорию .github/workflows (сам файл при этом может называться как угодно) и немного модифицируем.

 on:
  push:
    branches:
    - main
jobs:
  e2e-tests:
    runs-on: ubuntu-latest # or macos-latest, windows-latest 
# На каждый push в master используем ubuntu-latest (вообще можно использовать что угодно, потому что Playwright работает везде)

    steps:
      - uses: actions/checkout@v2  # чекаутим репозиторий
      - uses: actions/setup-node@v1  # установим Node
      - uses: microsoft/playwright-github-action@v1  # подготовим окружение
	- run: npm install  # проинсталлируем зависимости
	- run: demo.js  # запустим приложение

Есть еще скриншоты, которые мы генерируем. Мы хотим сразу загружать их в хранилище артефактов. Это тоже довольно просто, нужно лишь скопировать с той же страницы еще три строки, относящиеся к артефактам:  

- uses: actions/upload-artifact@v2
  with:
    name: test-artifacts
    path: path/to/artifacts

Значение name я заменю на screenshots, а в path укажу «screenshot*.png», чтобы загружать все имеющиеся скриншоты. С файлом всё, дальше добавлю CI:

git ci -am 'devolps: add ci

и запушу сразу в репозиторий. Это всё, что нужно для запуска всех моих тестов на облачной инфраструктуре GitHub. 

В репозитории должен появиться GitHub Action, если мы в него зайдем, то увидим внутри e2e-tests, который начнет исполняться. 

fd8b6bb19620d98d09052865f555d584.png

В конце сверху справа рядом с кнопкой «Cancel workflow» появится кнопка для сохранения артефактов. Можно будет посмотреть, какие скриншоты получатся в результате. 

Браузеры оперировали в headless-режиме, но можно запустить и в headful, добавив "headless: false". Тогда можно будет посмотреть, как всё работает, это достаточно быстро. Если надо что-нибудь отладить, всегда можно переключиться в режим работы с браузером, покликать там мышкой. 

Итак, мы увидели три скриншота, сделанные на трех разных браузерных движках. Проверили, что это работает и в headless, и в headful mode, а также на разных ОС. Загрузили это в облако и поняли, что это работает довольно просто. 

caa7bfdd38c9caef09a57680db614fcc.png

Вернемся к моменту доклада про веб-тестирование, где я ставил звездочку к слову «не существует» — звездочка относилась к проекту Playwright.

a1e3c0dcc321f5f6897cad82ec994aa8.png

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

Действие IV: Возможности

Теперь можем поиграться с API Playwright и посмотреть, что умеет делать кросс-браузерная система Playwright. Но сначала хочу рассказать про концепт, вокруг которого вращается весь наш API — браузерные контексты

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

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

881c179da05dfbbe58959c4c6fb58384.png

Свойства изоляции:

  • страницы не могут перебежать из одного контекста в другой, они не знают о существовании других контекстов;

  • когда убиваем контекст, то он умирает полностью, вместе со всеми вкладками и Service Workers. 

API Playwright построен вокруг этой идеи, чтобы сделать веб-тестирование и браузерную автоматизацию проще и доступнее.

Demo time!

Сначала покажу вам мобильную эмуляцию. Скопирую прошлый код, будем переиспользовать этот скрипт.

Сегодняшние браузеры есть не только на десктопах, но и в смартфонах. В частности, львиная доля людей с браузерами на WebKit, который находится на втором месте по распространенности среди браузерных движков, пользуется интернетом на айфоне. Браузеры для мобильных устройств немного отличаются от десктопных специальным метатегом viewport, событиями касаний, размерами экрана. Поэтому и верстку мы делаем по-другому и должны специально к этому подготовиться. 

У меня есть iPhone 11 Pro, могу открыть там GitHub и увидеть, как выглядит страница на нём. А могу получить точно такую же страницу на Linux, Mac или Windows — «pixel-perfect».

Мне нужен для этого один браузер: Safari работает на движке WebKit, поэтому и запускать я буду с его помощью.

const {webkit, chromium, firefox, devices} = require(‘playwright’); 
const iPhone11Pro = devices[‘iPhone 11 Pro’];
console.log(iPhone11Pro);
return;

(async () => {
    const browser = await webkit.launch({
      headless: false,
    });                                                                            
    const page = await browser.newPage();                 
    await page.goto(‘https://github.com/microsoft/playwright’); 
  } )();

Добавляем объект devices из библиотеки Playwright, и среди представленных там девайсов есть iPhone 11 Pro. Добавлю строку console.log(iPhone11Pro) — тогда, если мы запустим программу, увидим, что это коллекция из пяти полей:

cf99e7359dc36bca0fcb2b44877a4a7a.png

Все эти параметры можно настраивать на браузерном контексте.

const {webkit, chromium, firefox, devices} = require(‘playwright’); 
const iPhone11Pro = devices[‘iPhone 11 Pro’];

(async () => {
    const browser = await webkit.launch({
      headless: false,
    });
    const context = await browser.newContext({     //создание контекста
      ...iPhone11Pro,
    });                                                                             
    const page = await context.newPage();            //создание страницы в контексте     
    await page.goto(‘https://github.com/microsoft/playwright’); 
  } )();

Если мы запустим программу, то увидим:  

23e9a9ad20c2dc09817e415c5d9885bf.png

И если сравнить скриншот экрана с тем, что в моем айфоне, то всё будет рендериться один к одному. Это будет работать точно так же на Linux и Windows.

dfc17e53690d9ff98764e3af9545f9c0.png

Теперь можно тестировать мобильный Safari действительно автотестами, а не ручным тестированием, как обычно. 

Рассмотрим, какие еще вещи можно проставлять в контексте. 

Геолокация

Возьмем Chromium и настроим геолокацию. Сайт maps.google.com

const {webkit, chromium, firefox, devices} = require(‘playwright’); 

(async () => {
    const browser = await chromium.launch({
      headless: false,
    });
    const context = await browser.newContext({

    });                                                                             
    const page = await context.newPage();                
    await page.goto(‘https://maps.google.com’); 
  } )();

Запускаем программу и видим, что у нас есть геолокация, я сейчас нахожусь в Сан-Франциско. 

9e76594ad0041f1d16fee9b15aefd3e5.png

А я бы хотел сэмулировать, что нахожусь в театре «Глобус» в Лондоне. Копируем его координаты. В контексте можно поставить геолокацию:

const {webkit, chromium, firefox, devices} = require(‘playwright’); 

(async () => {
    const browser = await chromium.launch({
      headless: false,
    });
    const context = await browser.newContext({
      geolocation: {
        latitude: 51.508076,
        longitude: -0.0993827,
      }
    });                                                                             
    const page = await context.newPage();                
    await page.goto(‘https://maps.google.com’); 
  } )();

При запуске Google по умолчанию считает, что я нахожусь в Сан-Франциско (у меня IP-адрес Сан-Франциско). Но если я нажму на кнопку «show your location», меня спросят, готов ли я предоставить доступ к данным о геолокации. После согласия я окажусь в Лондоне. Заметим, что я проставил per context. Если я создам новую вкладку и зайду на maps.bing.com и сделаю то же, что в примере выше, то я снова окажусь в Лондоне. Все страницы, которые я открываю в этом контексте, будут считать, что я нахожусь в Лондоне. 

Также можно автоматизировать разрешения доступа к геолокации.

const {webkit, chromium, firefox, devices} = require(‘playwright’); 

(async () => {
    const browser = await chromium.launch({
      headless: false,
    });
    const context = await browser.newContext({
      geolocation: {
        latitude: 51.508076,
        longitude: -0.0993827,
      },
      permissions: [‘geolocation’]
    });                                                                             
    const page = await context.newPage();                
    await page.goto(‘https://maps.google.com’); 
  } )();

Строка permissions: [«geolocation»] говорит, что в этом контексте все вкладки имеют доступ к геолокации без спроса пользователя. 

Прокси

Для того, чтобы гугл не знал мой IP, нужно использовать прокси, их часто используют в тестировании. Когда нужно протестировать внутренние сервисы и попасть из тестируемого окружения в тестируемое окружение, нужно использовать веб-прокси. 

Как использовать веб-прокси в разных браузерах? Обычно в Firefox нужно проставить пользовательские настройки, в Chrome — хитрые флаги, а в WebKit нужно найти глобальные настройки прокси.

У нас не так, мы можем поставить прокси на весь браузер, просто указав его при запуске браузера. У меня есть свой запароленный прокси, созданный в образовательных целях:  

const {webkit, chromium, firefox, devices} = require(‘playwright’); 

(async () => {
    const browser = await chromium.launch({
      headless: false,
      proxy: {
        server: ‘http://proxy.aslushnikov.com:3128’,
        username: process.env.PROXY_USERNAME,
        password: process.env.PROXY_PASSWORD,
      },
    });
    const context = await browser.newContext({
      geolocation: {
        latitude: 51.508076,
        longitude: -0.0993827,
      },
      permissions: [‘geolocation’]
    });                                                                             
    const page = await context.newPage();                
    await page.goto(‘https://maps.google.com’); 
  } )();

Когда откроется приложение, Google Карты больше не знают, что я нахожусь в Сан-Франциско — показано, что я просто в каком-то месте США.

c4c5fe48d5158896a68fa40a50bb7799.png

Весь трафик идет через мой прокси. Если посмотрим на мой IP, это будет IP моего прокси-сервера.

1212ed9a2b1f096de33a83d89e7ecca4.png

Это всё работает независимо от всех браузеров. Могу использовать любой, и везде поведение будет одинаковым. 

Язык

Также рассмотрим другие интересные параметры, начнем с locale. Те, кто занимается локализацией и интернационализацией, знают, что он очень важен. Мы должны использовать locale, чтобы отображать контент на правильном языке. Можно поставить locale для всего контекста, чтобы все страницы в нем ее использовали. 

const {webkit, chromium, firefox, devices} = require(‘playwright’); 

(async () => {
    const browser = await chromium.launch({
      headless: false,
      proxy: {
        server: ‘http://proxy.aslushnikov.com:3128’,
        username: process.env.PROXY_USERNAME,
        password: process.env.PROXY_PASSWORD,
      },
    });
    const context = await browser.newContext({
      geolocation: {
        latitude: 51.508076,
        longitude: -0.0993827,
      },
      permissions: [‘geolocation’],
      locale: ‘de-DE’,  // добавили locale
    });                                                                             
    const page = await context.newPage();                
    await page.goto(‘https://maps.google.com’); 
  } )();

Теперь Google Карты на немецком, потому что я поставил немецкую locale:  

a3c7561d53125f7d229564316fa7efa1.png

Также можно проставить часовой пояс, если хотите протестировать что-то с ними. Для воспроизводимой среды часовые пояса очень важны. 

Темная и светлая темы

И еще один параметр — colorScheme. В операционных системах бывает светлая и темная темы. Например, я хочу темную на сайте Дэна Абрамова overreacted.io, который поддерживает настройки colorScheme. 

const {webkit, chromium, firefox, devices} = require(‘playwright’); 

(async () => {
    const browser = await chromium.launch({
      headless: false,
      proxy: {
        server: ‘http://proxy.aslushnikov.com:3128’,
        username: process.env.PROXY_USERNAME,
        password: process.env.PROXY_PASSWORD,
      },
    });
    const context = await browser.newContext({
      geolocation: {
        latitude: 51.508076,
        longitude: -0.0993827,
      },
      permissions: [‘geolocation’],
      locale: ‘de-DE’,
      colorScheme: ‘dark’,
    });                                                                             
    const page = await context.newPage();                
    await page.goto(‘https://overreacted.io’); 
    await page.waitForTimeout(1000);                    // чтобы было нагляднее переключение
    await page.emulateMedia({                            // переключаем на светлую тему
      colorScheme: ‘light’,
    });
  } )();

Темная схема поменялась через секунду на светлую. 

Фреймы

До этого мы говорили про API контекста, теперь давайте про страницу. Страница может рассказать про свои фреймы, у нас есть событие frameattached. Выведем количество фреймов, которые у нас есть на странице. 

const {webkit, chromium, firefox, devices} = require(‘playwright’); 

(async () => {
    const browser = await chromium.launch({
      headless: false,
    });
    const context = await browser.newContext();                                                                          
    const page = await context.newPage();
    page.on(‘frameattached’, frame => console.log(‘frames: ’ + page.frames().length)); 
    page.on(‘framedetached’, frame => console.log(‘frames: ’ + page.frames().length));               
    await page.goto(‘https://theverge.com’); 
  })();

На сайте theverge.com довольно много рекламы, и она добавляется фреймами на сайт. 

46970d3890e87c76a37e68439e9217c9.png

Все эти фреймы порождают большое количество трафика, его я тоже могу послушать:

(async () => {
    const browser = await chromium.launch({
      headless: false,
    });
    const context = await browser.newContext();                                                                          
    const page = await context.newPage();
    page.on(‘frameattached’, frame => console.log(‘frames: ’ + page.frames().length)); 
    page.on(‘framedetached’, frame => console.log(‘frames: ’ + page.frames().length));
    page.on(‘request’, request => console.log(request.method() + ‘ ‘ +  request.url()));   
    page.on(‘response’, response => console.log(response.status() + ‘ ‘ +  response.url()));             
    await page.goto(‘https://theverge.com’); 
  })();

Можно увидеть, что при запуске сайта летит огромное количество трафика, причем много рекламного, и это влияет на загрузку страницы. 

42868a07424594afb58ae170d155e442.png

Мы можем не только слушать трафик и смотреть на фреймы, но и использовать всю эту информацию, чтобы делать интересные вещи. Например, напишем маленький блокировщик рекламы для theverge.com.У нас есть request interception — технология, которая позволяет вклиниваться в сетевой стек браузера. Он представлен в виде page.route ().

(async () => {
    const browser = await chromium.launch({
      headless: false,
    });
    const context = await browser.newContext();                                                                          
    const page = await context.newPage();
    page.on(‘frameattached’, frame => console.log(‘frames: ’ + page.frames().length)); 
    page.on(‘framedetached’, frame => console.log(‘frames: ’ + page.frames().length));
    page.on(‘request’, request => console.log(request.method() + ‘ ‘ +  request.url()));   
    page.on(‘response’, response => console.log(response.status() + ‘ ‘ +  response.url())); 
    page.route(‘**/*’, route => {                               // перехват всех событий
      if (route.request().frame().parentFrame())    // если у запроса есть фрейм 
        route.abort();                                               // не будем пускать
      else
        route.continue();                                        // иначе разрешаем продолжиться
    });            
    await page.goto(‘https://theverge.com’); 
  })(); 

d3b9c5f3b8c6cf3f2f5324be4c6d64d2.png

Не должно быть видно ни одной рекламы. Но если перейти на другую страницу, то там отсутствует блокировщик рекламы. Реклама будет появляться, потому что перехват запросов работает только на уровне страницы. 

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

(async () => {
    const browser = await chromium.launch({
      headless: false,
    });
    const context = await browser.newContext();                                                                          
    const page = await context.newPage();
    page.on(‘frameattached’, frame => console.log(‘frames: ’ + page.frames().length)); 
    page.on(‘framedetached’, frame => console.log(‘frames: ’ + page.frames().length));
    page.on(‘request’, request => console.log(request.method() + ‘ ‘ +  request.url()));   
    page.on(‘response’, response => console.log(response.status() + ‘ ‘ +  response.url())); 
    context.route('**/*', route => {                               
      if (route.request().frame().parentFrame())    
        route.abort();                                               
      else
        route.continue();                                        
    });            
    await page.goto(‘https://theverge.com’); 
    const page2 = await context.newPage();
    await page2.goto(‘https://bbc.com’);
  })(); 

Мы можем обрывать, продолжать, мокать запросы или можем возвращать на самой странице какую-нибудь информацию. 

В качестве примера будет возвращаться случайная картинка на каждый запрос картинки. Это написать очень просто.

(async () => {
    const browser = await chromium.launch({
      headless: false,
    });
    const context = await browser.newContext();                                                                          
    const page = await context.newPage();
    context.route('**/*', route => {
      if (route.request().resourceType() !== ‘image’)         // если не картинка возвращаемся
        return route.continue();
      
      const LOREM_FLICKR = ‘https://loremflickr.com’;  
      if (route.request().url().startsWith(LOREM_FLICKR))  // чтобы не попасть в цикл
        return route.continue();

      route.fulfill({                                                         
        status: 301,
        headers: {
          location: LOREM_FLICKR + ‘/320/240/parrot’,
         },
      });                                      
    });            
    await page.goto(‘https://theverge.com’);
  })();

Посмотрим, как это работает:  

daf4f5cb1cbdb693ce22d5a76685b6c8.png

Видим попугаи вместо всех картинок или то, что прислал Flickr вместо попугая. 

Подведем итоги демо. Что мы увидели:

  • Мобильная эмуляция.

  • Геолокация.

  • Браузерные контексты: поработали с ними, пооткрывали вкладки, посмотрели на iframe внутри этих браузерных контекстов.

  • Перехват трафика — мы можем мониторить трафик, который идет со страниц на серверы.

  • WEB Proxy —  можем единообразно настраивать прокси для всех трех браузеров, это удобно для веб-тестирования.

  • Изменение часового пояса и языка.

  • Эмуляция цветовой схемы.

На этом возможности Playwright не заканчиваются, есть ещё и, например, такие:

  • Auto-waiting API (автоматическое заполнение форм).

  • Движки селекторов (Playwright поддерживает большое количество селекторов, их можно комбинировать, добавлять свои движки селекторов и использовать их в библиотеке).

  • Загрузка / отправка файлов (можно получать событие, когда браузер загружает файл).

  • Поддержка Shadow DOM (Shadow DOM пирсинг — это значит, что наши селекторы работают сквозь Shadow DOM).

  • Тестирование Electron-приложений.

  • Accessibility API (тестирование доступности сайтов).

  • Поддержка TypeScript.

  • Отладка скриптов.

12–14 апреля мы снова проведём Heisenbug: если вам был интересен этот доклад, то и на новой конференции будет актуальное для вас. Описания некоторых докладов уже доступны на сайте, прочая информация и билеты — там же.

© Habrahabr.ru