Хабрахабр

Разворачиваем автоматизацию за пару часов: TypeScript, Protractor, Jasmine

Привет, Хабр!

Недавно я участвовал в проекте по настройке автоматизации «с нуля» на стеке TypeScript + Protractor + Jasmine. Меня зовут Виталий Котов, я довольно много занимаюсь автоматизацией тестирования и мне это нравится. Для меня этот стек был новым и необходимую информацию я искал на просторах интернета.

Я решил, что на русском тоже надо такой сделать. Самые полезные и толковые мануалы мне удалось найти только на английском языке. Расскажу только основы: почему именно такой стек, что надо настроить и как выглядит самый простой тест.

Если где-то найдете ошибку в терминологии или какое-то из моих решений можно улучшить — буду рад узнать об этом в комментариях от более опытных ребят. Сразу оговорюсь, что довольно редко работаю с NodeJS, npm и в целом с серверным JavaScript (тем более с TypeScript).

К слову, у меня уже была подобная статья: «Разворачиваем автоматизацию за пару часов: PHPUnit, Selenium, Composer».

Задача

Прежде всего давайте разберемся с тем, какую задачу мы решаем. У нас есть веб-приложение, написанное с использованием AngularJS. Это JavaScript-фреймворк, на основе которого довольно часто пишутся веб-проекты.

Лишь пара слов об особенностях таких проектов с точки зрения написания для них e2e-тестов. В рамках этой статьи мы не будем рассматривать плюсы и минусы AngularJS-проектов.

Локатор — это строка, составленная по определенным правилам и идентифицирующая UI-элемент: один или несколько. Довольно важным аспектом автоматизации тестирования является работа с элементами страницы, которая происходит при помощи локаторов.

Иногда, если на странице есть элемент с уникальным ID, можно искать по нему. Для веба чаще всего используются CSS и Xpath. Однако мне кажется, что WebDriver все равно этот ID превращает в CSS-локатор в конечном итоге и уже работает с ним.

Если же мы посмотрим на HTML-код какого-нибудь AngularJS-проекта, мы увидим у элементов множество атрибутов, которых нет в классическом HTML:

Код взят со страницы protractor-demo.

Довольно типичной является ситуация, когда у элементов кроме этих управляющих атрибутов нет никаких других, что несколько усложняет процесс составления качественных локаторов. Все атрибуты, начинающиеся с ng-* используются AngularJS для работы с UI.

Ведь это редкость для крупных проектов. Кто много занимался автоматизацией, тот знает о ценности таких UI, для которых без труда можно составлять локаторы. 🙂

Поехали! Собственно, вот для такого проекта нам и надо настроить автоматизацию тестирования.

Что есть что

Прежде всего давайте разберемся, для чего нужна каждая составляющая нашего стека.

Именно он будет запускать наши браузеры, заставлять их открывать нужные страницы и взаимодействовать с необходимыми элементами. Protractor — это тестовый фреймворк на основе WebDriverJS.

Он предоставляет дополнительные способы задавать локаторы: Этот фреймворк специально заточен под AngularJS-проекты.

element(by.model('first'));
element(by.binding('latest'));
element(by.repeater('some'));

Полный список можно посмотреть на странице мануала.

Однако, надо понимать, что «под капотом» все это в любом случае преобразуется в css. Эти методы упрощают создание и поддержку локаторов на проекте. Этот список можно посмотреть на сайте w3.org. Дело в том, что протокол W3C, на основе которого происходит взаимодействие в WebDriver, умеет работать только с конечным набором локаторов.

TypeScript отличается от JavaScript возможностью типизировать переменные, поддержкой использования полноценных классов и возможностью подключения модулей. TypeScript — это язык программирования, созданный компанией Microsoft.

В ходе этого превращения происходит проверка кода на его соответствие. Написанный на TS-код для работы с движком V8 транспилируется в JS-код, который уже исполняется. Например, он не “скомпилируется”, если вместо int в функцию где-то явно передается string.

По сути именно благодаря нему наш JS-код превращается в то, что мы привыкли называть тестом. Jasmine — фреймворк для тестирования JavaScript-кода. Он же управляет этими тестами.

Чуть ниже мы посмотрим на его возможности.

Сборка и настройка проекта

Что ж, с набором фреймворков мы определились, давайте теперь все это дело соберем.

