Function stopped working after Protractor PageObject implementation - javascript

I've got a problem with executing multiple tests(i've got two here). It seems like function deleteOneTask() stopped working and my tests started to fail each other. Before i started to use PageObject everything was ok.
Stacktrace:
TypeError: Cannot read property 'click' of undefined
at c:\Users\Денис\WebstormProjects\ProtractorTest\pages\angular.page.js:29:30
It refers to this line: this.deleteButton.click();
Here is my spec.js
'use strict';
var todoAppPage = require('../pages/angular.page');
describe('angularjs todo list', function () {
var page;
beforeEach(function () {
page = new todoAppPage();
page.get();
});
it('should add a todo task', function () {
page.addNewTask('my first task');
expect(page.todoList.count()).toEqual(1);
expect(page.todoList.get(0).getText()).toEqual('my first task');
page.deleteOneTask(); //here it won't work
});
it('should show correct number of undone tasks', function () {
page.addNewTask('my first task');
expect((page.counter).getText()).toEqual('1');
page.deleteOneTask(); //here it won't work
});
it('should show correct number of undone tasks2', function () {
var deleteButton = element.all(by.className('destroy')).get(0);
var viewArea = element(by.model('todo.completed'));
page.addNewTask('my first task');
expect((page.counter).getText()).toEqual('1');
element(by.id('footer')).element(by.linkText('All')).click();
browser.driver.actions().mouseMove(viewArea).perform().then(function () { //hover and delete single task
deleteButton.click();
});; //here it will work
});
});
Here is Page Object file
'use strict';
var todoAppPage = function() {
this.newTodo = element(by.model('newTodo'));
this.todoList = element.all(by.repeater('todo in todos'));
this.viewArea = element(by.model('todo.completed'));
this.deleteButton = element.all(by.className('destroy')).get(0);
this.categoryAll = element(by.id('footer')).element(by.linkText('All'));
this.counter = element(by.id('todo-count')).element(by.className('ng-binding'));
this.get = function() {
browser.get('#/');
};
this.addNewTask = function (taskName) {
this.newTodo.sendKeys(taskName);
this.newTodo.sendKeys(protractor.Key.ENTER);
};
this.deleteOneTask = function () {
this.categoryAll.click(); //go to 'All' category
browser.driver.actions().mouseMove(this.viewArea).perform().then(function () { //hover and delete single task
this.deleteButton.click();
});
};
};
module.exports = todoAppPage;

