How to get karma to clear the dom after each test? - karma-runner

The component I'm testing does some changes to the dom in an it() test, however it remains there in the next it() test, and this breaks my test. Is there a way to reset the DOM each it() test?

For none JQuery users:
afterEach(function () {
document.body.innerHTML = '';
});
Above removes all the <script> tags which are added by default, but that does not matter, because they have alreay been executed once (and Karma does never refresh the browser, at least not till all tests finish).
Hence you can also use beforeEach(...) (but I like to think of it as a dining table, you don't clean up before you start, you clean up after yourself).
I wrote a little DomCleaner that keeps track of events boud during your tests and cleans the body on cleanup (requireJS but you can change the code to your needs):
define(function () {
'use strict';
var installed = false,
documentAddListener,
documentListeners,
windowAddListener,
windowListeners;
return {
install: function () {
if (installed) {
throw new Error('Trying to install document cleaner, but its already installed!');
}
installed = true;
documentAddListener = document.addEventListener;
windowAddListener = window.addEventListener;
spyOn(document, 'addEventListener');
spyOn(window, 'addEventListener');
documentListeners = [];
windowListeners = [];
document.addEventListener.and.callFake(function () {
documentListeners.push(arguments);
documentAddListener.apply(null, arguments);
});
window.addEventListener.and.callFake(function () {
documentListeners.push(arguments);
windowAddListener.apply(null, arguments);
});
},
cleanup: function () {
if (!installed) {
throw new Error('Trying to cleanup document, but cleaner is not installed!');
}
installed = false;
documentListeners.forEach(function (listener) {
document.removeEventListener.apply(null, listener);
});
windowListeners.forEach(function (listener) {
window.removeEventListener.apply(null, listener);
});
documentListeners = [];
windowListeners = [];
document.body.innerHTML = '';
},
};
});
use it like this (in your first describe):
beforeEach(function () {
domCleaner.install();
});
afterEach(function () {
domCleaner.cleanup();
});

If using Jasmine - Use the beforeEach function to run a command before each test. In that function, you can clear the DOM. An example using jQuery:
describe("A test suite", function() {
beforeEach(function () {
$('body').empty();
});
});

I had to do this manually in each test method.
Just use DOM operations that remove stuff from the html using javascript.
stuff like
document.body.innerHTML = '';

Related

Changed values are lost after returning from promise

I am doing E2E Test with protractor, using cucumber for test scenarios.
I face a strange problem: I lost the values I set inside of the promise.
What am I doing wrong?
My step-file.js:
var loggedUser = new User("dummyname", "dummyrole");
this.When(/^I click on a user name$/, function(){
userelem.element(by.className('username')).getText().then(function (txt) {
loggedUser.username = txt;
});
});
this.Then(/^The username of the object "loggedUser" is set to a new value$/, function(){
var answer = "dummyname" != loggedUser.username;
assert.isOk(answer, "username is still dummyname!"); //this line fails since usrname is set back to dummyname again!
});
Thank you #yong for your suggestion on my first post (which I have deleted meanwhile). Finally I understood what you meant. Here is the solution:
(Instead of upgrading to cucumber 2,) I "return" the promise from within the "When-Step", so that protractor waits until it is fullfilled before it executes the next step-function:
var loggedUser = new User("dummyname", "dummyrole");
this.When(/^I click on a user name$/, function(){
return userelem.element(by.className('username')).getText().then(function (txt) {
loggedUser.username = txt;
});
});
Now in the next step-functions, the username is the updated username.
The root cause is you need to make each step definition return a promise, I write a simple code for you and it worked well.
Make step definition return a promise is the key point.
For Cucumber 2:
var { defineSupportCode } = require("cucumber");
defineSupportCode(function({ Given, When, Then }) {
let title = 'test';
Given(/^open npmjs.com$/, function() {
browser.get("http://www.npmjs.com");
return browser.getTitle().then(function(title1){
title = title1;
console.log('title: ' + title);
});
});
Then(/^verify page title$/, function() {
return expect(title).to.equal('npm');
});
});
For Cucumber 1:
module.exports = function() {
let title = 'test';
this.Given(/^open npmjs.com$/, function() {
browser.get("http://www.npmjs.com");
return browser.getTitle().then(function(title1){
title = title1;
console.log('title: ' + title);
});
});
this.Then(/^verify page title$/, function() {
return expect(title).to.equal('npm');
});
};

function to take screenshots for cucumber-html-reporter generates "function timed out after 5000.." error

I am using protractor-cucumber-framework and I wanted to generate html report for the tests I wrote. I decided to use cucumber-html-reporter to achieve it. In my hooks.js I wrote a this.After object to take screenshot on test failure:
this.After(function(scenario, callback) {
if (scenario.isFailed()) {
browser.takeScreenshot().then(function(buffer) {
return scenario.attach(new Buffer(buffer, 'base64'), function(err) {
callback(err);
});
});
}
else {
callback();
}
});
Everything works just fine, the report is generated and the screenshots are taken and attached only on test failure. But I also got an error message when After step is proceeded (so when there is some failure):
function timed out after 5000 milliseconds
I would like to get rid of this message as it also appears on my html report. Can anyone provide me solution to do that?
Below code is working for me. I have added this in step definition js file. At the end of scenario in the report, it adds the screenshot.
defineSupportCode(({After}) => {
After(function(scenario) {
if (scenario.isFailed()) {
var attach = this.attach;
return browser.takeScreenshot().then(function(png) {
var decodedImage = new Buffer(png, "base64");
return attach(decodedImage, "image/png");
});
}
});
});
I had similar problem and it failed even after waiting for 60 seconds. The issue was that i didn't had proper callback implemented.
The below code worked for me. ( I am new to JavaScript and so my callback usage might be the right way. Please feel free to educate me if there is better way to do it. :))
After(function(scenario,done)
{
const world = this;
if (scenario.result.status === 'failed') {
browser.takeScreenshot().then(function (stream) {
let decodedImage = new Buffer(stream.replace(/^data:image\/(png|gif|jpeg);base64,/, ''), 'base64');
world.attach(decodedImage, 'image/png');
}).then(function () {
done();
});
}else {
done();
}
});
Your code seems absolutely fine.
Perhaps, it just needs longer timeouts?
You can set a timeout on hooks like this:
this.After({ timeout: 20 * 1000 }, function (scenario, callback) {
if (scenario.isFailed()) {
browser.takeScreenshot().then(function(buffer) {
return scenario.attach(new Buffer(buffer, 'base64'), function(err) {
callback(err);
});
});
}
else {
callback();
}
});

