Making modular login function with selenium-webdriver and mocha - javascript

I am trying to create a login function that I can use with mocha testing and selenium-webdriver for unit testing, since I have a bunch of things to do that start with "user logs in and..."
Below is my best shot, but when I run it, the console just logs
Already registered user logs in and sees homepage
1) should work (it's red text)
Login an existing user
and then nothing happens and I have to control-C out of the process. My code was working before I went for this modular login implementation, so I'm sure that's where my problem is. Is there something I am missing here? I am also open to general critiques of how I went about making the login function, since I don't have that much experience with webdriver.
Below is my code. It's in one js file.
var test = require('selenium-webdriver/testing'),
chai = require('chai');
chai.use(require('chai-string'));
chai.use(require('chai-as-promised'));
var expect = chai.expect,
webdriver = require('selenium-webdriver'),
By = webdriver.By;
// I want this to be a modular login function I can use multiple places
function login(driver) {
test.describe('Login an existing user', function() {
test.it('should work', function() {
this.timeout(0);
var email = 'myemail#test.com';
var password = 'password';
driver.get('http://localhost:9000');
driver.getTitle().then(function(title) {
expect(title).to.equal('my title');
})
.then(function() {
// do login stuff and click login button
});
.then(function() {
return driver;
});
});
});
}
//an example of when I would use the login function
test.describe('Already registered user logs in and sees homepage', function() {
test.it('should work', function() {
this.timeout(0);
var driver = new webdriver.Builder().
withCapabilities(webdriver.Capabilities.chrome()).
build();
driver = login(driver)
.then(function() {
driver.findElement(By.xpath("relevant xpath")).click();
})
})
})

By calling login from inside test.it, you are effectively calling describe inside it, which is not allowed. Mocha simply does not support calling describe or it from inside it. Unfortunately, it does not try to detect such occurrence and scream about it. It just goes ahead and do whatever it does. I all cases it results in undefined behavior, like the behavior you observed. In most cases, the undefined behavior is erratic. I very rare cases, it happens to match the developer's expectations but that's due to chance, not design.
In your case, I'd just remove the calls to test.describe and test.it from inside login, and make sure to return a promise. You can keep the expect there. You won't be able to call this.timeout(0) inside login, however. Something like this:
function login(driver) {
var email = 'myemail#test.com';
var password = 'password';
driver.get('http://localhost:9000');
return driver.getTitle().then(function(title) {
expect(title).to.equal('my title');
})
.then(function() {
// do login stuff and click login button
})
.then(function () {
return driver;
});
}
And modify your calling code to:
var promise = login(driver)
.then(function(driver) {
driver.findElement(By.xpath("relevant xpath")).click();
});
You can do whatever you want to the promise, or nothing at all. However, driver is not itself a promise so setting driver to that value won't work.

Related

Creating integration tests in Ember 2.16 that utilize window.confirm()?

I am writing integration tests for an Ember 2.16 component and I am testing some user actions.
One of the user actions calls window.confirm(), where the user is asked if they are sure they want to delete an item before the item is deleted.
I want to test the functionality of this component, both with accepting and declining the confirm. The component action looks similar to this:
delete(id){
if(confirm('Are you sure you want to delete?')){
//do stuff
} else {
//do other stuff
}
}
Inside my integration tests I am successfully clicking the button to bring up the prompt, but I am running into this error:
[Testem] Calling window.confirm() in tests is disabled, because it causes testem to fail with browser disconnect error.
How can I create an integration test that will bypass the window.confirm() functionality?
I have added in my component a way to bypass the confirm if the env is in "test" mode, but this does not really help as I am not testing the portion of code that relies on the window.confirm().
I have looked around to see if there is a variable I can pass to the component to make the window.confirm() true/false, but have been unsuccessful.
How can I create a test that will test a component that calls window.confirm() inside an action?
One solution would be to save the original implementation of window.confirm and write your own implementation before your test, then restore the original implementation at the end of the test.
This is how I would do it:
// Watch out, this test is written with the latest ember-qunit syntax which might not be exactly what you have in your Ember 2.16 application
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from 'ember-test-helpers';
import hbs from 'htmlbars-inline-precompile';
module('your component integration tests', function(hooks) {
setupRenderingTest(hooks);
test('clicking the OK confirm button', async function(assert) {
// save the original window.confirm implementation
const originalWindowConfirm = window.confirm;
// simulate the OK button clicked
window.confirm = function() { return true;}
// ADD YOUR TEST AND ASSERTIONS HERE
// restore the original window.confirm implementation
window.confirm = originalWindowConfirm;
});
});
I would stub window.confirm() in the test with a lib like sinon where I expect it to be called so that:
hopefully that error message won't occur
I know confirm() is actually called by the code and does what I
want it to do exactly (i.e., I can make it a simple fn)
it can be restored so the warning message will be logged in other
tests (which is helpful)
According to the testem code it overwrites window.confirm() to print this warning message:
window.confirm = function() {
throw new Error('[Testem] Calling window.confirm() in tests is disabled, because it causes testem to fail with browser disconnect error.');
};
So doing something like this in the test with sinon should work:
const confirm = sinon.stub(window, "confirm").callsFake(() => {
// fake implementation here which overwrites the testem implementation
});
// rest of the test
confirm.restore(); // restores the testem implementation

How do I access information about the currently running test case from the beforeEach function?

Using Protractor 5.1.2 and Jasmine2 for describing test cases, how does one get the current testcase/spec being run in the beforeEach method?
I would like to do some different setup based on which test case I'm running. I do not want to put these tests in different spec files with repeating code except for the little bit I want to change in the setup.
Example of what I'm looking for:
...
beforeEach(() => {
if(currentSpec/TestCase.name == "thisName") {
// Do a particular login specific to testcase.name
} else {
// Do a default login
}
});
...
My research into this brought up much older solutions (2+ years) that are very out of date and seem to keep saying that accessing the currently running testcase/spec is something they (protractor) try to keep hidden. I feel like wanting to do particular setup for a particular test case in a suite of test cases is not a unique thing. I could just be using the wrong search terms.
I am not sure how to do what you want with beforeEach(). But, I think you can get the same effect by using a helper file. This will allow you to setup a common file that any spec can reference so you can use a common set of functions. To set this up, you will:
Create a central file (I call mine util.js)
const helper = function(){
this.exampleFunction = function(num){
return num; //insert function here
}
this.exampleFunction2 = function(elem){
elem.click() //insert function here
}
}
Inside your spec.js file you will do:
const help = require('path/to/util.js');
const util = new help();
describe('Example with util',function(){
it('Should use util to click an element',function(){
let elem = $('div.yourItem');
util.exampleFunction2(elem);
});
});
You can then call these functions from any spec file. You would then be able to seperate your tests into seperate spec files, but have a common set of functions for the parts that are the same.
Another way to do this, without creating separate files is to just use a local function.
Example spec.js file:
describe('Should use functions',function(){
afterEach(function(){
$('button.logout').click();
)};
it('Should run test as user 1',function(){
$('#Username').sendKeys('User1');
$('#Password').sendKeys('Password1');
$('button.login).click();
doStuff();
)};
it('Should run test as user 2',function(){
$('#Username').sendKeys('User2');
$('#Password').sendKeys('Password2');
$('button.login').click();
doStuff();
)};
function doStuff(){
$('div.thing1').click();
$('div.thing2').click();
)};
)};
As per comments for multiple describes:
describe('Test with user 1',function(){
beforeEach(function(){
//login as user 1
});
it('Should do a thing',function(){
//does the thing as user 1
});
});
describe('Test with user 2',function(){
beforeEach(function(){
//login as user 2
});
it('Should do another thing',function(){
//does the other thing as user 2
});
});
The whole point of beforeEach is that it is the same for each test.
If you want to do different things, then they belong in the specific test.
Write a helper function and call it from the specific test if you want to have common functionality that does slightly different things depending on an argument.

close browser session after failed assertion with nightwatch

I implement a new testframework for automated tests in node.js with Nightwatch-Cucumber that based on Nightwatch.js. So, sometimes I use node.js Assertions to check some values. I work with the PageObject Pattern in my framework. My problem is that the browser session doen't close after a failed assertion and I don't know why and I don't know how to solve the problem.
Here is my StepDefinition:
const {
client
} = require('nightwatch-cucumber');
const {
defineSupportCode
} = require('cucumber');
const page = client.page.page();
defineSupportCode(({Given, When, Then}) => {
When(/^test$/, () => {
return page.test();
});
});
And that's my PageObject function:
module.exports = {
elements: {},
commands: [{
test() {
//here the assertion failed and the browser session still exist and doen't close
this.assert.equal(false, true);
return this.api;
}
}]
};
So, what can I do to realize it to close the browser and the session for ths test? It happened only if the node.js assertions fail.
Use an after or afterEach hook to always close the browser at the end of a test, regardless of outcome. See http://nightwatchjs.org/guide#using-before-each-and-after-each-hooks
after : function(browser) {
console.log('Closing down...');
browser.end();
},
To close your session after each test you have to add afterEach hook to your test file and use it like this:
afterEach : function(browser, done) {
browser.end(function(){
done();
});
}

Issue with callbacks in Cucumber.js scenario with ZombieJS

I've got some experience with BDD tools like Cucumber and Lettuce. I'm currently building a Phonegap app, and I'd like to start using Cucumber.js to create acceptance tests for it. Unfortunately I'm having a bit of an issue.
Here is the basic feature file I've thrown together:
Feature: Authentication
As a user
I want to be able to log in and out
Scenario: Logging in
Given I am not logged in
And I am on the page "login"
When I fill in the "username" field with "student"
And I fill in the "password" field with "password"
And I click the "LOG IN" button
Then I should see the text "STUDENT"
Here is my world.js:
var zombie = require('zombie');
var World = function World(callback) {
"use strict";
this.browser = new zombie(); // this.browser will be available in step definitions
this.visit = function (url, callback) {
this.browser.visit(url, callback);
};
callback(); // tell Cucumber we're finished and to use 'this' as the world instance
};
exports.World = World;
Here are my step definitions:
var wrapper = function () {
"use strict";
this.World = require("../support/world.js").World; // overwrite default World constructor
this.Given(/^I am not logged in$/, function (callback) {
// Clear local storage
this.browser.localStorage("localhost:9001").clear();
callback();
});
this.Given(/^I am on the page "([^"]*)"$/, function (page, callback) {
// Visit page
this.browser.visit('http://localhost:9001/app/index.html#' + page, callback);
});
};
module.exports = wrapper;
I've set up a Grunt task that first runs the connect server on port 9001, then runs the Cucumber scenarios. The documentation for Cucumber.js implies this should work, but it fails on the second step.
Here is the error message I get:
Running "connect:cucumber" (connect) task
Started connect web server on http://localhost:9001
Running "cucumberjs:src" (cucumberjs) task
.Cannot call method 'add' of undefined TypeError: Cannot call method 'add' of undefined
at <anonymous>:10:711
at <anonymous>:10:874
at <anonymous>:10:1224
at Contextify.sandbox.run (/Users/matthewdaly/Projects/myapp/node_modules/zombie/node_modules/jsdom/node_modules/contextify/lib/contextify.js:12:24)
at DOMWindow.window._evaluate (/Users/matthewdaly/Projects/myapp/node_modules/zombie/lib/zombie/window.js:188:25)
at Object.HTML.languageProcessors.javascript (/Users/matthewdaly/Projects/myapp/node_modules/zombie/lib/zombie/scripts.js:23:21)
at define.proto._eval (/Users/matthewdaly/Projects/myapp/node_modules/zombie/node_modules/jsdom/lib/jsdom/level2/html.js:1480:47)
at loaded (/Users/matthewdaly/Projects/myapp/node_modules/zombie/lib/zombie/scripts.js:74:23)
at /Users/matthewdaly/Projects/myapp/node_modules/zombie/node_modules/jsdom/lib/jsdom/level2/html.js:76:20
at Object.item.check (/Users/matthewdaly/Projects/myapp/node_modules/zombie/node_modules/jsdom/lib/jsdom/level2/html.js:345:11)
FUUUU
(::) failed steps (::)
TypeError: Cannot call method 'add' of undefined
at <anonymous>:10:711
at <anonymous>:10:874
at <anonymous>:10:1224
at Contextify.sandbox.run (/Users/matthewdaly/Projects/myapp/node_modules/zombie/node_modules/jsdom/node_modules/contextify/lib/contextify.js:12:24)
at DOMWindow.window._evaluate (/Users/matthewdaly/Projects/myapp/node_modules/zombie/lib/zombie/window.js:188:25)
at Object.HTML.languageProcessors.javascript (/Users/matthewdaly/Projects/myapp/node_modules/zombie/lib/zombie/scripts.js:23:21)
at define.proto._eval (/Users/matthewdaly/Projects/myapp/node_modules/zombie/node_modules/jsdom/lib/jsdom/level2/html.js:1480:47)
at loaded (/Users/matthewdaly/Projects/myapp/node_modules/zombie/lib/zombie/scripts.js:74:23)
at /Users/matthewdaly/Projects/myapp/node_modules/zombie/node_modules/jsdom/lib/jsdom/level2/html.js:76:20
at Object.item.check (/Users/matthewdaly/Projects/myapp/node_modules/zombie/node_modules/jsdom/lib/jsdom/level2/html.js:345:11)
If I insert callback(); after the body of the second step, it passes. I'm not sure what's going on. Why is this scenario failing? The app itself works as expected. It seems like the callback for the second step is never firing.
the test passes if you add the callback to the second step, because than visitPage is just skipped.
my visit function looks like this:
this.visit = function(url, callback) {
that.browser.visit(url, function(error) {
if (error) {
callback.fail(error);
} else {
callback.call(that, that.browser);
}
});
});
but I think the real problem is on your page, because sandbox.run is the point where zombie starts to execute custom (js)-code from the page. So it's an anonymous callback in your (minified) script in column 1224?
Maybe you have to track it down with console.log... (something with localStorage?, allthough zombie supports it), grep for 'add" in your custom code
Why use callbacks at all? They obfuscate your code. Whereas, the equivalent is to use the async/await pairs, which will mimic, so to speak, java coding and proper instructions starting and ending :
var R = await visit () ;
await do_this_when_visit_is_done () ;
await do_that_when_do_this_is_done() ;
in cucumber :
this.Given(/^I am on the page "(.*)"$/, async function (page)
{
await this.page_is_loaded() ;
}

Detecting console.log() calls

I'm trying to write a test case for a debugging method that writes messages to the JavaScript console using console.log(). The test has to check that the message has been successfully written to the console. I'm using jQuery.
Is there a way to attach a hook to console.log() or otherwise check that a message has been written to the console, or any other suggestions on how to write the test case?
console.log doesn't keep a record of messages that are logged, or emit any events that you could listen for. It's not possible for your tests to directly verify its output from JavaScript. Instead, your test code will need to replace console.log with a mock implementation that does keep track of log messages for later verification.
Mocking is a common feature supported by most JavaScript test frameworks. For example, the Jest test framework provides a jest.spyOn function which replaces a given method with a mock implementation that records the arguments for each call in a .mock property before passing them on to the original implementation. After each test you may want to call jest.clearAllMocks() to reset the recorded argument lists for the next test, or use the equivalent clearMocks: true config option.
function saySomething() {
console.log("Hello World");
}
jest.spyOn(console, 'log');
test("saySomething says hello", () => {
expect(console.log.mock.calls.length).toBe(0);
saySomething();
expect(console.log.mock.calls.length).toBe(1);
expect(console.log.mock.calls[0][0]).toBe("Hello World");
});
afterEach(() => {
jest.clearAllMocks();
});
If you're not using a test framework (you probably should), you can create a simple mock yourself.
function saySomething() {
console.log("Hello World");
}
function testSomething() {
// Replace console.log with stub implementation.
const originalLog = console.log;
const calls = [];
console.log = (...args) => {
calls.push(args);
originalLog(...args);
};
try {
console.assert(calls.length == 0);
saySomething();
console.assert(calls.length == 1);
console.assert(calls[0][0] == "Hello World");
} catch (error) {
console.error(error);
} finally {
// Restore original implementation after testing.
console.log = originalLog;
}
}
So not bad solutions, but if you're looking for a high powered logger try Paul Irish's log()
If that's too high powered, you can get by with something like this.
var console = window.console,
_log = console ? console.log : function(){};
_log.history = [];
console.log = function( ){
_log.history.push.apply( _log.history, arguments );
_log.apply( console, arguments );
}
Usage
console.log('I','have','an','important','message');
//Use native one instead
_log.call( console, _log.history );
http://jsfiddle.net/BeXdM/
If you're using Jasmine, it's dead simple:
it('is my test', function () {
spyOn(console, 'log');
// do your stuff that should log something
expect(console.log).toHaveBeenCalledWith('something');
});
Head to Jasmine docs for more info.
Just attach your own function to console.log.
On your page, after everything loads,
Before starting tests -
var originalLog = console.log;
console.log = function(msg){
alert('my .log hook received message - '+msg);
//add your logic here
}
After running tests, if necessary -
console.log = originalLog
Probably the easiest way out is to use the NPM package std-mocks.
From their documentation:
var stdMocks = require('std-mocks');
stdMocks.use();
process.stdout.write('ok');
console.log('log test\n');
stdMocks.restore();
var output = stdMocks.flush();
console.log(output.stdout); // ['ok', 'log test\n']
Note: make sure you stdMocks.restore() before your assertions so your test runner is still able to log information about failed assertions.

Categories

Resources