Use Protractor browser.driver as a variable - protractor

I'm using page object model and I'm stuck at how to put the browser.driver elements as a variable.
Here is an example of using it with Protractor's element:
var Messages = function() {};
var messagesLink = element(by.css('a[href*="/Messages"]'));
Messages.prototype.visitPage = function() {
messagesLink.click();
};
exports.Messages = new Messages();
Then I can use Messages.visitPage(); throughout my test. The problem is when I try to do the same thing with browser.driver:
var Login = function() {};
var usernameField = browser.driver.findElement(by.id('UserName'));
var passwordField = browser.driver.findElement(by.id('Password'));
var signOnButton = browser.driver.findElement(by.css('input[value="Sign On"]'));
var registeredUserName = 'user';
var registeredUserPass = 'pass';
Login.prototype.loginAsRegisteredUser = function() {
loginAs(registeredUserName, registeredUserPass);
};
var loginAs = function(userName, pass) {
usernameField.sendKeys(userName);
passwordField.sendKeys(pass);
signOnButton.click();
};
exports.Login = new Login();
The test instantly fails before even starting, throwing this error NoSuchElementError: Unable to locate element: *[id="UserName"]. The reason why I'm using browser.driver is because I'm accessing elements on a non-angular page. I want to try and keep angular and non-angular references separate from each other.
I'm not sure how Protractor handles this but in Selenium I can use the variable like so, static By cancelButton = By.id("cphMain_btnCancel");.
So, is there anyway that this can be done using Protractor?
Spec File:
var home = require('../../pages/home/Home.js').Home;
var headerHome = require('../../pages/home/HeaderHome.js').HeaderHome;
var login = require('../../pages/Login.js').Login;
describe('Registered User | DEV_Smoke |--- Home page: ', function() {
it('Navigates to the Home page', function() {
home.visitPage();
});
it('Prints the current URL (see build.log)', function() {
home.verifyHomeUrl();
});
it('Clicks Sign On link and signs in as a registered user', function() {
headerHome.clickSignOnLink();
login.loginAsRegisteredUser();
});
});

Easiest way would be to just wrap the findElement in functions and call them as needed
var Login = function() {};
var usernameField = function() {
return browser.driver.findElement(by.id('UserName')); //returns promise
}
var passwordField = function() {
return browser.driver.findElement(by.id('Password'));
}
var signOnButton = function() {
return browser.driver.findElement(by.css('input[value="Sign On"]'));
}
var registeredUserName = 'user';
var registeredUserPass = 'pass';
Login.prototype.loginAsRegisteredUser = function() {
loginAs(registeredUserName, registeredUserPass);
};
var loginAs = function(userName, pass) {
usernameField().sendKeys(userName);
passwordField().sendKeys(pass);
signOnButton().click();
};
exports.Login = new Login();

browser.driver is of type Webdriver and when calling findElement, selenium-webdriver will try to evaluate wherever it is stated in your code. So prior to your login method and possibly navigation to the login page, you are automatically looking for the WebElements for UserName, Password, and input[value="SignOn"].
In your code snippet, it looks like you should use element. When using element, at runtime, the findElement will be evaluated. This allows for more reusable code.
For non-angular pages, you might have to provide your own syncing or some arbitrary sleep. This usually occurs with animations, long load screens, etc.
Also make sure you return your promises so the jasmine wrapper evaluates your function properly.
var usernameField = element(by.id('UserName'));
var passwordField = element(by.id('Password'));
var signOnButton = element(by.css('input[value="Sign On"]'));
// make sure you return your promises so the jasmine wrapper
// evaluates your function properly.
var loginAs = function(userName, pass) {
return usernameField.sendKeys(userName).then(() => {
return passwordField.sendKeys(pass).then(() => {
return signOnButton.click();
});
});
};

Related

Protractor POM method is not recognizing

