I do end to end test using protractor.When I start up the test this message appears to me
conFusion App E2E Testing menu 0 item should show the first comment author as
Message:
Failed: No element found using locator: by.model("FiltText")
How can I make the protractor wait until element appears in the DOM?
the corresponding protractor configuration code is :
exports.config = {
allScriptsTimeout:11000,
specs: [
'e2e/*.js'
],
capabilities: {
'browserName': 'chrome'
},
baseUrl: 'http://localhost:3001/',
framework: 'jasmine',
directConnect: true,
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000
}
};
scenarios.js code that contain e2e test
describe('menu 0 item', function() {
beforeEach(function() {
browser.get('index.html#/menu/0');
});
it('should have a name', function() {
var name = element(by.binding('dish.name'));
expect(name.getText()).
toEqual('Uthapizza Hot $4.99');
});
it('should show the number of comments as', function() {
expect(element.all(by.repeater('comment in dish.comments'))
.count()).toEqual(5);
});
it('should show the first comment author as', function() {
element(by.model('FiltText')).sendKeys('author');
expect(element.all(by.repeater('comment in dish.comments'))
.count()).toEqual(5);
var author = element.all(by.repeater('comment in dish.comments'))
.first().element(by.binding('comment.author'));
expect(author.getText()).toContain('25 Cent');
});
});
You can use ExpectedConditions:
var EC = protractor.ExpectedConditions;
var timeout = 5000; // in miliseconds
// Waits for the element to be present on the dom.
browser.wait(EC.presenceOf(element(by.model("FiltText"))), timeout);
// Here you can safely use your element, because here it is guaranted to be present.
Because protractor uses control flow, this command will not finish until the FiltText is visible or the time is greater than timeout.
More information here:
http://www.protractortest.org/#/api?view=ProtractorExpectedConditions
there are many ways to achieve this, for example :
Use protractor.ExpectedConditions API
Wait for some seconds to ensure everything is loaded, here is a sample code :
utils.js
'use strict';
/**
* Navigate to an url and wait some seconds
* #param {string} path The path
* #param {seconds} [seconds] The number of seconds to wait for
* #returns {Promise}
* #see waitSomeSeconds
*/
function navigateAndWait(path, seconds) {
return browser.get(path)
.then(function () {
// Wait some seconds until page is fully loaded
return waitSomeSeconds(seconds);
});
}
/**
* Wait some seconds (default is 3)
* #param {int} [seconds]
* #returns {Promise}
*/
function waitSomeSeconds(seconds) {
return browser.sleep((seconds || 3) * 1000);
}
module.exports = {
navigateAndWait: navigateAndWait,
waitSomeSeconds: waitSomeSeconds
}
Then you can use it in your tests :
**sampleSpec.js**
'use strict';
var util = require('./utils');
describe('Products', function () {
it('should be redirected to products page after login', function (done) {
util.waitSomeSeconds().then(function() {
browser.getCurrentUrl().then(function (value) {
var relativePath = value.substring(value.lastIndexOf('/'));
expect(relativePath).toEqual('/products');
done();
});
});
it('should navigate to products list', function() {
return util.navigateAndWait('/products');
});
it('should display title with correct data', function() {
expect(element(by.css('h1')).getText()).toBe('Newest products');
});
});
Related
I am experiencing problems when running the following script on a specific site. The script works on firefox but on chrome it crashes with the following stacktrace:
Protractor.waitForAngular() - Locator: By(css selector, *[id="loading-app-content"])
at WebDriver.schedule (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/webdriver.js:377:17)
at ProtractorBrowser.executeAsyncScript_ (/usr/local/lib/node_modules/protractor/built/browser.js:232:28)
at runWaitForAngularScript (/usr/local/lib/node_modules/protractor/built/browser.js:263:30)
at ProtractorBrowser.waitForAngular (/usr/local/lib/node_modules/protractor/built/browser.js:266:16)
at ElementArrayFinder.getWebElements (/usr/local/lib/node_modules/protractor/built/element.js:154:29)
at ElementFinder.isPresent (/usr/local/lib/node_modules/protractor/built/element.js:904:46)
at /usr/local/lib/node_modules/protractor/built/expectedConditions.js:86:20
at /usr/local/lib/node_modules/protractor/built/expectedConditions.js:63:20
at /usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/webdriver.js:716:14
at TaskQueue.execute_ (/usr/local/lib/node_modules/protractor/node_modules/selenium-webdriver/lib/promise.js:2913:14)
My protractor-conf.js
exports.config = {
seleniumAddress: 'http://localhost:4444/wd/hub', // This is targetting your local running instance of the selenium webdriver
specs: [
'./features/**/*.feature'
],
capabilities: {
browserName: 'chrome',
},
framework: 'custom',
frameworkPath: require.resolve('protractor-cucumber-framework'), // Here it is
cucumberOpts: {
format: 'pretty',
require: './features/step_definitions/**/*.js' // This is where we'll be writing our actual tests
},
allScriptsTimeout: 11000,
onPrepare: function() {
browser.driver.manage().window().setSize(1400, 900);
browser.driver.manage().window().maximize();
},
useAllAngular2AppRoots: true
};
The test:
test = function() {
var EC = protractor.ExpectedConditions;
var el= element(by.className("preview-toggle"));
this.Given(/^I open up the application$/, function (callback) {
browser.get('foo.com').then(callback);
});
this.When(/^I click on add$/, function (callback) {
// Write code here that turns the phrase above into concrete actions
browser.wait(EC.invisibilityOf(element(by.id("loading-app-content"))), 30000).then(function () {
browser.wait(EC.presenceOf(element(by.css(".preview-toggle"))), 30000).then(function () {
browser.wait(EC.visibilityOf(element(by.css(".preview-toggle"))), 30000).then(function () {
browser.wait(EC.elementToBeClickable(element(by.css(".preview-toggle"))), 30000).then(function () {
el.click().then(callback);
});
});
});
});
});
this.Then(/^I should be the best$/, function (callback) {
callback();
});
};
module.exports=test;
Thing is every actions I do (even clicking on body) gives this error. Any other angular page works in chrome.
My specfic question is: what is causing chrome to crash?
I have simple test doing login and trying to check if it's success:
describe('My Angular App', function () {
describe('visiting the main homepage', function () {
beforeEach(function () {
browser.get('/');
element(By.id("siteLogin")).click();
});
it('should login successfully', function() {
element(By.name("login_username")).sendKeys("test#email.com");
element(By.name("login_password")).sendKeys("testpass");
element(By.id("formLoginButton")).click().then(function() {
browser.getCurrentUrl().then(function(url){
expect(url).toContain("profile");
});
});
});
});
});
It goes well until that last part where I'm checking URL, and in Selenium Server I get:
INFO - Executing: [execute async script: try { return (function (rootSelector, callback) {
var el = document.querySelector(rootSelector);
try {
if (window.getAngularTestability) {
window.getAngularTestability(el).whenStable(callback);
return;
}
if (!window.angular) {
throw new Error('angular could not be found on the window');
}
if (angular.getTestability) {
angular.getTestability(el).whenStable(callback);
} else {
if (!angular.element(el).injector()) {
throw new Error('root element (' + rootSelector + ') has no injector.' +
' this may mean it is not inside ng-app.');
}
angular.element(el).injector().get('$browser').
notifyWhenNoOutstandingRequests(callback);
}
} catch (err) {
callback(err.message);
}
}).apply(this, arguments); }
catch(e) { throw (e instanceof Error) ? e : new Error(e); }, [body]])
and also I get:
Failures:
1) My Angular App visiting the main homepage should login successfully
Message:
Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
My protractor-conf.js:
exports.config = {
seleniumAddress: 'http://localhost:4444/wd/hub',
baseUrl: 'http://localhost:8080/Mysite',
capabilities: {
'browserName': 'firefox' // muste use firefox because I can't get .click() to work in Chrome
},
specs: [
'spec-e2e/*.js'
],
framework: 'jasmine2',
jasmineNodeOpts: {
isVerbose: true,
showColors: true,
defaultTimeoutInterval: 30000
}
};
I appreciate any help on this one.
Thanks
Looks like there is a non-Angular page opened after a click. If this is the case, you need to turn the synchronization between Protractor and Angular off:
afterEach(function () {
browser.ignoreSynchronization = false;
});
it('should login successfully', function() {
element(By.name("login_username")).sendKeys("test#email.com");
element(By.name("login_password")).sendKeys("testpass");
browser.ignoreSynchronization = true;
element(By.id("formLoginButton")).click().then(function() {
expect(browser.getCurrentUrl()).toContain("profile");
});
});
Note that you don't need to explicitly resolve the promise returned by getCurrentUrl and can let the expect() do that for you implicitly.
You may also need to wait for the URL to change:
var urlChanged = function(desiredUrl) {
return browser.getCurrentUrl().then(function(url) {
return url == desiredUrl;
});
};
browser.wait(urlChanged("my desired url"), 5000);
Hi I have created a custom component that uses an external template and requires a service, when the template is defined as external, the unit tests break.
template reference in main.html <cc-accordion items="genres"></cc-accordion>
This is the component js
angular.module("ccAccordion", [])
.directive("ccAccordion", function () {
return {
restrict: "AE",
templateUrl: "components/accordion/accordion.html",
scope: {
items: "="
},
link: function (scope) {
scope.$watch("items", function (items) {
angular.forEach(items, function (item) {
item.selected = false;
});
scope.select = function (index) {
(scope.items[index].selected === true ) ? scope.items[index].selected = false : scope.items[index].selected = true;
angular.forEach(scope.items, function (item, key) {
if(key !== index) {
item.selected = false;
}
});
};
});
}
};
});
here is the unit test
describe('ccAccordion', function () {
var elm, scope;
beforeEach(module('ccAccordion'));
beforeEach(inject(function ($rootScope, $compile) {
elm = angular.element(
'<cc-accordion items="genres"></cc-accordion>'
);
scope = $rootScope;
scope.genres = [{
title: 'Scifi',
description: 'Scifi description'
}, {
title: 'Comedy',
description: 'Comedy description'
}];
$compile(elm)(scope);
scope.$digest();
}));
it('should create clickable titles', function () {
var titles = elm.find('.cc-accord h2');
expect(titles.length).toBe(2);
expect(titles.eq(0).text().trim()).toBe('Scifi');
expect(titles.eq(1).text().trim()).toBe('Comedy');
});
it('should bind the content', function () {
var contents = elm.find('.cc-accord-content p');
expect(contents.length).toBe(2);
expect(contents.eq(0).text().trim()).toBe('Scifi description');
expect(contents.eq(1).text().trim()).toBe('Comedy description');
});
it('should change active content when header clicked', function () {
var titles = elm.find('.cc-accord h2'),
divs = elm.find('.cc-accord');
// click the second header
titles.eq(1).find('a').click();
// second div should be active
expect(divs.eq(0)).not.toHaveClass('active');
expect(divs.eq(1)).toHaveClass('active');
});
});
here is the template
<div class="cc-accord" ng-repeat="item in items" ng-class="{active:item.selected}" ng-click="select($index)">
<h2><a>
{{item.title}} <cc-up-down-arrow></cc-up-down-arrow>
</a></h2>
<div class="cc-accord-content"><p>{{item.description}}</p></div>
</div>
if I put the template in the directive the tests pass. I've tried editing the karma.conf to use a preprocessor and 'ng-html2js' but can't get it to work. Any help would be much appreciated. Thanks in advance
here is my karma.conf, I'm using mean stack to scaffold the app
// Karma configuration
// http://karma-runner.github.io/0.10/config/configuration-file.html
module.exports = function (config) {
config.set({
// base path, that will be used to resolve files and exclude
basePath: '',
// testing framework to use (jasmine/mocha/qunit/...)
frameworks: ['jasmine'],
// list of files / patterns to load in the browser
files: [
'client/bower_components/jquery/dist/jquery.js',
'client/bower_components/angular/angular.js',
'client/bower_components/angular-mocks/angular-mocks.js',
'client/bower_components/angular-resource/angular-resource.js',
'client/bower_components/angular-animate/angular-animate.js',
'client/bower_components/angular-cookies/angular-cookies.js',
'client/bower_components/angular-sanitize/angular-sanitize.js',
'client/bower_components/angular-route/angular-route.js',
'client/bower_components/lodash/dist/lodash.compat.js',
'client/bower_components/angular-socket-io/socket.js',
'client/bower_components/angular-ui-router/release/angular-ui- router.js',
'client/app/app.js',
'client/app/**/*.js',
'client/components/**/*.js',
'client/app/**/*.html',
'client/components/**/*.html'
],
preprocessors: {
'client/components/accordion/accordion.html': 'ng-html2js'
},
ngHtml2JsPreprocessor: {
/*stripPrefix: 'client/',
stripSufix: '.ext',
// prepend this to the
prependPrefix: 'served/'*/
},
ngJade2JsPreprocessor: {
stripPrefix: 'client/'
},
// list of files / patterns to exclude
exclude: [],
// web server port
port: 8080,
// level of logging
// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: false,
// Start these browsers, currently available:
// - Chrome
// - ChromeCanary
// - Firefox
// - Opera
// - Safari (only Mac)
// - PhantomJS
// - IE (only Windows)
browsers: ['PhantomJS'],
// Continuous Integration mode
// if true, it capture browsers, run tests and exit
singleRun: false
});
};
New matcher function for jasmine 2.0
beforeEach(function () {
jasmine.addMatchers({
toHaveClass: function () {
return {
compare: function (actual, expected) {
var classTest = actual.hasClass(expected);
classTest ? classTest = true : classTest = false;
return {
pass: classTest,
message: 'Expected ' + angular.mock.dump(actual) + ' to have class ' + expected
};
}
};
}
});
});
Usage
expect(divs.eq(0)).not.toHaveClass('active');
expect(divs.eq(1)).toHaveClass('active');
I just compared you karma config file with mine, seeing following differences:
In field preprocessors, try providing an array instead of a single processor
Use ngHtml2JsPreprocessor field
Your karma conf file should contain following:
preprocessors: {
'client/components/accordion/accordion.html': ['ng-html2js']
},
ngHtml2JsPreprocessor: {
stripPrefix: 'client/'
}
As Himmet said the karma.conf file needs to be edited and you need to put the location of the template into the preprocessors section
preprocessors: {
'client/components/accordion/accordion.html': ['ng-html2js'],
'another-template-path/template.html': ['ng-html2js']
},
ngHtml2JsPreprocessor: {
stripPrefix: 'client/'
}
I'm trying to write a test for debouncing user input in a search query. The function is defined on a Backbone View:
SearchView = Backbone.View.extend({
events: {
"input .search-input": "search"
},
// init, render, etc.
search: _.debounce(function() {
this.collection.fetch();
}, 200)
});
Originally, the Backbone library (v0.9.10) used Underscore (v1.4.4), and the test was defined as follows:
describe("SearchView", function() {
var view, $viewContainer;
beforeEach(function() {
appendSetFixtures('<div class="jasmine-container"></div>');
$viewContainer = $(".jasmine-container");
view = new SearchView({
el: $viewContainer
});
});
afterEach(function() {
view.remove();
view.cleanup();
});
//...
describe("wires the search input", function() {
var collectionStub,
fakeTimer;
beforeEach(function() {
collectionStub = sinon.stub(
SearchResultsCollection.prototype,
"fetch"
);
fakeTimer = sinon.useFakeTimers();
});
afterEach(function() {
collectionStub.restore();
fakeTimer.restore();
});
it("should not trigger a search before 200ms", function() {
fakeTimer.tick(199);
expect(collectionStub).not.toHaveBeenCalled();
});
it("should trigger a search after 200ms", function() {
fakeTimer.tick(200);
expect(collectionStub).toHaveBeenCalled();
});
});
});
However, now I want to incorporate LoDash instead of Underscore. Using the latest Underscore compatibility build on their site (LoDash 2.4.1 / Underscore 1.5.6), all my tests pass except for the one using _.debounce!
I did some research and came across these relevant issues to create a LoDash Underscore build with runInContext, but I have no idea how to use it due to lack of examples. How can I use _.runInContext() in my spec(s) to work with sinon.fakeTimer?
SearchView = Backbone.View.extend({
events: {
"input .search-input": function() {
this.search();
}
},
initialize: function() {
this.search = _.debounce(this.search, 200);
}
// init, render, etc.
search: function() {
this.collection.fetch();
}
});
describe("SearchView", function() {
var view;
var $viewContainer;
var clock;
var lodash = window._;
beforeEach(function() {
appendSetFixtures('<div class="jasmine-container"></div>');
$viewContainer = $(".jasmine-container");
clock = sinon.useFakeTimers();
window._ = _.runInContext(window);
view = new SearchView({
el: $viewContainer
});
});
afterEach(function() {
view.remove();
view.cleanup();
clock.restore();
window._ = lodash;
});
//...
describe("wires the search input", function() {
var collectionStub;
beforeEach(function() {
collectionStub = sinon.stub(
SearchResultsCollection.prototype,
"fetch"
);
});
afterEach(function() {
collectionStub.restore();
});
it("should not trigger a search before 200ms", function() {
fakeTimer.tick(199);
expect(collectionStub).not.toHaveBeenCalled();
});
it("should trigger a search after 200ms", function() {
fakeTimer.tick(200);
expect(collectionStub).toHaveBeenCalled();
});
});
});
You need add this line
_ = _.runInContext(window);
before creation (not initialization) of SearchView or any call of _.debounce(). So it should be right after including Lo-Dash.
This allows you to run lodash in global window context so you can use overridden by SinonJS setTimeout.
I'm running a protractor tests and filling in a form with dropdowns. How can I ensure that each dropdown has a promise that is resolved before moving onto the next one? Right now I'm using sleeps and know that can't be the right way.
Describe('Filling site utility form', function() {
beforeEach(function(){
var input_monthly = by.linkText('Input Monthly');
browser.wait(function() {
return browser.isElementPresent(input_monthly);
});
var field1 = element(by.id('field1'));
Test.dropdown(field1, 1);
var field2 = element(by.id('field2'));
Test.dropdown(field2, 1);
});
My dropdown function is:
dropdown = function(element, index, milliseconds) {
element.findElements(by.tagName('option'))
.then(function(options) {
options[index].click();
});
if (typeof milliseconds !== 'undefined') {
browser.sleep(milliseconds);
}
}
Not 100% sure what you mean but I suspect what you're interested in is this:
describe('Filling site utility form', function() {
beforeEach(function(){
var input_monthly = by.linkText('Input Monthly');
browser.wait(function() {
return browser.isElementPresent(input_monthly);
});
var promise1 = element(by.id('field1')).all(by.tagName('option')).get(1).click();
var promise2 = element(by.id('field2')).all(by.tagName('option')).get(1).click();
protractor.promise.all([promise1, promise2]).then(function() {
//what do you want to do next?
});
});
});