Protractor- Generic wait for URL to change - javascript

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

Related

How to return string(variable value) from page object to test file in nightwatch?

I am practicing page object model in nightwatch but I am unable to return variable optionSelected value from page object to test. I have two pages: googleAdvanceSearch page where i have written locators to select options eg-what to search,result shud be of past month,country,language etc and result page where I am verifying if result is according to option selected.
googleAdvanceSearch Page code:
module.exports= {
url:"https://www.google.co.in/advanced_search",
elements: {
input :"input[name='as_q']",
languageBox :"#lr_button",
regionButton :"#cr_button",
lastUpdateBtn :"#as_qdr_button",
searchButton :"input[value='Advanced Search']",
},
commands: [
{
setQuery: function(value) {
return this.setValue('#input',value);
},
selectFilter: function(selector,value) {
return this
.click(selector)
.click(`li[value="${value}"]`);
},
selectAndStoreOption: function(selector,value) {
var optionSelected;
this.click(selector)
.getText(`li[value='${value}']`,function(result){
optionSelected=result.value;
})
.click(`li[value="${value}"]`)
return optionSelected; //Can we return like this?
},
clickSubmit: function() {
return this.click('#searchButton');
}
}
]
}
In selectAndStoreOption function, I am trying to save text 'upto a month ago' of 'm' option (.getText(li[value='${value}']) selected in a variable optionSelected.
Result page code :
module.exports= {
elements: {
monthBox : "div[class='hdtb-mn-hd EISXeb']"
},
commands: [
{
verifyCorrectResultDisplayed: function(optionSelected,textToSearch) {
this
.assert.urlContains("as_q=Elon+Musk","Elon Musk searched")
.assert.urlContains("lr=lang_pl","Language polish set")
.assert.visible(`input[title='Search'][value='${textToSearch}']`,"Search result visible on UI");
this.getText('#monthBox',function(result) {
if(optionSelected=="upto a month ago") { **//optionSelected is undefined**
this.assert.containsText('#monthBox',"Past month","Past month result displayed"); **//This assertion not printing**
}
})
return this;
}
}
]
}
Here I am checking if optionSelected variable value is "upto a month ago" then my result should contain Past month text. But this assertion is not working as optionSelected value is undefined.
Test:
module.exports= {
"#tags":["google"],
before : function(browser) {
let googleSearchPage=browser.page.googleSearchPage();
googleSearchPage.navigate();
browser.windowMaximize();
},
"Google Advanced Search Test": function(browser) {
var googleSearchPage=browser.page.googleSearchPage();
var resultPage=browser.page.resultPage();
var data=browser.globals;
googleSearchPage
.setQuery(data.googleAdvancedSearch.textToSearch)
.selectFilter('#languageBox','lang_pl')
.selectFilter('#regionButton','countryPH');
var optionSelec= googleSearchPage.selectAndStoreOption('#lastUpdateBtn','m');
console.log(optionSelec);
googleSearchPage.clickSubmit();
resultPage
.verifyCorrectResultDisplayed(optionSelec,data.googleAdvancedSearch.textToSearch);
},
after : function(browser) {
browser
.saveScreenshot('tests_output/google.png')
.end();
}
}
Please help
You must be aware of javascript being executed synchronously, which may result in your code being executed not necessarily in the same order as it is written somewhat.
For that reason, I suggest you to make your "Google Advanced Search Test" function asynchronous like so:
asynch function (browser) {
....
}
Then it will allow you to await for your actions on googleSearchPage like so:
var optionSelect = await googleSearchPage
.setQuery(data.googleAdvancedSearch.textToSearch)
.selectFilter('#languageBox','lang_pl')
.selectFilter('#regionButton','countryPH');
.selectAndStoreOption('#lastUpdateBtn','m');
console.log(optionSelec);

Using execute command in Page Objects in Nightwatch JS

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

load data from module before test executes

(I asked this question recently and accepted an answer but it's still not what I need.) I really need to create dynamic tests from data loaded from a module. Each item from the array will have it's own describe statement with certain protractor actions. My previous post has an answer that says to use an it statement, but I can't do that because there's too much going on.
My main problem is that the data doesn't get loaded in time for the describe. I had another suggestion to use VCR.js or something similar but I don't think those will work because I'm using a module. Is there a way I can save the data to a separate file and load it in? Would that be a good way to go?
var data = require('get-data'); //custom module here
describe('Test', function() {
var itemsArr;
beforeAll(function(done) {
data.get(function(err, result) {
itemsArr = result; //load data from module
done();
});
})
//error: Cannot read property 'forEach' of undefined
describe('check each item', function() {
itemsArr.forEach(function(item) {
checkItem(item);
});
});
function checkItem (item) {
var itemName = item.name;
describe(itemName, function() {
console.log('describe');
it('should work', function() {
console.log('it');
expect(true).toBeTruthy();
});
});
}
});
UPDATE:
I used Eugene's answer and came up with this. I can't test each individual study how I want because the it statement doesn't fire. Is this problem even solvable??
describe('check each item', function () {
it('should load data', function (done) {
browser.wait(itemsPromise, 5000);
itemsPromise.then(function(itemsArr) {
expect(itemsArr).toBeTruthy();
studyArr = itemsArr.filter(function (item) {
return item.enabled && _.contains(item.tags, 'study');
});
studyCount = studyArr.length;
expect(studies.count()).toEqual(studyCount);
checkItems(studyArr);
done();
});
});
function checkItems (itemsArr) {
itemsArr.forEach(function (item) {
describe(item.id, function () {
console.log('checkItems', item.id);
// doesn't work
it('should work', function (done) {
expect(false).toBeTruthy();
done();
});
});
});
}
});
You're trying to do something that Jasmine does not allow: generating tests after the test suite has started. See this comment on an issue of Jasmine:
Jasmine doesn't support adding specs once the suite has started running. Usually, when I've needed to do this, I've been able to know the list of options ahead of time and just loop through them to make the it calls. [...]
("adding specs" === "adding tests")
The point is that you can generate tests dynamically but only before the test suite has started executing tests. One corollary of this is that the test generation cannot be asynchronous.
Your second attempt does not work because it is trying to add tests to a suite that is already running.
Your first attempt is closer to what you need but it does not work either because describe calls its callback immediately, so beforeAll has not run by the time your describe tries to generate the tests.
Solutions
It all boils down to computing the value of itemsArr before the test suite start executing tests.
You could create a .getSync method that would return results synchronously. Your code would then be something like:
var data = require('get-data'); //custom module here
var itemsArr = data.getSync();
describe('Test', function() {
describe('check each item', function() {
itemsArr.forEach(function(item) {
checkItem(item);
});
});
[...]
If writing .getSync function is not possible, you could have an external process be responsible for producing a JSON output that you could then deserialize into itemsArr. You'd execute this external process with one of the ...Sync functions of child_process.
Here's an example of how the 2nd option could work. I've created a get-data.js file with the following code which uses setTimeout to simulate an asynchronous operation:
var Promise = require("bluebird"); // Bluebird is a promise library.
var get = exports.get = function () {
return new Promise(function (resolve, reject) {
var itemsArr = [
{
name: "one",
param: "2"
},
{
name: "two",
param: "2"
}
];
setTimeout(function () {
resolve(itemsArr);
}, 1000);
});
};
// This is what we run when were are running this module as a "script" instead
// of a "module".
function main() {
get().then(function (itemsArr) {
console.log(JSON.stringify(itemsArr));
});
};
// Check whether we are a script or a module...
if (require.main === module) {
main();
}
Then, inside the spec file:
var child_process = require('child_process');
var itemsArr = JSON.parse(child_process.execFileSync(
"/usr/bin/node", ["get-data.js"]));
describe('Test', function() {
itemsArr.forEach(function(item) {
checkItem(item);
});
function checkItem (item) {
var itemName = item.name;
describe(itemName, function() {
console.log('describe');
it('should work', function() {
console.log('it');
expect(true).toBeTruthy();
});
});
}
});
I've tested the code above using jasmine-node. And the following file structure:
.
├── data.js
├── get-data.js
└── test
└── foo.spec.js
./node_modules has bluebird and jasmine-node in it. This is what I get:
$ ./node_modules/.bin/jasmine-node --verbose test
describe
describe
it
it
Test - 5 ms
one - 4 ms
should work - 4 ms
two - 1 ms
should work - 1 ms
Finished in 0.007 seconds
2 tests, 2 assertions, 0 failures, 0 skipped
Try to use a promise, something like:
var deferred = protractor.promise.defer();
var itemsPromise = deferred.promise;
beforeAll(function() {
data.get(function(err, result) {
deferred.fulfill(result);
});
})
And then:
describe('check each item', function() {
itemsPromise.then(function(itemsArr) {
itemsArr.forEach(function(item) {
checkItem(item);
});
});
});
Another solution I can think of is to use browser.wait to wait until itemsArr becomes not empty.
Is your get-data module doing some browser things with protractor? If so, you will need to set/get itemsArr within the context of the controlFlow. Otherwise it will read all the code in the get-data module, but defer its execution and not wait for it to finish before moving right along to those expect statements.
var data = require('get-data'); //custom module here
var itemsArr;
describe('Test', function() {
beforeAll(function() {
// hook into the controlFlow and set the value of the variable
browser.controlFlow().execute(function() {
data.get(function(err, result) {
itemsArr = result; //load data from module
});
});
});
//error: Cannot read property 'forEach' of undefined
describe('check each item', function() {
// hook into the controlFlow and get the value of the variable (at that point in time)
browser.controlFlow().execute(function() {
itemsArr.forEach(function(item) {
checkItem(item);
});
});
});
function checkItem (item) {
var itemName = item.name;
describe(itemName, function() {
console.log('describe');
it('should work', function() {
console.log('it');
expect(true).toBeTruthy();
});
});
}
});

Nightmarejs multiple pages in same test

I'm trying to pull the title-tag text from two webpages on a Drupal site. I want to use Nightmarejs.
Here is my code so far:
// get the <title> text from inside the Drupal site
var Nightmare = require('nightmare');
var user = 'foobar#example.com';
var pass = 'foobar';
new Nightmare()
.goto('http://example.com/user')
.type('#edit-name', user)
.type('#edit-pass', pass)
.click('.searchsubmit')
.wait()
.evaluate(function () {
return document.getElementsByTagName("TITLE")[0];
}, function (res) {
console.log('Homepage title: '+res.text);
})
.run(function(err, nightmare){
console.log('Done1.');
// step 2
nightmare
.goto('http://example.com/admin')
.wait()
.evaluate(function () {
return document.getElementsByTagName("TITLE")[0];
}, function (res) {
console.log('Admin page title: '+res.text);
})
.run(function(err, nightmare){
console.log('Done2.');
})
;
})
;
When I run this, with: node app.js I am able to log in successfully to the first page. Unfortunately when I try to open the second page I see an access refused on the second page call (http://example.com/admin). The session is not being carried into the second "goto" command.
What can I do to be able to open up many pages with the same nightmarejs session?
Have you tried chaining the goto methods?
new Nightmare()
.goto('http://example.com/user')
.type('#edit-name', user)
.type('#edit-pass', pass)
.click('.searchsubmit')
.wait()
.evaluate(function () {
return document.getElementsByTagName("TITLE")[0];
}, function (res) {
console.log('Homepage title: '+res.text);
})
.goto('http://example.com/admin')
.wait()
.evaluate(function () {
return document.getElementsByTagName("TITLE")[0];
}, function (res) {
console.log('Admin page title: '+res.text);
})
.run(function(err, nightmare){
console.log('Done2.');
})
;
}).run();
From reading the api docs run only executes the commands that came before it.
After some testing, I discovered that it seems goto() should be used only once. In order to switch to a new page, I use click() instead of an additional goto().

Emberjs Change routes to !# for seo? [duplicate]

I am trying to set up my Router to use "hashbang" URLs (#!).
I tried this, but obviously it doesn't work:
App.Router.map(function() {
this.route("index", { path: "!/" });
this.route("otherState", { path: "!/otherState" });
});
Is this possible to do in Ember?
Teddy Zeenny's answer is mostly correct, and registerImplementation seems to be a clean way to implement this. I tried to just edit his answer to make it fully answer the question, but my edit got rejected.
Anyway here is the full code to make Ember use hashbang URLs:
(function() {
var get = Ember.get, set = Ember.set;
Ember.Location.registerImplementation('hashbang', Ember.HashLocation.extend({
getURL: function() {
return get(this, 'location').hash.substr(2);
},
setURL: function(path) {
get(this, 'location').hash = "!"+path;
set(this, 'lastSetURL', "!"+path);
},
onUpdateURL: function(callback) {
var self = this;
var guid = Ember.guidFor(this);
Ember.$(window).bind('hashchange.ember-location-'+guid, function() {
Ember.run(function() {
var path = location.hash.substr(2);
if (get(self, 'lastSetURL') === path) { return; }
set(self, 'lastSetURL', null);
callback(location.hash.substr(2));
});
});
},
formatURL: function(url) {
return '#!'+url;
}
}));
})();
Then once you create your app you need to change the router to utilize the "hashbang" location implementation:
App.Router.reopen({
location: 'hashbang'
})
Extending Ember.HashLocation would be the way to go.
For a clean implementation, you can do the following.
Ember.Location.registerImplementation('hashbang', Ember.HashLocation.extend({
// overwrite what you need, for example:
formatURL: function(url) {
return '#!' + url;
}
// you'll also need to overwrite setURL, getURL, onUpdateURL...
})
Then instruct your App Router to use your custom implementation for location management:
App.Router.reopen({
location: 'hashbang'
})

Categories

Resources