Практичний погляд на патерн об’єктного пулу для тестів Playwright

pic

Ілюстрація патерну Object Pool для Playwright

Патерн Object Pool (Пул об'єктів) — це креаційний патерн проектування, який зберігає набір попередньо ініціалізованих об'єктів, відомих як «пул», готових до використання замість того, щоб створювати та знищувати їх за потребою. Він працює через чотири прості кроки:

  1. Ініціалізація пулу: Створення та завантаження об'єктів у пул.
  2. Пошук підходящого об'єкта: Вибір доступного об'єкта за специфічними критеріями.
  3. Отримання об'єкта: Резервування об'єкта для ексклюзивного використання.
  4. Повернення об'єкта: Повернення об'єкта в пул після використання.

Чому варто використовувати патерн Object Pool?

Основні переваги — це ефективне управління ресурсами та паралельна синергія, особливо при запуску паралельних тестів. Також цей патерн допомагає вирішити поширені проблеми:

  • Конфлікти сесій: Перешкоджає використанню однієї сесії кількома тестами.
  • Виснаження ресурсів: Забезпечує, щоб користувачі/акаунти не використовувалися занадто часто.
  • Парадокс пестицидів: Уникає застарілих даних тесту, оновлюючи об'єкти.
  • Ферментація даних: Забезпечує роботу старих даних після оновлень системи.

Найкраще використання:

Коли у вас є обмежена кількість об'єктів, наприклад тестових акаунтів, які не можна генерувати динамічно під час виконання тестів.

Альтернативні підходи (та чому вони не працюють)

Без пулу об'єктів поширеними обходами є:

  1. Виділення для кожного тесту: Призначення користувачів у розділі Arrange кожного тесту.
  2. Виділення на рівні набору тестів: Призначення користувачів у хуку @beforeAll.

Обидва підходи вводять залежності тестів, що вимагають суворого порядку виконання для уникнення конфліктів. Це ручне зусилля, яке може призвести до зниження продуктивності, навіть якщо тести розподілені рівномірно по наборам.

Наприклад, якщо один набір має 20 тестів, а інший 50 тестів, паралельне виконання призведе до того, що один потік буде вільним після завершення меншого набору. Перерозподіл тестів допомагає, але не є масштабованим.

Чому патерн Object Pool виграє

Пул об'єктів гарантує, що:

  • Один тест отримує одного користувача за один раз.
  • Тести виконуються незалежно без конфліктів.
  • Продуктивність залишається оптимальною, коли працівники = розмір пулу.

Однак можуть виникнути умови гонки, коли кілька працівників одночасно отримують доступ до пулу. Рішення, такі як м'ютекси, семафори та потокобезпечні структури, є звичними, але однопотокова природа JavaScript ускладнює ситуацію.

Реалізація пулу об'єктів у Playwright

Playwright Test запускає кожного працівника в ізольованому середовищі без спільного стану. Попереднє завантаження користувачів створює приватні пули, тоді як м'ютекси не можуть синхронізуватися між працівниками. Є кілька способів реалізації:

  1. Підхід з файлом блокування: Створення файлу блокування (наприклад, users_copy.json.lock) при отриманні користувача. Це запобігає конфліктам, але може ввести накладні витрати на введення/виведення.
  2. API сервер (кращий варіант): Централізований API керує пулом, забезпечуючи синхронізацію серед усіх працівників. Цей підхід мені подобається, тому що він надає:
  • Єдине джерело правди для всіх працівників.
  • Вбудована синхронізація запобігає умовам гонки (синхронізацію потрібно реалізувати самостійно).
  • Легка реалізація за допомогою сучасних бекенд-фреймворків.

Зазначено, що вам знадобиться принаймні 2 API кінцевих точки:

  1. /acquire — Зарезервувати об'єкт.
  2. /release — Повернути об'єкт після використання.

Також вбудований webServer Playwright може запускати цей API перед кожним запуском тестів.

pic

Приклад команди Playwright webServer

Завдання CI та горизонтальне масштабування

Обидва варіанти працювали добре до того моменту, поки ми не впровадили горизонтальне масштабування — запуск тестів Playwright на кількох машинах. Кожна машина створювала свій приватний пул, що робило синхронізацію неможливою.

Підхід з файлом блокування тут не працює, оскільки файли блокування є локальними для кожної машини.

Щоб подолати це, можна налаштувати процес таким чином:

  • Запуск API сервера як окремого кроку: Запустіть API сервер перед виконанням тестів Playwright, додавши крок у файл .yaml.
  • Мережевий доступ (попередня умова): Забезпечте, щоб усі машини могли доступити сервер, на якому розгорнуто API.

pic

Приклад файлу github action .yaml

Цей підхід централізує пул серед усіх машин, забезпечуючи безперебійну обробку користувачів і запобігаючи умовам гонки, навіть при горизонтальному масштабуванні.

Висновок

Ось моя реалізація API сервера, разом з іншими згаданими підходами https://github.com/eotsevych/pw-object-pool.git.
Я запускаю цей сервер перед виконанням тестів, і ви можете легко повторно використовувати його для своїх цілей.

Порада: Не забудьте оновити інтерфейс об'єкта (у мене це User), якщо ви хочете додати нові властивості або змінити структуру об'єкта.

Для кінцевої точки /acquire я передаю role користувача та workerId, що дозволяє мені відстежувати, який працівник отримує і повертає кожного користувача.

Перекладено з: A Practical Look at the Object Pool Pattern for Playwright Tests