Protractor how to wait for options

I have code like this:
element(by.model("roleSelection.role")).element(by.cssContainingText('option', newRole)).click();//.then(function() {console.log('role click')})//;
where the options is loaded via a call to the server.
I can wait for the first element by doing this
browser.wait(function() {
return browser.isElementPresent(by.model("roleSelection.role")).then(function(present){
return present;
});}, 8000);
and it seems to work. But how can I wait until the "sub-element" is clickable.
I have tried this
browser.wait(function() {
return browser.isElementPresent(by.model("roleSelection.role")).then(function(present){
if (present) {
var elm = element(by.model("roleSelection.role"));
return elm.isElementPresent(by.cssContainingText('option', newRole)).then(function(subpresent) {
return subpresent;
});
}
}); }, 8000);
Have you tried clickable? Something along these lines
var EC = protractor.ExpectedConditions;
var select = element(by.model("roleSelection.role"))
var isClickable = EC.elementToBeClickable(select);
browser.wait(isClickable,5000); //now options should have been loaded by now
Well, try to this: https://angular.github.io/protractor/#/api?view=ExpectedConditions.prototype.elementToBeClickable
But, Please keep in mind, Protractor is suitable for angular webpages and interactions, and animations. For example ng-animate. So, it is not sure to working for example jquery, or other animates.
In this way:
onPrepare: function () {
// disable animations when testing to speed things up
var disableNgAnimate = function () {
angular.module('disableNgAnimate', []).run(function ($animate) {
$animate.enabled(false);
});
};
browser.addMockModule('disableNgAnimate', disableNgAnimate);
}
Or you can switch in script way in browser.executeScript().
Please see this link. It works only jquery animations.
If you not have animate problems. Use setTimeout() JS function.

Waiting for Ionic Loading dialogs with Protractor