Хотя многие пишут в WebStorm или даже Intellij Idea от JetBrains. Для работы с кодом я выбрал Visual Studio Code от Microsoft.

6. У меня уже были установлены NodeJS (v11. 9. 0) и NPM (6. Если у вас нет, это не проблема, установить их не составит труда. 0). Все довольно подробно описано на официальном сайте.

Вместо NPM можно использовать Yarn, хотя для небольшого проекта это не важно.

В корне проекта создаем package.json — именно в нем мы опишем все те пакеты, которые нам необходимы для проекта. В нашем IDE мы создаем новый проект.

А можно просто скопировать содержимое в файл. Можно его создать с помощью команды npm init.

Изначально package.json выглядит так:


}

После мы выполняем команду npm install для установки всех необходимых модулей и их зависимостей (ну вы помните ту самую картинку про то, что тяжелее черной дыры...)

Если она появилась, то все идет по плану. В результате у нас должна появиться директория node_modules. Если нет — стоит заглянуть в результат выполнения команды, там обычно все довольно подробно описано.

TypeScript и его конфиг

Для установки TypeScript нам понадобится npm:

npm install -g typescript

Убедимся, что он установился:

$ tsc -v
Version 3.4.1

Похоже, все в порядке.

Он также должен лежать в корне проекта и называться tsconfig.json Теперь надо создать конфиг для работы с TS.

Его содержимое будет таким:

{ "compilerOptions": { "lib": ["es6"], "strict": true, "outDir" : "output_js", "types" : ["jasmine", "node"] }, "exclude": [ "node_modules/*" ]
}

Если коротко, мы в этом конфиге указали следующее:

  • В какую директорию класть итоговый JS-код (в нашем случае это output_js)
  • Включили strict mode
  • Указали с какими фреймворками работаем
  • Исключили node_modules из компиляции

У TS существует огромное множество настроек. Для нашего проекта этих пока хватит. Более подробно можно изучить на сайте typescriptlang.org.

Для этого создадим простой файл check_tsc.ts со следующим содержимым: Теперь давайте посмотрим, как работает команда tsc, которая будет превращать наш TS-код в JS-код.

saySomething("Hello, world!"); function saySomething(message: string) { console.log(message);
}

И далее выполним команду tsc (для этого надо быть внутри директории с проектом).

Мы увидим, что у нас появилась директория output_js и внутри появился аналогичный js-файл вот с таким содержимым:

"use strict";
saySomething("Hello, world!");
function saySomething(message) { console.log(message);
}

Этот файл уже можно запускать с помощью команды node:

$ node output_js/check_tsc.js
Hello, world!

Итак, мы написали нашу первую программу на TypeScipt, мои поздравления. Давайте теперь писать тесты. 🙂

Protractor конфиг

Для Protractor нам тоже понадобится конфиг. Но он уже будет не в виде json, а в виде ts-файла. Назовем его config.ts и напишем там следующий код:

import { Config } from "protractor"; export const config: Config = { seleniumAddress: "http://127.0.0.1:4444/wd/hub", SELENIUM_PROMISE_MANAGER: false, capabilities: { browserName: "chrome", /*chromeOptions: { args: [ "--headless", "--window-size=800,600" ] }*/ }, specs: [ "Tests/*Test.js", ]
};

В этом файле мы указали следующее:

Его довольно просто запустить, надо скачать jar-файл Standalone Server и необходимые драйверы (например, chrome-драйвер для браузера Chrome). Во-первых — путь до запущенного Selenium-сервера. Далее прописать следующую команду:

java -jar -Dwebdriver.chrome.driver=/path/to/chromedriver /path/to/selenium-server-standalone.jar

В результате мы должны увидеть такой вывод:

23:52:41.691 INFO [GridLauncherV3.launch] - Selenium build info: version: '3.11.0', revision: 'e59cfb3'
23:52:41.693 INFO [GridLauncherV3$1.launch] - Launching a standalone Selenium Server on port 4444
2019-05-02 23:52:41.860:INFO::main: Logging initialized @555ms to org.seleniumhq.jetty9.util.log.StdErrLog
23:52:42.149 INFO [SeleniumServer.boot] - Welcome to Selenium for Workgroups....
23:52:42.149 INFO [SeleniumServer.boot] - Selenium Server is up and running on port 4444

Порт 4444 является дефолтным. Его можно задать с помощью параметра -port или через конфиг параметром «seleniumArgs» => "-port".