spec.js
describe('Testing an animal adoption flow using page object', function() {
beforeEach(function() {
browser.get('http://www.thetestroom.com/jswebapp/index.html');
});
var home_page = require('./pages/home_page.js');
it ('Should be able to adopt an animal by page object', function() {
home_page.enterName('Blabla');
expect(home_page.getDynamicText()).toBe('Blabla');
var animal_page = home_page.clickContinue();
animal_page.selectAnimal(1);
var confirm_page = animal_page.clickContinue();
expect(confirm_page.getTitle()).toContain('Thank');
});
});
home_page.js
require('./animal_page.js');
var home_page = function() {
this.nameTextBox = element(by.model('person.name'));
this.dynamicText = element(by.binding('person.name'));
this.continueButton = element(by.buttonText('CONTINUE'));
this.enterName = function(name) {
this.nameTextBox.sendKeys(name);
};
this.getDynamicText = function() {
return this.dynamicText.getText();
};
this.clickContinue = function() {
this.continueButton.click();
return require('./animal_page.js');
};
};
Failures:
Testing an animal adoption flow using page object Should be able to adopt an animal by page object
Message:
[31m Failed: home_page.enterName is not a function[0m
Stack:
TypeError: home_page.enterName is not a function
You don't create an instance of your constructor function with new keyword. It should have been
var home_page = new (require('./pages/home_page.js'));
and you need to instruct js what you are exporting, so your home page should be
require('./animal_page.js');
var home_page = function() {
this.nameTextBox = element(by.model('person.name'));
this.dynamicText = element(by.binding('person.name'));
this.continueButton = element(by.buttonText('CONTINUE'));
this.enterName = function(name) {
this.nameTextBox.sendKeys(name);
};
this.getDynamicText = function() {
return this.dynamicText.getText();
};
this.clickContinue = function() {
this.continueButton.click();
return require('./animal_page.js');
};
}
module.exports = home_page; // <------ this line
but make sure you do the same with animal_page
I got the answer, we need to include
spec.js
const { browser } = require('protractor');
home_page.js
module.exports = new home_page();

Unable to use page objects in my spec file in protractor

//This is my AngularPage.cs page object file
var AngularPage= function()
{
var nameInput= element(by.model('yourName'));
var greeting = element(by.binding('yourName'));
this.get=function()
{
browser.get('http://www.angularjs.org');
};
this.setName= function(name)
{
nameInput.sendKeys(name);
};
this.getGreeting= function()
{
return greeting.getText();`
};
};
module.exports = new AngularPage();
//This is my AngularHome_spec.js file
var angularPage = require('./AngularPage.js');
describe('angularjs homepage',function()
{
var angular_page;
beforeEach(function()
{
angular_page= new AngularPage();
});
it('greetings for new user', function()
{
// var angular_page= new AngularPage();
angular_page.get();
angular_page.setName('Rahul');
expect(angular_page.getGreeting()).toEqual('Hello Rahul!');
}
);
}
);
//I am unable to use page objects in my spec file as it is throwing an error
:AngularPage is not defined
In your code, object has been created twice. First time on page "AngularPage.js" and second time on spec "AngularHome_spec.js" level.
Do following on page "AngularHome_spec.js"
module.exports = AngularPage;
Change the
var AngularPage = require('./AngularPage.js');//Capital the 'A'

When I isolate a test it passes, but when run sequentially with other tests it fails with NoSuchElementError

I was making some changes to the page objects we use for running our Protractor tests to run on Sauce Labs i.e., calling a utility method to get browser and platform so we can use the appropriate test user, and after making the change I kept getting a NoSuchElementError when running the test suite.
When I isolate the logout test, it passes, but when run in conjunction with any other files, it fails. Currently, I'm only running the login test and logout test on Chrome to limit the possible causes.
We use page objects to navigate to a testable state, in this case a login page object and a dashboard page object (logging in takes you to the dashboard).
The login page object:
'use strict';
var TestUtils = require('../../util/test-utils.js');
var HeaderPageElement = require('../page_elements/header-page-element.js');
var LoginPage = function () {
var self = this;
this.get = function () {
browser.get('http://localhost:9000/index.html');
this.header = new HeaderPageElement();
this.loginForm = element(by.name('loginForm'));
this.usernameInput = element(by.model('credentials.username'));
this.passwordInput = element(by.model('credentials.password'));
this.loginButton = element(by.name('loginButton'));
this.signupLink = element(by.xpath('//a[#ui-sref="signup"]'));
};
this.setCredentials = function (username, password) {
var deferred = protractor.promise.defer();
var testUtils = new TestUtils();
testUtils.getCapabilities().then(function (capabilities) {
return testUtils.getTestUser(capabilities.browserName, capabilities.platform);
}).then(function (testUser) {
username = username || testUser.username;
password = password || testUser.password;
self.usernameInput.sendKeys(username);
self.passwordInput.sendKeys(password);
deferred.fulfill();
});
return deferred.promise;
};
this.login = function (username, password) {
return this.setCredentials(username, password).then(function () {
return self.loginButton.click();
});
};
this.signup = function () {
return this.signupLink.click();
};
this.get();
};
module.exports = LoginPage;
The dashboard page object:
'use strict';
var LoginPage = require('./login-page.js');
var HeaderPageElement = require('../page_elements/header-page-element.js');
var ProjectCreateModalPageElement = require('../page_elements/project-create-modal-page-element.js');
var DashboardPage = function () {
var self = this;
this.get = function () {
var loginPage = new LoginPage();
loginPage.login();
this.header = new HeaderPageElement();
this.newProjectButton = element(by.name('newProjectButton'));
this.projectFilterInput = element(by.name('projectFilterInput'));
};
this.createNewProject = function (projectTitle, projectTypes) {
var deferred = protractor.promise.defer();
this.newProjectButton.click().then(function () {
var modalPage = new ProjectCreateModalPageElement();
modalPage.createNewProject(projectTitle, projectTypes);
deferred.fulfill();
});
return deferred.promise;
};
this.get();
};
module.exports = DashboardPage;
These are the tests that are being run.
The login test:
'use strict';
var LoginPage = require('./pages/login-page.js');
describe('login test', function () {
var page;
beforeEach(function () {
page = new LoginPage();
});
it('should be directed to login', function () {
expect(page.loginForm.isPresent()).toBe(true);
});
it('Login button should be disabled', function () {
expect(page.loginButton.getAttribute('disabled')).toEqual('true');
page.setCredentials('wrong', 'user').then(function () {
expect(page.loginButton.getAttribute('disabled')).toEqual(null);
});
});
it('login should fail and remain at login screen', function () {
page.login('wrong', 'user').then(function () {
expect(page.loginForm.isPresent()).toBe(true);
});
});
it('login success should redirect to dashboard', function () {
page.login().then(function () {
browser.wait(function () {
return $('#dashboard').isPresent();
});
expect($('#dashboard').isDisplayed()).toBe(true);
});
});
});
The logout test:
'use strict';
var DashboardPage = require('./pages/dashboard-page.js');
describe('logout test', function () {
var page;
beforeEach(function () {
page = new DashboardPage();
});
it('logout success should redirect to login page', function () {
page.header.logout().then(function() {
browser.wait(function () {
return $('#login').isPresent();
});
expect($('#login').isDisplayed()).toBe(true);
});
});
});
The error I get when running these tests sequentially is as follows:
NoSuchElementError: No element found using locator: by.model("credentials.username")
The line it specifies is the get method inside the DashboardPage object, whereby it instantiates a LoginPage object and calls the login method so as to navigate to the dashboard:
this.get = function () {
var loginPage = new LoginPage();
loginPage.login();
this.header = new HeaderPageElement();
this.newProjectButton = element(by.name('newProjectButton'));
this.projectFilterInput = element(by.name('projectFilterInput'));
};
For whatever reason, the usernameInput of the login page hasn't been set by the time the login method is called.
I'm quite sure it's got something to do with not having coded promises correctly, but I've been bashing my head against it for days without any success. Any help would be greatly appreciated.

Fancybox not working with Simple Configurable Products (SCP)

I need some help with Javascript to get fancybox working with SCP. The following solution has not worked for me although I'm aware I'm missing some fundamental code. The first product image works perfectly opening the fancybox lightbox but once you select from the configurable dropdowns it changes the image which then does not call the lightbox and opens in the browser.
SCP advice is:
To fix, it's often just a matter of editing the showFullImageDiv function in the scp_product_extension.js file: Change evalScripts to true if it's not already, and possibly you'll also need to remove the code which exists in a few places which looks like: product_zoom = new Product.Zoom('image', 'track', 'handle', 'zoom_in', 'zoom_out', 'track_hint');
I tried this but it's not just a simple matter of removing "product_zoom..." my understanding is that fancybox needs to be called replace this line of code.
Original:
Product.Config.prototype.showFullImageDiv = function(productId, parentId) {
var imgUrl = this.config.ajaxBaseUrl + "image/?id=" + productId + '&pid=' + parentId;
var prodForm = $('product_addtocart_form');
var destElement = false;
var defaultZoomer = this.config.imageZoomer;
prodForm.select('div.product-img-box').each(function(el) {
destElement = el;
});
if(productId) {
new Ajax.Updater(destElement, imgUrl, {
method: 'get',
evalScripts: true,
onComplete: function() {
//Product.Zoom needs the *image* (not just the html source from the ajax)
//to have loaded before it works, hence image object and onload handler
if ($('image')){
var imgObj = new Image();
imgObj.src = $('image').src;
imgObj.onload = function() {product_zoom = new Product.Zoom('image', 'track', 'handle', 'zoom_in', 'zoom_out', 'track_hint'); };
} else {
destElement.innerHTML = defaultZoomer;
product_zoom = new Product.Zoom('image', 'track', 'handle', 'zoom_in', 'zoom_out', 'track_hint')
}
}
});
} else {
destElement.innerHTML = defaultZoomer;
product_zoom = new Product.Zoom('image', 'track', 'handle', 'zoom_in', 'zoom_out', 'track_hint');
}
};
I know I need to call fancybox in the below locations but not sure how to go about it. From what I understand fancybox is called on pageload so not sure imgObj.onload will even work?
Product.Config.prototype.showFullImageDiv = function(productId, parentId) {
var imgUrl = this.config.ajaxBaseUrl + "image/?id=" + productId + '&pid=' + parentId;
var prodForm = $('product_addtocart_form');
var destElement = false;
var defaultZoomer = this.config.imageZoomer;
prodForm.select('div.product-img-box').each(function(el) {
destElement = el;
});
if(productId) {
new Ajax.Updater(destElement, imgUrl, {
method: 'get',
evalScripts: true,
onComplete: function() {
//Product.Zoom needs the *image* (not just the html source from the ajax)
//to have loaded before it works, hence image object and onload handler
if ($('image')){
var imgObj = new Image();
imgObj.src = $('image').src;
imgObj.onload = CALL FANCYBOX
} else {
destElement.innerHTML = defaultZoomer;
CALL FANCYBOX
}
}
});
} else {
destElement.innerHTML = defaultZoomer;
CALL FANCYBOX
}
};
Unfortunately my javascript is very basic and any help on what I need to add would be gratefully received. I found a few posts with the same issue but no solution.
Thanks

Was using .bind but now haved to use .delegate... have tried .undelegate?

Heres the jsfiddle, jsfiddle.net/kqreJ
So I was using .bind no problem for this function but then I loaded more updates to the page and found out that .bind doesn't work for content imported to the page but just for content already on the page! Great!
So I switched it up to .delegate which is pretty cool but now I can't figure out how to .bind .unbind my function the way it was???
Function using .bind which worked perfect... except didn't work on ajax content.. :(
$('.open').bind("mouseup",function(event) {
var $this = $(this), handler = arguments.callee;
$this.unbind('mouseup', handler);
var id = $(this).attr("id");
var create = 'nope';
var regex = /\d+$/,
statusId = $('#maindiv .open').toArray().map(function(e){
return parseInt(e.id.match(regex));
});
var divsToCreate = [ parseInt(id) ];
$.each(divsToCreate, function(i,e)
{
if ( $.inArray(e, statusId) == -1 ) {
create = 'yup';
}
});
if( create == 'yup' ) {
if(id) {
$.ajax({
type: "POST",
url: "../includes/open.php",
data: "post="+ id,
cache: false,
success: function(html) {
$('.open').html(html);
$this.click(handler);
}
});
}
}
});
New function using .delegate that is not binded and creates multiple instances?
$('#maindiv').delegate("span.open", "mouseup",function(event) {
var $this = $(this), handler = arguments.callee;
$this.unbind('mouseup', handler);
var id = $(this).attr("id");
var create = 'nope';
var regex = /\d+$/,
statusId = $('#maindiv .open').toArray().map(function(e){
return parseInt(e.id.match(regex));
});
var divsToCreate = [ parseInt(id) ];
$.each(divsToCreate, function(i,e)
{
if ( $.inArray(e, statusId) == -1 ) {
create = 'yup';
}
});
if( create == 'yup' ) {
if(id) {
$.ajax({
type: "POST",
url: "../includes/open.php",
data: "post="+ id,
cache: false,
success: function(html) {
$('.open').html(html);
$this.click(handler);
}
});
}
}
});
I've spent hours trying to figure this out because I like learning how to do it myself but I had to break down and ask for help... getting frustrated!
I also read that when your binding and unbinding .delegate you have to put it above the ajax content? I've tried using .die() and .undelegate()... Maybe I just don't know where to place it?
Take a look at undelegate
It does to delegate what unbind does to bind.
In your case, I think it'd be something like:
$('#maindiv').undelegate("span.open", "mouseup").delegate("span.open", "mouseup" ...
Then you can drop the $this.unbind('mouseup', handler); within the function.