:: Ionic2/Angular2:CucumberJs + 量角器 + TypeScript 配置

:: Ionic2/Angular2: CucumberJs + Protractor + TypeScript Configuration

我不太精通 TypeScript 和 Angular2,我一直在尝试 运行 黄瓜的功能使用用 TypeScript 编写的步骤。但是,在执行 steps.ts 文件时,出现以下错误:

[launcher] Running 1 instances of WebDriver
[launcher] Error: TypeError: step.Given is not a function
    at Object.module.exports (/Users/roalcantara/Documents/Tango/tango/test/features/step_definitions/signIn.steps.ts:13:8)
    at /Users/roalcantara/Documents/Tango/tango/node_modules/cucumber/lib/cucumber/cli/support_code_loader.js:65:25
    at Array.forEach (native)
    at Object.wrapper (/Users/roalcantara/Documents/Tango/tango/node_modules/cucumber/lib/cucumber/cli/support_code_loader.js:62:15)
    at Object.initializer (/Users/roalcantara/Documents/Tango/tango/node_modules/cucumber/lib/cucumber/cli/support_code_loader.js:24:41)
    at Object.Library (/Users/roalcantara/Documents/Tango/tango/node_modules/cucumber/lib/cucumber/support_code/library.js:118:25)
    at Object.getSupportCodeLibrary (/Users/roalcantara/Documents/Tango/tango/node_modules/cucumber/lib/cucumber/cli/support_code_loader.js:10:58)
    at Object.getSupportCodeLibrary (/Users/roalcantara/Documents/Tango/tango/node_modules/cucumber/lib/cucumber/cli/configuration.js:126:32)
    at Object.getSupportCodeLibrary (/Users/roalcantara/Documents/Tango/tango/node_modules/cucumber/lib/cucumber/runtime.js:43:46)
    at Object.start (/Users/roalcantara/Documents/Tango/tango/node_modules/cucumber/lib/cucumber/runtime.js:12:37)
[launcher] Process exited with error code 100

好像黄瓜定义还没有编译。

这些是我的(相关)配置:

我的目录结构是:

/test/
|-/features/
|-xpto.feature
|--/step_definitions
|---xpto.step.ts

/package.json

{
  "name": "Tango",
  "version": "0.0.1",
  "private": true,
  "devDependencies": {
    "awesome-typescript-loader": "^0.17.0-rc.5",
    "chai": "^3.5.0",
    "chai-as-promised": "^5.3.0",
    "chalk": "^1.1.3",
    "codecov.io": "0.1.6",
    "cucumber": "^0.10.2",
    "cz-conventional-changelog": "^1.1.6",
    "del": "2.2.0",
    "es6-module-loader": "0.17.11",
    "gulp": "3.9.1",
    "gulp-autoprefixer": "^3.1.0",
    "gulp-inline-ng2-template": "^1.1.4",
    "gulp-load-plugins": "1.2.0",
    "gulp-sass": "2.2.0",
    "gulp-sourcemaps": "^1.6.0",
    "gulp-tslint": "^4.3.5",
    "gulp-typescript": "^2.12.1",
    "gulp-util": "^3.0.7",
    "gulp-watch": "4.3.5",
    "ionic-gulp-browserify-typescript": "^1.0.1",
    "ionic-gulp-fonts-copy": "^1.0.0",
    "ionic-gulp-html-copy": "^1.0.0",
    "ionic-gulp-sass-build": "^1.0.0",
    "ionic-gulp-scripts-copy": "^1.0.1",
    "jasmine-core": "2.4.1",
    "jasmine-spec-reporter": "^2.4.0",
    "karma": "0.13.22",
    "karma-chrome-launcher": "^0.2.3",
    "karma-coverage": "0.5.5",
    "karma-jasmine": "0.3.8",
    "karma-mocha-reporter": "^2.0.0",
    "karma-phantomjs-launcher": "1.0.0",
    "nconf": "^0.8.4",
    "phantomjs-prebuilt": "^2.1.7",
    "protractor": "^3.2.2",
    "protractor-cucumber-framework": "^0.5.0",
    "run-sequence": "1.1.5",
    "strip-sourcemap-loader": "0.0.1",
    "systemjs": "0.19.23",
    "traceur": "0.0.102",
    "ts-node": "0.5.5",
    "tslint": "^3.5.0",
    "tslint-eslint-rules": "1.0.1",
    "typescript": "^1.8.10",
    "typings": "^0.7.12"
  },
  "dependencies": {
    "angular2": "2.0.0-beta.13",
    "es6-promise": "3.0.2",
    "es6-shim": "^0.35.0",
    "ionic-angular": "2.0.0-beta.4",
    "ionic-native": "^1.1.0",
    "ionicons": "3.0.0-alpha.3",
    "reflect-metadata": "0.1.2",
    "rxjs": "5.0.0-beta.2",
    "zone.js": "^0.6.11"
  },
  "cordovaPlugins": [
    "cordova-plugin-device",
    "cordova-plugin-console",
    "cordova-plugin-whitelist",
    "cordova-plugin-inappbrowser",
    "cordova-plugin-splashscreen",
    "cordova-plugin-statusbar",
    "cordova-plugin-camera",
    "ionic-plugin-keyboard",
    "onesignal-cordova-plugin",
    "cordova-plugin-file",
    "cordova-plugin-crop"
  ],
  "cordovaPlatforms": [
    "ios",
    "android"
  ],
  "scripts": {
    "build": "gulp --gulpfile test/gulpfile.ts --cwd ./ ionic.build",
    "protractor": "./node_modules/protractor/bin/protractor protractor.conf.js",
    "e2e": "gulp --gulpfile test/gulpfile.ts --cwd ./ test.build.e2e && npm run protractor",
    "karma": "gulp --gulpfile test/gulpfile.ts --cwd ./ test.karma.debug",
    "postinstall": "typings install",
    "start": "ionic serve",
    "test": "gulp --gulpfile test/gulpfile.ts --cwd ./ test",
    "test.watch": "gulp --gulpfile test/gulpfile.ts --cwd ./ test.watch.build",
    "webdriver-update": "webdriver-manager update"
  }
}

