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

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).

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Reactions: 20
  • Comments: 49 (7 by maintainers)

Commits related to this issue

Most upvoted comments

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

@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.

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

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.

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

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.

@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…

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

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

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

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.

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

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

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()

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

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

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

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 😃

@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.

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