AngularJS testing with Protractor- executing async

2019-08-27 08:04发布

问题:

I am developing a suite of automated tests to run on an AngularJS application using Protractor. I have never used automated testing prior to working on this task, so am still quite unfamiliar with how it works/ how to implement the tests, etc.

I recently had a bit of trouble with implementing an automated click on the 'Cancel' button of a dialog that was displayed automatically when one of the tests browsed to a particular page.

I have now got the click to the 'Cancel' button working successfully, but the test script is still failing, due to an error with an asynchronous callback.

The test script is written as:

it('should navigate to the Charts page', function(done) {
    console.log("Start Charts page test");
    browser.waitForAngularEnabled(false);
    browser.actions().mouseMove(chartsMenuBtn).perform();
    chartsMenuBtn.click();
    browser.waitForAngularEnabled(true);
    //browser.sleep(5000);
    browser.call(closeDlg);
    //var closeDlgFn = closeDlg();
    //browser.wait(closeDlgFn, 5 * 1000, 'Dialog should close within 5 seconds');
    console.log("End Charts page test");
    expect(browser.getCurrentUrl()).toBe('http://192.168.1.212:8080/#/charts');
});

and the closeDlg() function called within the script is defined with:

function closeDlg(){
    browser.waitForAngularEnabled(false);
    console.log("closeDlg() function called ")
    var EC = protractor.ExpectedConditions;
    var chartConfigForm = element(by.id('chartConfigForm'));
    var closeDlgBtn = element(by.id('editGraphCancelBtn'));
    console.log("About to click closeDlgBtn ");
    closeDlgBtn.click();
    console.log("just clicked closeDlgBtn ");
    console.log("End of closeDlg() function ")
}



    browser.call(closeDlg).then(function(){console.log("End Charts page test"); done();});

When I run the script as it currently stands, I get the following output in the command line:

.Start Charts page test

End Charts page test

-- Next command: findElements

-- Next command: mouseMoveTo

-- Next command: findElements

-- Next command: clickElement

closeDlg() function called

About to click closeDlgBtn

just clicked closeDlgBtn

End of closeDlg() function -- Next command: findElements

When this is all displayed, the dialog is still currently open in the browser. As I continue to step through the test, I get the next two prompts in the command line:

-- Next command: clickElement

End Charts page test

-- Next command: executeAsyncScript

and that is when the dialog is then closed.

If I then continue to execute my test script, I get a failure:

FA Jasmine spec timed out. Resetting the WebDriver Control Flow.

A Jasmine spec timed out. Resetting the WebDriver Control Flow.

Failures:

1) App should navigate to the Charts page

Message:

Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.

Stack:

Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL. at ontimeout (timers.js:386:11) at tryOnTimeout (timers.js:250:5) at Timer.listOnTimeout (timers.js:214:5)

Message:

Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.

Stack:

Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL. at ontimeout (timers.js:386:11) at tryOnTimeout (timers.js:250:5) at Timer.listOnTimeout (timers.js:214:5)

5 specs, 1 failure

Finished in 260.014 seconds

Everything I have found online seems to indicate that I should increase the value of the j$.DEFAULT_TIMEOUT_INTERVAL variable in jasmine.js, which I have done- I've increased it from 10000 (default value) to 500000.

I have also added an allScriptsTimeout value in my conf.js file:

exports.config = {
    framework: 'jasmine',
    seleniumAddress: 'http://localhost:4444/wd/hub',
    specs: ['spec.js'], // List all of the different files in which the tests are defined here
    allScriptsTimeout: 120000,
    onPrepare: function() {
        browser.addMockModule('disableModalAnimations', function(){
            angular.module('disableModalAnimations', []).value('foo', 'bar');
        });
    },
    multiCapabilities: {
        browserName: 'firefox'
    }
}

I don't understand why I'm getting this timeout error?

I tried changing the call to closeDlg() in the test script to:

browser.call(closeDlg).then(function(){console.log("End Charts page test"); done();});

but still get the same error.

I doubt that the issue is actually a timeout issue, but I'm not sure about whether I am executing the asynchronous scripts correctly.

Can anyone spot what I'm doing wrong here? What is the Async callback that the error is telling me was not invoked within the timeout specified? What part of what I'm doing is asynchronous?

回答1:

So after a lot of messing around with the code, chopping & changing, trying different approaches, and looking at various articles online for suggestions of how to do this, I finally got it working by putting the expect clause of my test inside a .then() function chained to the call to my closeDlg() function (and removing it from the it() level of the test:

it('should navigate to the Charts page', function() {
    console.log("Start Charts page test");
    browser.waitForAngularEnabled(false);
    browser.actions().mouseMove(chartsMenuBtn).perform();
    chartsMenuBtn.click();
    browser.waitForAngularEnabled(true);
    browser.call(closeDlg).then(function(){
        expect(browser.getCurrentUrl()).toBe('http://192.168.1.212:8080/#/alarms');
});

This test now passes correctly, and following it through step by step, I can see that it executes the commands as expected, and produces the correct results.



回答2:

Though you answered it yourself, I'll add some information to it:

  • In which order promises get resolved, can be found out here
  • this answer here explains, what browser.call() is used for.

From my point of view your browser.call() is meaningless with only the function call. It most probably even causes your problem, as it adds the function you call to the end of your controlFlow, resulting in its execution at last, after everything else is executed.

Further your switch-off/on/off of browser.waitForAngularEnabled() is additionally confusing. I suggest to not switch it on at all, as it probably had a reason you switched it off at first.

By using then() after your browser-call you force your expect-part to be executed after your browser.call(). Though you didn't solve the problem you had, you worked around it by forcing other lines of codes behind your "problem"-line.

Reading your code I don't see any reason for using browser.call().

var closeDlgPage = require('[relativePathTo]/closeDlgPage.js'); 

it('should navigate to the Charts page', function(done) {
    console.log("Start Charts page test");
    browser.waitForAngularEnabled(false);
    browser.actions().mouseMove(chartsMenuBtn).perform();
    chartsMenuBtn.click();
    //browser.waitForAngularEnabled(true);
    closeDlgPage.closeDlg();
    console.log("End Charts page test");
 expect(browser.getCurrentUrl()).toBe('http://192.168.1.212:8080/#/charts');
});

and your closeDlg() (you can of course remove all console.log() to make it shorter:

//look how PageObjects in Protractor work, if this looks new to you.
closeDlgPage= function(){
    this.closeDlg = function(){
        console.log("closeDlg() function called ");
        //var EC = protractor.ExpectedConditions; //You're not using EC in this function. 
        var chartConfigForm = element(by.id('chartConfigForm'));
        var closeDlgBtn = element(by.id('editGraphCancelBtn'));
        console.log("About to click closeDlgBtn ");
        closeDlgBtn.click();
        console.log("just clicked closeDlgBtn ");
        console.log("End of closeDlg() function ")
    };
};
module.exports = new closeDlgPage();