Перевод статьи «Refactor Playwright Locators Like a Boss».
Ваши первые попытки автоматизации в Playwright, скорее всего, будут немного… беспорядочными. В конце концов, в вебе вечно творится беспорядок. Но доведение автоматизированных тестов до продакшена означает распутывание этого клубка спагетти-кода и превращение его в нечто более понятное, производительное и удобное для сопровождения.
В этой статье мы рассмотрим несколько отличных методов рефакторинга локаторов Playwright. Причем каждая описанная ниже идея быстра и проста в реализации. В конце концов, нет смысла предлагать оптимизацию, которая сделает жизнь разработчика автотестов мучительной.
Давайте начнем!
Подпишитесь на наш ТЕЛЕГРАМ КАНАЛ ПО АВТОМАТИЗАЦИИ ТЕСТИРОВАНИЯ
Расширение Playwright с помощью пользовательских селекторов
Playwright открывает мир возможностей с помощью своего механизма селекторов. При этом он не только поддерживает широкий спектр встроенных селекторов, но и позволяет пользователям определять свои собственные.
Пользовательские селекторы можно адаптировать к уникальным требованиям приложения. Это поможет сделать скрипты более читаемыми и поддерживать сложную логику выбора, выходящую за рамки базовой.
Вот несколько идей:
data-state
: выбор элементов на основе атрибута данных, представляющего состояние (например,data-state=\"active\"
), чтобы нацелить их на определенные состояния пользовательского интерфейса в одностраничном приложении.closest
: выбор ближайшего предка элемента, соответствующего определенному селектору, по аналогии с методомElement.closest()
в JavaScript.shadow
: таргетирование элементов, находящихся в определенном теневом DOM.rotation
: выбор элемента, ротированного в определенном диапазоне.
Чтобы продемонстрировать возможности пользовательских селекторов в Playwright, давайте создадим селектор data-state
, описанный выше.
import * as pw from 'playwright'; // register custom selector await pw.selectors.register('data-state', () => ({ query(root: Node, selector: string) { return root.querySelector(`[data-state="${selector}"]`); }, queryAll(root: Node, selector: string) { return Array.from( root.querySelectorAll(`[data-state="${selector}"]`) ); }, })); const browser = await pw.firefox.launch(); const page = await browser.newPage(); await page.goto('https://example.com'); // use the custom selector const $active = page.locator('data-state=active'); await $active.click();
Как видите, для создания пользовательского селектора требуется совсем немного. Поскольку Playwright предоставляет вам доступ к базовому DOM, вы можете использовать все его возможности.
Использование объектной модели страницы
Объектная модель страницы (POM) — это шаблон проектирования, широко используемый в автоматизации для повышения удобства обслуживания и уменьшения количества дубликатов. Объект страницы инкапсулирует поведение и элементы конкретной страницы на вашем целевом сайте, что позволяет легко обновлять код по мере необходимости.
Вот пример того, как может выглядеть объект страницы для страницы входа в систему. Обратите внимание, как он собирает общие локаторы:
import {Page} from 'playwright'; export class LoginPage { page: Page; constructor(page: Page) { this.page = page; } get $usernameInput() { return this.page.getByLabel('Username'); } get $passwordInput() { return this.page.getByLabel('Password'); } get $loginButton() { return this.page.getByText('Login'); } public async login(username: string, password: string) { await this.$usernameInput.fill(username); await this.$passwordInput.fill(password); await this.$loginButton.click(); } }
Вот как вы можете использовать POM LoginPage
:
import * as pw from 'playwright'; import {LoginPage} from './LoginPage'; const browser = await pw.webkit.launch(); const page = await browser.newPage(); await page.goto('https://app.browsercat.com/sign-in'); const loginPage = new LoginPage(page); await loginPage.login('user@example.com', 'p1a2s3s4word!'); await loginPage.$loginButton.click();
Составление объектов страницы из компонентов
Я обнаружил, что смысл объектной модели страницы можно расширить еще больше. Подумайте о том, что большинство веб-сайтов построено из переиспользуемых компонентов, которые повторяются на нескольких страницах. Вместо того чтобы разрабатывать POM с нуля, можно составлять их из компонентов, встречающихся на каждой странице.
Вот как мы можем использовать объекты компонентов для AppMenuBar
, ChatWidget
и AppFooter
:
// Define the separate components class AppMenuBar { constructor(public page: Page) {} get $logo () {} get $menuButton () {} } class ChatWidget { constructor(public page: Page) {} get $chatToggle () {} get $messageInput () {} get $sendButton () {} async sendMessage(message: string) {} } class AppFooter { constructor(public page: Page) {} get $uptimeStatus () {} } // Compose these components into a complete page object class ContactPage { menuBar: AppMenuBar; chatWidget: ChatWidget; footer: AppFooter; constructor(public page: Page) { this.menuBar = new AppMenuBar(page); this.chatWidget = new ChatWidget(page); this.footer = new AppFooter(page); } } // Use the composed POM const browser = await pw.chromium.launch(); const page = await browser.newPage(); await page.goto('https://www.browsercat.com/contact'); const contactPage = new ContactPage(page); // Use the component methods await contactPage.menuBar.$menuButton.click(); await contactPage.chatWidget.sendMessage('Hello there!');
В приведенном выше примере составленный объект ContactPage
становится мощной и удобной абстракцией, объединяющей взаимодействия для строки меню, виджета чата и нижнего колонтитула приложения, которые затем используются в повторяющихся взаимодействиях. Такой подход позволяет избежать лишних повторов в ваших скриптах и легко обновлять взаимодействия компонентов при изменении UI без необходимости изменения каждого скрипта.
Следующие шаги
Как видите, эти приемы быстро реализуются и могут оказать большое влияние на читаемость, сопровождаемость и производительность ваших скриптов Playwright. Попробуйте один или два из них на существующем проекте и посмотрите, что получится!
Экспериментирование — это лучший способ обучения.
Счастливой автоматизации!