Is there a tool to split Protractor tests into component files? - angularjs

I'm working on building an extensible automated test suite with Protractor (angularJS/Jasmine framework).
As long as all of my variables and functions and jasmine are in the same file, it runs semi-okay.
But every effort I make to break it into export/require is a nightmare.
Is there a tool that will just find the parts of my test and automatically reformat it and break it into individual files and folders, so that the thing will actually run?
Thanks!

I don´t know a tool for what you want. However if I were you, I would keep working with node's way of sharing files (export/require). Once you understand it, if you keep it clean and tidy, you could grow your app in a "clean" way.
EDIT:
As #MBielski said, Page Objects Model is also helpful when maintaining you test code.
Definition from the Selenium team:
Page Object is a Design Pattern which has become popular in test
automation for enhancing test maintenance and reducing code
duplication. A page object is an object-oriented class that serves as
an interface to a page of your AUT. The tests then use the methods of
this page object class whenever they need to interact with that page
of the UI. The benefit is that if the UI changes for the page, the
tests themselves don’t need to change, only the code within the page
object needs to change. Subsequently all changes to support that new
UI are located in one place.
And now an example without using page objects and then one using it.
Without:
describe('angularjs homepage', function() {
it('should greet the named user', function() {
browser.get('http://www.angularjs.org');
element(by.model('yourName')).sendKeys('Julie');
var greeting = element(by.binding('yourName'));
expect(greeting.getText()).toEqual('Hello Julie!');
});
});
With:
var AngularHomepage = function() {
var nameInput = element(by.model('yourName'));
var greeting = element(by.binding('yourName'));
this.get = function() {
browser.get('http://www.angularjs.org');
};
this.setName = function(name) {
nameInput.sendKeys(name);
};
this.getGreeting = function() {
return greeting.getText();
};
};
describe('angularjs homepage', function() {
it('should greet the named user', function() {
var angularHomepage = new AngularHomepage();
angularHomepage.get();
angularHomepage.setName('Julie');
expect(angularHomepage.getGreeting()).toEqual('Hello Julie!');
});
});
You can also define various test suites. Take a look at this config file:
exports.config = {
seleniumAddress: 'http://localhost:4444/wd/hub',
capabilities: {
'browserName': 'chrome'
},
// Spec patterns are relative to the location of the spec file. They may
// include glob patterns.
suites: {
homepage: 'tests/e2e/homepage/**/*Spec.js',
search: ['tests/e2e/contact_search/**/*Spec.js',
'tests/e2e/venue_search/**/*Spec.js']
},
jasmineNodeOpts: {
showColors: true, // Use colors in the command line report.
}
};

Related

Use the same variable in each test file

For purpose of testing I am using multiple files. Each one represent one part of website for test.
In one of start test file I create variabile for name of new case, it looks like:
var moment = require('../../../../../node_modules/moment');
describe('Create new case', function() {
var caseNumber = moment().format('YYYYMMDD-HHmmss-SS');
But somewhere at the end of all (in another test file) I would like to use this caseNumber again (exactly same as was used in first test, not generate new one).
Can anyone advise me how can I do it in protractor?
It doesn't sound quite right to have one test depending on an another test to define and export a variable. Set the global variable inside onPrepare() using global:
onPrepare: function() {
global.caseNumber = moment().format('YYYYMMDD-HHmmss-SS');
},
Then, you'll have caseNumber as a global variable across all the tests.
There's no need to use globals. You can make it more readable by creating your own module and requiring it:
//test/lib/homepage.js
var moment = require('moment');
module.exports = {
caseNumber: moment().format('YYYYMMDD-HHmmss-SS'),
getContent: function () { //another example of reuse
return element(by.css('body'));
});
};
//test/homepage.spec.js
var page = require('./lib/homepage');
describe('Homepage', function() {
it('should display correct date', function () {
expect(page.getContent()).toContain(page.caseNumber);
});
});

require.js dependencies - how to incorporate dependency methods in additional script

I'm using raphael.js in a backbone.js project. Importing raphael works like a charm (this version is AMD compliant). Everything is working as expected. "app" here is a predefined global object defined in another app.js file.
define(['jquery', 'underscore', 'app', 'backbone','raphael' ],
function($, _, app, Backbone, Raphael) {
var AppView = Backbone.View.extend({
initialize: function() {
app.paper = Raphael(0, 0, app.w, app.h);
}
...
}) })
Now my app.paper has all Raphael methods. Awesome!
I just discovered in the Raphael API that I can add my own predefined methods using Raphael.el and Raphael.fn
initialize: function() {
app.paper = Raphael(0, 0, app.w, app.h);
Raphael.el.myfill = function(){
this.attr('fill', '90-#fff-#000');
}
app.paper.circle(x,y,r).myfill(); //it works! (Brilliant!)
}
}
My question is, how can I put the Raphael.el.myfill definition along with other Raphael.fn.mydefinedmethods into another javascript file and bring it into the AppView above?
I don't want to clog up my AppView file with lengthy definitions, and I also would like to provide variability as to which Raphael.[el|fn] definitions I use in different views. But since these object constructors are already part of the Raphael.js object that I've already pulled in as a dependency, I'm not sure how to separate the Raphael.el and Raphael.fn definitions out using the require.js protocol. Before require.js I would have simply put such definitions in another myRaphaelDefs.js file, added another "script" tag to my html and they'd all be available always, but this is the 2015 and I've jumped on the modular js bandwagon.
I'm using RedRaphael branch which is AMD compliant, so I have no "define" wrapper on the Raphael.js itself. If this library did come with such a wrapper I might try adding the outsourced definitions directly into the Raphael.js as dependencies. (not an option) RedRaphael works with require.js right out of the box, so there's no "define" wrapper there.
What we do is add a wrapper around our libraries and route it with the map option in require.config, so the wrapper gets the original library, but everything else goes throw the wrapper:
raphael-wrapper.js:
define(['raphael'], function (Raphael) {
Raphael.el.myfill = function(){
this.attr('fill', '90-#fff-#000');
};
return Raphael;
});
require.config:
{
paths: {
'raphael': '/path/to/rahpael'
},
map: {
'*': {
'raphael': '/path/to/rahpael-wrapper'
},
'/path/to/rahpael-wrapper': {
'raphael': 'raphael'
}
}
}

