angular / protractor

E2E test framework for Angular apps
http://www.protractortest.org
MIT License
8.75k stars 2.31k forks source link

getPageTimeout global option in protractor config does not seem to overwrite default timeout #2181

Closed gologox closed 8 years ago

gologox commented 9 years ago

Protractor 2.1.0 I've added the following to my conf:

    getPageTimeout: 30000,
    allScriptsTimeout: 30000,

The reason for this is because Sauce instances take like 1,000 years to load.

But Protractor begins to check for Angular ~10000ms into the test: http://screencast.com/t/Wai11lzcLliZ

sjelin commented 9 years ago

Oh, that's weird. Your script seems to be calling waitForAngular on a non-angular page (or on an angular page but before angular has loaded). Could you let us see your test script?

gologox commented 9 years ago
describe('My Account - Address Update', function() {
  //Set window size
  var width = 1600;
  var height = 875;
  browser.driver.manage().window().setSize(width, height);

  it('should resolve my account landing page', function(done) {
    browser.driver.get(url, globalTimeout).then( function() {
      browser.getTitle().then(function(title) {
        if (title == 'Madison Reed - Healthy, Professional-Grade Hair Color and Care') {
          done();
        } else {
          done(new Error('Could not resolve MR address.'));
        }
      });
    });
  });

  it('should create new account', function(done) {
    element.all(by.css('.sign_in')).last().click();
    element(by.cssContainingText('a', 'Create Account')).click();

    input = element(by.css('#create_account_email'));
    input.sendKeys(userData.email);

    input = element(by.css('#create_account_password'));
    input.sendKeys(userData.pass);

    element(by.cssContainingText('.mr-btn', 'sign up')).click().then(function() {
      if (browser.browserName == 'safari') {
        browser.driver.get(url + '/account');
      } else {
        element(by.cssContainingText('.nav-item.hide-mobile', 'My Account')).click();
        element.all(by.css('[href="/account"]')).last().click();
      }
      browser.getTitle().then( function(title) {
        if (title != 'My Account' && title != 'Madison Reed') {
          done(new Error('Could not create a new account.'));
        } else {
          done();
        }
      });
    });
  });

  it('should navigate to the Shipping/Billing tab and click on Add a New Address', function(done) {
    element(by.css('a[href="/account/shipping-billing"]')).click();
    element(by.cssContainingText('.button', 'add a new address')).click();
    done();
  });

  it('should fill out the address input fields', function(done) {
    var input = element(by.model('shipAddress.first_name'));
    input.sendKeys(userData.first);

    input = element(by.model('shipAddress.last_name'));
    input.sendKeys(userData.last);

    input = element(by.model('shipAddress.street_1'));
    input.sendKeys(userData.address_1);

    input = element(by.model('shipAddress.street_2'));
    input.sendKeys(userData.address_2);

    input = element(by.model('shipAddress.post_code'));
    input.sendKeys(userData.zip);

    input = element(by.model('shipAddress.phone'));
    input.sendKeys(userData.phone);
    done();
  });

  it('should save all the input information', function(done) {
    element.all(by.cssContainingText('.mr-button.save-button', 'Save')).first().click();
    done();
  });

  it('should validate the address', function(done) {
    //Construct validation array with local values
    var scriptData = [];
    scriptData.push(userData.first + ' ' + userData.last);
    scriptData.push((userData.address_1 + ' ' + userData.address_2).toUpperCase());
    scriptData.push((userData.city + ', ' + userData.state + ' ' + userData.zip).toUpperCase());
    //sub-mask formatting for phone number
    var num = userData.phone.split('');
    num.splice(0, 0, '(');
    num.splice(4, 0, ') ');
    num.splice(8, 0, '-');
    scriptData.push(num.join(''));

    num = 0;
    element.all(by.css('.address.ng-scope div')).each(function(field) {
      field.getText().then(function(text) {
        if (num <= 3) {
          expect(text).toBe(scriptData[num]);
          num++;
        }
      });
    }).then(function() {
      done();
    });
  });

  it('should validate the checkboxes', function(done) {
    element(by.model('address.is_default_shipping')).isSelected().then(function(selected) {
      expect(selected).toBe(true);
      done();
    });
  });

  it('should send session data to Sauce, if not already', function(done) {
    if (typeof(process.env['SauceOnDemandSessionID']) == 'undefined') {
      browser.getSession().then(function(session) {
        process.env['SauceOnDemandSessionID'] = session.getId();
        done();
      });
    } else {
      done();
    }
  });

});
sjelin commented 9 years ago

Do you have a stack trace for the angular could not be found on the window error message? In particular, it'd be great to know which line in the test code is executing the waitForAngular

gologox commented 9 years ago

I blacked it out in the screenshot, but the command that was sent to Sauce before it timed out was:

browser.driver.get(url, globalTimeout).then( function() {
  \\
});

The stack trace is not helpful at all:

