Handling assertions in a custom helper - javascript

I've started playing around with CodeceptJs and I've got it up working quite easily. I'm currently using it with NightmareJs and all seems fine.
The specific area I'm testing is a gallery that fetches data from an interface via JSONP creating a list of images wrapped in <div>s.
A portion of the tests I'm implementing is like the following:
Feature('gallery')
Scenario('clicking on an element adds "selected" class', (I) => {
I.amOnPage('/')
I.seeElement('#gallery .col-md-3')
I.click('#gallery .col-md-3')
I.seeElement('#gallery .selected')
})
Now since the elements can be any number, it's currently silently using the first element, but in order to give it a bit more entropy I wanted to pick an element at random, something like the following
Scenario('clicking on an element adds "selected" class', (I) => {
I.amOnPage('/')
I.seeMoreThanElements('#gallery .col-md-3', 1)
I.clickOnRandomElement('#gallery .col-md-3')
I.seeElement('#gallery .selected')
})
Or even better, if I could grab the list of elements so I can decide which one to click on, like:
Scenario('clicking on an element adds "selected" class', (I) => {
I.amOnPage('/')
I.seeMoreThanElements('#gallery .col-md-3', 1)
const elements = I.grabRandomElement('#gallery .col-md-3')
const random = getRandomInt(1, elements.length)
I.click(`#gallery .col-md-3:nth-child(${random})`)
I.seeElement(`#gallery .col-md-3.selected:nth-child(${random})`)
})
The current helpers available don't allow me to perform some particular actions, so I started implementing a custom handler as described in the guide at http://codecept.io/helpers/
In my configuration I have the following:
"helpers": {
"Nightmare": {
"url": "http://localhost:3000"
},
"DOMElements": {
"require": "./__tests__/helpers/domelements_helper.js"
}
}
and domelements_helper.js currently looks like the following:
'use strict'
let assert = require('assert')
class DOMElements extends Helper {
seeMoreThanElements (locator, count) {
this.helpers['Nightmare']._locate(locator).then(function (els) {
return assert(els.length >= count, `Found more than ${count} elements`)
})
}
}
module.exports = DOMElements
This doesn't - clearly - work. This is where I'm getting a bit confused.
First of all, I'm using the default Node.js assertion library, and if there's any need I'm happy to move over to something more robust like Protractor or Chai-as-promised, but the slimmer the better.
Secondly, the documentation clearly states the following:
any helper method should return a value in order to be added to promise chain
Which doesn't really make sense... should I return a promise or should I handle the whole thing within the then() statement? As returning a basic value doesn't really do much. Even then, how do I handle failed assertions?
I've also seen a Nightmare clientscript in the code base but I have no idea if it's of any use for my case, as I've just started digging through the code base in order to understand a little bit better how to customise and extend CodeceptJs.
Any pointers are really appreciated

since nobody seems to have got this far, I'm going to add an answer as I seem to have found how this thing works by going through the codebase and understand a bit more how it works.
tl;dr: the quick solution is the following:
/* __tests__/helpers/domelements_helper.js */
const assert = require('assert')
class DOMElements extends Helper {
seeMoreThanElements (locator, count) {
return this.helpers['Nightmare']._locate(locator)
.then((elementsArray) => {
if (elementsArray.length < count) {
return assert.fail(elementsArray.length, count, `Found more than ${count} elements`)
}
})
}
}
module.exports = DOMElements
The way the whole thing works is by promises and you have to handle failure appropriately so that the whole system can fail gracefully (sorta).
In particular _locate() returns a promise and everything has to be handled asynchronously, although by design this seems to be quite awkward and it's making things particularly hard to implement, at least in the current state.

Related

Non-blocking UI while Search/Sort/Filter operations

