Организация кода проекта

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

  1. Создание структуры каталогов

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

Рекомендуемая структура каталогов:

/project
 /tests
     /e2e
     /unit
 /pages
 /utils
 /data
 /config
 /node_modules
 package.json
  • /tests — основной каталог для тестов. Его можно разделить на подкаталоги в зависимости от типа тестирования: e2e (end-to-end), unit (модульные тесты), интеграционные тесты.

  • /pages — хранит страницы и компоненты, с которыми взаимодействует Puppeteer. Это помогает реализовать концепцию «Page Object Model» (POM).

  • /utils — каталог для вспомогательных утилит и функций.

  • /data — каталог для хранения тестовых данных, например, JSON-файлов.

  • /config — конфигурационные файлы, например, для настройки среды выполнения Puppeteer.

Такой подход упрощает ориентирование в проекте, а также помогает избежать дублирования кода.

  1. Принципы Page Object Model (POM)

Для упрощения работы с тестами и обеспечения их повторяемости важно использовать паттерн Page Object Model. Этот паттерн предлагает создавать отдельные классы для каждой страницы, с которой взаимодействует тест, инкапсулируя логику взаимодействия с элементами страницы.

Пример реализации POM:

// /pages/LoginPage.js class LoginPage { constructor(page) {
this.page = page; this.usernameField = & this.passwordField =
'#password'; this.submitButton = '#submit'; }

async navigate() { await this.page.goto('https://example.com/login'); }

async login(username, password) { await this.page.type(this.usernameField, username); await this.page.type(this.passwordField, password); await this.page.click(this.submitButton); } }

module.exports = LoginPage;

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

// /tests/e2e/login.test.js
const puppeteer = require('puppeteer');
const LoginPage = require('../. ./pages/LoginPage');

describe('Login functionality', () => {
    let browser;
    let page;
    let loginPage;

    beforeAll(async () => {
        browser = await puppeteer.launch();
        page = await browser.newPage();
        loginPage = new LoginPage(page);
    });

    afterAll(async () => {
        await browser.close();
    });

    test('should allow user to login with valid credentials', async () => {
        await loginPage.navigate();
        await loginPage.login('user', 'password');
        // Далее, добавьте проверки, что страница перешла на ожидаемый URL или что элементы появились
    });
});

С помощью Page Object Model можно не только повысить повторное использование кода, но и улучшить поддержку, так как изменения на странице (например, изменение селекторов) потребуется вносить только в одном месте.

  1. Вспомогательные утилиты

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

Пример утилиты для ожидания элементов:

// /utils/waitForElement.js async function waitForElement(page,
selector, timeout = 30000) { try { await page.waitForSelector(selector,
{ timeout }); } catch (error) { throw new
Error(Element with selector "${selector}" was not found within ${timeout}ms);
} }

module.exports = waitForElement;

Использование утилиты в тестах:

const waitForElement = require('../. ./utils/waitForElement');

test('should display the login button', async () => {
    await waitForElement(page, '#loginButton');
    // После этого можно выполнить проверку, что кнопка действительно появилась
});

Такие утилиты значительно упрощают тесты и делают их более читаемыми.

  1. Конфигурация Puppeteer

Для улучшения гибкости и удобства тестирования полезно вынести конфигурацию Puppeteer в отдельные файлы, чтобы можно было легко изменять параметры (например, запускать браузер в режиме без головы, устанавливать конкретные тайм-ауты или настройку прокси).

Пример конфигурации:

// /config/puppeteerConfig.js
module.exports = {
 headless: true,
 slowMo: 50,
 timeout: 30000,
};

При запуске тестов эти настройки можно будет применять для каждого нового браузера:

const puppeteer = require('puppeteer'); const puppeteerConfig =
require('../config/puppeteerConfig');

beforeAll(async () => { browser = await
puppeteer.launch(puppeteerConfig); });

Такой подход позволяет гибко настраивать тестирование и быстро адаптировать проект под новые требования.

  1. Разделение тестов по типам

Разделение тестов на разные категории помогает избежать перегрузки и сделать код тестов более понятным. Основные типы тестов:

  • E2E тесты (End-to-End) — тестируют приложение в целом, от начала до конца.
  • Интеграционные тесты — проверяют взаимодействие нескольких компонентов или систем.
  • Модульные тесты — тестируют отдельные функции или классы.

Каждый тип тестов имеет свою цель и помогает сосредоточиться на разных аспектах приложения. Хорошая практика — использовать разные подходы и инструменты для разных типов тестов, например, для e2e тестов можно использовать Puppeteer, а для модульных — Jest или Mocha.

  1. Параллельное выполнение тестов

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

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

{
  "jest": {
 "maxWorkers": "50%"  // Параллельное выполнение на 50% доступных ядер
  }
}

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

  1. Использование CI/CD

Интеграция с системами CI/CD позволяет автоматизировать процесс тестирования и развертывания. Популярные системы, такие как GitHub Actions, GitLab CI, CircleCI, позволяют настроить автоматический запуск тестов Puppeteer при каждом коммите в репозиторий.

Пример конфигурации для GitHub Actions:

name: Run Puppeteer Tests



on: push: branches: - main pull_request: branches: - main

jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Node.js uses: actions/setup-node@v2 with: node-version: '14' - run: npm install - run: npm test

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

  1. Обработка ошибок и отчеты

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

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

const { expect } = require('chai');

describe('Login test', () => { it('should show error for invalid
credentials', async () => { try { await
loginPage.login('invalidUser', 'wrongPassword'); const error = await
page.$('.error-message'); expect(error).to.not.be.null; } catch (error)
{ console.error('Test failed', error); throw error; } }); });

Внедрение автоматических отчетов также помогает быстрее выявлять проблемные места.

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