Создайте новую папку и добавьте следующие папки:

  • строить
  • сборка\задачи
  • Особенности
  • страницы-объекты
  • отчеты
  • шаги
  • служба поддержки

Создайте файл пакета по умолчанию, выполнив:

npm init -y

Установите все необходимые пакеты:

npm i chai cucumber cucumber-tsflow gulp gulp-clean gulp-protractor gulp-protractor-cucumber-html-report gulp-typescript protractor protractor-cucumber-framework require-dir typings --save-dev

Это установит следующие пакеты npm:

Настройка глотка

В корневой папке создайте файл с именем gulpfile.ts и добавьте следующее содержимое:

// all gulp tasks are located in the ./build/tasks directory // gulp configuration is in files in ./build directory require('require-dir')('build/tasks');

Настройка машинописного транспиля

Создайте файл build.js в каталоге build\tasks и добавьте следующее содержимое:

var gulp = require("gulp"); var ts = require("gulp-typescript"); var tsProject = ts.createProject('tsconfig.json'); var clean = require("gulp-clean"); var paths = require('../paths'); gulp.task("clean", function () { return gulp.src(paths.dist, { read: false }).pipe(clean()); }); gulp.task("build", ["clean"], function () { var tsResult = tsProject.src().pipe(ts(tsProject)); return tsResult.js.pipe(gulp.dest(paths.dist)); });

В папку build добавьте файл с именем paths.js и добавьте следующее содержимое:

module.exports = { dist: "dist/" };

Это будет место, где вы будете хранить все пути к ресурсам, используемым в ваших сценариях сборки. Затем добавьте параметры конфигурации для Typescript, добавив файл с именем tsconfig.json в корневую папку вашего проекта со следующим содержимым:

{ "compilerOptions": { "moduleResolution": "node", "module": "umd", "target": "es5", "declaration": true, "sourceMap": true, "removeComments": false, "experimentalDecorators": true }, "exclude": [ "node_modules" ], "filesGlob": [ "steps/**/*.ts" ] }

Добавьте определения типизации для библиотек javascript, которые мы используем:

typings i dt~chai dt~angular-protractor dt~selenium-webdriver --save --global

Настройка транспортира

В папку build добавьте файл с именем test.js и добавьте следующее содержимое:

var gulp = require("gulp"); var webdriver_update = require("gulp-protractor").webdriver_update; var protractor = require("gulp-protractor").protractor; var reporter = require("gulp-protractor-cucumber-html-report"); var paths = require('../paths'); gulp.task("webdriver_update", webdriver_update); gulp.task("e2e", ["build"], function () { return gulp.src(paths.features) .pipe(protractor({ configFile: "protractor.conf.js" })) .on("error", function (e) { throw e; }); }); gulp.task("e2e-report", function () { gulp.src(paths.testResultJson) .pipe(reporter({ dest: paths.e2eReports })); });

Это позволит вам использовать gulp для запуска теста транспортира. Расширьте свой файл paths.js, включив в него новые пути, использованные выше:

module.exports = { dist: "dist/", features: "features/**/*.feature", testResultJson: "./reports/cucumber-test-results.json", e2eReports: "reports/e2e" };

В корневую папку вашего проекта добавьте файл protractor.conf.js и добавьте следующее содержимое:

var paths = require('build/paths'); exports.config = { directConnect: true, multiCapabilities: [ {'browserName': 'firefox'}, {'browserName': 'chrome'}, {'browserName': 'internet explorer'} ] seleniumServerJar: './node_modules/gulp-protractor/node_modules/protractor/selenium/selenium-server-standalone-2.53.1.jar', framework: 'custom', frameworkPath: 'node_modules/protractor-cucumber-framework', specs: [ paths.features ], jasmineNodeOpts: { showColors: true, defaultTimeoutInterval: 30000 }, cucumberOpts: { require: [paths.distFiles, paths.support], format: "json" } };

Вышеупомянутая установка будет запускать ваши сквозные тесты в Chrome, Firefox и Internet Explorer 11 параллельно. Вы можете удалить браузеры, если, например, вы можете тестировать свои тесты только в Chrome.

Вы также можете заменить свойство multiCapabilities текстом ниже, чтобы тестировать только в Chrome.

capabilities: { 'browserName': 'chrome' },

Расширьте файл build\paths.js еще раз, чтобы включить новые пути:

