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

Общение с драматургом

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

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

Два контекста взаимодействуют через асинхронный механизм. NodeJS отправляет запросы в браузер и получает ответ.

Вы можете представить это как разговор в WhatsApp между двумя людьми, каждый из которых занимается своими делами во время этого чата:

(Подсказка: чтобы увидеть реальную связь, запустите тест Playwright с DEBUG=pw:api)

Ожидание активности

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

Попытка №1

Первоначально вы можете написать довольно простой код:

await page.locator('button').click();
await page.waitForRequest('https://example.com/resource');

Этот код будет идти от неработающего до нестабильного. Посмотрим на сроки:

Во-первых, NodeJS отправит браузеру запрос на нажатие кнопки. Как только кнопка нажата и NodeJS получает ответ, он отправляет браузеру инструкцию проверить, отправлен ли запрос. Но поскольку мы не знаем, сколько времени потребуется NodeJS для отправки этого запроса, он может попасть в браузер в любое время в течение фиолетового прямоугольника. Теперь, если инструкция попадает в браузер до отправки запроса, помеченного как A zone, нам повезло, и браузер ее поймает. Но мы также можем попасть в браузер во время раздела, отмеченного буквой B, и тогда будет слишком поздно. Наш запрос уже покинул станцию, и мы его пропустим.

Это все равно, что сказать ребенку открыть дверь, а затем отправить ему сообщение, чтобы убедиться, что собака не убежит. Что ж, к тому времени, когда они прочитают второе сообщение, собака, вероятно, уже гоняется за всеми соседскими кошками.

Попытка №2

Итак, давайте отправим запрос на проверку сетевого запроса, прежде чем нажимать кнопку.

await page.waitForRequest('https://example.com/resource');
await page.locator('button').click();

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

Попытка №3

Решение состоит в том, чтобы отправить оба запроса параллельно, используя promise.all:

await Promise.all([
  page.waitForRequest('https://example.com/resource'),
  page.locator('button').click()
])

Что здесь происходит?

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

Как только запрос запущен, браузер уже следит за ним и перехватывает его. Сейчас тест стабилен.

Недолговечный элемент

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

Мы хотим отправить 3 инструкции в браузер:

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

«Проблемная» зона здесь — B. Мы не можем быть уверены, что попадем в нее, чтобы проверить, появляется ли загрузчик до того, как загрузчик исчезнет. Поэтому нам нужно отправить запрос, чтобы проверить, появляется ли загрузчик параллельно и до того, как появится загрузчик.

Затем наш код отправит:

await Promise.all([
  page.locator('#loader').waitFor(),
  page.locator('button').click()
];

Это гарантирует, что мы дождемся появления загрузчика в зоне A выше. Затем нам просто нужно подождать, пока он исчезнет. Мы можем сделать это после того, как получим ответ о том, что загрузчик появился:

await page.locator('#loader').waitFor({state: 'detached'};

А вот так выглядит весь поток:

Наш загрузчик может попасть в браузер до или после того, как загрузчик исчез. Если запрос попадает в зону C, он будет ждать, пока загрузчик не исчезнет. Если он попадет в зону D, он немедленно разрешится.

Заключение

Pkaywright работает в очень быстром темпе, и мы не можем предположить, как синхронизируются NodeJS и браузер. Promise.all() чрезвычайно мощен, делая ваши тесты надежными и уменьшая ненадежность.