Перевод статьи «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. Попробуйте один или два из них на существующем проекте и посмотрите, что получится!
Экспериментирование — это лучший способ обучения.
Счастливой автоматизации!