/typings.json

{
  "dependencies": {},
  "devDependencies": {},
  "ambientDependencies": {
    "angular-protractor": "registry:dt/angular-protractor#1.5.0+20160317120654",
    "bluebird": "registry:dt/bluebird#2.0.0+20160319051630",
    "chalk": "registry:dt/chalk#0.4.0+20160317120654",
    "cucumber": "registry:dt/cucumber#0.0.0+20160316171810",
    "del": "registry:dt/del#2.2.0+20160317120654",
    "es6-shim": "registry:dt/es6-shim#0.31.2+20160317120654",
    "express": "registry:dt/express#4.0.0+20160317120654",
    "express-serve-static-core": "registry:dt/express-serve-static-core#0.0.0+20160322035842",
    "glob": "registry:dt/glob#5.0.10+20160317120654",
    "gulp": "registry:dt/gulp#3.8.0+20160316155526",
    "gulp-load-plugins": "registry:dt/gulp-load-plugins#0.0.0+20160316155526",
    "gulp-typescript": "registry:dt/gulp-typescript#0.0.0+20160317120654",
    "gulp-util": "registry:dt/gulp-util#3.0.0+20141016163602",
    "jasmine": "registry:dt/jasmine#2.2.0+20160412134438",
    "karma": "registry:dt/karma#0.13.9+20160316155526",
    "log4js": "registry:dt/log4js#0.0.0+20160316155526",
    "mime": "registry:dt/mime#0.0.0+20160316155526",
    "minimatch": "registry:dt/minimatch#2.0.8+20160317120654",
    "node": "registry:dt/node#4.0.0+20160412142033",
    "orchestrator": "registry:dt/orchestrator#0.0.0+20160316155526",
    "q": "registry:dt/q#0.0.0+20160323171452",
    "run-sequence": "registry:dt/run-sequence#0.0.0+20160316155526",
    "selenium-webdriver": "registry:dt/selenium-webdriver#2.44.0+20160317120654",
    "serve-static": "registry:dt/serve-static#1.7.1+20160104095738",
    "through2": "registry:dt/through2#2.0.0+20160317120654",
    "vinyl": "registry:dt/vinyl#1.1.0+20160316155526"
  }
}

/protractor.conf.js:

// @AngularClass
require('ts-node/register');
var helpers = require('./helpers');

exports.config = {
  /**
   * Angular 2 configuration
   *
   * useAllAngular2AppRoots: tells Protractor to wait for any angular2 apps on the page instead of just the one matching
   * `rootEl`
   *
   */
  useAllAngular2AppRoots: true,

  /* LOCALHOST CONFIG */
  seleniumServerJar: "node_modules/protractor/selenium/selenium-server-standalone-2.52.0.jar",
  baseUrl: 'http://localhost:8100',

  exclude: [],

  allScriptsTimeout: 110000,

  framework: 'custom',
  frameworkPath: require.resolve('protractor-cucumber-framework'),
  specs: [
    helpers.root('test/features/**/*.feature')
  ],
  cucumberOpts: {
    format: 'pretty',
    require: [
      'test/features/step_definitions/**/*.steps.ts'
    ],
    compiler: 'ts:ts-node/register'
  },

  directConnect: true,

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

  onPrepare: function() {
    browser.ignoreSynchronization = false;
  }
};

一个 step_definition 的例子是:

/test/features/step_definitions/signUp.steps.ts

import cucumber = require('cucumber')
import {SignUpPage} from '../pages/signUp.page';
import {SignInPage} from '../pages/signIn.page';

let chai = require('chai').use(require('chai-as-promised'));
let expect = chai.expect;