CasperJS Waiting for live DOM to populate

I'm evaluating using Casper.js to do functional/acceptance testing for my app. The biggest problem I've seen so far is that my app is an SPA that uses handlebars templates (which are compiled to JS) The pages of my app are nothing more than a shell with an empty div where the markup will be injected via JS.
I've messed around with Casper a little and tried using its waitFor functions. All I can seem to get from it are my main empty page before any of the markup is injected. I've tried waitForSelector but it just times out after 5 seconds. Should I try increasing the timeout? The page typically loads in a browser very quickly, so it seems like there may be another issue.
I'm using Yadda along with Casper for step definitions:
module.exports.init = function() {
var dictionary = new Dictionary()
.define('LOCALE', /(fr|es|ie)/)
.define('NUM', /(\d+)/);
var tiles;
function getTiles() {
return document.querySelectorAll('.m-product-tile');
}
function getFirstTile(collection) {
return Array.prototype.slice.call(collection)[0];
}
var library = English.library(dictionary)
.given('product tiles', function() {
casper.open('http://www.example.com/#/search?keywords=ipods&resultIndex=1&resultsPerPage=24');
casper.then(function() {
// casper.capture('test.png');
casper.echo(casper.getHTML());
casper.waitForSelector('.m-product-tile', function() {
tiles = getTiles();
});
});
})
.when('I tap a tile', function() {
casper.then(function() {
casper.echo(tiles); //nodelist
var tile = Array.prototype.slice.call(tiles)[0];
casper.echo(tile); //undefined!
var pid = tile.getAttribute('data-pid');
})
})
.then('I am taken to a product page', function() {
});
return library;
};
Any Angular, Backbone, Ember folks running into issues like this?

Testing backbone router with jasmine and sinon. Cannot call the pushState.

