So I am brand new at Javascript, my only language before this was Ruby. I have written API tests with Cucumber and Ruby for years, but now I am trying to figure out UI tests for an angular app using Protractor and Cucumber.js. I have the framework set up and the test steps run are passing, but falsely so.
Here is a snippet of my step definitions, with a few edits to change data in assertions and the string for the assertion is intentionally wrong to trigger a failure. They run and are passing, but only because it ignores the assertion. I don't see it actually doing anything in the browser, but if I put in console.log messages I do see them in the console. However, if I comment out the last callback, then I can see it run in the browser and it actually checks the assertions and fails as it should.
Cucumber doesn't require callbacks, and removing them results in it running in exactly the same way... only I can't comment out a callback of course and watch it run like I mentioned above.
And if I don't put that timeout in the first step, then the whole thing errors out at the first step with "Error: function timed out after 5000 milliseconds"
Why?!? Thanks!!
Protractor 5.3.0 with Cucumber 4.0.0 and protractor-cucumber-framework 4.2.0
Given('I am on the home page', {timeout: 30000}, (callback) => {
browser.waitForAngularEnabled(false);
browser.get(browser.params.env.int).then(callback);
});
Then('the log in form is displayed', callback => {
expect(element(by.id('email')).isPresent()).to.eventually.be.true;
expect(element(by.id('password')).isPresent()).to.eventually.be.true;
callback();
});
When('I enter my user name', callback => {
element(by.name('email')).sendKeys('my_addy#example.com');
expect(element(by.id('email')).getAttribute('value')).to.eventually.equal('something that does match');
callback();
});
When('I enter my password', callback => {
element(by.name('password')).sendKeys('blah');
callback();
});
When('I click the log in button', callback => {
element(by.buttonText('Log In')).click();
callback();
});
Then('I am on the X page', callback => {
expect(browser.getCurrentUrl()).to.eventually.contains('Y');
// callback();
});
1) For issue: "Error: function timed out after 5000 milliseconds
This is due to your step definition function execution time duration exceeds the default timeout: 5 secs.
You can change this time out globally by following my another post, or as you did
add timeout only on needed step definition functions individually.
2) For Cucumber callback issue,
You can choose to use callback, or choose to return a promise likely in each step definition function. I prefer the latter approach.
var { Given, Then, When } = require("cucumber");
Given(/^open cucumberjs github page$/, ()=> {
browser.get("https://github.com/cucumber/cucumber-js");
return expect(browser.getTitle()).to.eventually
.equal("cucumber/cucumber-js: Cucumber for JavaScript");
});
When('I enter my password', ()=> {
return element(by.name('password')).sendKeys('blah');
});
When('I click the log in button', ()=> {
return element(by.buttonText('Log In')).click();
});
More code example, you can find from my example project here
Related
I'm trying to understand how The WebDriver Control Flow works exactly.
According to the linked documentation (https://github.com/angular/protractor/blob/master/docs/control-flow.md) no callback method / call is needed in jasmine:
Protractor adapts Jasmine so that each spec automatically waits until the control flow is empty before exiting.
However, I have to use cucumber. I'm using the library protractor-cucumber-framework as described here: https://github.com/angular/protractor/blob/master/docs/frameworks.md#using-cucumber
It works well, but for some reason it works better when I skip the callback variable then when I try using it. For instance, this code fails:
this.Given(/^the login page is active$/, function (callback) {
browser.get('/').then(callback);
});
With the error ...
TypeError: text.split is not a function
[launcher] Process exited with error code 1
On the other hand, this codes works as I want it to work and cucumber / protractor seems to be waiting until the page is loaded, before executing further functions:
me.Given(/^the login page is active$/, function () {
browser.get('/');
});
But I couldn't find any documentation confirming that I really can omit the callback function.
Currently the page I tried to test doesn't use Angular and therefore I have the following code in my config file:
onPrepare: function() {
browser.ignoreSynchronization = true;
}
Protractor uses WebDriverJS underneath. And WebDriverJS uses a promise manager where it queues its commands. Here is some excerpts from their wiki page here
Internally, the promise manager maintains a call stack. Upon each turn
of the manager's execution loop, it will pull a task to execute from
the queue of the top-most frame. Any commands scheduled within the
callback of a previous command will be scheduled in a new frame,
ensuring they run before any tasks previously scheduled. The end
result is that if your test is written-in line, with all callbacks
defined by function literals, commands should execute in the order
they are read vertically on the screen. For example, consider the
following WebDriverJS test case:
driver.get(MY_APP_URL);
driver.getTitle().then(function(title) {
if (title === 'Login page') {
driver.findElement(webdriver.By.id('user')).sendKeys('bugs');
driver.findElement(webdriver.By.id('pw')).sendKeys('bunny');
driver.findElement(webdriver.By.id('login')).click();
}
});
driver.findElement(webdriver.By.id('userPreferences')).click();
This test case could be rewritten using !WebDriver's Java API as follows:
driver.get(MY_APP_URL);
if ("Login Page".equals(driver.getTitle())) {
driver.findElement(By.id("user")).sendKeys("bugs");
driver.findElement(By.id("pw")).sendKeys("bunny");
driver.findElement(By.id("login")).click();
}
driver.findElement(By.id("userPreferences")).click();
Now going back to your question, since you are omitting callback from your steps, cucumber is treating your test code as synchronous. See documentation here. And because the way protractor/WebdriverJS handles promise manager the way described above, everything works as expected for you.
As far as the error you are getting when using callback, I'm not sure. I do it exactly the same way you are doing. See here. I'm using cucumber ^0.9.2. It could be that your cucumber version has issues.
On a side note, I found that you could return promises instead of using callbacks to let cucumber know that you are done executing. So something like this works as well (assuming you are using ^0.9.2). I tested it,
me.Given(/^the login page is active$/, function () {
return browser.get('/');
});
I have written one scenario to test the application using protractor. My application starts with login page which is non-angular page then, after logging in moves on to the angular page.
Below is the javascript code snippet that i used to run:
var chai = require('chai');
var chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
var expect = chai.expect;
var myStepDefinitionsWrapper = function () {
this.Given(/^that I login with valid user credentials$/, function (callback) {
console.log("I'm in before URL");
browser.driver.get('http://localhost:8000/#');
console.log("I'm in after URL");
browser.driver.wait(function() {
console.log("I'm in Wait");
return browser.driver.isElementPresent(by.xpath("//input[contains(#placeholder,'Username')]"));
},10000);
browser.driver.findElement(by.xpath("//input[contains(#placeholder,'Username')]")).then(function(username) {
console.log("I'm in Username");
username.sendKeys("welby");
});
browser.driver.findElement(by.xpath("//input[contains(#type,'password')]")).then(function(password) {
console.log("I'm in Password");
password.sendKeys("asdf");
});
browser.driver.findElement(by.xpath("//button[#type='submit']")).click();
console.log("I'm after click");
callback();
});
this.When(/^I click perform button in Tasks window$/, function (callback) {
browser.waitForAngular();
element(by.xpath("//*[text()[contains(.,'Smith, Sally')]]/following::td[2]/button[text()='Perform']")).click();
console.log("Clicked Perform");
callback();
});
}
Output:
"C:\Program Files (x86)\JetBrains\WebStorm 10.0.4\bin\runnerw.exe" "C:\Program Files (x86)\nodejs\node.exe" node_modules\protractor\lib\cli.js E2E\protractor-conf.js Using the selenium server at http://127.0.0.1:4444/wd/hub [launcher] Running 1 instances of WebDriver
- I'm in before URL
- I'm in after URL
- I'm after click
- Clicked Perform
1 scenario (1 passed) 3 steps (3 passed)
[launcher] 0 instance(s) of WebDriver still running [launcher] chrome #1 passed
Process finished with exit code 0
Judging by the style of the code in your question you appear to be using Cucumber.js for your test runner. In this case then, you should be able to omit the callback parameter to your step definitions and simply return a promise:
this.Given(/^that I login with valid user credentials$/, function () {
// The rest of the code remains the same.
return browser.driver.findElement(by.xpath("//button[#type='submit']")).click();
});
And:
this.When(/^I click perform button in Tasks window$/, function () {
browser.waitForAngular();
return element(by.xpath("//*[text()[contains(.,'Smith, Sally')]]/following::td[2]/button[text()='Perform']")).click();
});
The capability of Cucumber.js to use promises is documented here.
Protractor is built on Selenium. I strongly suggest reading the entire "Understanding the API" section of the Selenium documentation so that you understand how the JavaScript version of Selenium uses and sequences promises.
The reason your code is not working right now is that by calling callback() like you do you are telling Cucumber.js that your step is finished before Protractor (and Selenium) has actually performed the actions you want. When you return a promise, Cucumber.js waits until the promise is resolved or fails before moving on.
I have a following function:
function prompt_mandatory_field_completion(){
$("#mandatory_fail").show(150, function() {
setTimeout(function() {
$("#mandatory_fail").fadeOut(500)
}, 2000);
});
window.scrollTo(0,0)
}
That I would like to test with jasmine but regardless to what I put in my spec file the test seems to pass.
The spec file contains the following code :
it(' NEED TO FIX THAT FADE OUT Should prompt user to fill in mandatory questions via prompt_mandatory_field_completion function', function() {
prompt_mandatory_field_completion();
setTimeout(2000, function(){
expect($('#mandatory_fail').css('display').toEqual('random thing'));
});
In my SpecRunner.html I am using the following function that I run in before each test in this description block:
function setupFixtures(){
setFixtures('<div id="mandatory_fail" style="display:none;"></div>');
prompt_mandatory_field_completion();
};
Any idea how to make this into a meaningful test? I guess I have been staring at it way too long and poking it from all the directions.
Best,
Adam
You're trying to write a functional test of asynchronous behavior. You might have a lot better experience trying to use protractor for this sort of test. It's tuned more toward asserting things that will eventually be true.
However, jasmine does have an asynchronous facility since about 2.0, known as done(), that will insist that all of the asynchronous code has run before the test passes or fails.
You have to pass the done function to get asynchronous tests :
it(' NEED TO FIX THAT FADE OUT Should prompt user to fill in mandatory questions via prompt_mandatory_field_completion function', function(done) {
prompt_mandatory_field_completion();
setTimeout(2000, function(){
expect($('#mandatory_fail').css('display').toEqual('random thing'));
done();
});
}, 3000);
You can also pass a timeout as a last parameter, depending on what you've set in your jasmine's settings.
Otherwise, Jasmine will consider this test to fail if its execution exceed its timeout.
I'm using CasperJS to automate a series of clicks, completed forms, parsing data, etc through a website.
Casper seems to be organized into a list of preset steps in the form of then statements (see their example here: http://casperjs.org/quickstart.html) but it's unclear what triggers the next statement to actually run.
For example, does then wait for all pending requests to complete? Does injectJS count as a pending request? What happens if I have a then statement nested - chained to the end of an open statement?
casper.thenOpen('http://example.com/list', function(){
casper.page.injectJs('/libs/jquery.js');
casper.evaluate(function(){
var id = jQuery("span:contains('"+itemName+"')").closest("tr").find("input:first").val();
casper.open("http://example.com/show/"+id); //what if 'then' was added here?
});
});
casper.then(function(){
//parse the 'show' page
});
I'm looking for a technical explanation of how the flow works in CasperJS. My specific problem is that my last then statement (above) runs before my casper.open statement & I don't know why.
then() basically adds a new navigation step in a stack. A step is a javascript function which can do two different things:
waiting for the previous step - if any - being executed
waiting for a requested url and related page to load
Let's take a simple navigation scenario:
var casper = require('casper').create();
casper.start();
casper.then(function step1() {
this.echo('this is step one');
});
casper.then(function step2() {
this.echo('this is step two');
});
casper.thenOpen('http://google.com/', function step3() {
this.echo('this is step 3 (google.com is loaded)');
});
You can print out all the created steps within the stack like this:
require('utils').dump(casper.steps.map(function(step) {
return step.toString();
}));
That gives:
$ casperjs test-steps.js
[
"function step1() { this.echo('this is step one'); }",
"function step2() { this.echo('this is step two'); }",
"function _step() { this.open(location, settings); }",
"function step3() { this.echo('this is step 3 (google.com is loaded)'); }"
]
Notice the _step() function which has been added automatically by CasperJS to load the url for us; when the url is loaded, the next step available in the stack — which is step3() — is called.
When you have defined your navigation steps, run() executes them one by one sequentially:
casper.run();
Footnote: the callback/listener stuff is an implementation of the Promise pattern.
then() merely registers a series of steps.
run() and its family of runner functions, callbacks, and listeners, are all what actually do the work of executing each step.
Whenever a step is completed, CasperJS will check against 3 flags: pendingWait, loadInProgress, and navigationRequested. If any of those flags is true, then do nothing, go idle until a later time (setInterval style). If none of those flags is true, then the next step will get executed.
As of CasperJS 1.0.0-RC4, a flaw exists, where, under certain time-based circumstances, the "try to do next step" method will be triggered before CasperJS had the time to raise either one of the loadInProgress or navigationRequested flags. The solution is to raise one of those flags before leaving any step where those flags are expected to be raised (ex: raise a flag either before or after asking for a casper.click()), maybe like so:
(Note: This is only illustrative, more like psuedocode than proper CasperJS form...)
step_one = function(){
casper.click(/* something */);
do_whatever_you_want()
casper.click(/* something else */); // Click something else, why not?
more_magic_that_you_like()
here_be_dragons()
// Raise a flag before exiting this "step"
profit()
}
To wrap up that solution into a single-line of code, I introduced blockStep() in this github pull request, extending click() and clickLabel() as a means to help guarantee that we get the expected behaviour when using then(). Check out the request for more info, usage patterns, and minimum test files.
According to the CasperJS Documentation:
then()
Signature: then(Function then)
This method is the standard way to add a new navigation step to the stack, by providing a simple function:
casper.start('http://google.fr/');
casper.then(function() {
this.echo('I\'m in your google.');
});
casper.then(function() {
this.echo('Now, let me write something');
});
casper.then(function() {
this.echo('Oh well.');
});
casper.run();
You can add as many steps as you need. Note that the current Casper instance automatically binds the this keyword for you within step functions.
To run all the steps you defined, call the run() method, and voila.
Note: You must start() the casper instance in order to use the then() method.
Warning: Step functions added to then() are processed in two different cases:
when the previous step function has been executed,
when the previous main HTTP request has been executed and the page loaded;
Note that there's no single definition of page loaded; is it when the DOMReady event has been triggered? Is it "all requests being finished"? Is it "all application logic being performed"? Or "all elements being rendered"? The answer always depends on the context. Hence why you're encouraged to always use the waitFor() family methods to keep explicit control on what you actually expect.
A common trick is to use waitForSelector():
casper.start('http://my.website.com/');
casper.waitForSelector('#plop', function() {
this.echo('I\'m sure #plop is available in the DOM');
});
casper.run();
Behind the scenes, the source code for Casper.prototype.then is shown below:
/**
* Schedules the next step in the navigation process.
*
* #param function step A function to be called as a step
* #return Casper
*/
Casper.prototype.then = function then(step) {
"use strict";
this.checkStarted();
if (!utils.isFunction(step)) {
throw new CasperError("You can only define a step as a function");
}
// check if casper is running
if (this.checker === null) {
// append step to the end of the queue
step.level = 0;
this.steps.push(step);
} else {
// insert substep a level deeper
try {
step.level = this.steps[this.step - 1].level + 1;
} catch (e) {
step.level = 0;
}
var insertIndex = this.step;
while (this.steps[insertIndex] && step.level === this.steps[insertIndex].level) {
insertIndex++;
}
this.steps.splice(insertIndex, 0, step);
}
this.emit('step.added', step);
return this;
};
Explanation:
In other words, then() schedules the next step in the navigation process.
When then() is called, it is passed a function as a parameter which is to be called as a step.
It checks if an instance has started, and if it has not, it displays the following error:
CasperError: Casper is not started, can't execute `then()`.
Next, it checks if the page object is null.
If the condition is true, Casper creates a new page object.
After that, then() validates the step parameter to check if it is not a function.
If the parameter is not a function, it displays the following error:
CasperError: You can only define a step as a function
Then, the function checks if Casper is running.
If Casper is not running, then() appends the step to the end of the queue.
Otherwise, if Casper is running, it inserts a substep a level deeper than the previous step.
Finally, the then() function concludes by emitting a step.added event, and returns the Casper object.
I am trying to write unit test for async db calls. I'm using NodeJS with ORMnomnom package installed as orm db access and nodeunit for unit testing.
But it hang for this simple test:
Here is code test\test1.js
modelsdb = require('../model/model_db')
exports['test db'] = function (test) {
test.expect(1);
console.log('test db');
modelsdb.MyTable.objects.all( function (err, data) {
test.ok(true, "this assertion should pass");
test.done();
console.log('test must finish here!');
});
}
exports.testSomething = function(test){
console.log('testSomething');
test.expect(1);
test.ok(true, "this assertion should pass");
test.done();
};
When I run this test all assertions passed, I see my messages in console: 'test db' 'test must finish here!' 'testSomething' (means that test.done() reached inside callback function) but test doesn't finish. I need to stop it manually, get: 'Process finished with exit code 1'. If I change to test.ok(false,""), so I get AssertionError but test doesn't finish either. If I remove 'test db' and left only testSomething function - test finished as expected, all assertion passed.
I also try testpilot package which is based on nodeunit.
It gives
test1
FAIL : test db
an error occurred during test:
Error: timed out waiting for test
Any suggestions?
Thanks.
Add a tearDown method to your exports to close mongoose's connection pool. As long as it's still open the test won't exit.
You don't show how you're opening your mongoose connection, but something like this:
exports.tearDown = function (callback) {
mongoose.disconnect(callback);
};
I'm having a similar issue with nodeunit and q/mongoose - There is a bug that has been opened for it. I've tried to do the terrible process.exit(0) after calling test.done(), but any logging that is pending may not be flushed, so it isn't ideal. Ended up having to call process.exit(0) in a timeout block.