As mentioned in a comment, using a that = this will solve your problem. Eg.
this.deleteOneTask = function () {
var that = this;
this.categoryAll.click(); //go to 'All' category
browser.driver.actions().mouseMove(this.viewArea).perform().then(function () { //hover and delete single task
that.deleteButton.click();
});
That's the best solution I've seen but I'd be happy to see a better one too.

Related

Jasmine Dom element test

I'm new to writing tests and I'm trying to figure out how to test if after a click the right content is loaded. I'm testing a directive, but the loaded content in the center panel. I'm first firing a click event and then I try to compile the loaded content and check if the title is the one I expected (Test).
What I'm getting as an error is
"Cannot read property 'innerHTML' of undefined"
Any idea why and how to fix the test?
describe('someSelector', function () {
beforeEach(function () {
module(function($provide) {
$provide.decorator('$timeout', function($delegate) {
var flush = $delegate.flush;
var $timeout = jasmine.createSpy('$timeout').and.callFake($delegate);
$timeout.flush = flush;
return $timeout;
});
});
this.injectDependencies('$compile',
'$scope',
'$httpBackend',
'renderTemplateAndAppendToDom',
'renderTemplate',
'$timeout');
this.render = this.renderTemplateAndAppendToDom;
this.dirStr = '<div class="some-navigator" vx-view="history[history.currentIndex].naviView"></div>';
this.viewStr = '<center-panel></center-panel>';
});
describe('when navigating to an object', function () {
beforeEach(function () {
this.$httpBackend.resetExpectations();
});
it('should load the correct content', function () {
var dir = this.$compile(this.dirStr)(this.$scope);
dir.find('span.some-navigator-label [title="Test"]').click();
dir.remove();
var view = this.$compile(this.viewStr)(this.$scope);
var contentCheck = view.find('span.titlebar-text');
expect(contentCheck[0].innerHTML).toEqual('Test');
this.$scope.$destroy();
view.remove();
});
});
});

Protractor tests inconsistently passing / failing

The problem that I have with this test is that sometimes it passes, sometimes it fails, and when it does the latter a "Failed: No element found using locator: By(css selector, .add.create_ServiceOrders)" message appears in the console. Idk what to do to fix it :(
describe('angularjs homepage', function() {
it('should greet the named user', function() {
//browser.ignoreSynchronization = true
browser.get('https://int.m-tech.com/hotsosmobile/app/Index?/login#/login');
browser.waitForAngular();
var input = element(by.model('loginInfo.login'));
input.sendKeys('xxx');
expect(input.getAttribute('value')).toBe('xxx');
var input = element(by.model('loginInfo.password'));
input.sendKeys('yyy');
expect(input.getAttribute('value')).toBe('yyy');
browser.waitForAngular();
browser.driver.actions().sendKeys(protractor.Key.ENTER).perform();
browser.waitForAngular();
var AddButton = element(by.css(".add.create_ServiceOrders" ));
browser.actions().mouseDown(AddButton).mouseUp().perform();
browser.actions().mouseMove(AddButton).click().perform();
browser.waitForAngular();
var AddButton = element(by.css(".icon-standard-issue-floors" ));
browser.actions().mouseDown(AddButton).mouseUp().perform();
browser.actions().mouseMove(AddButton).click().perform();
browser.waitForAngular();
.....
});
});
Base on my experience, I usually do separate it in another it function something like this.
var AddButton = element(by.css(".add.create_ServiceOrders" ));
it ( 'should pass', function () {
browser.actions().mouseDown(AddButton).mouseUp().perform();
});
it ( 'should pass', function () {
browser.actions().mouseMove(AddButton).click().perform();
});
You need to wait for element to be ready, before manipulating. Try this:
var AddButton = $(".add.create_ServiceOrders");
browser.wait(protractor.ExpectedConditions.visibilityOf(AddButton), 5000, 'Button should be visible');
browser.actions().mouseDown(AddButton).mouseUp().perform();
browser.actions().mouseMove(AddButton).click().perform();
...

jQuery plugins functions overwrite

I have implemented several jQuery plugins for my current project.
Since some plugins have functions with the same name, the one called in the last one defined.
Here is the definition of my first plugin:
$(function($)
{
$.fn.initPlugin1 = function(parameters)
{
var defaultParameters = {};
$(this).data('parameters', $.extend(defaultParameters, parameters));
return $(this);
};
$.fn.function1 = function(){ console.log('Function 1.'); };
$.fn.callFunction = function(){ $(this).function1(); };
});
And here is the definition of my second plugin:
$(function($)
{
$.fn.initPlugin2 = function(parameters)
{
var defaultParameters = {};
$(this).data('parameters', $.extend(defaultParameters, parameters));
return $(this);
};
$.fn.function2 = function(){ console.log('Function 2.'); };
$.fn.callFunction = function(){ $(this).function2(); };
});
I have also this scenario :
$("#div1").initPlugin1().callFunction();
$("#div2").initPlugin2().callFunction();
For this specific scenario the consoles shows: Function 2. Function 2.
In fact, since the callFunction() is also defined in the second plugin, this is the one used.
I would like some advise on what is the best way to solve this problem.
Is it possible to create a thing similiar to a namespace ?
Thank to #syms answer, I have created the following example.
Plugin1:
$(function($) {
$.fn.initPlugin1 = function() {
console.log('Initialized Plugin1');
return $(this);
};
$.fn.initPlugin1.testFunction = function() {
$(this).append('Function 1.');
};
});
Plugin2:
$(function($) {
$.fn.initPlugin2 = function() {
console.log('Initialized Plugin2');
return $(this);
};
$.fn.initPlugin2.testFunction = function() {
$(this).append('Function 2.');
};
});
Main:
(function($)
{
$(document).ready(
function()
{
$("#div1").initPlugin1(); //Run correctly
$("#div2").initPlugin2(); //Run correctly
$("#div1").initPlugin1.testFunction(); //Fail
$("#div2").initPlugin2.testFunction(); //Fail
});
})(jQuery);
When I run my code, I got the following error: Cannot read property 'createDocumentFragment' of null.
Apparently, the this object is corrupted.
you can try this,
$(function($) {
$.fn.initPlugin1 = function() {
console.log('Initialized Plugin1');
return $(this);
};
});
$(function($) {
$.fn.initPlugin2 = function() {
console.log('Initialized Plugin2');
return $(this);
};
$.fn.callFunction = function(param) {
$(this).append(param);
};
});
(function($) {
$(document).ready(
function() {
$("#div1").initPlugin1(); //Run correctly
$("#div2").initPlugin2(); //Run correctly
$("#div1").initPlugin1().callFunction('function1');
$("#div2").initPlugin2().callFunction('function2');
});
})(jQuery);

How to test if method has been called only once and not the second time in Jasmine?

I am testing mechanism that should call method once and prevent any subsequent calls with jasmine, I can see by attaching breakpoints that method is NOT being called second time however jasmine test fails. I would assume it has to do with spy not being designed to be used for multiple checks.
What would be proper solution to given situation?
JSfiddle of Code that is being tested I could not figure out how to do jasmine test jsfiddle properly (Jasmine version I am using is 1.3.1 while test template is on 1.2.0).
Test looks like this:
it("Invoking OnPreQuery will add event listener for OnTheMoveViewPreLoad event. Triggering OnTheMoveViewPreLoad twice will call getChildrenForMasterRecordList only first time", function () {
AddTreeSettingsObjectToBC({ bc: bc, Tree: { IncludeChildren: true} });
ComposeMockPageObjWithObservableFieldsWithChildren();
var preQuerySpy = spyOnEvent(onTheMove.PageDataRoles, 'OnPreQuery');
$(onTheMove.PageDataRoles).trigger('OnPreQuery', { knockoutContextName: 'bc' });
expect('OnPreQuery').toHaveBeenTriggeredOn(onTheMove.PageDataRoles);
expect(preQuerySpy).toHaveBeenTriggered();
var getChildrenForMasterRecordListSpy = spyOn(window, 'getChildrenForMasterRecordList');
$(onTheMove.PageDataRoles).trigger('OnTheMoveViewPreLoad', { knockoutContextName: 'bc' });
expect(getChildrenForMasterRecordListSpy).toHaveBeenCalled();
$(onTheMove.PageDataRoles).trigger('OnTheMoveViewPreLoad', { knockoutContextName: 'bc' });
expect(getChildrenForMasterRecordListSpy).not.toHaveBeenCalled();
});
Code that is being tested:
HTML
<div data-role="page"></div>
Javascript
var onTheMove = function(){};
$.extend(onTheMove,{
NullValue : "null",
PageDataRoles : 'div[data-role="page"], div[data-role="dialog"]',
OnTheMovePrefix : 'OnTheMove_'
});
$(document).on('OnPreQuery', onTheMove.PageDataRoles, function (e, options) {
var isChildAttachmentQueued = true;
var knockoutContextName = options.knockoutContextName;
if (TreeEnabled(knockoutContextName)) {
var isModelReadyToAttachChildren = function () {
var isReady = false;
if (PageObj[knockoutContextName] != undefined) {
isReady = (PageObj[knockoutContextName]().length > 0) && isChildAttachmentQueued;
}
return isReady;
};
var treeSettings = eval(knockoutContextName).Tree;
treeSettings.knockoutContextName = knockoutContextName;
$(onTheMove.PageDataRoles).on('OnTheMoveViewPreLoad', function (e, options) {
if (isModelReadyToAttachChildren()) {
getChildrenForMasterRecordList({
parentTable: eval(knockoutContextName).primaryTableName,
knockoutContextName: treeSettings.knockoutContextName,
parentIdColumn: treeSettings.ParentIdColumn,
masterIdColumn: treeSettings.MasterIdColumn
});
isChildAttachmentQueued = false;
}
});
}
});
function getChildrenForMasterRecordList(options) {
console.log('beep');
}
toHaveBeenCalledTimes(1)
now makes this much easier.
expect(yourSpy).toHaveBeenCalledTimes(1);
Figured it out myself, spy has property callCount that auto-increments by one on each call.
it("Invoking OnPreQuery will add event listener for OnTheMoveViewPreLoad event. Triggering OnTheMoveViewPreLoad twice will call getChildrenForMasterRecordList only first time", function () {
AddTreeSettingsObjectToBC({ bc: bc, Tree: { IncludeChildren: true} });
ComposeMockPageObjWithObservableFieldsWithChildren();
var preQuerySpy = spyOnEvent(onTheMove.PageDataRoles, 'OnPreQuery');
$(onTheMove.PageDataRoles).trigger('OnPreQuery', { knockoutContextName: 'bc' });
expect('OnPreQuery').toHaveBeenTriggeredOn(onTheMove.PageDataRoles);
expect(preQuerySpy).toHaveBeenTriggered();
var getChildrenForMasterRecordListSpy = spyOn(window, 'getChildrenForMasterRecordList');
$(onTheMove.PageDataRoles).trigger('OnTheMoveViewPreLoad', { knockoutContextName: 'bc' });
expect(getChildrenForMasterRecordListSpy).toHaveBeenCalled();
$(onTheMove.PageDataRoles).trigger('OnTheMoveViewPreLoad', { knockoutContextName: 'bc' });
expect(getChildrenForMasterRecordListSpy.callCount).toEqual(1);
});
as per comment
in Jasmine 2.0 its
expect(object.func.calls.count()).toBe(1);

Change hash without triggering Sammy event

function UsersVM(start_page){
var self = this;
console.log('start form ' + start_page);
self.go_to = function(page) {
location.hash = '#Users/' + pageNumber;
}
}
Sammy(function() {
this.get('/app/?#Users/:page', function () {
var vm = new UsersVM(this.params.page);
ko.applyBinding(vm);
});
}).run();
I would like to change the page's hash with the following code:
location.hash = '#Users/' + pageNumber;
But in this case Sammy triggers routing. Say in Backbone we can do it this way:
app.navigate("help/troubleshooting", {trigger: false});
Is it possible to do it in Sammy?
Thanks!
I don't know of a native way to do this in Sammy, but here is a solution that has worked for me:
var sam = $.sammy(function () {
var sammy = this; //get a persistent reference to this
sammy.quiet = false; //set quiet to false by default
//I set quiet to true before running a route
sammy.quietRoute = function (location) {
sammy.quiet = true;
sammy.setLocation(location);
}
//I'm called after every route to reset quiet to false
sammy.after(function () {
sammy.quiet = false;
});
//I'm a 'normal' route that does not have the capability to be 'quiet'
this.get('#normalRoute', function () {
//routing code
});
//I am a route that can be 'quieted' so that when the url or
//hash changes my routing code doesn't run
this.get('#quietableRoute', function () {
if (!sammy.quiet) {
//routing code
} else {
return;
}
});
});
Then call the quietRoute function in your code:
//This will work
sam.quietRoute("#quietableRoute");
//This will not work because the "if(!sammy.quiet)..." code has not been
//implemented on this route
sam.quietRoute("#normalRoute");
Use the following code:
var new_location = '#foo';
app.trigger('redirect', {to: new_location});
app.last_location = ['get', new_location];
app.setLocation(new_location);

Categories