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

I'm not really savvy in TypeScript and Angular2 and I've been trying to run the cucumber's features using steps that have been written in TypeScript. However, on executing the steps.ts files, I'm getting the following error:

[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

It seems as if the cucumber definitions had not been compiled.

These are my (relevant) configurations:

My directory structure is:

/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;
  }
};

And an example of one step_definition is:

/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);
  });
};

Is there anything that I've missed?

Upvotes: 2

Views: 2512

Answers (2)

What really did the trick was fix the export declaration, as such:

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);
  });
};

After that, the cucumber started running accordingly.

Upvotes: 2

Dennis Jaamann
Dennis Jaamann

Reputation: 3565

In your protractor.conf.js you can remove the compiler option from your cucumberOpts

  cucumberOpts: {
    format: 'pretty',
    require: [
      'test/features/step_definitions/**/*.steps.ts'
    ],
    //remove this compiler: 'ts:ts-node/register'
  },

Second, your signUp.steps.ts should look something like this:

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;

You could also use https://github.com/timjroberts/cucumber-js-tsflow for writing cleaner step definitions

@see https://github.com/samvloeberghs/protractor-gherkin-cucumberjs-angular2 for a full implementation.

Cheers

Upvotes: 0

Related Questions