Создание переиспользуемых компонентов

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

1. Структура проекта и роли компонентов

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

  • Page Object — объекты, представляющие страницы приложения.
  • Компоненты UI — такие как формы, кнопки, модальные окна и другие элементы интерфейса.
  • Функции-утилиты — вспомогательные методы для выполнения общих задач, например, взаимодействие с базой данных или загрузка тестовых данных.

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

2. Использование Page Object Model (POM)

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

Пример использования Page Object для формы авторизации:

// authPage.js
class AuthPage {
  constructor(page) {
    this.page = page;
    this.usernameField = page.locator('#username');
    this.passwordField = page.locator('#password');
    this.submitButton = page.locator('#submit');
  }

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

module.exports = AuthPage;

В тесте можно использовать этот объект:

const { test, expect } = require('@playwright/test');
const AuthPage = require('./authPage');

test('Тестирование авторизации', async ({ page }) => {
  const authPage = new AuthPage(page);
  await authPage.login('user123', 'password123');
  await expect(page).toHaveURL('/dashboard');
});

3. Создание переиспользуемых компонентов UI

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

Пример компонента кнопки:

// buttonComponent.js
class Button {
  constructor(page, selector) {
    this.page = page;
    this.selector = selector;
  }

  async click() {
    await this.page.locator(this.selector).click();
  }

  async isVisible() {
    return await this.page.locator(this.selector).isVisible();
  }
}

module.exports = Button;

Пример использования компонента кнопки в тестах:

const { test, expect } = require('@playwright/test');
const Button = require('./buttonComponent');

test('Тестирование клика по кнопке', async ({ page }) => {
  const button = new Button(page, '#submit');
  await button.click();
  await expect(page).toHaveURL('/success');
});

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

4. Организация общих функций

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

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

// utils.js
async function waitForElement(page, selector, timeout = 5000) {
  await page.locator(selector).waitFor({ timeout });
}

module.exports = { waitForElement };

Пример использования функции-утилиты в тесте:

const { test, expect } = require('@playwright/test');
const { waitForElement } = require('./utils');

test('Тестирование ожидания элемента', async ({ page }) => {
  await page.goto('/somepage');
  await waitForElement(page, '#some-element');
  await expect(page.locator('#some-element')).toBeVisible();
});

5. Параметризация и конфигурация компонентов

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

// formComponent.js
class Form {
  constructor(page, formSelector) {
    this.page = page;
    this.form = page.locator(formSelector);
  }

  async fillField(fieldName, value) {
    await this.form.locator(`[name=${fieldName}]`).fill(value);
  }

  async submit() {
    await this.form.locator('button[type=submit]').click();
  }
}

module.exports = Form;

Пример использования:

const { test, expect } = require('@playwright/test');
const Form = require('./formComponent');

test('Тестирование формы', async ({ page }) => {
  const form = new Form(page, '#login-form');
  await form.fillField('username', 'user123');
  await form.fillField('password', 'password123');
  await form.submit();
  await expect(page).toHaveURL('/dashboard');
});

6. Использование классов и наследования

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

Пример использования наследования для расширения функционала компонента кнопки:

// extendedButton.js
const Button = require('./buttonComponent');

class ExtendedButton extends Button {
  constructor(page, selector) {
    super(page, selector);
  }

  async doubleClick() {
    await this.page.locator(this.selector).dblclick();
  }
}

module.exports = ExtendedButton;

Пример использования расширенной кнопки в тесте:

const { test, expect } = require('@playwright/test');
const ExtendedButton = require('./extendedButton');

test('Тестирование двойного клика по кнопке', async ({ page }) => {
  const button = new ExtendedButton(page, '#double-click-btn');
  await button.doubleClick();
  await expect(page).toHaveURL('/double-clicked');
});

7. Поддержка многократных пользователей и данных

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

Пример создания компонента для тестирования разных пользователей:

// userComponent.js
class User {
  constructor(page, username, password) {
    this.page = page;
    this.username = username;
    this.password = password;
  }

  async login() {
    await this.page.locator('#username').fill(this.username);
    await this.page.locator('#password').fill(this.password);
    await this.page.locator('#submit').click();
  }
}

module.exports = User;

Пример использования:

const { test, expect } = require('@playwright/test');
const User = require('./userComponent');

test('Тестирование авторизации для разных пользователей', async ({ page }) => {
  const user1 = new User(page, 'user1', 'password1');
  await user1.login();
  await expect(page).toHaveURL('/dashboard');

  const user2 = new User(page, 'user2', 'password2');
  await user2.login();
  await expect(page).toHaveURL('/dashboard');
});

8. Советы по улучшению тестируемости и масштабируемости

При создании переиспользуемых компонентов важно соблюдать следующие рекомендации:

  • Минимизировать зависимости: Стремитесь к тому, чтобы компоненты не имели сложных зависимостей от других частей системы.
  • Избегать жесткой привязки к конкретному приложению: Компоненты должны быть достаточно абстрактными, чтобы их можно было использовать в разных проектах.
  • Документировать компоненты: Описание поведения компонентов, их методов и параметров улучшает поддержку кода в долгосрочной перспективе.