I was following this question to test the router. My router is really simple:
App.Router = Backbone.Router.extend({
routes:{
"": "index",
"help": "help"
},
help: function() {/* not really needed */ },
index: function(){
// does something
}
});
And this is an apptempted translation of what should be the test using jasmine with sinon:
it('triggers the "index" route', function() {
var router = new App.Router();
Backbone.history.start();
//Not calling navigate it's a problem
router.navigate('help', {
trigger : true, replace: true
});
var index = sinon.spy(router, 'index');
var spyHasPS = sinon.spy(function(
data, title, url) {
expect(url).toEqual('/');
router.index();
});
var spyNoPS = sinon.spy(function(loc, frag) {
expect(frag).toEqual('');
router.index();
});
if (Backbone.history._hasPushState) {
pushStateSpy = sinon.stub(window.history, 'pushState', spyHasPS );
// window.history.pushState();
} else if (Backbone.history._wantsHashChange) {
pushStateSpy = sinon.stub(Backbone.history, '_updateHash', spyNoPS);
//Backbone.history._updateHash(window.location, '');
}
router.navigate('', {
trigger : true, replace: true
});
expect(pushStateSpy.called).toBe(true);
expect(index.called).toBe(true);
});
This test works but I could achieve it because I navigated first on "help". "help" was just something I created to pass the test but the original question didn't do it and was passing. Did I do something wrong? I also run his test but the error I'm getting is:
Expected spy _updateHash to have been called. Error: Expected spy
_updateHash to have been called.
at null.<anonymous> (/src/test/js/spec/wfcRouter.spec.js:65:32) Expected spy index to have been called.
I believe the "problem" is in the navigate function. At a certain point in the navigate: function(fragment, options) we have this control:
fragment = this.getFragment(fragment || '');
if (this.fragment === fragment) return;
So...does it make sense to test the pushState when you just have one route (remember I added "help" just to make this test pass so I don't need it)? If it does make sense, how can I achieve this test?
It seems like what you are testing is Backbone code, but there's no need for you to test that: presumably the Backbone code has been tested plenty by Jeremy Ashkenas (and if you look at the Backbone project on GitHub you will see that he does in fact have a comprehensive test suite). So, rather than re-testing code you didn't write that's already been tested, what you really should be testing is the code you wrote.
If you agree with that principle, then you can simplify your test a great deal, down to just:
it('triggers the "index" route', function() {
var router = new App.Router();
router.index();
expect(thingThatShouldHaveHappenedInIndexRouteDidHappen).toBe(true);
});

Understanding Backbone architecture base concepts

I'm trying to working with backbone but I'm missing it's base concepts because this is the first JavaScript MVVM Framework I try.
I've taken a look to some guide but I think I still missing how it should be used.
I'll show my app to get some direction:
// Search.js
var Search = {
Models: {},
Collections: {},
Views: {},
Templates:{}
};
Search.Models.Product = Backbone.Model.extend({
defaults: search.product.defaults || {},
toUrl:function (url) {
// an example method
return url.replace(" ", "-").toLowerCase();
},
initialize:function () {
console.log("initialize Search.Models.Product");
}
});
Search.Views.Product = Backbone.View.extend({
initialize:function () {
console.log("initialize Search.Views.Product");
},
render:function (response) {
console.log("render Search.Views.Product");
console.log(this.model.toJSON());
// do default behavior here
}
});
Search.Models.Manufacturer = Backbone.Model.etc...
Search.Views.Manufacturer = Backbone.View.etc...
then in my web application view:
<head>
<script src="js/jquery.min.js"></script>
<script src="js/underscore.min.js"></script>
<script src="js/backbone/backbone.min.js"></script>
<script src="js/backbone/Search.js"></script>
</head>
<body>
<script>
var search = {};
search.product = {};
search.product.defaults = {
id:0,
container:"#search-results",
type:"product",
text:"<?php echo __('No result');?>",
image:"<?php echo $this->webroot;?>files/product/default.png"
};
$(function(){
var ProductModel = new Search.Models.Product();
var ProductView = new Search.Views.Product({
model:ProductModel,
template:$("#results-product-template"),
render:function (response) {
// do specific view behavior here if needed
console.log('render ProductView override Search.Views.Product');
}
});
function onServerResponse (ajax_data) {
// let's assume there is some callback set for onServerResponse method
ProductView.render(ajax_data);
}
});
</script>
</body>
I think I missing how Backbone new instances are intended to be used for, I thought with Backbone Search.js I should build the base app like Search.Views.Product and extend it in the view due to the situation with ProductView.
So in my example, with render method, use it with a default behavior in the Search.js and with specific behavior in my html view.
After some try, it seems ProductModel and ProductView are just instances and you have to do all the code in the Search.js without creating specific behaviors.
I understand doing it in this way make everything easiest to be kept up to date, but what if I use this app in different views and relative places?
I'm sure I'm missing the way it should be used.
In this guides there is no code used inside the html view, so should I write all the code in the app without insert specific situations?
If not, how I should write the code for specific situations of the html view?
Is it permitted to override methods of my Backbone application?
Basically, you should think of the different parts like this:
templates indicate what should be displayed and where. They are writtent in HTML
views dictate how the display should react to changes in the environment (user clicks, data changing). They are written in javascript
models and collections hold the data and make it easier to work with. For example, if a model is displayed in a view, you can tell the view to refresh when the model's data changes
then, you have javascript code that will create new instances of views with the proper model/collection and display them in the browser
I'm writing a book on Marionette.js, which is a framework to make working with Backbone easier. The first chapters are available in a free sample, and explain the above points in more detail: http://samples.leanpub.com/marionette-gentle-introduction-sample.pdf

Resources