e2e protractor test requiring oauth authentication
Asked Answered
B

3

21

I've got an Angular app that requires authentication with Google, granting of some scopes, etc, and I'm trying to set up automatic e2e tests for it. I have protractor working well for me in general, but when we get to the google auth page, login, and get redirected, protractor fails the test because "document unloaded while waiting for result."

Is there a tool or technique I can use to authenticate to a development google account beforeEach test?

If I could just get the framework to hold on for a second while plain-old webdriver drives the login, and only really activate the angular stuff after I get to my target page, that would be perfect!

Barnet answered 6/1, 2014 at 21:38 Comment(0)
D
1

You should use waitForAngularEnabled to enable/disable waiting for Angular tasks with protractor. By default it will be enabled. You can use the following:

// do things on your Angular application
// go to external oauth page

waitForAngularEnabled(false)

// login on oauth page and redirect to your Angular application

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

waitForAngularEnabled returns a promise. The browser.get function blocks until the Angular page is loaded.

Dogeared answered 24/5, 2020 at 19:12 Comment(1)
I don't know why this is downvoted. Can this be clarified? The answer posted by demee and Riley Lark works but now that waitForAngularEnabled is available we should use that instead of sleep timers. Set the timer too high and your tests will take long, set them too low and your tests may fail. This answer will immediately continue with testing your Angular application the moment we switch back from the oauth page.Dogeared
B
21

The key is to use browser.driver.get instead of browser.get, and to use browser.driver.sleep(someMilliseconds) to let angular load at your final destination before using the angular-specific commands.

Here's my working protractor spec that first authorizes to Google and then counts the items in a repeater:

it('allows the user to add new slides', function () {
    browser.driver.get('http://localhost:3000/editor/?state=%7B"action":"create"%7D');

    // at this point my server redirects to google's auth page, so let's log in
    var emailInput = browser.driver.findElement(by.id('Email'));
    emailInput.sendKeys('[email protected]');

    var passwordInput = browser.driver.findElement(by.id('Passwd'));
    passwordInput.sendKeys('pa$sWo2d');  //you should not commit this to VCS

    var signInButton = browser.driver.findElement(by.id('signIn'));
    signInButton.click();

    // we're about to authorize some permissions, but the button isn't enabled for a second
    browser.driver.sleep(1500);

    var submitApproveAccess = browser.driver.findElement(by.id('submit_approve_access'));
    submitApproveAccess.click();

    // this nap is necessary to let angular load.
    browser.driver.sleep(10000);

    // at this point the protractor functions have something to hook into and 
    // will work as normal!
    element(by.id('new-slide-dropdown-trigger')).click();
    element(by.id('new-text-slide-trigger')).click();

    var slideList = element.all(by.repeater('slide in deck.getSlides()'));
    slideList.then(function(slideElements) {
        expect(slideElements.length).toEqual(1);
    });

});
Barnet answered 7/1, 2014 at 1:50 Comment(2)
I would use seeps in protractor tests as the last resource.Volva
Rather than sleeping for an indeterminate time, why not wait for an element to change in the DOM?Housman
V
8

I have a Google Auth page object (below) that does the whole job for me.

The key here is "isAngularSite(false);" function witch instructs webdriver if we are entering 'angular' or 'non angular' website. (implementation below).

/* global element, browser, by */

'use strict';

var GOOGLE_USERNAME = '[email protected]';
var GOOGLE_PASSWORD = 'password';
var ec = protractor.ExpectedConditions;

var Google = function () {
  this.emailInput = element(by.id('Email'));
  this.passwordInput = element(by.id('Passwd'));
  this.nextButton = element(by.id('next'));
  this.signInButton = element(by.id('signIn'));
  this.approveAccess = element(by.id('submit_approve_access'));

  this.loginToGoogle = function () {
    var self = this;

    /* Entering non angular site, it instructs webdriver to switch 
       to synchronous mode. At this point I assume we are on google
       login page */ 
    isAngularSite(false); 
    this.emailInput.sendKeys(GOOGLE_USERNAME);
    this.nextButton.click();

    this.passwordInput.isPresent().then(function () {
      browser.wait(ec.visibilityOf(self.passwordInput), BROWSER_WAIT).then(function () {
        self.passwordInput.sendKeys(GOOGLE_PASSWORD);
        self.signInButton.click();
        browser.wait(ec.elementToBeClickable(self.approveAccess), BROWSER_WAIT).then(function () {
          self.approveAccess.click();
          /* Now we are being redirected to our app, switch back to
             async mode (page with angular) */
          isAngularSite(true);
        });
      });
    });
  }
}

module.exports = new Google();

--- throw this into protractor.conf.js

onPrepare: function () {
    global.isAngularSite = function (flag) {
      console.log('Switching to ' + (flag ? 'Asynchronous' : 'Synchronous') + ' mode.')
      browser.ignoreSynchronization = !flag;
    },
    global.BROWSER_WAIT = 5000;
  }

--- that's how you would use it:

it('should login though google', function(done) {
    mainPage.loginBtn.click().
    then(function () {
      loginPage.connectWithGoogleBtn.click();
      googlePage.loginToGoogle();
      browser.wait(mainPage.userName.isPresent()).
      then(function () {
        expect(mainPage.userName.getText()).
        toEqual('[email protected]');
        done();
      });
    });
  });
Volva answered 20/11, 2015 at 16:41 Comment(3)
Sorry for the long time until comment, but what are mainPage and loginPage? They aren't defined anywhere.Roundly
mainPage and logingPage are basically your page objects that you might have in you testing framework... although you can just remove those variables and the code would make similar senseVolva
Ok, so what about the googlePage variable - how can I use the first code, I currently have it in another file that gets called in specs. Is that right - sorry for the newb questions, I could probably use a chat.Roundly
D
1

You should use waitForAngularEnabled to enable/disable waiting for Angular tasks with protractor. By default it will be enabled. You can use the following:

// do things on your Angular application
// go to external oauth page

waitForAngularEnabled(false)

// login on oauth page and redirect to your Angular application

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

waitForAngularEnabled returns a promise. The browser.get function blocks until the Angular page is loaded.

Dogeared answered 24/5, 2020 at 19:12 Comment(1)
I don't know why this is downvoted. Can this be clarified? The answer posted by demee and Riley Lark works but now that waitForAngularEnabled is available we should use that instead of sleep timers. Set the timer too high and your tests will take long, set them too low and your tests may fail. This answer will immediately continue with testing your Angular application the moment we switch back from the oauth page.Dogeared

© 2022 - 2024 — McMap. All rights reserved.