[chrome 41.0 Windows 7 #4] Failures:
[chrome 41.0 Windows 7 #4] 1) My Account - Address Update should resolve my account landing page
[chrome 41.0 Windows 7 #4]   Message:
[chrome 41.0 Windows 7 #4]     Failed: Error while waiting for Protractor to sync with the page: "root element (body) has no injector. this may mean it is not inside ng-app."
[chrome 41.0 Windows 7 #4]   Stack:
[chrome 41.0 Windows 7 #4]     Error: Failed: Error while waiting for Protractor to sync with the page: "root element (body) has no injector. this may mean it is not inside ng-app."
[chrome 41.0 Windows 7 #4]         at Array.forEach (native)
[chrome 41.0 Windows 7 #4]         at runMicrotasksCallback (node.js:337:7)
[chrome 41.0 Windows 7 #4]         at process._tickCallback (node.js:355:11)

The page is indeed Angular as I have tested this script hundreds of times locally.

My concern is the timeouts, it does not seem to be waiting 30000ms for Angular to load.

sjelin commented 9 years ago

Well, it probably is actually waiting 30000ms for Angular to load. browser.get() is the function which waits for Angular to load, but the error message you're getting is from waitForAngular. waitForAngular waits for angular to have no outstanding asynchronous commands, but it assumes angular is already loaded on the page. So what appears to be going on is that somehow waitForAngular is getting called before Angular has been loaded.

In other words, the error message isn't saying "We waited 30000ms for Angular to load and nothing showed up". It's saying "Angular was already supposed to have been loaded by this point in the execution and it hasn't been"

gologox commented 9 years ago

Here is the production version of the page the script is trying to render: https://www.madison-reed.com/

The only differences I can think of that I have made in my current setup from when I was working with 1.6.1 is:

sjelin commented 9 years ago

The error messages I'm seeing seem to indicate that you're having these problems because you're using a non-angular page. Are you sure sync is off at all the times it needs to be?

sjelin commented 9 years ago

Also, can you generate these failures locally or only on sauce?

gologox commented 9 years ago

I was able to reproduce these errors locally when our sandboxed server is slow. When the server is very responsive, the scripts pass 100% of the time. I included the URL(https://www.madison-reed.com/) to the page above, as you can see in the source, Angular is present. And yes, one of my script turns off sync well into the script, not at the beginning when all the scripts are performing a browser.driver.get()

gologox commented 9 years ago

I found something that may be related to the problem. The way Protractor is handling page redirects is very fragile.

e.g script:

var url = 'https://www.madison-reed.com/product/root-touch-up/';

describe("Render product page", function() {
  it('should retrieve web page title', function(done) {
    browser.driver.get(url).then(function() {
      browser.getTitle().then(function(title) {
        expect(title).toBe('Madison Reed');
        done();
      });
    });
  });
});

When it tries to load 'https://www.madison-reed.com/product/root-touch-up/', our routing in express redirects them to a landing page at 'https://www.madison-reed.com/welcome/1', so initially waitForAngular returns true and then when the redirect occurs, in which Angular being present is no longer the case, but it's on the sync phase.

I am not sure how Protractor is supposed to handle these situations.

To test this, I have updated my tests to access the landing pages themselves, that have no redirects and it has improved my results.

sjelin commented 9 years ago

Oh, that would be a problem. It waits for angular in browser.get(), but after that point it just assumes Angular will stay loaded until the next browser.get().

So if you remove all the redirects you can't reproduce the problem then?

gologox commented 9 years ago

I could not. But then I downgraded to 2.0.0 and put framework: "jasmine", in my config to solve this issue: #1095

Now this is happening: http://screencast.com/t/wEGq1nCFb

As you can see, I have a 30000ms timeout and yet it wasn't able to find Angular in a 10000ms window. Which goes back to my initial concern.

sjelin commented 9 years ago

Ok, so I guess I haven't been clear, but the fact that your timeout is 30000ms is actually irrelevant because that error message doesn't mean what you think it means.

What's going on is this:

  1. You browser.get() to a page
  2. Protractor waits for Angular to load on the page
  3. Angular loads and Protractor finds it
  4. The page redirects to a second page
  5. Protractor, thinking that Angular has already been loaded, executes the next command in your test script. This is the step where the error message is generated.
  6. The second page loads Angular, but too late!

A longer wait wouldn't help, because Protractor is actually finding Angular back in step 3, but for the wrong page.

sjelin commented 9 years ago

@juliemr Do you have any thoughts on what to do with this sort of client-side navigation?

gologox commented 9 years ago

In my latest screenshot, there is no redirect, I am getting a 200 from the server for http://localhost:3000/product/varese-blonde, equivalent to https://www.madison-reed.com/product/varese-blonde

I will continue to investigate. Thanks.

sjelin commented 9 years ago

Oh, sorry, I misunderstood your previous comment

AksimO commented 9 years ago

If changing config option 'jasmine2' to 'jasmine' helped to @gologox, it might be related to issue described in a comment https://github.com/angular/protractor/issues/1095#issuecomment-107103042.

qjnz commented 9 years ago

Hi guys, I am having the same redirect issue that would cause the angular not found issue via cucumber-js. Have anyone encountered the issue via cucubmer.js?

juliemr commented 8 years ago

I've tried to reproduce and can't - I think this issue is just stale. Please open a new one if there is still a reproducible problem. Thanks!