С ростом количества e2e-тестов, сложность поддержки значительно возрастает из-за хрупкости таких тестов. Изменение текста в сквозном блоке, может повлечь за собой падение и необходимости менять локаторы в значительной части тестов.
Для решения этой задачи придумано несколько решений, одно из которых называется Page Object Pattern. Его плюсом является универсальность, оно не завязано на конкретный фреймворк и может применяться везде.
Page Object Pattern (POP) — это шаблон проектирования, который используется для создания абстракции веб-страницы в виде объектов в коде теста. Каждый объект страницы представляет собой отдельную веб-страницу или компонент, инкапсулируя взаимодействия с элементами пользовательского интерфейса. Это позволяет сделать тесты более модульными и переиспользуемыми.
Основные характеристики Page Object Pattern:
- Инкапсуляция: Все взаимодействия с элементами страницы (например, клики, ввод текста) инкапсулированы в одном классе, который представляет эту страницу.
- Переиспользуемость: Один и тот же объект страницы может быть использован в нескольких тестах, что снижает дублирование. При изменении интерфейса веб-страницы необходимо обновить только соответствующий объект страницы, а не все тесты, что значительно упрощает поддержку.
- Читаемость: Тесты становятся более читаемыми, так как высокоуровневые операции (например, "вход в систему") абстрагированы от низкоуровневых взаимодействий с элементами страницы.
Использование POP в Playwright не требует никаких специальных инструментов. Достаточно создать класс, который выставляет наружу методы, скрывающие работу с объектом page
для данной страницы.
Преобразуем наш тест с использованием POP. Исходный тест:
test('test', async ({ page }) => {
await page.goto('https://demo.playwright.dev/todomvc/')
const input = page.getByPlaceholder('What needs to be done?')
const taskName = 'Finish Hexlet\'s course'
await input.fill(taskName)
await input.press('Enter')
await input.fill('second todo')
await input.press('Enter')
const item = page.getByTestId('todo-title').filter({ hasText: taskName })
await expect(item).toBeVisible()
})
Для начала выделим класс, который описывает страницу. Его можно расположить например рядом с папкой тестов, внутри models/TodoMVCPage.js.
import { Page } from '@playwright/test'
export default class TodoMVCPage {
/**
* @param {Page} page
*/
constructor(page) {
this.page = page
// Описываем нужные локаторы
// Так как реальный поиск происходит во время использования,
// то мы можем без проблем задать все нужные локаторы прямо в конструкторе
this.inputForNewTodo = page.getByPlaceholder('What needs to be done?')
}
async goto() {
await this.page.goto('https://demo.playwright.dev/todomvc/')
}
getTaskItemByName(taskName) {
return this.page.getByTestId('todo-title').filter({ hasText: taskName })
}
async addTodo(text) {
await this.inputForNewTodo.fill(text)
await this.inputForNewTodo.press('Enter')
}
}
Конструктор класса принимает на вход страницу и описывает все локаторы. Методы такого класса, обычно, состоят из действий, которые можно выполнять на страницах и методов для выборки нужных локаторов. Проверки, можно описывать и внутри, но, чаще, их выносят в сам тест, так как в этом и состоит его смысл.
И сам тест:
test('test', async ({ page }) => {
const todoMvcPage = new TodoMVCPage(page)
await todoMvcPage.goto()
const taskName = 'Finish Hexlet\'s course'
await todoMvcPage.addTodo(taskName)
await todoMvcPage.addTodo('second todo')
const item = todoMvcPage.getTaskItemByName(taskName)
await expect(item).toBeVisible()
})
Тест стал немного меньше, но главное, то, что он стал значительно более читаемым. К тому же, теперь мы можем легко повторно использовать получившийся класс для других тестов. Если класс правильно спроектирован, то изменения на странице затронут только сам класс.
Дополнительные материалы
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.