2
votes

I have a Protractor test that initiates a reloading of the same Angular app. Occasionally it fails in a Travis build, but will pass after one or two restarts of the build. Rarely it fails on my local machine. The Travis builds use Firefox, but the times it has failed on my machine have been using both Firefox and Selenium's chromedriver.

I've set rootElement: 'html' (as recommended in this GitHub issue on Protractor) because that is where the ng-app is, but I still get this error:

Error while waiting for Protractor to sync with the page: "root element (html) has no injector. this may mean it is not inside ng-app."

The error is the result of angular.element('html').injector() returning falsey, even though Angular has loaded. A previous test in the Protractor code confirms this.

The test involves being redirected to a different index.html that happens to be the same angular app. Reason for the redirect is to offer a language choice then reload the app in that language.

There are multiple locales (two shown here for simplicity). app is the document root, so app/index.html is the entry point, which detects the system language or defaults to en-gb and then redirects to app/<locale>/index.html. The former is not an Angular app (app/); the latter is (app/<locale>/) and links to ../common/ for library and Angular module files. The index.html files in the locale folders are identical.

What happens when you visit the document root?

/ -> redirect /en-gb/#/language

And when you choose the German language?

/en-gb/#/language redirect ../de/#/menu -> /de/#/menu

Directory structure:

└── app
    ├── common
    │   ├── fonts
    │   ├── images
    │   ├── lib
    │   ├── modules
    │   └── styles
    ├── de
    │   ├── i18n.js
    │   └── index.html
    ├── en-gb
    │   ├── i18n.js
    │   └── index.html
    └── index.html

Test:

(function () {

    'use strict';

    describe('Module: app, language screen:', function () {

        beforeEach(function () {
            browser.get('http://localhost:9000/en-gb/#/language/');
            this.$germanChoice = // get the clickable element to select German
            this.$buttonContinue = // get the clickable element to continue
        });

        describe('continue button', function () {
            it('should go forward to the menu', function () {
                this.$germanChoice.click();
                this.$buttonContinue.click();
                expect(browser.getCurrentUrl()).toBe('http://localhost:9000/de/#/menu/');
            });
        });

    });

}());

Locale index.html using minified files:

<!doctype html>
<!--[if IE 8]>         <html id="ng-app" ng-app="app" lang="{{ app.locale.html }}" class="no-js lt-ie10 lt-ie9"> <![endif]-->
<!--[if IE 9]>         <html id="ng-app" ng-app="app" lang="{{ app.locale.html }}" class="no-js lt-ie10">        <![endif]-->
<!--[if gt IE 9]><!--> <html id="ng-app" ng-app="app" lang="{{ app.locale.html }}" class="no-js">                <!--<![endif]-->
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title ng-bind="app.page.title ? app.page.title + ' - ' + app.page.titleBase : app.page.titleBase"></title>
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <link rel="stylesheet" href="../common/lib/nouislider/jquery.nouislider.css" />

        <!--[if gt IE 8]><!-->
            <link rel="stylesheet" href="../common/styles/b8a085fb.main.css">
        <!--<![endif]-->

        <!--[if lt IE 9]>
            <link rel="stylesheet" href="../common/styles/f71e8123.ie.css">
        <![endif]-->

        <!--[if lt IE 9]>
            <script src="../common/scripts/a5663f12.lt-ie9.js"></script>
        <![endif]-->
    </head>

    <body ng-class="{ 'ios6': app.environment.ios6, 'gt-ios6': app.environment.gtIos6, 'cordova': app.environment.cordova }">

        <div ng-view="" autoscroll="true"></div>

        <script src="../common/scripts/cbd2241e.app.js"></script>
        <script src="i18n.js"></script>

    </body>
</html>
1

1 Answers

2
votes

I occasionally run into issues with non-Angular login pages (some of which have several steps). I think your language selection page might be similar. The solution is a bit of logic in your tests, combined with the exposed Webdriver API in browser.driver.

if ('some condition that determines you are on a non-Angular page') {
  browser.driver.findElement(by.id('language-choice')).click();
}

The best conditions to test are:

  1. Test if some unique element is displayed.
  2. Get the URL of the current page and compare it to an expected value.

And use browser.driver any time you're not on an Angular page, because it bypasses Protractor. Otherwise, Protractor will look for Angular, fail to find it, and throw errors. Everything you can do with browser.driver can be found here, in the lower regions of the menu: http://angular.github.io/protractor/#/api

Then, in order to wait for your Angular page to show up before you use Protractor, try this snippet:

browser.driver.wait(function() {
    return browser.driver.getCurrentUrl().then(function(url) {
        return url.toString().indexOf('my angular page url') !== -1;
    }, function(err) {
        throw err;
    });
}, 5000, 'Timed out waiting for your Angular page to load.');

This is particularly useful in your onPrepare statement (just add return to the very beginning, before browser.driver.wait). The test will wait for your Angular page to load, after which you can Protractor around to your heart's content.

With a bit of messing around, this same snippet can be used to react to multi-step non-Angular introductory pages. And, encapsulated into its own neat little function, this can be reused anywhere that Protractor is liable to get dumped onto a non-Angular page.