export = () => {

  type Callback = cucumber.CallbackStepDefinition;
  let step = <cucumber.StepDefinitions>this;
  let index = new SignInPage();
  let page = new SignUpPage();

  step.Given(/^I am not authenticated$/, (callback:Callback) => {
    index.openApp();
    callback();
  });

  step.When(/^I go to register$/, (callback:Callback) => {
    index.signUp();
    callback();
  });

  step.When(/^I fill 'name' with '([^"]*)'$/, (value:string, callback:Callback) => {
    page.setName(value);
    callback();
  });

  step.When(/^I fill 'email' with '([^"]*)'$/, (value:string, callback:Callback) => {
    page.setEmail(value);
    callback();
  });

  step.When(/^I fill 'password' with '([^"]*)'$/, (value:string, callback:Callback) => {
    page.setPassword(value);
    callback();
  });

  step.When(/^I fill 'passwordConfirmation' with '([^"]*)'$/, (value:string, callback:Callback) => {
    page.setPasswordConfirmation(value);
    callback();
  });

  step.When(/^I press 'Sign up'$/, (callback:Callback) => {
    page.submit();
    callback();
  });

  step.Then(/^the register form is validated '(.*)'$/, (valid:string, callback:Callback) => {
    let isValid = (valid === 'true');
    expect(page.formIsValid()).to.become(isValid).and.notify(callback);
  });
};

有什么我遗漏的吗?

在您的 protractor.conf.js 中,您可以从 cucumberOpts

中删除编译器选项
  cucumberOpts: {
    format: 'pretty',
    require: [
      'test/features/step_definitions/**/*.steps.ts'
    ],
    //remove this compiler: 'ts:ts-node/register'
  },

其次,您的 signUp.steps.ts 应该看起来像这样:

let chai = require('chai').use(require('chai-as-promised'));
let expect = chai.expect;

import {SignUpPage} from '../pages/signUp.page';
import {SignInPage} from '../pages/signIn.page';

import Callback = cucumber.CallbackStepDefinition;

//1. create a class first
class SignupSteps{

  private index:SignInPage = new SignInPage();
  private page:SignUpPage = new SignUpPage();

  this.Given(/^I am not authenticated$/, (callback:Callback) => {
    index.openApp();
    callback();
  });

  this.When(/^I go to register$/, (callback:Callback) => {
    index.signUp();
    callback();
  });

  this.When(/^I fill 'name' with '([^"]*)'$/, (value:string, callback:Callback) => {
    page.setName(value);
    callback();
  });

  this.When(/^I fill 'email' with '([^"]*)'$/, (value:string, callback:Callback) => {
    page.setEmail(value);
    callback();
  });

  this.When(/^I fill 'password' with '([^"]*)'$/, (value:string, callback:Callback) => {
    page.setPassword(value);
    callback();
  });

  this.When(/^I fill 'passwordConfirmation' with '([^"]*)'$/, (value:string, callback:Callback) => {
    page.setPasswordConfirmation(value);
    callback();
  });

  this.When(/^I press 'Sign up'$/, (callback:Callback) => {
    page.submit();
    callback();
  });

  this.Then(/^the register form is validated '(.*)'$/, (valid:string, callback:Callback) => {
    let isValid = (valid === 'true');
    expect(page.formIsValid()).to.become(isValid).and.notify(callback);
  });
}

//2. this is really key, expose the class
export = SignupSteps;

您还可以使用 https://github.com/timjroberts/cucumber-js-tsflow 编写更清晰的步骤定义

@查看 https://github.com/samvloeberghs/protractor-gherkin-cucumberjs-angular2 了解完整实施。

干杯

真正的诀窍是修复导出声明,如下所示:

import {CallbackStepDefinition} from 'cucumber';
import {SignUpPage} from '../pages/signUp.page';
import {SignInPage} from '../pages/signIn.page';

let chai = require('chai').use(require('chai-as-promised'));
let expect = chai.expect;

export = function() {

  let index = new SignInPage();
  let page = new SignUpPage();

  this.When(/^I go to register$/, (callback:CallbackStepDefinition) => {
    index.signUp();
    callback();
  });

  this.When(/^I set 'name' with '([^"]*)'$/, (name:string, callback:CallbackStepDefinition) => {
    page.setName(name);
    callback();
  });

  this.When(/^I set 'email' with '([^"]*)'$/, (email:string, callback:CallbackStepDefinition) => {
    page.setEmail(email);
    callback();
  });

  this.When(/^I set 'password' with '([^"]*)'$/, (password:string, callback:CallbackStepDefinition) => {
    page.setPassword(password);
    callback();
  });

  this.When(/^I set 'passwordConfirmation' with '([^"]*)'$/, (value:string, callback:CallbackStepDefinition) => {
    page.setPasswordConfirmation(value);
    callback();
  });

  this.When(/^I press 'Sign up'$/, (callback:CallbackStepDefinition) => {
    page.submit();
    callback();
  });

  this.Then(/^the register form is validated '(.*)'$/, (valid:string, callback:CallbackStepDefinition) => {
    let isValid = (valid === 'true');
    expect(page.formIsValid()).to.become(isValid).and.notify(callback);
  });
};

之后,黄瓜相应地开始了运行。