There are similar questions (linked below) but none solves this problem. I'm writing Protractor tests for an Ionic Project. I need to execute tests at times when an Ionic Loading dialog appears and disappears.
I've created a repo with the bare bones of the app and the tests that need to be made. Solve this and you solve the problem (I describe the problem below): https://github.com/TmanTman/StackoverflowQ. Just adapt the path to your Chrome for your system in conf.js.
To simulate an asynchronous Ionic Loading dialog I just add this to the controller in a blank Ionic project:
$interval( function() {
$ionicLoading.show({
template: 'Async ionicLoading',
duration: 5000
});
}, 5000 , 1);
})
I need to get protractor to wait for the dialog to appear, do some tests, wait for the dialog to disappear, and then do some more tests. My latest attempt in my test file is:
it('should only test when ionicLoading appears', function() {
browser.wait(function(){
return element(by.css('.loading-container.visible.active')).isPresent();
}, 10000);
var ionicLoadingText = element(by.css('.loading-container.visible.active')).getText();
expect(ionicLoadingText).toEqual('Async IonicLoading');
})
it('should only test once ionicLoading disappears', function() {
browser.wait(function() {
var deferred = protractor.promise.defer();
var q = element(by.css('.loading-container.visible.active')).isPresent()
q.then( function (isPresent) {
deferred.fulfill(!isPresent);
});
return deferred.promise;
});
expect(1).toEqual(1);
})
I'm trying to avoid using synchronous sleep function, as my code is highly asynchronous. I've tried countless variations but I can't get it to work. Links I've used for info includes:
Protractor blocking wait
Asynchronous Testing with Protractor's ControlFlow
Protractor wait for isElementPresent and click on waited element fails
The problem is two-fold:
1) From what I can deduce, the duration property of the $ionicLoading method is implemented with a timeout function. Protractor does not work well with $timeout. So instead of using the duration property, the $ionicLoading dialog can be hidden with a $interval call (adapting the code from the question):
$interval( function() {
$ionicLoading.show({
template: 'Async IonicLoading'
});
$interval( function() {
$ionicLoading.hide();
}, 5000, 1)
}, 5000 , 1);
2) The code to detect the asynchronous change is the following:
it('should only test when ionicLoading appears', function() {
browser.wait(function() {
var deferred = protractor.promise.defer();
var q = element(by.css('.loading-container.visible.active')).isPresent()
q.then( function (isPresent) {
deferred.fulfill(isPresent);
});
return deferred.promise;
}, 10000);
var ionicLoadingText = element(by.css('.loading-container.visible.active')).getText();
expect(ionicLoadingText).toEqual('Async IonicLoading');
})
it('should only test once ionicLoading disappears', function() {
browser.wait(function() {
var deferred = protractor.promise.defer();
var q = element(by.css('.loading-container.visible.active')).isPresent()
q.then( function (isPresent) {
deferred.fulfill(!isPresent);
});
return deferred.promise;
}, 10000);
expect(1).toEqual(1);
})
Then both tests pass.

How to I find an element with protractor?

It seems if I use element(by.css('#id')); protractor sometimes finds it, sometimes doesn't. Depends if the DOM is settled down or not. I've gone and some this:
getElementsByCss: function (cssVal) {
// return element.all((by.css(cssVal)));
return self.wait(function () {
return element.all((by.css(cssVal))).then(function (els) {
return els;
}, function () {
return false; // Don't fail, we want to retry
});
}, GET_ELEMENT_TIMEOUT, 'Elements not found by css: ' + cssVal);
},
which plays a game of retrying to get the element for some timeout period. We use 5 seconds. This seems to work, most of the time. When running locally we have no issues.
When we run on the cloud (sauce labs) they have exceedingly slow VMs. We still get random issues. Unfortunately these games have to be played everywhere, for example:
getText: function (el) {
return self.wait(function () {
return el.getText();
}, 1000, 'Could not get element text.');
},
or
expectBulletin: function () {
var el, text;
return self.wait(function () {
// Find the bulleting element, make sure it's visible and grab its text.
// If any of those three fail, try all of them again.
return self.getElementByCss('.bulletin')
.then(function (elm) {
el = elm;
return self.isElementVisible(el);
}, function () {
return false;
})
.then(function () {
return self.getText(el).then(function (text) {
return text;
}, function () {
return false;
});
}, function () {
return false;
});
}, 10000, 'Count not get bulletin text.')
.then(function (result) {
text = result;
return self.executeScript('arguments[0].click();', el.getWebElement());
})
.then(function () {
return self.isElementNotVisible(el);
})
.then(function () {
return text;
});
},
all the self.waits are just a browser.driver.wait wrapper...
wait: function (fn, timeout, msg) {
return browser.driver.wait(fn, timeout, msg);
},
This feels like a big pack of bandaids and its not working all the time. expectBulletin works 99% of the time locally, but when run remotely on the slow VMs, it works about 50% of the time. Sometimes the text comes back blank, or issues about clicking an invisible element or just not finding the bulletin element.
Is there any better way to do this?
it seems if I use element(by.css('#id')); protractor sometimes finds
it, sometimes doesn't.
You know that it returns a promise right? If you want to get the absolute value of an element, you have to handle its promise first.
Protractor waits for the page to be ready before doing any assertions. If your page is "ready" but some data is missing, there is something wrong with your angular application.