How to write reusable methods in cucumber + Protractor - protractor

/* jshint -W117 */
'use strict';
var Support = require('./_support');
var $ = new Support();
var LoginPage = require('./login.page');
/**
* Collection of basic steps definitions reusable across features
*/
var basicSteps = function (page) {
//var page = new LoginPage();
this.Then(/^the title should equal "([^"]*)"$/, function (arg1, callback) {
$.expect(browser.getTitle()).to.eventually.equal(arg1).and.notify(callback);
});
this.Then(/^I should see validation message "([^"]*)"$/, function (arg1, callback) {
$.expect(LoginPage.hasErrors()).to.eventually.equal(arg1).and.notify(callback);
});
this.Then(/^the "([^"]*)" button should be disabled$/, function (arg1, callback) {
$.expect(LoginPage.buttonDisabled.isEnabled()).to.eventually.to.equal(false).and.notify(callback);;
});
};
module.exports = basicSteps;
I want to use this code to all page files to make reusable.
I have other login.page file so if I create any other page file as per the feature I should be able to reuse this code

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

Is it possible to bind two models to one control?

I need to display two numbers in my StandardTile. The data source is a SOAP web service, which I had to call twice with different parameters to obtain these two numbers. Is there any way to fill the tile with these two figures? I tried creating one XMLModel per each ajax call to the web service, and then binding the property of the control to the node from the response, but I'm just getting the same figure duplicated.
Below is my onInit method in the controller
onInit: function () {
// callback from ajax request
SOAPRequester.getMessageOverview(function (data) {
var oModel = new sap.ui.model.xml.XMLModel();
oModel.setData(data);
var oStandardTile = sap.ui.getCore().byId("__xmlview0--messageOverviewTile");
if (oStandardTile !== undefined) {
oStandardTile.setModel(oModel, "overview");
oStandardTile.bindProperty("number", {
path: "/SOAP-ENV:Body/rpl:getMessageListResponse/Response/rn5:number",
model: "overview"
});
}
});
//callback from the second ajax call
SOAPRequester.getErrorMessages(function (callbackData) {
var oModel = new sap.ui.model.xml.XMLModel();
oModel.setData(callbackData);
var oStandardTile = sap.ui.getCore().byId("__xmlview0--messageOverviewTile");
if (oStandardTile !== undefined) {
oStandardTile.setModel(oModel, "overview");
oStandardTile.bindProperty("infoState", "Error");
oStandardTile.bindProperty("info", {
path: "/SOAP-ENV:Body/rpl:getMessageListResponse/Response/rn5:number",
model: "overview"
});
}
});
},
Yes, it is. You are using the same model name twice, thus the firs one is not visible anymore. Simply use different model name, i.e. "overview" and "overview2" or what ever you prefer:
onInit: function () {
// callback from ajax request
SOAPRequester.getMessageOverview(function (data) {
var oModel = new sap.ui.model.xml.XMLModel();
oModel.setData(data);
var oStandardTile = sap.ui.getCore().byId("__xmlview0--messageOverviewTile");
if (oStandardTile !== undefined) {
oStandardTile.setModel(oModel, "overview");
oStandardTile.bindProperty("number", {
path: "/SOAP-ENV:Body/rpl:getMessageListResponse/Response/rn5:number",
model: "overview"
});
}
});
//callback from the second ajax call
SOAPRequester.getErrorMessages(function (callbackData) {
var oModel = new sap.ui.model.xml.XMLModel();
oModel.setData(callbackData);
var oStandardTile = sap.ui.getCore().byId("__xmlview0--messageOverviewTile");
if (oStandardTile !== undefined) {
oStandardTile.setModel(oModel, "overview2");
oStandardTile.bindProperty("infoState", "Error");
oStandardTile.bindProperty("info", {
path: "/SOAP-ENV:Body/rpl:getMessageListResponse/Response/rn5:number",
model: "overview2"
});
}
});
},
Hint: You could also improve your code a little, i.e.
call this.getView().byId("messageOverviewTile") or if you have the right UI5 version this.byId("messageOverviewTile") instead of sap.ui.getCore().byId("__xmlview0--messageOverviewTile")
Do the binding for your controls in your view and then in onInit() call this.getView().setModel(oModel, "overview") and this.getView().setModel(oModel, "overview2")

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'

Use Protractor browser.driver as a variable

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

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.