Если хочется проще и быстрее: можно скачать npm-пакет webdriver-manager.

Особой разницы нет, просто мне привычнее работать с jar-файлом. И далее управлять сервером при помощи команд start, shutdown и так далее. 🙂

Об этом чуть позже. Во-вторых — мы указали, что не хотим использовать Promise-менеджер.

Я закомментировал часть, например то, что мы можем довольно легко запускать браузер в headless-режиме. В-третьих — мы указали capabilities для нашего браузера. А пока мы только учимся — хотелось бы. Это классная фича, но она не позволит визуально наблюдать за нашими тестами. 🙂

Все, что лежит в папке Tests и оканчивается на Test.js. В-четвертых — мы указали маску для спеков (тестов). Все потому, что в итоге Node будет работать именно c JS-файлами, а не с TS-файлами. Почему именно на js, а не на ts? В этом важно не запутаться, особенно в начале работы.

Он будет делать следующее: Теперь создаем папку Tests и пишем первый тест.

  • Отключит проверку на то, что это Angular-страница. Без этого мы получим вот такое сообщение об ошибке: Error while running testForAngular. Само собой, для Angular-страниц эту проверку выключать не надо.
  • Зайдет на страницу Google.
  • Проверит, что есть поле ввода текста.
  • Введет текст “protractor”.
  • Кликнет по кнопке submit (у нее довольно сложный локатор, так как кнопок две и первая невидимая).
  • Дождется, что URL будет содержать слово “protractor” — это значит, что мы все сделали верно и поиск начался.

Вот такой код у меня получился:

