Protractor: chore(core): Add better error message for client-side redirects

Created on 28 Oct 2015  ·  49Comments  ·  Source: angular/protractor

If the test clicks a link or something which causes a redirect outside of browser.get or browser.refresh, then the next time waitForAngular is called the user will probably run into this error message (since we didn't wait for Angular to load), which is not very helpful.

Users should be told what's going wrong and about potential work-arounds (e.g. using browser.get or browser.refresh to fix the bootstrapping).

debuggability

Most helpful comment

@sjelin would you please explain a bit more on what can be done to mitigate this problem?

My use case: I have a test that enters username/password and hits a log in button, that redirects to another page on successful log in.

All 49 comments

@sjelin would you please explain a bit more on what can be done to mitigate this problem?

My use case: I have a test that enters username/password and hits a log in button, that redirects to another page on successful log in.

If both pages are angular pages, you should just add a browser.refresh after the new page loads to fix the problem

We're working on a feature to make this better in the future

I've been struggling with this issue for a week now. browser.refresh() doesn't work if I'm doing a POST.

That's good to keep in mind, thanks. For now, you could probably mitigate the problem by just having your test wait a few hundred milliseconds after the client side redirect occurs. This will stop Protractor from throwing the error message, though it wouldn't allow Protractor to bootstrap properly so it may cause some other unexpected problems (especially if you're using plugins or mock modules).

Actually, I'm using something like this as workaround where I know there will be a redirection:

(function (expectedUrl, timeout) {
    var loaded = false;

    browser.wait(function () {
        browser.executeScript(function () {
            return {
                url: window.location.href,
                haveAngular: !!window.angular
            };
        }).then(function (obj) {
            loaded = (obj.url == expectedUrl && obj.haveAngular);
        });

        return loaded;
    }, timeout);
})(expectedUrl, timeout);

@tassoevan, that's perfect! It's what I'd expect waitForAngular to do - wait for angular to be available.

When protractor starts, it always goes to the baseUrl then my test page. Is that considered the redirect you are talking about?

Not quite @patrickliechty, I mean when you click on a link (like <a href='google.com'>) and it directs the browser to google.cm

@juliemr with @hankduan gone is there anyone working on the longer term fix for this anymore?

@sjelin , is this issue still exists in protractor 3.1.1.I can see that one

+1 for a better fix. Ran into this today with no idea how to reliably work-around it. Thanks!

Having the same Issue!!! thanks @tassoevan for a reliable fix

+1 for a fix.

I got this error by doing element().getText() outside of the specs (without a browser.get() having occurred) -- was trying to create a shortcut for the output of a particular div outside the specs. Turned it into a function and all was well.

I get this error when running against \node_modules\protractor\example\conf.js, surely the example conf.js should always work regardless??

For people using Protractor without Angular, is there a workaround ?

@Overdrivr You can set browser.ignoreSynchronization = true; to tell Protractor to not wait for Angular.

browser.ignoreSynchronization = true works for me when i switch windows.

OK but then U need to set browser.ignoreSynchronization = false;

I am getting this error and it contains a link to this github issue.

Here is what I have:

/**
 * Waits for MR job to complete.
 * @param {string} url The GET URL to kick off the map reduce job.
 * @return {!webdriver.promise.Promise}
 */
function waitForMapReduceJobToComplete(url) {
  browser.ignoreSynchronization = true;
  browser.get(url);
  $('a[href*="/mapreduce"]').click();
  browser.wait(()=> {
    console.log('wait');
    return $('#auto-refresh').isPresent();
  });
  browser.controlFlow().execute(function() {
    console.log('after wait, before click');
  });
  browser.wait(()=> {
    console.log('isDisplayed');
    return $('#auto-refresh').isDisplayed();
  });
  $('#auto-refresh').click();
  browser.controlFlow().execute(function() {
    console.log('after click');
  });

  browser.wait(() => {
    return $('.status-box').isPresent().then((isPresent)=> {
      if(isPresent) {
        return hasClass($('.status-box'), 'status-done')
            .then((containsClass) => {
              if(!containsClass) {
                return browser.refresh().then(() => false);
              }
              return true;
            });
      }
      return browser.sleep(constants.BROWSER_WAIT_MS)
          .then(() => false);
    })
  });

  return browser.controlFlow().execute(function() {
    browser.ignoreSynchronization = false;
  });
}
  fit('mapreduce job is idempotent', function() {
    var subBuildingUrl = '/xyz/#all';
    var mapReduceJob = '/import/xyz/';
    helpers.waitForMapReduceJobToComplete(mapReduceJob);

    browser.get(subBuildingUrl);
    var tableView = new TableView();
    var originalCount = 0;
    tableView.count().then((cnt)=>{
      originalCount = cnt;
    });

    tableView.deleteRowNumber(1);
    browser.sleep(5000);
    browser.controlFlow().execute(function() {
      expect(tableView.count()).toBeLessThan(originalCount);
    });

    helpers.waitForMapReduceJobToComplete(mapReduceJob);
    browser.get(subBuildingUrl);
    browser.controlFlow().execute(function() {
      tableView = new TableView();
      expect(tableView.count()).toEqual(originalCount);
    });
  });

//Arrange
The test starts on an angular app.
kicks off a mapreduce job on a URL not an angular app
goes back to the angular app to record row counts

//Act
deletes 1 record
verifies delete occurs

// Assert
kicks off a mapreduce job on a URL not an angular app
_fails_ the second time before we can confirm the mapreduce job recreates the data (same row count before the delete).

Here is the relevant log details:

wait
after wait, before click
isDisplayed
isDisplayed
after click
[17:48:09] W/element - more than one element found for locator By(css selector, .status-box) - the first result will be used
[17:48:24] W/element - more than one element found for locator By(css selector, .status-box) - the first result will be used
[17:48:40] W/element - more than one element found for locator By(css selector, .status-box) - the first result will be used
[17:48:55] W/element - more than one element found for locator By(css selector, .status-box) - the first result will be used
wait
F

Failures:
  Message:
    Failed: Error while waiting for Protractor to sync with the page: "window.angular is undefined.  This could be either because this is a non-angular page or because your test involves client-side navigation, which can interfere with Protractor's bootstrapping.  See http://git.io/v4gXM for details"
  Stack:

It always fails on second time through waitForMapReduceJobToComplete on the 'wait' log.

The solution is "documented" here: https://github.com/angular/protractor/issues/2787

Inside the browser.wait you have to reset ignoreSynchronization. I did this for all the browser.waits where ignoreSynchronization was already true.

  browser.wait(()=> {
    browser.ignoreSynchronization = true;
    return $('#auto-refresh').isPresent();
  });

My test passes now.

Do you have any solution for wait using browser.ExpectedConditions?

How the patch will look like if you will use this code?

browser.wait(EC.visibilityOf(element), timeout, message);?

Thanks!

@azachar this one seems to work:

browser.wait(() => {
  browser.ignoreSynchronization = true;
  return EC.visibilityOf(element);
}, timeout, message);

I don't know what's going on, for some reason in this block ignoreSynchronization is still false until you force-set it. I followed up in #2787 with this.

UPDATE: There are race conditions if you set/reset ignoreSynchronization, better put that on control flow. See #4061.

I am getting this without ignoreSynchronization with something like this....

afterAll(function(){
    adminPage.header.logout();
    // FIXME: WTF why isn't even this working 100% of the time
    setTimeout(function(){
      expect(loginPage.isVisible()).toBeTruthy();
    }, 1000 )
  })

loginPage seems to take a sec to load and this setTimeout doesn't help. browser.wait seems to help a little bit but still fails.

@tassoevan hi dude, could you tell me how to config that browser.wait() like your description and where to config it

(function (expectedUrl, timeout) {
    var loaded = false;

    browser.wait(function () {
        browser.executeScript(function () {
            return {
                url: window.location.href,
                haveAngular: !!window.angular
            };
        }).then(function (obj) {
            loaded = (obj.url == expectedUrl && obj.haveAngular);
        });

        return loaded;
    }, timeout);
})(expectedUrl, timeout);

in angular (2) - Make sure your spec begins w/ browser.get('/');

import { browser, element, by } from 'protractor';

it('should display message saying app works', () => {
    browser.get('/');

    let title = element(by.id('title')).getText();
    expect<any>(title).toEqual('cool');
});

After looking at @tassoevan's great workaround and getting my redirect tests working, I figured I'd share my TypeScript and Angular2 solution. I put this function on my abstract page class, but I think it might fit better on the browser object.

/**
  Wait for the page to make it to the next URL before continuing.
  A supplement to browser.waitForAngular() because protractor will continue control
  flow before navigation starts.

  Inputs:
    necessaryUrlFragment: a portion of the url that signifies you have successfully navigated
    timeout: the number of ms to wait before throwing an error. Defaults to the browsers default
      page timeout.
 */
waitForRedirect(necessaryUrlFragment: string, timeout: number = browser.getPageTimeout) {
  // Before we tell the browser to wait, assume it has not navigated
  let hasRedirected = false;

  // Passing a function to browser.wait() tells protractor to call that function repeatedly.
  // This function returns the closure variable hasRedirected, which will be set to true once the
  // necessaryUrlFragment has been found in the url
  browser.wait(() => {
    browser.getCurrentUrl()
      // Check to see if necessaryUrlFragment is in the current url
      .then(url => url.includes(necessaryUrlFragment))
      // Update our navigation status
      .then(hasNavigated => {
        hasRedirected = hasNavigated;
      });

    // Return our navigation status every time protractor asks for it - even if navigation is
    // not complete
    return hasRedirected;
  }, timeout);
}

Here's an example of how I used it in a test:

it('Should be able load the preferences page', () => {
  page.navigateToMarketing();
  page.clickButton('Manage Preferences');
  page.waitForRedirect('preferences/preferences');
  expect(page.getPageTitle()).toBe('MY PREFERENCES');
});

My login page is non-Angular js page. Once i login to the application Angular js page is created. So I created spec file as given below:

describe('Login to Application', function() {
var userID = element(by.name('username'));
var password = element(by.name('password'));
var logInButton = element(by.id('Submit'));

function loginToApplication(stringSSOID, stringPassword) {
userID.sendKeys(stringSSOID);
password.sendKeys(stringPassword);
logInButton.click();

}

beforeEach(function() {
browser.ignoreSynchronisation = true;
browser.get(''); // which will navigate to login page of the application
});

it('have login to application', function() {
loginToApplication('77777','test')
browser.ignoreSynchronisation=false
expect(browser.getTitle()).toEqual('Software');
});
});

Getting below error message:
Failed: Error while waiting for Protractor to sync with the page: "window.an
gular is undefined. This could be either because this is a non-angular page or
because your test involves client-side navigation, which can interfere with Prot
ractor's bootstrapping. See http://git.io/v4gXM for details"

Hi take a look at http://www.protractortest.org/#/timeouts#how-to-disable-waiting-for-angular
Besically there are couple of ways to login using non-Angular page but firstly try this one show in the docs.
Best Regards
Camel

I realize it's a lengthy discussion already, but this might help someone.

Such an error message can also happen when the page is reloading unexpectedly because of an unhandled error happening in an Angular component. The fact that browser console may not be persisted, together with how fast the tests execute made it quite not-straightforward for me to understand what was going on.

So maybe something could be done as far as the error message is concerned, since the aforementioned case is not 'client side navigation' per se :)

Hi @ArnaudPel

Tnx for your extra info. Maybe you can add a PR to explain this in the docs if you want?

I currently get this error when doing the trip to our login page (a common problem):

Failed: Error while waiting for Protractor to sync with the page: "window.angular is undefined.  This could be either because this is a non-angular page or because your test involves client-side navigation, which can interfere with Protractor's bootstrapping.  See http://git.io/v4gXM for details"

Doing the ignoreSync trick doesn't seem to help:

browser.ignoreSynchronization = true;
// do work here with browser.driver 
browser.ignoreSynchronization = false;

The link lands here at this thread, but what to do next is not exactly clear.

Tested with Protractor versions: 5.2.1, 5.1.1, 5.1.0, 5.0.0

@benjaminapetersen - You could try using waitForAngularEnabled instead of ignoreSynchronization. I'm looking at the API reference page for Protractor v5.1.2 and it no longer includes ignoreSynchronization, but looking through the CHANGELOG I don't see when, if ever, that property was officially deprecated...

I tried waitForAngularEnabled but i still get the same error as @benjaminapetersen gets. Here is my current config:

exports.config = {
    rootElement: 'app',
    baseUrl: 'http://localhost:1384/#/',
    framework: 'jasmine',
    seleniumAddress: 'http://localhost:4444/wd/hub',
    specs: ['./tests/e2e/*.js'],
    multiCapabilities: [{
      'browserName': 'firefox',
      //'browserName': 'chrome',
    }, 
  ],
  jasmineNodeOpts: {
    showColors: true
  },
}

//example test: 

describe('Overall navigation', function () {
  it('test link', function () {
     browser.get('http://localhost:1384/#/registrerprodukt');
  });
});

@thompsnm thanks! Though it looks like waitForAngularEnabled is also deprecated....

@benjaminapetersen that deprecated doc tag is on the ignoreSynchronization property, not waitForAngularEnabled

waitForAngularEnabled doesn't work for me either. It worked with IgnoreSynchronization before upgrading to Protractor 5. I need to switch into non-Angular iframe, and now I can't. Please advise. Thanks

@seel93 Try commenting rootElement: 'app', or try replacing it with rootElement: '*[ng-app]'.
I just installed protractor latest version and wrote a few specs for an Angular 1.6.1 app and seems to be working ok so far. I encountered that error message when I was trying to login in my app but I'm now using this approach:

onPrepare: function () {
    browser.driver.manage().timeouts().setScriptTimeout(60000);

    browser.driver.get('http://app.local.dev'+ '/login');
    browser.driver.findElement(by.id('user_login')).sendKeys('d','e','m', 'o');
    browser.driver.findElement(by.id('user_pass')).sendKeys('d','e','m', 'o');
    browser.driver.findElement(by.id('submit')).click();

    // Login takes some time, so wait until it's done.
    return browser.driver.wait(function() {
        return browser.driver.getCurrentUrl().then(function(url) {
            return /students\/agenda/.test(url);
        });
    }, 30000);
}

Note: I have ng-app set on the html tag

rootElement: '*[ng-app]'? works to just search the page for whatever node? Haven't seen this before.

I just solved my issue. 🥇 Root cause: Developer console side effect

I was having this problem and the fix for me ended up being to change to protractor "^5.3.2" in my package.json

I have a regular login page and then I redirect to angular page.

So I don't exactly know how or why it works, because I'm really new to angular and angular e2e, but I got it working once I used the browser.waitForAngularEnabled(false); twice.

logIn() {
    const email = 'test';
    const password = 'test';

    browser.waitForAngularEnabled(false);
    browser.get('/login');

    element(by.id('username')).sendKeys(email);
    element(by.id('password')).sendKeys(password);
    element(by.css('[type="submit"]')).click();

    return browser.wait(function() {
        browser.waitForAngularEnabled(false);
        return browser.getCurrentUrl().then(function(url) {
            browser.waitForAngularEnabled(true);
            return /dashboard/.test(url);
        });
    }, 5000);
}

Hope it helps.

That last solution almost work for me unless I got an error after the test passes :
Error: ECONNREFUSED connect ECONNREFUSED 127.0.0.1:4444 From: Task: WebDriver.getCurrentUrl()

I got this error by doing element().getText() outside of the specs (without a browser.get() having occurred) -- was trying to create a shortcut for the output of a particular div outside the specs. Turned it into a function and all was well.

It works for anything, you can't use more than one 'element().anyfunction()' outside an ' it '.
Found it out the hard way...

Got error when switch from one web page to another in Protractor:

Failed: Error while waiting for Protractor to sync with the page: "both angularJS testability and angular testability are undefined. This could be either because this is a non-angular page or because your test involves client-side navigation, which can interfere with Protractor's bootstrapping. See http://git.io/v4gXM for details"

the code is :
var url = browser.params.login.Url;
browser.get(url+'/#!/app/manage-users');

I started getting this error suddenly my CT builds. I noticed that this error occurs only with Firefox headless and worked fine with Chrome headless on Linux. I upgraded from Firefox v69 to the latest one (v72.0.2 at the moment) which fixed this issue.

browser.ignoreSynchronization has been deprecated and should not be used. As hinted from the original post, using browser.get seems to solve the issue.

If you want to use it in a way that you can disable and enable waitForAngularEnabled at will, you need to use browser.get after setting waitForAngularEnabled(true). For example:

// do things on your Angular application

waitForAngularEnabled(false)

// do things on non-angular page

waitForAngularEnabled(true)
browser.get('/home') // this is a page from your Angular application

browser.get blocks until the Angular page is loaded.

I'm currently hitting this error:

_Exception has occurred: Error: Error while waiting for Protractor to sync with the page: "both angularJS testability and angular testability are undefined. This could be either because this is a non-angular page or because your test involves client-side navigation, which can interfere with Protractor's bootstrapping. See http://git.io/v4gXM for details"_

But only when trying to debug through either VS Code debugger or chrome inspector debug (followed the instructions here for both cases https://www.protractortest.org/#/debugging).

Here's my launch.json config in VSCode.

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "E2E Test",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "program": "${workspaceFolder}\\node_modules\\protractor\\bin\\protractor",
            "args": ["${workspaceFolder}\\e2e\\protractor.conf.js"],
            "outFiles": [
                "${workspaceFolder}/e2e/**/*.js",
                "${workspaceFolder}/src/**/*.js"
            ]
        }
    ]
}

Maybe it's something I have set up wrong. Any help is appreciated, trying to write these without being able to properly debug is insanely difficult.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

davidkarlsen picture davidkarlsen  ·  3Comments

andyman3693 picture andyman3693  ·  3Comments

codef0rmer picture codef0rmer  ·  3Comments

mvolkmann picture mvolkmann  ·  3Comments

psech picture psech  ·  3Comments