«Будучи специалистом по контролю качества, работающим на быстрорастущей платформе электронной коммерции, я начал испытывать трудности, связанные с использованием традиционной объектной модели страницы (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, ориентированного на пользовательский поток:
- Естественное представление потока: Тестовый сценарий соответствует пути пользователя, что делает его более простым и понятным.
- Упрощенная структура: Связанные действия сгруппированы, что сокращает количество отдельных импортов и вызовов.
- Сокращение обслуживания: Изменения в потоке оформления заказа ограничиваются конкретными классами, что ускоряет обновление и снижает вероятность ошибок.