Наследование и переиспользование кода

WebdriverIO — это мощный инструмент для автоматизации тестирования веб-приложений на JavaScript. Одним из ключевых аспектов эффективной разработки тестов является организация кода таким образом, чтобы минимизировать дублирование и облегчить поддержку. Для этого активно используются механизмы наследования и переиспользования кода.


Классы и наследование

В WebdriverIO тестовые сценарии часто строятся на основе классов. Использование классов позволяет создавать структурированные объекты страниц (Page Objects), которые инкапсулируют элементы и методы взаимодействия с ними.

class BasePage {
    open(path) {
        return browser.url(path);
    }

    waitForElement(element, timeout = 5000) {
        element.waitForDisplayed({ timeout });
    }
}

class LoginPage extends BasePage {
    get usernameInput() { return $('#username'); }
    get passwordInput() { return $('#password'); }
    get loginButton() { return $('#login'); }

    login(username, password) {
        this.usernameInput.setValue(username);
        this.passwordInput.setValue(password);
        this.loginButton.click();
    }
}

Ключевые моменты:

  • BasePage содержит общие методы, применимые ко всем страницам.
  • LoginPage наследует функционал BasePage, не дублируя методы open или waitForElement.
  • Наследование упрощает поддержку тестов: при изменении поведения метода в базовом классе все наследники автоматически получают обновления.

Паттерн Page Object

Page Object Model (POM) является стандартом для организации тестов WebdriverIO. Каждая страница представляется отдельным классом с методами взаимодействия.

Пример использования наследования в POM:

class ProductPage extends BasePage {
    get addToCartButton() { return $('#add-to-cart'); }
    get productTitle() { return $('h1.product-title'); }

    addProductToCart() {
        this.waitForElement(this.addToCartButton);
        this.addToCartButton.click();
    }
}

class SpecialProductPage extends ProductPage {
    get specialOfferBanner() { return $('#special-offer'); }

    verifySpecialOffer() {
        this.waitForElement(this.specialOfferBanner);
        return this.specialOfferBanner.isDisplayed();
    }
}

Пояснения:

  • SpecialProductPage расширяет ProductPage, переиспользуя методы добавления товара в корзину.
  • Дополнительный функционал (verifySpecialOffer) добавляется без изменения базового класса.
  • Это позволяет создавать иерархии страниц, где общие методы содержатся в родителях, а уникальные — в дочерних классах.

Переиспользование тестовых утилит

Помимо Page Objects, переиспользование кода достигается через модульные функции и вспомогательные утилиты.

// utils/wait.js
function waitForVisible(element, timeout = 5000) {
    element.waitForDisplayed({ timeout });
}

function waitForClickable(element, timeout = 5000) {
    element.waitForClickable({ timeout });
}

module.exports = { waitForVisible, waitForClickable };
// В тесте
const { waitForVisible, waitForClickable } = require('./utils/wait');

describe('Cart tests', () => {
    it('should add product to cart', () => {
        const productPage = new ProductPage();
        productPage.open('/product/1');
        waitForVisible(productPage.addToCartButton);
        waitForClickable(productPage.addToCartButton);
        productPage.addProductToCart();
    });
});

Преимущества подхода:

  • Повторяющийся код вынесен в отдельные утилиты.
  • Тесты остаются чистыми и читаемыми.
  • Изменение логики ожиданий происходит в одном месте, что уменьшает риск ошибок.

Миксины и композиция

Для дополнительного переиспользования иногда применяются миксины — объекты с методами, которые можно включать в класс.

const LoggingMixin = Base => class extends Base {
    logAction(action) {
        console.log(`Action performed: ${action}`);
    }
};

class CheckoutPage extends LoggingMixin(BasePage) {
    get checkoutButton() { return $('#checkout'); }

    proceedToCheckout() {
        this.logAction('Proceeding to checkout');
        this.checkoutButton.click();
    }
}

Особенности:

  • Миксины позволяют добавлять функционал без изменения иерархии наследования.
  • Можно комбинировать несколько миксинов, расширяя возможности классов.
  • Подходит для кросс-сценарного функционала (логирование, скриншоты, тайминги).

Организация тестов через абстракции

Использование наследования и переиспользования кода позволяет выстраивать многоуровневую архитектуру тестов:

  1. Базовые классы и утилиты — общие методы взаимодействия и ожидания.
  2. Page Objects — конкретные страницы с локаторами и действиями.
  3. Расширенные страницы — наследники с дополнительным поведением.
  4. Тестовые сценарии — компактные и читаемые, используют методы страниц и утилиты.

Такой подход снижает количество дублируемого кода и облегчает масштабирование автоматизации.


Паттерн DRY (Don’t Repeat Yourself)

Применение наследования и утилит полностью соответствует принципу DRY:

  • Общие действия и логика вынесены в базовые классы или функции.
  • Изменения в функционале применяются единожды в базовом компоненте.
  • Тесты становятся более стабильными и поддерживаемыми.

Рекомендации по структуре

  • Каждый Page Object в отдельном файле.
  • Общие методы в BasePage или утилитах (utils).
  • Наследование использовать для логически связанных страниц.
  • Миксины применять для функций, которые нужны в нескольких несвязанных классах.
  • Тесты должны быть максимально декларативными, без ручного дублирования шагов.

Этот подход к организации тестов в WebdriverIO делает код структурированным, масштабируемым и удобным для поддержки, позволяя создавать сложные автоматизированные сценарии без излишнего дублирования и хаоса в кодовой базе.