module.exports = { dist: "dist/", distFiles: "dist/**/*.js", testResultJson: "./reports/cucumber-test-results.json", e2eReports: "reports/e2e" features: "features/**/*.feature", support: "support/*.js" };

Настройка создания скриншота при сбое

Было бы неплохо увидеть скриншот текущего представления, если тест не пройден. Сценарий ниже добавит скриншот к неудачному шагу.

Создайте новый файл с именем TakeScreenshot.js в папке support со следующим содержимым:

module.exports = function TakeScreenshot() { this.After(function (scenario, callback) { if (scenario.isFailed()) { browser.takeScreenshot().then(function (png) { var decodedImage = new Buffer(png.replace(/^data:image\/(png|gif|jpeg);base64,/,''), 'base64'); scenario.attach(decodedImage, 'image/png'); callback(); }); } else { callback(); } }); };

Добавьте еще один новый файл с именем jsonOutputHook.js (тоже в папку support) и добавьте содержимое:

var paths = require("../build/paths"); module.exports = function JsonOutputHook() { var Cucumber = require('cucumber'); var JsonFormatter = Cucumber.Listener.JsonFormatter(); var fs = require('fs'); var path = require('path'); JsonFormatter.log = function (json) { fs.writeFile(path.join(__dirname, '../'+ paths.testResultJson), json, function (err) { if (err) throw err; console.log('json file location: ' + path.join(__dirname, '../' + paths.testResultJson)); }); }; this.registerListener(JsonFormatter); };

Когда это будет сделано, gulp e2e проведет ваш сквозной тест, а gulp e2e-report создаст отчет на основе результатов последнего gulp e2e запуска.

Пример неудачного сценария

Пример функции

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

Файл .feature (хранящийся в папке features) может, например, содержать следующий сценарий:

Feature: Create a new teamie In order to start a new teamie As an team member that is the organizer of a teamie survey for my team I want to be able to create a teamie so that my team can improve Background: Given I received an link to create a new Teamie And I opened the link in my browser Scenario: Team name must be set Given I don't enter a team name for my team Then A error message should occur 'The team name cannot be empty'

Чтобы реализовать логику вышеописанной истории, мы реализуем определение шага (в папке steps) и объекты страницы (в папке page-objects).

Объект страницы — это многократно используемая структура самой страницы. Это позволяет нам написать эту логику один раз и использовать ее в любом определении шага, которое требует этого. Это означает, что объект страницы — это объект, который содержит весь наш Selenium-подобный синтаксис для доступа к объектам на нашей странице.

export default class NewTeamiePageObject { public static setTeamName(name: string): webdriver.promise.Promise<void> { return element(by.id("lblName")).sendKeys(name); } }

Затем у нас есть файл шагов, в этом файле будут методы, которые будут вызываться CucumberJS, если они соответствуют шаблону регулярного выражения, определенному в декораторе. И, в свою очередь, вызовет методы, определенные в нашем объекте страницы.

Ниже приведен пример кода для обработки шага Given I don't enter a team name for my team. Каждый класс, содержащий шаги, должен быть украшен декоратором @bindings, а каждый метод должен быть украшен декоратором @given (или @when, @then).

CucumberJS проверит все регулярные выражения, определенные в определениях шагов, и выполнит определение шага, если оно совпадает с текстом шага.

Как вы можете видеть ниже, есть также метод с регулярным выражением /^I enter the team name '(.*)' for my team$/. Это регулярное выражение будет обрабатывать строки, соответствующие I enter the team name 'team 1' for my team, и передавать значение team 1 методу, чтобы сделать ваш код более гибким.

import { binding, given, then, when } from "cucumber-tsflow"; import { expect } from "chai"; import { newTeamiePageObject } from "./../../page-objects/NewTeamiePageObject"; @binding() class NewTeamieSteps { @given(/^I don't enter a team name for my team$/) public GivenIDontEnterATeamNameForMyTeam (callback): void { newTeamiePageObject.setTeamName("").then(callback); } @given(/^I enter the team name '(.*)' for my team$/) public GivenEnterATeamNameForMyTeam (teamName, callback): void { newTeamiePageObject.setTeamName(teamName).then(callback); } } export = NewTeamieSteps;

Примечание: последняя строка (экспорт) должна быть написана таким образом, чтобы все заработало, использование export default class не сработает :-(

Первоначально опубликовано на www.eriklieben.com 23 августа 2016 г.