Не QA, а QE-отдел
В Slack качество — общая ответственность. Выделенная команда Quality Engineering сфокусирована на укреплении культуры тестирования, увеличении тестового покрытия и ускорении релиза новых функций.
Мы поощряем наших разработчиков самим писать сквозные тесты (E2E) и владеть ими. В свою очередь, сотрудники отдела Quality Engineering (QE) отвечают за тестовые фреймворки и придумывают лучшие практики — реюзабельных, масштабируемых и хорошо поддерживаемых тестов.
В этом посте расскажем о том, как мы пришли к созданию кастомного фреймворка автоматизации, и о некоторых архитектурных деталях.
Выбор фреймворка для E2E
Автоматизация сквозных тестов UI Slack началась как проект HackDay — это внутренний хакатон в Slack.
Целью было создать фреймворк, который бы 1) поддерживал JavaScript и 2) использовал cypress.io для прогона тестов.
Фреймворк, созданный на хакатоне, сразу получился неплохим. Несколько QE-инженеров внесли свой вклад, добавляя каждый свои тесты. А поскольку еще не было инструкций по добавлению, фреймворк получился с большим количеством дублирующегося кода, и в результате flaky-тесты. В общем, это привело к рендомным проблемам в тестах и разборам с приоритетами багов.
Но на всякую проблему есть решение. QE-команда поставила себе задачу найти решение некоторых проблем с cypress. Решение состояло в применении Page Object Model: мы создали слой абстракции между пользовательским интерфейсом и собственно тестом. На это ушел один месяц, и затем подход показал эффективность.
Абстракция UI
Чтобы было проще читать, писать и поддерживать наши сквозные тесты, мы создали ряд уникальных Slack-методов и объединили их в библиотеку.
Благодаря этому слою абстракции у нас появилось централизованное место для описания действий пользовательского интерфейса.
Поскольку Slack — это в основном приложение, а не традиционный веб-сайт, мы больше думали о компонентах, а не о страницах, позволяя нашим инженерам больше работать, например, с боковой панелью Slack.
Чтобы облегчить написание и чтение тестового кода, наши JavaScript-объекты являются цепочечными (chainable). Например:
В рамках принятых нами подходов Абстракции UI и Page Object Model мы по возможности поделили всё на компоненты:
Наши best practices
Мы выработали несколько хороших практик, которыми и далее руководствуемся в своем тестировании:
- Элементы: Вместо того чтобы давать имена классов или id элементов, зависящие от продукта (product-driven class names), мы добавляем кастомный атрибут «
data-qa
» к тестируемым элементам. Это позволяет обеспечить хороший контекст для наших селекторов, чтобы они не зависели от частых изменений в JS/CSS. - Создаем новые компоненты только в случае необходимости. Не стараемся описать все вероятные действия пользователя, а только те, которые будут в этом тесте.
- Методы в компоненте должны изменять только ту часть UI, для которой написаны. Например, компонент боковой панели канала не должен взаимодействовать с вводом сообщения.
- Стараемся делить элементы на компоненты только там, где это имеет смысл, а не создавать множество мелких компонентов.
- Абстракция UI является stateless. Тест должен поддерживать состояние и валидировать его.
Итак, с учетом этих правил посмотрим, как мы организовали наши классы и компоненты.
Классы и компоненты
Приведенная выше диаграмма представляет собой упрощенную версию нашей объектной модели страницы (POM):
- Web Element — Представление части UI. Например, кнопка или текстовое поле — это WebItem. Здесь мы описываем все действия, которые будем выполнять с элементом.
- BaseComponent — Представляет компонент. Любой компонент должен расширять этот класс. Компонент состоит из Web Elements и других компонентов.
- BaseModal Представляет модал в Slack (подробнее о них здесь). Любой модал должен расширять этот класс.
Если посмотреть на Slack-клиент, то видим там компоненты:
Рассмотрим MessageInputComponent
:
/** * MessageInputComponent * This is the component where people type to add a message and communicate. */ export class MessageInputComponent extends BaseComponent { constructor(parentSelector) { super('[data-qa="message_input"]', parentSelector); this.textBox = this.getChildElement('[data-qa="input_box"]'); } /** * @description Type a message into the message input component * @param {string} text - text you want to type in * @param {string} submit - if true will type enter after. if false will just type in text * @returns {this} returns the MessageInputComponent component */ typeMessage(text, submit = false) { this.textBox.type(text); if (submit) { this.textBox.type('{enter}'); } return this; } }
MessageInputComponent
расширяет BaseComponent
, в котором определены все общие методы и свойства компонента. Отсюда передаем селектор для MessageInputComponent
и определяем, какие селекторы находятся внутри MessageInputComponent
.
Мы создали объект верхнего уровня под названием client. Если в тесте введем стандартный helloworld, получим:
client.messageInput.typeMessage(‘hello world’, true);
Итак
Если сравнить результаты прежних тестов в Slack и тестов с применением UI Abstraction, то мы получили 60%-е улучшение надежности тестов. И нашим тестировщикам-автоматизаторам (которых мы называем QE), и разработчикам-фронтендерам стало проще с тестированием.
Slack старается перевести все E2E-тесты на вышеописанную UI Abstraction-практику. Работаем над стабильностью тестов.