item
item

Reputation: 95

Protractor tests with CucumberJS passing irregularly

Trying out some BDD with AngularJS so I'm having a go at automating scenarios with Protractor and CucumberJS. Strangely, It been the devils job trying to get step defintions to fail intelligently.

Features.feature

Feature: Calculator
  As a user
  I want to perform arithmetic operations
  So that I don't have to think too hard

  Scenario: Addition 
    Given I have opened the calculator application
    When I add 2 and 2
    Then the result 4 should be displayed 

Steps.js

module.exports = function() {

  this.Given(/^I have opened the calculator application$/, function (callback) {
    //load protractor config baseurl
    browser.get('').then(
    callback());
  });

  this.When(/^I add (\d+) and (\d+)$/, function (arg1, arg2, callback) {
    //enter numbers to be added
    element(by.model('firstNumber')).sendKeys(arg1);
    element(by.model('secondNumber')).sendKeys(arg2);
    //select mathematical operator from dropdown list
    element(by.css('select')).click();
    element(by.css('select option[value="0"]')).click();
    //hit the calculate button
    element(by.buttonText('=')).click();
    callback();
  });

  this.Then(/^the result (\d+) should be displayed$/, function (arg1, callback) {

   element(by.binding('result')).getText()
    .then(function(result){
       result === arg1 ? callback() : callback.fail();
    });
  });
};

Index.html

    <!doctype html>
<html class="no-js">
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body ng-app="calculator" ng-controller="MainCtrl">
    <input ng-model="firstNumber">
    <select ng-model="selectedOperation" ng-options="op as op.value for op in operations"></select>
    <input ng-model="secondNumber">
    <button ng-click="Calculate()">=</button>
    <span ng-bind="result"></span> 

    <script src="bower_components/angular/angular.js"></script>
    <script src="scripts/app.js"></script>
  </body>
</html>

App.js

    angular
  .module('calculator', [])
  .controller('MainCtrl', function ($scope) {
    $scope.operations = [
        { label: 'Add', value: '+' },
        { label: 'Subtract', value: '-' }
    ];
    $scope.selectedOperation = $scope.operations[0];
    $scope.Calculate = function(){
        switch($scope.selectedOperation.label) {
            case 'Add':
                var result = Number($scope.firstNumber) + Number($scope.secondNumber);
                break;
            case 'Subtract':
                var result = Number($scope.firstNumber) - Number($scope.secondNumber);
                break;
        };
        $scope.result = result !== NaN || result === 0 ? result : 'Boo! bad input!';
    };
  });

Protractor output:

1 scenario (1 passed) 3 steps (3 passed)

The setup above works fine. Protractor gives the correct output and I can fail the scenario by evaluating incorrect outcomes in the Then() step. Seems nice.

First problem I see is when I try to make the When step fail. For example using the same setup above but attempting to locate a nonexistent element.

  this.When(/^I add (\d+) and (\d+)$/, function (arg1, arg2, callback) {
    //enter numbers to be added. Sabotage edition!
    element(by.model('AintNoGood')).sendKeys(arg1);
    element(by.model('secondNumber')).sendKeys(arg2);
    //select mathematical operator from dropdown list
    element(by.css('select')).click();
    element(by.css('select option[value="0"]')).click();
    //hit the calculate button
    element(by.buttonText('=')).click();
    callback();
  });

Protractor output: NoSuchElementError: No element found using locator: by.model("AintNoGood") ... 1 scenario (1 failed) 3 steps (1 failed, 2 passed)

The 2nd step fails correctly. I'm under the impression that when a step fail all subsequent steps are skipped, but protractor continues on to the 3rd step which passes anyway.

Stranger still...I empty the HTML. BDD test-first and all.

Inmdex.html

<!doctype html>
<html class="no-js">
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body ng-app="calculator" ng-controller="MainCtrl">

    <!--Ghost town here-->

    <script src="bower_components/angular/angular.js"></script>
    <script src="scripts/app.js"></script>
  </body>
</html>

Assuming I was going through the scenario one step at a time, I write the defintion for the 2nd step assuming it will fail.

module.exports = function() {

  this.Given(/^I have opened the calculator application$/, function (callback) {
    //load protractor config baseurl
    browser.get('').then(
    callback());
  });

  this.When(/^I add (\d+) and (\d+)$/, function (arg1, arg2, callback) {
    //enter numbers to be added
    element(by.model('firstNumber')).sendKeys(arg1);
    element(by.model('secondNumber')).sendKeys(arg2);
    //select mathematical operator from dropdown list
    element(by.css('select')).click();
    element(by.css('select option[value="0"]')).click();
    //hit the calculate button
    element(by.buttonText('=')).click();
    callback();
  });

  this.Then(/^the result (\d+) should be displayed$/, function (arg1, callback) {
    callback.pending();
  });
};

Protractor output: 1 scenario (1 pending) 3 steps (1 pending, 2 passed)

So the 2nd step passes. Clearly it should not when there are none of the elements it it supposed to be locating in the html.

Questions:

Any idea what's going on here?

If not, before I spend more time trying to make sense of it, I'm wondering if anyone has had success using Protractor with CucumberJS?

Upvotes: 2

Views: 1407

Answers (1)

P.T.
P.T.

Reputation: 25177

This:

browser.get('').then(
    callback());
});

should be:

browser.get('').then(callback);

As it is, you're invoking the callback immediately and passing whatever it returns as the argument to the then.

In your first this.When you end with:

 callback();

But the element() call chains preceding it do not block. They simply schedule operations to be done on the WebDriver control flow, so I suspect this "callback()" will get invoked almost immediately. You can fix this by doing:

element(by.buttonText('=')).click().then(callback);

to get the callback scheduled in the controlflow.

The WebDriver control flow is counter-intuitive and often obtuse, so you'll want to read https://github.com/angular/protractor/blob/master/docs/control-flow.md and https://github.com/SeleniumHQ/selenium/wiki/WebDriverJs#control-flows.

Upvotes: 2

Related Questions