I have a problem with implementing Page Object in Nightwatch. Let's say that I have a login scenario. I need to scroll to the element - I'm using for thar execute function.
module.exports = {
'Login' : function (browser) {
browser.url(this.launchUrl)
.setValue('input[name=username]', 'admin')
.setValue('input[name=password]', 'password')
.execute(function () {
document.querySelector('input[type=submit]').scrollIntoView();
}, [])
.click('input[type=submit]');
browser.end();
}
}
I'd like to refactor this login code into Page Object like that
module.exports = {
url: function() {
return this.api.launchUrl;
},
commands: [scrolling],
elements: {
usernameField: {
selector: 'input[name=username]'
},
passwordField: {
selector: 'input[name=password]'
},
submit: {
selector: 'input[type=submit]'
}
}
};
I'd like to 'hide' also this execute command and pack it into commands, like that:
var scrolling = {
scroll: function(){
return this.execute(function () {
document.querySelector(input[type=submit]').scrollIntoView();
}, []);
}
};
Unfortunately it seems that execute command doesn't work with Page Object.
How I can overcome this issue with executing JavaScript code when I want to use Page Object? How can I encapsulate it?
The answer was very simple
1) There was a quotation mark missing in a selector.
2) Using execute() in Object Pattern it is needed to run it using this.api :
this.api.execute(function () {
document.querySelector('input[type=submit]').scrollIntoView();
}, []);
Found the answer
ForthStCheck:function(){
this.api.execute('scrollTo(0,500)')
this.waitForElementVisible('#forthStationPlayBtn',5000)
}
Related
Hi I am new to javascript and Nightwatch, I am a manual tester who started doing automation about 6 months ago.
I am writing test cases for checking the details of a product, with collapsible menus. Pressing + button will open and display a list of elements, when closing with the same button, it closes the list, and shows a counter with the number of items on the list.
I have a function that is correctly doing this procedure, but I have it written on the test. I would like to use it in the Page where I have all elements and functions related to that page. And I would like to call that function from the test. I have been able to do this, but not with cases with nested functions, because I do not know how to write it.
These are my pages:
loginPage.js;
productPage.js;
productFuntionalityListPage.js;
This is my test:
module.exports = {
'Buy a Product with Bank Account': function (browser) {
const login = browser.page.loginPage();
const productList = browser.page.productPage();
const productFunctionalityList = browser.page.productFuntionalityListPage();
login
.navigate()
.checkLoginPage();
productList
.getAProduct()
//------------------------------------------Features--------------------------------------
//function to click on each button for functionalities and wait for list to appear
function displayFunctionsList(elems) {
elems.value.forEach(function (element) {
browser.elementIdClick(element.ELEMENT)
//wait for list to appear
.waitForElementVisible('.list_of_items')
.pause(2000)
})
}
// click on each function and wait for list to appear
browser.elements('css selector', '.expand_collapse_btn', displayFunctionsList, 5000)
browser.useCss()
// close each function
function closeFunctionsList(elems) {
elems.value.forEach(function (element) {
browser.elementIdClick(element.ELEMENT)
//after click close wait for count to appear
.waitForElementVisible("input[data-id='counter']")
.pause(2000)
})
}
browser.elements('css selector', '.expand_collapse_btn', closeFunctionsList, 2000)
browser.end()
}
}
This is working correctly.
Below it's what I have tried and does not work:
Page:
productFuntionalityListPage.js
module.exports = {
elements: {
counterOfItemsInList: {
locatorStrategy: 'css selector'
selector: "input[data-id='counter']",
},
expandCollapseBtn: {
locateStrategy: 'css selector',
selector: '.expand_collapse_btn',
},
listOfItems: {
locateStrategy: 'css selector',
selector: '.list_of_items',
}
},
commands: [{
displayFunctionsList: function () {
function displayFunctionsList(elems) {
elems.value.forEach(function (element) {
this.elementIdClick(element.ELEMENT)
//wait for list to appear
.waitForElementVisible('#listOfItems')
.pause(2000)
})
}
this.elements('css selector', '#expandCollapseBtn', displayFunctionsList, 5000)
},
closeFunctionsList: function () {
function closeFunctionsList(elems) {
elems.value.forEach(function (element) {
this.elementIdClick(element.ELEMENT)
//wait for list to appear
.waitForElementVisible('#counterOfItemsInList')
.pause(2000)
})
}
this.elements('css selector', '#expandCollapseBtn', closeFunctionsList, 5000)
}
}]
}
Test calling function from page:
module.exports = {
'Buy a Product with Bank Account': function (browser) {
const login = browser.page.loginPage();
const productList = browser.page.productPage();
const productFunctionalityList = browser.page.productFuntionalityListPage();
login
.navigate()
.checkLoginPage();
productList
.getAProduct()
//------------------------------------------Features--------------------------------------
//calling displayFunctionsList from productFuntionalityListPage.js
productFunctionalityList.displayFunctionsList()
//calling closeFunctionsList from productFuntionalityListPage.js
productFunctionalityList.closeFunctionsList()
browser.end()
}
}
Result after running the test above:
Error:
TypeError: this.elements is not a function
- writing an ES6 async test case? - keep in mind that commands return a Promise;
- writing unit tests? - make sure to specify "unit_tests_mode=true" in your config.
Could anyone please help me adding these functions as custom commands in the productFuntionalityListPage.js and call these functions from the test itself? Not sure what's wrong, because of my lack of javascript and nightwatch knowledge.
Try passing browser as a variable when calling the function like this -
##Test page##
//Example call
gmail.selectEmail(browser, 'browser authentication')
And then the method in the pageObject -
##Page Object##
//Example Method
selectEmail(browser, searchValue){
browser.blah(searchValue);
browser.blah
browser.blah
};
Its slightly messy way of getting it to work but this has saved my bacon a few times
When writing tests for my web app; I have to first simulate login before the rest of my tests can run and see inner pages. Right now I'm working on modulating the code, so that way I can just make an 'include' for the common function; such as my login. But as soon as I move the below code in a separate file, and call the include via require - it no longer runs as expected.
ie. the below logs in and allows my other functions, if, included in the same file. above my other inner screen functions.
// Login screen, create opportunity
this.LoginScreen = function(browser) {
browser
.url(Data.urls.home)
.waitForElementVisible('#login', 2000, false)
.click('#login')
.waitForElementVisible('div.side-panel.open', 4000, false)
.waitForElementVisible('input#email', 2000, false)
.waitForElementVisible('input#password', 2000, false)
.click('input#email')
.pause(500)
.setValue('input#email', Data.ProjMan.username)
.click('input#password')
.pause(500)
.setValue('input#password', Data.ProjMan.password)
.click('input#email')
.pause(500)
.click('div.form.login-form .btn')
.pause(5000)
Errors.checkForErrors(browser);
};
// Inner functions run after here, sequentially
But as soon as I move the above in a separate file, for instance; Logins.js, then call it at the top of the original test file with. (yes, correct path).
var Logins = require("../../lib/Logins.js");
It just doesn't simulate the login anymore. Any thoughts? Should I remove the this.LoginScreen function wrapper, and call it differently to execute from the external file, or do I need to fire it from the original file again, aside from the external require path?
I have also tried wrapping 'module.exports = {' around the login function from separate file, but still failing.
Nightwatch allows you to run your Page object based tests i.e you can externalize your common test functions and use them in your regular tests. This can be achieved using 'page_objects_path' property. I have added the common 'login' functionality and used it in sample 'single test' in the project here.
Working:
Place your common function in .js file and place it under a folder(ex: tests/pages/login.js) and pass the folder path in nighwatch config file as below:
nightwatch_config = {
src_folders : [ 'tests/single' ],
page_objects_path: ['tests/pages'],
Below is an example of common login function (login.js):
var loginCommands = {
login: function() {
return this.waitForElementVisible('body', 1000)
.verify.visible('#userName')
.verify.visible('#password')
.verify.visible('#submit')
.setValue('#userName', 'Enter Github user name')
.setValue('#password', 'Enter Github password')
.waitForElementVisible('body', 2000)
}
};
module.exports = {
commands: [loginCommands],
url: function() {
return 'https://github.com/login';
},
elements: {
userName: {
selector: '//input[#name=\'login\']',
locateStrategy: 'xpath'
},
password: {
selector: '//input[#name=\'password\']',
locateStrategy: 'xpath'
},
submit: {
selector: '//input[#name=\'commit\']',
locateStrategy: 'xpath'
}
}
};
Now, in your regular test file, create an object for the common function as below and use it.
module.exports = {
'Github login Functionality' : function (browser) {
//create an object for login
var login = browser.page.login();
//execute the login method from //tests/pages/login.js file
login.navigate().login();
//You can continue with your tests below:
// Also, you can use similar Page objects to increase reusability
browser
.pause(3000)
.end();
}
};
The above answer is absolutly correct however I did struggle with how to supply login user details.
This is what I ended up using:
var loginCommands = {
login: function() {
return this.waitForElementVisible('body', 1000)
.setValue("#email", "<some rnd email address>")
.setValue('#password', "<some rnd password>")
.click('button[type=submit]')
.pause(1000)
}
};
module.exports = {
commands: [loginCommands],
url: function() {
return 'https://example.com/login';
}
};
This can be used in the same way as the accepted answer just posting for others who come searching.
I'm enjoying working with Meteor and trying out new things, but I often try to keep security in mind. So while I'm building out a prototype app, I'm trying to find the best practices for keeping the app secure. One thing I keep coming across is restricting a user based on either a roll, or whether or not they're logged in. Here are two examples of issues I'm having.
// First example, trying to only fire an event if the user is an admin
// This is using the alaning:roles package
Template.homeIndex.events({
"click .someclass": function(event) {
if (Roles.userIsInRole(Meteor.user(), 'admin', 'admin-group') {
// Do something only if an admin in admin-group
}
});
My problem with the above is I can override this by typing:
Roles.userIsInRole = function() { return true; } in this console. Ouch.
The second example is using Iron Router. Here I want to allow a user to the "/chat" route only if they're logged in.
Router.route("/chat", {
name: 'chatHome',
onBeforeAction: function() {
// Not secure! Meteor.user = function() { return true; } in the console.
if (!Meteor.user()) {
return this.redirect('homeIndex');
} else {
this.next();
}
},
waitOn: function () {
if (!!Meteor.user()) {
return Meteor.subscribe("messages");
}
},
data: function () {
return {
chatActive: true
}
}
});
Again I run into the same problem. Meteor.user = function() { return true; } in this console blows this pattern up. The only way around this I have found thus far is using a Meteor.method call, which seems improper, as they are stubs that require callbacks.
What is the proper way to address this issue?
Edit:
Using a Meteor.call callback doesn't work for me since it's calling for a response asynchronously. It's moving out of the hook before it can handle the response.
onBeforeAction: function() {
var self = this;
Meteor.call('someBooleanFunc', function(err, res) {
if (!res) {
return self.redirect('homeIndex');
} else {
self.next();
}
})
},
I guess you should try adding a check in the publish method in server.
Something like this:
Meteor.publish('messages') {
if (Roles.userIsInRole(this.userId, 'admin', 'admin-group')) {
return Meteor.messages.find();
}
else {
// user not authorized. do not publish messages
this.stop();
return;
}
});
You may do a similar check in your call methods in server.
In previous questions I have seen that a nice way to wait for the url to change is to use:
browser.wait( function() {
return browser.getCurrentUrl().then(function(url) {
return /myURL/.test(url);
});
}, 10000, "url has not changed");`
But I am trying to have a method that I can pass myURL as a variable (in case I need to use it with other sites) and is not working.
I am trying this in my Page Object file:
this.waitUrl = function(myUrl) {
browser.wait( function(myUrl) {
return browser.getCurrentUrl().then(function(url, myUrl) {
return myUrl.test(url);
});
}, 10000, "url has not changed");
};
Any ideas if this is even possible and how to do it if so?
Update (July 2016): with Protractor 4.0.0 you can solve it with urlIs and urlContains built-in Expected Conditions.
Original answer:
Don't pass myUrl inside the then function, it is available from the page object function scope:
browser.wait(function() {
return browser.getCurrentUrl().then(function(url) {
return myUrl.test(url);
});
}, 10000, "url has not changed");
I would though define it as an Expected Condition:
function waitUrl (myUrl) {
return function () {
return browser.getCurrentUrl().then(function(url) {
return myUrl.test(url);
});
}
}
So that you can then use it this way:
browser.wait(waitUrl(/my\.url/), 5000);
For those that want an example for Protractor 4.0.0 through 5.3.0
You can use "ExpectedConditions" like so...
var expectedCondition = protractor.ExpectedConditions;
// Waits for the URL to contain 'login page'.
browser.wait(expectedCondition.urlContains('app/pages/login'), 5000);
If you want to validate this with an e2e test.
it('should go to login page', function() {
loginPage.login();
const EC = protractor.ExpectedConditions;
browser.wait(EC.urlContains('app/pages/login'), 5000).then(function(result) {
expect(result).toEqual(true);
});
});
I am trying to add an WatchList feature in the existing code discourse ember rails application
I have addded the following code
Discourse.Route.buildRoutes(function() {
var router = this;
this.resource('watchLists', { path: '/watch_lists' }, function() {
this.resource('watchList', {path: ':watch_list_id'});
});
});
In the ember Controller
Discourse.WatchListsController = Discourse.ObjectController.extend({});
In the ember model
Discourse.WatchList = Discourse.Model.extend({});
Discourse.WatchList.reopenClass({
find: function() {
jQuery.getJSON("watch_lists").then(function(json) {
var watch_lists = json.watch_lists.map(function(attrs) {
return Discourse.WatchList.create(attrs);
});
});
In the ember view js
Discourse.WatchListsView = Ember.View.extend({});
In ember route js
Discourse.WatchListsRoute = Discourse.Route.extend({
model: function() {
return Discourse.WatchList.find();
}
});
When i renderring the handlebars template I am getting an WatchListsController object withot the data we have got from the ajax.
Can any body point out where i am doing wrong.
I see two possible problems.
First, you probably want WatchListsController to extend Discourse.ArrayController, not Discourse.ObjectController.
Second your reopen block is not valid JavaScript in the example code that you posted. I count four { but only two }. You probably want something kind of like this:
Discourse.WatchList.reopenClass({
find: function() {
return jQuery.getJSON("watch_lists").then(function(json) {
return json.watch_lists.map(function(attrs) {
return Discourse.WatchList.create(attrs);
}
});
}
});