import { browser, by, element, protractor } from "protractor"; describe('Search', () => { it('Open google and find a text', async () => { // Создаем объект для работы с ожиданиями let EC = protractor.ExpectedConditions; // выключаем проверку на AngularJS await browser.waitForAngularEnabled(false); // открываем страницу Google await browser.get('https://www.google.com/'); // создаем элемент по css = input[role='combobox'] let input_button = element(by.css("input[role='combobox']")); // ждем появление этого элемента (события presenceOf) await browser.wait(EC.presenceOf(input_button), 5000); // пишем в элемент текст “protractor” await input_button.sendKeys("protractor"); // создаем элемент кнопки сабмита по css let submit_button = element(by.css(".FPdoLc input[type='submit'][name='btnK']")); // дожидаемся его появления на странице (не обязательно, ведь мы уже дождались input-элемента, значит страница загрузилась) await browser.wait(EC.presenceOf(submit_button), 5000); // кликаем по кнопке сабмита await submit_button.click(); // ждем, когда URL будет содержать текст 'protractor' await browser.wait(EC.urlContains('protractor'), 5000); });
});

В коде мы видим, что все начинается с функции describe(). Она пришла к нам из фреймворка Jasmine. Это обертка для нашего сценария. Внутри нее могут быть функции beforeAll() и beforeEach() для выполнения каких-либо манипуляций перед всеми тестами и перед каждым тестом. Сколько угодно функций it() — это, собственно, наши тесты. В конце, если определены, будут выполнены afterAll() и afterEach() для манипуляций после каждого теста и всех тестов.

Обо всех возможностях Jasmine не буду рассказывать, о них можно почитать на сайте jasmine.github.io

Для запуска нашего теста сначала надо превратить TS-код в JS-код, а затем запустить его:

$ tsc
$ protractor output_js/config.js

Наш тест запустился — мы молодцы. 🙂

Если тест не запустился, стоит проверить:

  • Что код написан верно. В целом, если в коде есть критические ошибки, мы их выловим в процессе выполнения команды tsc.
  • Что Selenium Server запущен. Для этого можно открыть URL http://127.0.0.1:4444/wd/hub — там должен быть интерфейс Selenium-сессий.
  • Что Chrome запускается нормально со скачанной версией chrome-driver. Для этого на странице wd/hub/ надо кликнуть Create Session и выбрать Chrome. Если он не запустится, значит надо либо обновить Chrome, либо скачать другую версию chrome-driver.
  • Если все это не помогло, можно проверить, точно ли успешно завершилась команда npm install.
  • Если все написано правильно, но все равно ничего не запускается — попробуйте погуглить ошибку. Это чаще всего помогает. 🙂

NPM scripts

Для того, чтобы сделать жизнь проще, можно часть команд сделать npm-алиасами. Например, я бы хотел перед каждым запуском тестов сначала удалять директорию с предыдущими JS-файлами и пересоздавать ее с новыми.

Для этого в package.json добавим пункт “scripts”:

{ "name": "protractor", "scripts": { "test": "rm -rf output_js/; tsc; protractor output_js/config.js" }, "dependencies": { "@types/node": "^10.5.2", "@types/jasmine": "^3.3.12", "protractor": "^5.4.2", "typescript": "^3.4.1" }
}

Теперь введя команду npm test произойдет следующее: удалится директория output_js со старым кодом, создастся заново и в нее запишется новый JS-код. После чего сразу произойдет запуск тестов.

Например, вы можете запускать и гасить selenium-сервер между запусками тестов. Вместо этого набора команд можно указать любой другой, который нужен для работы лично вам. Хотя это, конечно, проще контролировать внутри самого кода тестов.

Немного о Promise

В конце немного расскажу о Promise, async / await и о том, чем написание тестов на NodeJS отличается от той же Java или Python.

Это означает, что код не всегда выполняется в том порядке, в котором он написан. JavaScript является асинхронным языком. В том числе это касается HTTP-запросов, а мы помним, что код общается с Selenium Server именно по HTTP.

О них можно довольно подробно почитать на learn.javascript.ru. Promise (обычно так и называют «промисы») предоставляют удобный способ организации асинхронного кода.

Protractor очень активно работает с этими объектами. По сути это объекты, позволяющие один код сделать зависимым от выполнения другого, тем самым гарантируя определенный порядок.

Предположим, что мы выполняем такой код: Давайте рассмотрим на примере.

driver.findElement().getText();

В Java мы ожидаем, что нам вернется объект типа String. В Protractor все не совсем так, нам вернется объект Promise. И просто так распечатать его с целью дебага не выйдет.

Нам надо его передать в какой-то другой метод, который уже будет с этим значением работать. Обычно нам не надо распечатывать полученное значение. Например, проверит текст на соответствие ожидаемому.

Но, если все же хочется увидеть значение, нам пригодится функция then(). Подобные методы в Protractor на вход принимают тоже Promise-объекты, так что проблем нет.

это button, текст находится внутри атрибута value): Вот так мы можем распечатать текст кнопки на странице Google (обратите внимание, что т.к.

// создаем элемент
let submit_button = element(by.css(".FPdoLc input[type='submit'][name='btnK']")); // дожидаемся его появления
await browser.wait(EC.presenceOf(submit_button), 5000); // при помощи then() получаем текст
await submit_button.getAttribute("value").then((text) => { console.log(text);
});

Что же касается ключевых слов async / await — это чуть более новый подход к работе с асинхронным кодом. Он позволяет избегать promise hell, который раньше образовывался в коде из-за большого количества вложенностей. Тем не менее, полностью от Promise избавиться не получится и уметь работать с ними надо. Об это понятно и подробно можно почитать в статье Конструкция async/await в JavaScript: сильные стороны, подводные камни и особенности использования.

Домашнее задание

В качестве домашнего задания предлагаю написать тесты для страницы, написанной на AngularJS: protractor-demo.

И обязательно поработайте с локаторами, специально созданными для AngularJS. Не забудьте убрать из кода строчку про выключение проверки страницы на AngularJS. В этом нет особой магии, но это довольно удобно.

Итог

Давайте подводить итоги. Нам удалось написать тесты, которые работают на связке TypeScript + Protractor + Jasmine. Мы научились собирать такой проект, создавать необходимые конфиги и написали первый тест.

Вроде неплохо для пары часов. По ходу дела немного обсудили особенности работы с автотестами на JavaScript. 🙂

Что почитать, куда посмотреть

У Protractor есть довольно хороший мануал с примерами на JavaScript: https://www.protractortest.org/#/tutorial
У Jasmine есть мануал: https://jasmine.github.io/pages/docs_home.html
У TypeScipt есть хороший get started: https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html

На Медиуме есть неплохая статья на английском про TypeScript + Protractor + Cucumber: https://medium.com/@igniteram/e2e-testing-with-protractor-cucumber-using-typescript-564575814e4a

А в своем репозитории я выложил итоговый код того, что мы с вами обсуждали в этой статье: https://github.com/KotovVitaliy/HarbProtractorJasmineJasmine.

В интернете можно найти примеры более сложных и больших проектов на этом стеке.

🙂 Спасибо за внимание!

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть