«Будучи специалистом по контролю качества, работающим на быстрорастущей платформе электронной коммерции, я начал испытывать трудности, связанные с использованием традиционной объектной модели страницы (POM).
Поначалу она работала хорошо, но по мере расширения нашего приложения — с увеличением количества вариантов поиска, новых функций корзины и расширения процесса оформления заказа — ограничения стали очевидны. Управление тестами становилось все сложнее, а сами тесты начинали быть оторванными от реальных действий пользователей.

После ряда экспериментов я остановился на подходе POM, ориентированном на пользовательский поток (user flow-centric POM), который изменил всё. Структурировав тесты на основе реальных пользовательских путей, а не отдельных страниц, мы сделали автоматизацию более плавной, простой в обслуживании и более согласованной с пользовательским опытом.
Вот взгляд на проблемы, с которыми мы столкнулись, и на то, как эта простая модель сделала тестирование более эффективным.
1. Проблема: традиционный POM плохо масштабируется со сложными пользовательскими путями
В начале мы использовали традиционный подход POM, где каждая страница имела свою собственную изолированную структуру тестов. Такая схема хорошо работала для простых случаев, но по мере роста нашего приложения мы столкнулись с проблемами:
- Избыточные шаги: Повторение действий на разных страницах отнимало много времени и приводило к дублированию.
- Головная боль при обслуживании: Каждое изменение пользовательского интерфейса означало обновление тестов в нескольких файлах, что отнимало много времени и увеличивало вероятность ошибок.
- Разрозненный пользовательский поток: наши тесты казались разъединенными, разрозненными. Поскольку мы организовывали тесты по страницам, было сложно получить полную картину сквозного пользовательского опыта.
С учетом того, что постоянно добавлялись новые фичи, особенно в поиске и оформлении заказа, традиционный POM быстро становился слишком жестким. Пришло время искать лучшую структуру.
2. Решение: Простая POM-модель, ориентированная на поток пользователей
Чтобы решить эти проблемы, я перестроил нашу модель POM, ориентированную на потоки пользователей, а не на отдельные страницы. Эта модель основана на основных маршрутах пользователей, а не на отдельных действиях на конкретных страницах. Вот как я ее создал:
1. Определил ключевые пользовательские потоки: я сосредоточился на основных путях пользователей, таких как:
- Поток поиска продукта: от поиска продукта до его выбора из результатов.
- Поток добавления в корзину: от просмотра информации о продукте до добавления товара в корзину.
- Поток оформления заказа: от просмотра корзины до завершения оформления заказа.
2. Организация классов страниц по потокам: Каждый класс страницы представлял собой часть потока, включающую элементы и действия для этого пути. Таким образом, классы страниц естественным образом соответствовали реальным действиям пользователей, что облегчало понимание и поддержку.
3. Централизация реюзабельных действий: Такие действия, как searchProduct, addToCart и proceedToCheckout, определяются один раз, что позволяет использовать их в различных потоках и сократить дублирование.
3. Пример: Традиционный POM и POM, ориентированный на пользовательский поток, для потока оформления заказа
В нашем приложении электронной коммерции давайте разберем поток оформления заказа:
- Вход в систему,
- Поиск товара,
- Добавление его в корзину,
- Оформление заказа,
- Ввод данных о доставке,
- Завершение оплаты
- И подтверждение заказа.
Давайте сравним, как этот поток будет обрабатываться с помощью традиционного POM и POM, ориентированного на пользовательский поток.
Традиционный POM: каждая страница изолирована
В традиционном POM каждая страница имеет свой собственный класс с определенными действиями только для этой страницы. Каждый шаг в потоке изолирован, и они связываются вручную в тестовом сценарии.
Классы страниц в традиционном POM
LoginPage.js
class LoginPage {
constructor(page) {
this.page = page;
this.usernameField = '#username';
this.passwordField = '#password';
this.loginButton = '#login';
}
async navigate() {
await this.page.goto('https://ecommerce.com/login');
}
async login(username, password) {
await this.page.fill(this.usernameField, username);
await this.page.fill(this.passwordField, password);
await this.page.click(this.loginButton);
}
}
module.exports = LoginPage;
ProductSearchPage.js
class ProductSearchPage {
constructor(page) {
this.page = page;
this.searchBox = '#search';
this.searchButton = '#search-btn';
}
async searchProduct(productName) {
await this.page.fill(this.searchBox, productName);
await this.page.click(this.searchButton);
}
}
module.exports = ProductSearchPage;
CartPage.js
class CartPage {
constructor(page) {
this.page = page;
this.addToCartButton = '#add-to-cart';
}
async addToCart() {
await this.page.click(this.addToCartButton);
}
}
module.exports = CartPage;
CheckoutPage.js
class CheckoutPage {
constructor(page) {
this.page = page;
this.proceedToCheckoutButton = '#proceed-to-checkout';
}
async proceedToCheckout() {
await this.page.click(this.proceedToCheckoutButton);
}
}
module.exports = CheckoutPage;
PaymentPage.js
class PaymentPage {
constructor(page) {
this.page = page;
this.paymentButton = '#complete-payment';
}
async completePayment() {
await this.page.click(this.paymentButton);
}
}
module.exports = PaymentPage;
Тестовый сценарий для традиционного POM:
const LoginPage = require('../page_objects/LoginPage');
const ProductSearchPage = require('../page_objects/ProductSearchPage');
const CartPage = require('../page_objects/CartPage');
const CheckoutPage = require('../page_objects/CheckoutPage');
const PaymentPage = require('../page_objects/PaymentPage');
// Execute the Order Checkout Flow
const loginPage = new LoginPage(page);
await loginPage.navigate();
await loginPage.login('testuser', 'password123');
const productSearchPage = new ProductSearchPage(page);
await productSearchPage.searchProduct('laptop');
const cartPage = new CartPage(page);
await cartPage.addToCart();
const checkoutPage = new CheckoutPage(page);
await checkoutPage.proceedToCheckout();
const paymentPage = new PaymentPage(page);
await paymentPage.completePayment();
Проблемы традиционного POM:
- Разрозненный поток: тестовому сценарию приходится вручную подключать каждую страницу, что затрудняет прослеживание пути пользователя.
- Повторяющиеся импорты и вызовы: Каждый шаг в путешествии — это отдельный импорт и действие, что приводит к разрозненности кода.
- Сложность обслуживания: При изменении потока оформления заказа вам придется обновлять каждый отдельный класс и следить за тем, чтобы тестовый сценарий оставался точным.
POM, ориентированный на поток пользователей: страницы строятся с учетом потока
В POM, ориентированном на поток пользователей, каждая страница разрабатывается с учетом всего пути, поэтому связанные действия логически группируются, что упрощает управление и следование потоку.
Классы страниц в POM, ориентированном на поток пользователей
LoginPage.jsостается прежнимShoppingPage.js(объединяет поиск товаров и добавление в корзину):
class ShoppingPage {
constructor(page) {
this.page = page;
this.searchBox = '#search';
this.searchButton = '#search-btn';
this.addToCartButton = '#add-to-cart';
}
async searchProduct(productName) {
await this.page.fill(this.searchBox, productName);
await this.page.click(this.searchButton);
}
async addToCart() {
await this.page.click(this.addToCartButton);
}
}
module.exports = ShoppingPage;
OrderPage.js(объединяет действия по оформлению заказа и оплате)
class OrderPage {
constructor(page) {
this.page = page;
this.proceedToCheckoutButton = '#proceed-to-checkout';
this.paymentButton = '#complete-payment';
}
async proceedToCheckout() {
await this.page.click(this.proceedToCheckoutButton);
}
async completePayment() {
await this.page.click(this.paymentButton);
}
}
module.exports = OrderPage;
Сценарий тестирования для POM, ориентированного на пользовательский поток
const LoginPage = require('../page_objects/LoginPage');
const ShoppingPage = require('../page_objects/ShoppingPage');
const OrderPage = require('../page_objects/OrderPage');
// Order Checkout Flow
const loginPage = new LoginPage(page);
await loginPage.navigate();
await loginPage.login('testuser', 'password123');
const shoppingPage = new ShoppingPage(page);
await shoppingPage.searchProduct('laptop');
await shoppingPage.addToCart();
const orderPage = new OrderPage(page);
await orderPage.proceedToCheckout();
await orderPage.completePayment();
Преимущества POM, ориентированного на пользовательский поток:
- Естественное представление потока: Тестовый сценарий соответствует пути пользователя, что делает его более простым и понятным.
- Упрощенная структура: Связанные действия сгруппированы, что сокращает количество отдельных импортов и вызовов.
- Сокращение обслуживания: Изменения в потоке оформления заказа ограничиваются конкретными классами, что ускоряет обновление и снижает вероятность ошибок.