I am new to Mocha and Webdriver.io, so please excuse me if I am being stupid...
Here is my code -
// required libraries
var webdriverio = require('webdriverio'),
should = require('should');
// a test script block or suite
describe('Login to ND', function() {
// set timeout to 10 seconds
this.timeout(10000);
var driver = {};
// hook to run before tests
before( function () {
// load the driver for browser
driver = webdriverio.remote({ desiredCapabilities: {browserName: 'firefox'} });
return driver.init();
});
// a test spec - "specification"
it('should be load correct page and title', function () {
// load page, then call function()
return driver
.url('https://ND/ilogin.php3')
// get title, then pass title to function()
.getTitle().then( function (title) {
// verify title
(title).should.be.equal("NetDespatch Login");
// uncomment for console debug
console.log('Current Page Title: ' + title);
return driver.setValue("#userid", "user");
return driver.setValue("#password", "pass");
return driver.click("input[alt='Log in']");
});
});
// a "hook" to run after all tests in this block
after(function() {
return driver.end();
});
});
I can execute this with Mocha, and the test passes, even though it doesn't seem to do all of the "steps" I have defined..
It opens the page, logs the website title, and enters 'user' in the userid, BUT..
It doesn't populate the password field, or select the login link, and there doesn't appear to be any errors displayed..
Login to ND
Current Page Title: ND Login
✓ should be load correct page and title (2665ms)
1 passing (13s)
But, as it hasn't executed all the steps, I don't expect it to pass, though, I also don't understand why it won't do the last few steps.
Any help would be welcome.
Thanks
Karl
As mentioned in the original post comments, you should only have one return in your test:
it('should be load correct page and title', function () {
// load page, then call function()
return driver
.url('https://ND/ilogin.php3')
// get title, then pass title to function()
.getTitle().then( function (title) {
// verify title
(title).should.be.equal("NetDespatch Login");
// uncomment for console debug
console.log('Current Page Title: ' + title);
})
.setValue("#userid", "user")
.setValue("#password", "pass")
.click("input[alt='Log in']");
});
Related
I am beginner in using protractor. I am just trying to do a page logon in a non angular page. I tried many solutions/workarounds, the last one was based on the following link:
http://agiletesters.com.br/topic/71/protractor-page-objects-typeerror-object-object-has-no-method-metodo/7
Basically, i have two files: conf.js and login.js (see codes further). I execute the following command (in my prompt, os windows):
protractor conf.js
// Code conf.js =>
exports.config = {
framework: 'jasmine',
specs: ['login.js'],
directConnect: true
}
// Code login.js =>
describe('Protractor Demo App', function() {
browser.driver.ignoreSynchronization = true;
var originalTimeout;
beforeEach(function() {
browser.driver.ignoreSynchronization = true;
originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100000;
browser.driver.get('http://www.maisbolao.com.br');
});
afterEach(function() {
jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
});
it('should fill user and password and logins', function() {
var loginNameInputElm = browser.driver.findElement(by.xpath('//*[#id="Email"]'));
var passwordInputElm = browser.driver.findElement(by.xpath('//*[#id="Senha"]'));
var loginBtnElm = browser.driver.findElement(by.xpath('//*[#id="form-login"]'));
loginNameInputElm.sendKeys('login');//i omitted the login and password information just for security reasons
passwordInputElm.sendKeys('password');
loginBtnElm.click();
});
it('reach?', function() {
console.log("browser.driver.getCurrentUrl(): "+browser.driver.getCurrentUrl());
browser.driver.sleep(5000);
});
});
I also tried to use the addCookie function with the JSESSIONID cookie value, but the same message below was displayed and the same behaviour observed.
The message protractor gives to me is:
There are some issue in your code:
it('reach?', function() {
browser.sleep(5000);
// sleep 5s to wait page load after click login button for debug purpose
// should replace to browser.wait()
browser.getCurrentUrl().then(function(url){
console.log("browser.driver.getCurrentUrl(): " + url);
});
// browser.getCurrentUrl() return a promise
// you should consume the eventual value of promise inside then()
browser.sleep(5000);
});
According to your screenshot there is some warnings but your 2 tests passed (2 specs 0 failures).
If your test fails you will have red F instead of green dot.
I've got protractor's code written with Jasmine that is supposed to log in the user. Unfortunately there's a redirect going on after going to root url that takes quite some time (about 5 seconds) and I cannot make protractor wait for it. I've already tried browser.wait, I've tried using promises, I've tried with this blogpost, but nothing did it. It still doesn't wait. The login page is a page from Keycloak server, that's why I use driver.findElement instead of element. Here's my current code:
describe('my app', function() {
it('login', function() {
var driver = browser.driver;
browser.get('/');
console.log('get');
driver.findElement(by.id('username')).isPresent().then(function() {
console.log('waited');
driver.findElement(by.id('username')).sendKeys("test");
driver.findElement(by.id('password')).sendKeys("test");
driver.findElement(by.id('kc-login')).click();
driver.findElement(by.css('.page-header')).isPresent().then(function() {
console.log('ok');
expect(browser.getLocationAbsUrl()).toMatch("/test");
});
});
});
});
Do you know what can I do to make it work? I've started protractor project with this seed: https://github.com/angular/angular-seed
You need to turn the synchronization off:
var EC = protractor.ExpectedConditions;
describe('my app', function() {
beforeEach(function () {
browser.ignoreSynchronization = true;
browser.get('/');
});
it('login', function() {
var username = element(by.id('username'));
browser.wait(EC.visibilityOf(username), 10000);
username.sendKeys("test");
element(by.id('password')).sendKeys("test");
element(by.id('kc-login')).click();
var header = element(by.css('.page-header'));
browser.wait(EC.visibilityOf(header), 10000).then(function () {
console.log('logged in');
});
});
});
Note that I've also updated the test: switched back to element and browser, added browser.wait() with built-in Expected Conditions.
My test passes without problems in safari while in chrome this particular bit doesn't seem to work:
it('should click the first source and get to the source preview page', function () {
var grid_icon = element(by.css('.fa-th'));
var sources = element.all(by.repeater('source in shownSources'));
sources.get(0).element(by.tagName('a')).click();
browser.pause();
// Check url
expect(browser.getCurrentUrl()).toContain('/source/');
});
After clicking on the hyperlink it should change to a url containing "/source/". This works perfectly fine in Safari but in Chrome it fails
My protractor config file:
exports.config = {
framework: 'jasmine2',
seleniumServerJar: '../node_modules/protractor/selenium/selenium-server-standalone-2.45.0.jar',
seleniumPort: 4444,
troubleshoot: false,
basePath: '../',
specs: ['protractor/***/**/*.js'],
baseUrl: 'http://localhost:9000',
capabilities: {
browserName: 'chrome'
},
onPrepare: function() {
browser.manage().window().maximize();
}
};
Edit: My initial problem seems to not occur anymore. But the test still behaves very strange. This bit here works perfectly fine in Safari:
it('should add all sources to the list and show the cart icon on the source in inventory', function () {
browser.get('/sources');
var ordersources = element.all(by.repeater('orderSource in orderSources'));
var sources = element.all(by.repeater('source in shownSources'));
sources.get(0).element(by.css('a')).click();
var add_to_cart_btn = element(by.binding('addBtnText'));
add_to_cart_btn.click();
browser.get('/sources');
sources.get(1).element(by.css('a')).click();
var add_to_cart_btn = element(by.binding('addBtnText'));
add_to_cart_btn.click();
browser.get('/sources');
browser.pause();
sources.get(2).element(by.css('a')).click();
var add_to_cart_btn = element(by.binding('addBtnText'));
add_to_cart_btn.click();
browser.get('/sources');
expect(ordersources.count()).toBe(3);
sources.each(function (field) {
var isInCart_symbol = field.element(by.css('.fa-cart-arrow-down'));
expect(isInCart_symbol.getAttribute('aria-hidden')).toBe('false');
});
});
In Chrome however the 'a' item isn't found the second time and the browser.sleep() is never executed and the next 'it' begins to run.
EDIT: I got it to work by using a another html element by the class attribute.
element.(by.css('.example'))
I'm assumming that when you say it fails, the expect is failing? Here are 3 possible things you could try.
// Wait Till Url Contains
function WaitTillUrlContains(text, time, errMessage){
if(typeof(time) ==='undefined') time = 5000;
browser.getCurrentUrl().then(function (currentUrl) {
browser.wait(function () {
return browser.getCurrentUrl().then(function (newUrl) {
var test = newUrl;
if( test.indexOf(text) >= 0){
// Found word
return true;
}
});
}, time , errMessage);
});
};
(1) add a wait before the expect.
it('should click the first source and get to the source preview page', function () {
var grid_icon = element(by.css('.fa-th'));
var sources = element.all(by.repeater('source in shownSources'));
sources.get(0).element(by.tagName('a')).click();
// Check url
WaitTillUrlContains("/source/", 5000, "✗ Failed to wait for page to load");
expect(browser.getCurrentUrl()).toContain('/source/');
});
(2) do a .then() function after the click
it('should click the first source and get to the source preview page', function () {
var grid_icon = element(by.css('.fa-th'));
var sources = element.all(by.repeater('source in shownSources'));
sources.get(0).element(by.tagName('a')).click().then(function(){
// Check url
WaitTillUrlContains("/source/", 5000, "✗ Failed to wait for page to load");
expect(browser.getCurrentUrl()).toContain('/source/');
});
});
(3) do a .then() function after getting the element then do the click
it('should click the first source and get to the source preview page', function () {
var grid_icon = element(by.css('.fa-th'));
var sources = element.all(by.repeater('source in shownSources'));
sources.get(0).element(by.tagName('a')).then(function(elem){
elem.click();
// Check url
WaitTillUrlContains("/source/", 5000, "✗ Failed to wait for page to load");
expect(browser.getCurrentUrl()).toContain('/source/');
});
});
The reason why your link isn't found the second time is because you reload the page with browser.get(). After the reload, the sources handle is lost and webdriver doesn't know what element to operate on.
You either need to declare sources variable again after the page reload or avoid reloading the page.
We have written several test cases with casperjs now. In comparison to other testing frameworks it works like charm. But there is one crucial part of our app, where we fail to write a suitable test case.
In our app we have integrated a type of autocomplete plugin which is called Geocomplete (http://ubilabs.github.io/geocomplete/) which makes it possible to fetch geodata from the Google Maps Api.
There is the following workflow. On the start page of our site there is a form with one single input field, which is used for the autocomplete functionality. There the user can enter the name of a specific city and Google returns the data. In the background a backbone model is populated with that data.
Here is the code of the testcase:
casper.test.begin('Test User Login Form', 4, function suite(test) {
casper.options.verbose = true;
casper.options.logLevel = 'debug';
var url = 'http://localhost:8889/';
var session;
casper.start(url);
casper.test.comment('Start Testing');
casper.waitFor(function check() {
return this.evaluate(function() {
return document.getElementById('page-wrap');
});
}, function then() {
casper.waitForSelector('#landingForm', function() {
this.echo('waiting');
});
});
// input is populated with a some letters
casper.then(function() {
casper.sendKeys('#landingForm input[name="location.name"]', 'Klag', {
keepFocus: true
});
});
// .pac-item container whill show the autocomplete suggestions
casper.then(function() {
casper.waitUntilVisible('.pac-item', function() {
// we have tried several methods here like mouse_over + click etc.
this.sendKeys('#landingForm input[name="location.name"]', casper.page.event.key.Down, {
keepFocus: true
});
this.sendKeys('#landingForm input[name="location.name"]', casper.page.event.key.Enter, {
keepFocus: true
});
// form is submitted
this.click('#landingForm > div > div > div > span > button');
});
});
casper.then(function() {
// wait until next page is visible
casper.waitUntilVisible('div.activity-pic', function() {
// get backbone session model
session = casper.evaluate(function() {
return require('model/session');
});
// test if model was populated correctly with the data from google
test.assertEquals(session.filterModel.attributes.location.name, 'Klagenfurt', 'Name equals expected values.');
});
});
casper.run(function() {
casper.test.comment('Ending Testing');
test.done();
});
});
The test
test.assertEquals(session.filterModel.attributes.location.name, 'Klagenfurt', 'Name equals expected values.');
always fails and tells me that the name-attribute is undefined. The input field is filled in correclty with the name of the city. We have used the evaluate-method in other testcases to check the values and attributes of our models too, there it worked.
Does anybody has the same problem?
There are two possible approaches to this. Based on this comment you can add an event listener through evaluate and waitFor its execution (here as a reusable casper function):
casper.waitForGeocodeResult = function(){
this.thenEvaluate(function(){
// TODO: initialize $input
$input.bind('geocode:result', function(event, result) {
window._myGeocodeResultArrived = true;
}
});
this.waitFor(function check(){
return this.evaluate(function(){
return "_myGeocodeResultArrived" in window && window._myGeocodeResultArrived;
});
});
this.thenEvaluate(function(){
window._myGeocodeResultArrived = false;
});
};
You may call it like this:
casper.waitForGeocodeResult();
casper.then(function() {
// get backbone session model
session = casper.evaluate(function() {
return require('model/session');
});
// test if model was populated correctly with the data from google
test.assertEquals(session.filterModel.attributes.location.name, 'Klagenfurt', 'Name equals expected values.');
});
If this doesn't work for you may directly check the session model repeatedly (again as a reusable casper function):
casper.getBackboneModel = function(name, keyFunc){
var oldRetry;
this.then(function(){
oldRetry = this.options.retryTimeout;
// set retry timeout a little higher in case the require is a time intensive function
this.options.retryTimeout = 500;
});
this.waitFor(function check(){
var model = casper.evaluate(function(modelName){
return require(modelName);
}, name);
return keyFunc(model);
}, null, function onTimeout(){
this.echo("warning: geocomplete was unsuccessful");
});
this.then(function(){
// reset timeout
this.options.retryTimeout = oldRetry;
});
};
Call it like this:
casper.getBackboneModel(function(session){
try {
var temp = session.filterModel.attributes.location.name;
return "name" in session.filterModel.attributes.location;
} catch(e){
return false;
}
});
casper.then(function() {
// get backbone session model
session = casper.evaluate(function() {
return require('model/session');
});
// test if model was populated correctly with the data from google
test.assertEquals(session.filterModel.attributes.location.name, 'Klagenfurt', 'Name equals expected values.');
});
I'm trying to test different parts of a "mostly" single page application. I'd like to split the tests up, but I really only want to load the page once and then have the tests go through and click the links etc.
Here's my code:
PRE.js
var port = require('system').env.PORT
var tester;
casper.options.viewportSize = {width: 1024, height: 768};
casper.test.begin('Test login', function suite(test) {
var done = false;
casper.on("page.error", function(msg, trace) {
this.echo("Error: " + msg, "ERROR");
this.echo("file: " + trace[0].file, "WARNING");
this.echo("line: " + trace[0].line, "WARNING");
this.echo("function: " + trace[0]["function"], "WARNING");
});
casper.on('remote.message', function(message) {
this.echo('remote message caught: ' + message);
if (message == "done") {
done = true;
}
});
casper.start('http://localhost:' + port, function() {
// Verify that the main menu links are present.
test.assertExists('input[name=username]');
// 10 articles should be listed.
test.assertElementCount('input', 3);
casper.fill("form", {
"username": "username",
"password": "my password goes right here you cant have it"
}, true);
casper.then(function() {
casper.waitFor(function(){
return done;
}, function(){
tester = casper.evaluate(function(){
return tester;
});
test.assert("undefined" != typeof tester);
test.assert(Object.keys(tester).length > 0);
});
});
});
casper.run(function() {
test.done();
});
});
and then I have a second file (and there will be lots more like this):
TEST.js
casper.test.assert(true);
casper.capture('.screenshot.png');
casper.test.done();
I'm hoping to get a screenshot of the browser session from pre.js.
I run it from a specialized program that starts up my program, but in essence it runs:
casperjs test casper_tests --pre=pre.js
casper_tests holds both files above
My Question:
What's the right way to do this? No screenshot is being taken, and perhaps more important (though I haven't tried it yet) I want to be able to click things inside and verify that other pieces are working. The screenshot just verifies that i'm in the right neighborhood.
This will not be easily possible and potentially dangerous. Every action that you do, would need to be reversed to not break the other tests. If you later decide that writing tests in a modular manner is a good thing, you will have a headache writing your tests.
PRE.js will be your start script which you modify to execute your tests in between. In the following fully working example you see how you can schedule multiple test cases for one execution of casper. This is bad, because the canvas test case depends on the proper back execution of the link test case.
casper.start('http://example.com');
casper.then(function() {
this.test.begin("link", function(test){
var url = casper.getCurrentUrl();
test.assertExists("a");
casper.click("a");
casper.then(function(){
test.assert(this.getCurrentUrl() !== url);
this.back(); // this is bad
test.done();
});
});
this.test.begin("canvas", function(test){
test.assertNotExists("canvas");
test.done();
});
});
casper.run();
Of course you can open the root again for the new test case, but then you have the same problem as with your initial code.
var url = 'http://example.com';
casper.start();
casper.thenOpen(url, function() {
this.test.begin("link", function(test){
var url = casper.getCurrentUrl();
test.assertExists("a");
casper.click("a");
casper.then(function(){
test.assert(this.getCurrentUrl() !== url);
test.done();
});
});
});
casper.thenOpen(url, function() {
this.test.begin("canvas", function(test){
test.assertNotExists("canvas");
test.done();
});
});
casper.run();
Now the test cases don't depend on each other, but you also load the page multiple times.
If you need some initial actions for every test case then the PRE.js is not the right place for that.
Create include.js and put the following code there:
function login(suite, username, password){
username = username || "defaultUsername";
password = password || "defaultPassword";
casper.test.begin('Test login', function suite(test) {
var done = false;
// event handlers
casper.start('http://localhost:' + port, function() {
// login if the session expired or it is the first run
if (!loggedIn) {
// login
}
// wait
});
casper.then(function(){
suite.call(casper, test);
});
casper.run(function() {
test.done();
});
});
}
Then you can run it as casperjs test casper_tests --includes=include.js with test files like
login(function(test){
this.click("#something");
this.waitForSelector(".somethingChanged");
this.then(function(){
test.assertExists(".somethingElseAlsoHappened");
});
});
Of course you can have different login functions (with different names) or more lightweight ones.
Building on the previous snippets, you can make a start script and load the test files yourself. Then you have all the flexibility you need to do this.
include.js:
function login(testScript, username, password, next){
// event handlers
casper.start('http://localhost:' + port, function() {
// login if the session expired or it is the first run
// waiting
});
testScript.forEach(function(case){
casper.thenOpen(case.name, function(){
this.test.begin(function suite(test){
case.func.call(casper, test);
casper.then(function(){
test.done();
});
});
});
});
casper.run(next);
}
start.js:
// pass the test file folder to the script and read it with sys.args
// fs.list(path) all files in that path and iterate over them
var filesContents = files.map(function(filename){
return require(filename).testcases;
});
var end = null;
// stack the test cases into the `run` callback of the previous execution
filesContents.forEach(function(case){
var newEnd = end;
var newFunc = function(){ login(case, u, p, newEnd) };
end = newFunc;
});
end(); // run the stack in reverse
each test file would look like this:
exports.testcases = [
{
name: "sometest",
func: function(test){
test.assert(true)
this.echo(this.getCurrenturl());
}
},
{
name: "sometest2",
func: function(test){
test.assert(true)
this.echo(this.getCurrenturl());
}
},
];
This is just a suggestion.