We are using the Page Object pattern to organize our internal AngularJS application tests.
Here is an example page object we have:
var LoginPage = function () {
this.username = element(by.id("username"));
this.password = element(by.id("password"));
this.loginButton = element(by.id("submit"));
}
module.exports = LoginPage;
In a single-browser test, it is quite clear how to use it:
var LoginPage = require("./../po/login.po.js");
describe("Login functionality", function () {
var scope = {};
beforeEach(function () {
browser.get("/#login");
scope.page = new LoginPage();
});
it("should successfully log in a user", function () {
scope.page.username.clear();
scope.page.username.sendKeys(login);
scope.page.password.sendKeys(password);
scope.page.loginButton.click();
// assert we are logged in
});
});
But, when it comes to a test when multiple browsers are instantiated and there is the need to switch between them in a single test, it is becoming unclear how to use the same page object with multiple browsers:
describe("Login functionality", function () {
var scope = {};
beforeEach(function () {
browser.get("/#login");
scope.page = new LoginPage();
});
it("should warn there is an opened session", function () {
scope.page.username.clear();
scope.page.username.sendKeys(login);
scope.page.password.sendKeys(password);
scope.page.loginButton.click();
// assert we are logged in
// fire up a different browser and log in
var browser2 = browser.forkNewDriverInstance();
// the problem is here - scope.page.username.clear() would be applied to the main "browser"
});
});
Problem:
After we forked a new browser, how can we use the same Page Object fields and functions, but applied to a newly instantiated browser (browser2
in this case)?
In other words, all element()
calls here would be applied to browser
, but needed to be applied to browser2
. How can we switch the context?
Thoughts:
one possible approach here would be to redefine the global
element = browser2.element
temporarily while being in the context ofbrowser2
. The problem with this approach is that we also havebrowser.wait()
calls inside the page object functions. This means thatbrowser = browser2
should be also set. In this case, we would need to remember thebrowser
global object in a temp variable and restore it once we switch back to the mainbrowser
context..another possible approach would be to pass the browser instance into the page object, something like:
var LoginPage = function (browserInstance) { browser = browserInstance ? browserInstance : browser; var element = browser.element; // ... }
but this would probably require to change every page object we have..
Hope the question is clear - let me know if it needs clarification.
Maybe you could write few functions to make the the browser registration/start/switch smoother. (Basically it is your first option with some support.)
For example:
And you use it this way in your example:
So you can leave your page objects as they are.
To be honest I think your second approach is cleaner... Using global variables can bite back later. But if you don't want to change your POs, this can also work.
(I did not test it... sorry for the likely typos/errors.) (You can place the support functions to your protractor conf's onprepare section for example.)
Look at my solution. I simplified example, but we are using this approach in current project. My app has pages for both user permissions types, and i need to do some complex actions same time in both browsers. I hope this might show you some new, better way!