Пулы браузеров

Puppeteer предоставляет возможность управлять экземплярами браузеров Chrome или Chromium через API высокого уровня. В больших проектах автоматизированного тестирования одной инстанции браузера часто недостаточно: для ускорения выполнения тестов, параллельного запуска и распределения нагрузки применяется концепция пулов браузеров.

Основные принципы работы пула

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

Ключевые моменты:

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

Структура пула

Типичный пул состоит из трёх уровней:

  1. Браузерный уровень Каждый элемент пула — это полноценный экземпляр Chromium или Chrome, запущенный через puppeteer.launch().

  2. Уровень страниц (Page) Для каждого теста обычно создаётся отдельная страница browser.newPage(). Иногда используются несколько страниц на одном браузере, но это требует аккуратного управления ресурсами.

  3. Очередь задач Пул реализует очередь тестов, которые ожидают свободного браузера или страницы. Очередь гарантирует, что все задания будут выполнены без конфликта ресурсов.

Пример реализации пула

const puppeteer = require(&

class BrowserPool {
    constructor(size = 2) {
        this.size = size;
        this.pool = [];
        this.queue = [];
    }

    async init() {
        for (let i = 0; i < this.size; i++) {
            const browser = await puppeteer.launch({ headless: true });
            this.pool.push({ browser, busy: false });
        }
    }

    async acquire() {
        const freeBrowser = this.pool.find(b => !b.busy);
        if (freeBrowser) {
            freeBrowser.busy = true;
            return freeBrowser.browser;
        }
        return new Promise(resolve => {
            this.queue.push(resolve);
        });
    }

    release(browser) {
        const browserObj = this.pool.find(b => b.browser === browser);
        if (browserObj) {
            browserObj.busy = false;
            if (this.queue.length > 0) {
                const next = this.queue.shift();
                browserObj.busy = true;
                next(browserObj.browser);
            }
        }
    }

    async closeAll() {
        for (const b of this.pool) {
            await b.browser.close();
        }
        this.pool = [];
    }
}

// Использование
(async () => {
    const pool = new BrowserPool(3);
    await pool.init();

    const browser = await pool.acquire();
    const page = await browser.newPage();
    await page.goto('https://example.com');
    await page.screenshot({ path: 'example.png' });

    await page.close();
    pool.release(browser);

    await pool.closeAll();
})();

Управление ресурсами и оптимизация

  • Размер пула выбирается исходя из объёма тестов и доступной оперативной памяти. Слишком большой пул приводит к перегрузке системы, слишком маленький — к очередям и задержкам.
  • Повторное использование страниц сокращает накладные расходы, но требует очистки состояния перед новым тестом (page.goto(‘about:blank’), page.deleteCookie() и очистка localStorage).
  • Мониторинг ошибок: браузеры могут аварийно завершаться, поэтому пул должен уметь перезапускать неработающие экземпляры.

Интеграция с фреймворками тестирования

Пулы браузеров эффективно интегрируются с Jest, Mocha или другими фреймворками через хуки beforeAll, afterAll, beforeEach, afterEach. Такой подход позволяет запускать тесты параллельно, сокращая общее время выполнения.

Пример для Jest:

let pool;
let browser;

beforeAll(async () => {
    pool = new BrowserPool(2);
    await pool.init();
});

beforeEach(async () => {
    browser = await pool.acquire();
});

afterEach(async () => {
    pool.release(browser);
});

afterAll(async () => {
    await pool.closeAll();
});

test('Example test', async () => {
    const page = await browser.newPage();
    await page.goto('https://example.com');
    const title = await page.title();
    expect(title).toBe('Example Domain');
    await page.close();
});

Особенности параллельного выполнения

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

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