I have a large list of items (3000 - 5000) to display. The user may filter / sort them in order to display only a particular subset. My initial implementation was essentially:
import { filterItems, sortItems }
const items = [...];
const results = flow([
filterItems,
sortItems,
])(items);
...where the imported functions are basically wrappers around array .sort and .filter ( + a lot of custom options). This works, but it blocks the UI. I'm looking for recommendations on how to proceed while still utilizing these two core functions. I feel I've two general paths:
1) JS scheduling / time-slicing
How might I break up the filter/sort functions to accommodate this? I've tried using something like this queue scheduler + slightly modifying sortItems to yield each loop, but as the whole thing is in a sort callback it does not work:
// simplified for brevity
function sortItems(items, context) { // context comes from a Scheduler
return items.sort((a, b) => {
context.yield(); // can I return control back here ...?
return (+!a - +!b) || (a > b ? 1 : -1);
}
}
I want to try other variations here on this theme (ie. setTimeout, rAF etc) but as there is no "unit of work" or "step" function, I'm wondering if I'd need to implement my own sorting/filtering using a for loop and not rely on [].filter / [].sort. Are there performance implications to this?
2) Web-worker
This is another option. However, for maintainability I'd prefer to re-use filterItems, sortItems without having to copy/paste them into a worker file. Is there a way to do this? I'm aware of inlining the worker i.e. using URL + Blobs, or Greenlet... but I've not been able to get it to work.
let results = greenlet(async (items) => {
const filtered = filterItems(items); // <-- i realize this is not possible (and doesn't work)
...
});
Relatedly - is it possible to somehow use an imported function via toString() in a worker? i.e filterItems.toString() + an inlining method?
If you have general advice or can share experience in similar ventures (or if i'm doing it wrong), please let me know. Thanks in advance ~

How should I write good snapshot tests?

When composing snapshot tests, I need to do sequences of interactions. So for example, selecting the “merchandise” catalog looks like this:
const exploreMerchandiseCatalogAction = async element => {
await aTimeout(2);
element.service.send(hydrateEvent);
await aTimeout(2);
catalogSelectElement(element).click();
await aTimeout(2);
merchandiseCatalog(element).click();
};
But say I now need to click the “add selection” button, which is only clickable/present when an option in the middle column has been selected:
const addSelectionAction = async element => { /* snip! */ }
This function will need to select the merchandise catalog, and then click on “electronics” (for example).
Should:
the function which executes this “action” execute all the
previous steps needed for this to happen? (then its not composable,
but its safe)
this function should exist on its own, and its up
to you to ensure you’re using it right
this function should throw
an error if there’s something wrong
(potentially, idk how this
would work) I should make this all typesafe somehow through clever
use of return signature types, and input types
Option 1 inevitably beings to look like functions which are shaped together like attached picture, but I’m not sure how to make them safely independently composable.
Another relevant link: https://kentcdodds.com/blog/effective-snapshot-testing
Should I take approach 1, 2, 3, 4, or something else? Why?

Angular 4 weird error: query doesn't end when -not- showing a message toast

Here this is, my weirdest error in my whole programming career. I've been struggling through this, yet I can't find what's going on in this code. It just seems not to make any sense in any way.
I'm using the following tools:
Ionic 3
Angular 4
Typescript / ES6
I'm trying to do a method, "assignChat(user)", which assigns a chat to a user. It has to use several APIs, geolocation... it's a big method, actually. That's why I've split it in two parts connected by promises, and used them after, so my method looks pretty much like this:
assignChat(user){
const getLocationName = () => {
return new Promise((resolve,reject) => {
// 30 lines of code
});
}
const assignOrCreateChat= (area) => {
return new Promise((resolve,reject) => {
// 40 lines of code
});
}
const getLocationName = () => {
return new Promise((resolve,reject) => {
// 30 lines of code
});
}
// then I use the inner functions here and write an extra 60-70 lines of code
}
Ok! This works neat. Didn't have much problems with this algorithm after some several testing, although is quite heavy and takes ~0.5s to properly execute, finish it's queries, and show the result.
Thing is... I had some toasts displaying some information, like where you're located. I wanted to remove them, and started by this one, in the inner function getLocationName(). This is the code I want to talk you about:
const getLocationName = () => {
return new Promise( (resolve, reject) => {
const ADDRESS_LEVEL = 2;
this.reverseGeocode(ADDRESS_LEVEL).then( address => {
---> this.toastify("You have been located at: "+address, 1500);
let query = new Parse.Query("PoliticalArea");
// more code
The line I marked with an arrow, is the line which is giving me problems. I mean, you probably think the code fails because of the line, but it's totally the oposite! If I remove that line, the algorithm suddenly stops working and fails to display any result.
The "toastify" method is a quick way I did for myself for displaying toasts. It works well, actually! This is the implementation:
toastify(message, duration){
this.toastCtrl.create({
message: message,
duration: duration
}).present();
}
Not like the most dangerous method. Well, in fact, it seems that the code won't work without it. If I comment the line, or erase it, I never get any result, or any error, from the big algorithm I showed you before. I've got every possible exception catched, although the API connectors don't have timeout, but it's like it gets stuck every time it doesn't display the toast.
I just don't understand what's going on. Seems like a very serious thing the Angular team should look into, in my very honest opinion.
Any idea of what kind of black magic is going there?
UPDATE:
Some further info: when I navigate through the "bugged" view (without the toastify line, and therefore not displaying the chat result), and per example, click in another chat (which pushes a view into the Navigation Controller), it somehow starts showing the chat result I expected. When I pop the new view from the navCtrl, and get back to the page, the expected result is now visible.
Is this some problem with angular watches?
Ok, the solution was not obvious.
It seems that the view was being rendered before the task completed. It was a tough task, so maybe that's the reason why Angular didn't work properly. Tried executing it both in the constructor and in ionViewDidEnter(), though nothing worked.
My final solution was to force component's re-rendering, through ApplicationRef, using the .tick() method at the dead end of my method.
That fixed it all!

Testing tab navigation order

In one of our tests, we need to make sure that the tab keyboard navigation inside a form is performed in the correct order.
Question: What is the conventional way to check the tab navigation order with protractor?
Currently we are solving it by repeating the following step for as many input fields existing in a form (code below):
check the ID of the currently focused element (using getId())
send TAB key to the currently focused element
Here is the example spec:
it("should navigate with tab correctly", function () {
var regCodePage = new RegCodePage();
browser.wait(protractor.ExpectedConditions.visibilityOf(regCodePage.title), 10000);
// registration code field has focus by default
expect(regCodePage.registrationCode.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
// focus moved to Remember Registration Code
regCodePage.registrationCode.sendKeys(protractor.Key.TAB);
expect(regCodePage.rememberRegistrationCode.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
// focus moved to Request Code
regCodePage.rememberRegistrationCode.sendKeys(protractor.Key.TAB);
expect(regCodePage.requestCode.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
// focus moved to Cancel
regCodePage.requestCode.sendKeys(protractor.Key.TAB);
expect(regCodePage.cancelButton.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
// focus moved back to the input
regCodePage.cancelButton.sendKeys(protractor.Key.TAB);
expect(regCodePage.registrationCode.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
});
where regCodePage is a Page Object:
var RegCodePage = function () {
this.title = element(by.css("div.modal-header b.login-modal-title"));
this.registrationCode = element(by.id("regCode"));
this.rememberRegistrationCode = element(by.id("rememberRegCode"));
this.requestCode = element(by.id("forgotCode"));
this.errorMessage = element(by.css("div.auth-reg-code-block div#message"));
this.sendRegCode = element(by.id("sendRegCode"));
this.cancelButton = element(by.id("cancelButton"));
this.closeButton = element(by.css("div.modal-header button.close"));
};
module.exports = RegCodePage;
It is working, but it is not really explicit and readable which makes it difficult to maintain. Also, another "smell" in the current approach is a code duplication.
If the current approach is how you would also do it, I would appreciate any insights about making it reusable.
I think the PageObject should define a tab order list, since that is really a direct property of the page, and should be expressible as simple data. An array of items seems like a sufficient representation, so something like:
this.tabOrder = [ this.registrationCode, this.rememberRegistrationCode, this.requestCode, this.cancelButton ];
Then you need a bit of generic code that can check a tab order.
function testTabOrder(tabOrder) {
// Assumes TAB order hasn't been messed with and page is on default element
tabOrder.forEach(function(el) {
expect(el.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
el.sendKeys(protractor.Key.TAB);
});
}
Then your test would be something like:
it('has correct tab order', function() {
var regCodePage = new RegCodePage(); // this should probably be in the beforeEach
testTabOrder(regCodePage.tabOrder);
});
Of course, this assumes each element has a "getId()" method that works. (That seems like a reasonable assumption to me, but some environments may not support it.)
I think this keeps the tab-order nicely isolated on the PageObject (so its easy to keep in sync with the page content and doesn't get lost in the code that verifies the order). The testing code seem "optimistic" (I suspect the real world will introduce enough problems that you will end up expanding this code a bit).
I haven't tried any of this yet, so feel free to downvote if this doesn't work. :)
Also, I believe the forEach loop will work as-is, but I wouldn't be surprised if it needs some more explicit promise handling to make the dependencies explicit.

Using jasmine the correct way or creating a testAll-spec

I have to say I'm kinda stuck with using jasmine(-node) in (most probably) a slightly wrong way. Currently I have jasmine tests so, that they need to be run in a correct order. I would at the moment need to have a file that can collect and initiate the different jasmine-files in the correct order.
Now I appreciate advice, which show me other ways of making the testing work well and not necessarily just fix this immediate problem, my testing skills are fairly limited, mostly because as a long time solo-coder I haven't had that much use for them.
So the actual problem is that I have test-specs:
mapGenerator-spec.js // Initializes / generates a clean map.
orders-spec.js // Simulates orders given by players (turn based)
map-spec.js // tests the part which is used front-end and backend, creating map from components
moveOrders-spec.js // Generates a part of the turn by resolving orders -> moving units.
So simply the logic in a game is that first generates a map, order generate orders given by players
These need to be executed in the precise order, because they create database-entries that are dependant on the previous test. I would like to keep the tests as product-ready / real as possible and not try to skip the database-inserts / fetches. There is a separate test-database, where the tests are generated.
So can you advice me, what you think is the correct way to do this (if this is not a good way) and / or advice me, if needed, how can I sum up these tests to one collection test "testAll-spec.js", which would run the tests synchronously.
If you need something to happen before you run a test, then you should use beforeEach().
You can also nest jasmine tests, and run a beforeEach() within each test, as below:
describe('testInitialiser', () => {
var firstVariable;
beforeEach(() => {
firstVariable = 1;
});
it('should set firstVariable to 1', () => {
expect(firstVariable).toBe(1);
});
// this is a nested test - these tests will run after the above beforeEach.
describe('nested test ', () => {
// all tests within this describe will have access to firstVariable.
var secondVariable;
beforeEach(() => {
secondVariable = firstVariable + 1;
});
it('should set secondVariable to firstVariable + 1 ', () => {
expect(secondVariable).toBe(2);
});
});
});
Hope this helps.

Categories

Resources