Waiting for multiple async operations in Nightwatch.js - javascript

I am attempting to test multiple sites for section headers being in the correct order. Of course everything is asynchronous in Nightwatch, including getting text from an element. The following code leads to the timeout never being called.
client.url(`www.oneofmyrealestatesites.com`);
client.waitForElementPresent("body", 5000);
var _ = require("underscore");
// This is the order I expect things to be in
var expected = ["Homes For Sale", "New Homes", "Apartments & Rentals"];
client.elements("css selector", ".listings .module-title .label", function (data) {
var listings = [];
data.value.forEach(function (element) {
client.elementIdText(element.ELEMENT, function (result) {
listings.push(result.value);
})
})
setTimeout(function () {
// Some of the sites have extra sections
var diff = _.intersection(listings, expected);
client.assert.ok(listings == diff);
}, 5000);
});
It would appear that no matter how much delay I give, listings is ALWAYS empty. If I console.log listings as it's being pushed to, it is getting populated, so that's not the issue. client.pause is always ignored as well.
Is there a way to make sure that listings is populated before asserting the diff?

I'm using async library for such cases https://github.com/caolan/async
Docs: https://github.com/caolan/async/blob/v1.5.2/README.md
var async = require("async");
/*use each, eachSeries or eachLimit - check docs for differences */
async.eachSeries(data.value, function (element, cb) {
client.elementIdText(element.ELEMENT, function (result) {
listings.push(result.value);
// this job is done
cb();
});
}, function() {
// Some of the sites have extra sections
var diff = _.intersection(listings, expected);
client.assert.ok(listings == diff);
});

setTimeout can only be called from .execute or .executeAsync because its actual javascript. The function below was only working until I used .executeAsync
Hope this works for you.
Cheers, Rody
LoopQuestionsLogSymptom: function() {
this.waitForElementVisible('.next-button', constants.timeout.medium, false);
this.api.executeAsync(function() {
let checkQuestion = function() {
// GET THE ELEMENTS
let nextButton = document.querySelectorAll('.next-button');
let answers = document.getElementsByClassName('flex-row');
let blueButton = document.querySelector('.blue-inverse-button');
let doneButton = document.querySelector('#doneButton');
let monitor = document.querySelector('.monitor');
//CHECK THE TYPES OF QUESTIONS AND CLICK THE RIGHT BUTTONS
if (!blueButton) {
answers[0].click();
nextButton[0].click()
} else if(blueButton){
blueButton.click();
}
setTimeout(() => {
if(!doneButton) {
console.log('Answering another question!');
checkQuestion();
}
if(doneButton){
doneButton.click();
}
else if(monitor) {
console.log("Exiting?")
.assert(monitor);
return this;
}
}, 2000);
};
// Initiating the check question function
return checkQuestion();
},[], function(){
console.log('Done?')
});
this.waitForElementVisible('.monitor', constants.timeout.medium);
this.assert.elementPresent('.monitor');
this.assert.urlEquals('https://member-portal.env-dev4.vivantehealth.org/progress');
return this;
},

Related

JS - How to retrieve variable after IndexedDB transaction.oncomplete() executes?

My problem is simple, but incredibly frustrating as I'm now on my second week of trying to figure this out and on the verge of giving up. I would like to retrieve my 'notesObject' variable outside my getAllNotes() function when after the transaction.oncomplete() listener executes.
(function() {
// check for IndexedDB support
if (!window.indexedDB) {
console.log(`Your browser doesn't support IndexedDB`);
return;
}
// open the CRM database with the version 1
let request = indexedDB.open('Notes', 1);
// create the Contacts object store and indexes
request.onupgradeneeded = (event) => {
let db = event.target.result;
// create the Notes object store ('table')
let store = db.createObjectStore('Notes', {
autoIncrement: true
});
// create an index on the sections property.
let index = store.createIndex('Sections', 'sections', {
unique: true
});
}
function insertData() {
let myDB = indexedDB.open('Notes');
myDB.onsuccess = (event) => {
// myDB.transaction('Notes', 'readwrite')
event.target.result.transaction('Notes', 'readwrite')
.objectStore('Notes')
.put({
sections: "New Note",
pages: "New page",
lastSelectedPage: ""
});
console.log("insert successful");
}
myDB.onerror = (event) => {
console.log('Error in NotesDB - insertData(): ' + event.target.errorCode);
}
myDB.oncomplete = (event) => {
myDB.close();
console.log('closed');
}
}
insertData()
function getAllNotes() {
let myDB = indexedDB.open('Notes');
let notesObject = [];
myDB.onsuccess = (event) => {
let dbObjectStore = event.target.result
.transaction("Notes", "readwrite").objectStore("Notes");
dbObjectStore.openCursor().onsuccess = (e) => {
let cursor = e.target.result;
if (cursor) {
let primaryKey = cursor.key;
let section = cursor.value.sections;
notesObject.push({
primaryKey,
section
})
cursor.continue();
}
}
dbObjectStore.transaction.onerror = (event) => {
console.log('Error in NotesDB - getAllData() tranaction: ' + event.target.errorCode);
}
dbObjectStore.transaction.oncomplete = (event) => {
return notesObject;
console.log(notesObject)
}
}
}
let notes = getAllNotes()
console.log("Getting Notes sucessful: " + notes)
})()
I've tried setting global variables, but nothing seems to work. I am a complete noob and honestly, I'm completely lost on how to retrieve the notesObject variable outside my getAllNotes() function. The results I get are 'undefined'. Any help would be greatly appreciated.
This is effectively a duplicate of Indexeddb: return value after openrequest.onsuccess
The operations getAllNotes() kicks off are asynchronous (they will run in the background and take time to complete), whereas your final console.log() call is run synchronously, immediately after getAllNotes(). The operations haven't completed at the time that is run, so there's nothing to log.
If you search SO for "indexeddb asynchronous" you'll find plenty of questions and answers about this topic.

Promise with async function is not waiting to be fulfilled

I am struggling with the issue of Promise and async/await for last two days. I am trying to configure my protractor.conf.js that would get the browser name just at the starting of the suit and will join with the suit name. I have written jasmine allure reporter code in customized way so that I can get browser name in asynchronously and then use with the suit name. But nothing working properly. In the code I have tried, I get only suit name. Browser name in few seconds later. As a result I could not use that browser name in suit name. Here is my code in detail
Edited
var AllureReporter = function CustomJasmine2AllureReporter(userDefinedConfig, allureReporter) {
let browser = {
getCapabilities: function() {
return new Promise(resolve => {
setTimeout(() => {
resolve({
get: str => str
});
}, 2000);
});
}
};
var result;
let bName = (async () => {
try {
var result = (await browser.getCapabilities()).get('Browser Name');
return result;
} catch (err) {
return "Error or smth"
}
})();
this.suiteStarted = function(suite) {
this.allure.startSuite(suite.fullName + result);
console.log(suite.fullName + result);
};
// other methods like spec done,, spec description.
}
the index code from Allure that can be changed is
'use strict';
var assign = require('object-assign'),
Suite = require('./beans/suite'),
Test = require('./beans/test'),
Step = require('./beans/step'),
Attachment = require('./beans/attachment'),
util = require('./util'),
writer = require('./writer');
function Allure() {
this.suites = [];
this.options = {
targetDir: 'allure-results'
};
}
Allure.prototype.setOptions = function(options) {
assign(this.options, options);
};
Allure.prototype.getCurrentSuite = function() {
return this.suites[0];
};
Allure.prototype.startSuite = function(suiteName, timestamp) {
this.suites.unshift(new Suite(suiteName,timestamp));
};
module.exports = Allure;
and the Suit.js class
function Suite(name, timestamp) {
this.name = name;
this.start = timestamp || Date.now();
this.testcases = [];
}
Suite.prototype.end = function(timestamp) {
this.stop = timestamp || Date.now();
};
Suite.prototype.addTest = function(test) {
this.testcases.push(test);
};
Suite.prototype.toXML = function() {
var result = {
'#': {
'xmlns:ns2' : 'urn:model.allure.qatools.yandex.ru',
start: this.start
},
name: this.name,
title: this.name,
'test-cases': {
'test-case': this.testcases.map(function(testcase) {
return testcase.toXML();
})
}
};
if(this.stop) {
result['#'].stop = this.stop;
}
return result;
};
module.exports = Suite;
I am getting this output after edited the question.the result is undefined in the suit name
Executing 7 defined specs...
Test Suites & Specs:
Test for correct login undefined
1. Test for correct login
(node:9764) [DEP0005] DeprecationWarning: Buffer() is deprecated due to
security and usability issues. Please use the Buffer.alloc(),
Buffer.allocUnsafe(), or Buffer.from() methods instead.
√ Navigate to the login page (5520ms)
√ Click onto language button (406ms)
√ English Language is selected (417ms)
√ Correct user name is written into email field (609ms)
√ Correct password is written into password field (486ms)
√ Login button is clicked and home page is opened with Machine on left top
menu (5622ms)
√ Logout button is clicked and redirect to login page (4049ms)
7 specs, 0 failures
Finished in 17.127 seconds
I want to get browser name after the the line 'Test Suites & Specs:' and want to add the name with suit name.
The function where you want to use await should be async.
I have made a small example for you. hope it will help
//The function we want to use wait in should be async!
async function myFunction() {
//Using callback
thisTakeSomeTime().then((res) => console.log(res)); //Will fire when time out is done. but continue to the next line
//Using await
let a = await thisTakeSomeTime();
console.log(a);//will fire after waiting. a will be defined with the result.
}
function thisTakeSomeTime() {
return new Promise((res) => {
setTimeout(()=>{res("This is the result of the promise")}, 5000)
})
}
myFunction();

Adding [0] causes callback to be called twice?

I'm writing utility for some minecraft stuff, whatever... So, first of all I have a code that can extract specified files from archive and give there content in callback:
const unzip = require("unzip-stream");
const Volume = require("memfs").Volume;
const mfs = new Volume();
const fs = require("fs");
function getFile(archive, path, cb) {
let called = false;
fs.createReadStream(archive)
.pipe(unzip.Parse())
.on("entry", function(entity) {
if (path.includes(entity.path)) {
entity.pipe(mfs.createWriteStream("/" + path))
.on("close", function() {
mfs.readFile("/" + path, function(err, content) {
if (!called) cb(content);
called = true;
mfs.reset();
});
}).on("err", () => {});
} else {
entity.autodrain();
}
});
}
module.exports = { getFile };
It works perfect when I test it in interactive console:
require("./zip").getFile("minecraft-mod.jar", ["mcmod.info", "cccmod.info"], console.log); // <= Works fine! Calls callback ONCE!
When I started to develop utility using this code I discovered a VERY strange thing.
So I have filenames in files array.
I'm using async/eachSeries to iterate over it. I have no callback function - only iterate one.
I have this code to parse .json files in mods:
let modinfo = Object.create(JSON.parse(content.replace(/(\r\n|\n|\r)/gm,"")));
It also works fine. But here comes magic...
So, .json files can contain array or object. If it's array we need to take first element of it:
if (modinfo[0]) modinfo = modinfo[0];
It works.
But, if it's object we need to take first element of modlist property in in:
else modinfo = modinfo.modlist[0];
And if modinfo was and object boom - callback now fires TWICE! WHAT?
But, if I remove [0] from else condition:
else modinfo = moninfo.modlist; // <= No [0]
Callback will be called ONCE! ???
If I try to do something like this:
if (modinfo[0]) modinfo = modinfo[0];
else {
const x = modinfo.modlist;
modinfo = x[0];
}
Same thing happens...
Also, it's called without arguments.
I tried to investigate - where callback is called twice. Read the zip extractor code again... It has those lines:
This:
let called = false;
And those:
if (!called) cb(content);
called = true;
So, if for some reason even this condition fires up two times:
if (path.includes(entity.path)) {
It should not call callback, right? No! Not only that, but if I try to
console.log(called);
It will log false two times!
NodeJS version: v8.0.0
Full code:
function startSignCheck() {
clear();
const files = fs.readdirSync("../mods");
async.eachSeries(files, function(file, cb) {
console.log("[>]", file);
zip.getFile("../mods/" + file, ["mcmod.info", "cccmod.info"], function(content) {
console.log(content);
console.log(Buffer.isBuffer(content));
if (content != undefined) content = content.toString();
if (!content) return cb();
let modinfo = Object.create(JSON.parse(content.replace(/(\r\n|\n|\r)/gm, "")));
if (modinfo[0]) modinfo = modinfo[0];
else modinfo = modinfo.modlist[0];
//if (!modinfo.name) return cb();
/*curse.searchMod(modinfo.name, modinfo.version, curse.versions[modinfo.mcversion], function(link) {
if (!link) return cb();
signature.generateMD5("../mods/" + file, function(localSignature) {
signature.URLgenerateMD5(link, function(curseSignature) {
if (localSignature === curseSignature) {
console.log(file, "- Подпись верна".green);
} else {
console.log(file.bgWhite.red + " - Подпись неверна".bgWhite.red);
}
cb();
});
});
});*/
});
});
}
Example contents of mcmod.info is:
{
"modListVersion": 2,
"modList": [{
"modid": "journeymap",
"name": "JourneyMap",
"description": "JourneyMap Unlimited Edition: Real-time map in-game or in a web browser as you explore.",
"version": "1.7.10-5.1.4p2",
"mcversion": "1.7.10",
"url": "http://journeymap.info",
"updateUrl": "",
"authorList": ["techbrew", "mysticdrew"],
"logoFile": "assets/journeymap/web/img/ico/journeymap144.png",
"screenshots": [],
"dependants":[],
"dependencies": ["Forge#[10.13.4.1558,)"],
"requiredMods": ["Forge#[10.13.4.1558,)"],
"useDependencyInformation": true
}]
}
Problem was that I was using modlist instead of modList. Not it works! Thanks for solution, Barmar

Matching text in element with Protractor

I have an element on page. And there could be different text. I am trying to do like (code is below), and it is not printed to console.
this.checkStatus = function () {
var element = $('.message')
browser.wait(EC.visibilityOf(element), 5000).then(function () {
browser.wait(EC.textToBePresentInElement(conStatus, 'TEXT1'), 500).then(function () {
console.log('TEXT1');
})
browser.wait(EC.textToBePresentInElement(element, 'TEXT2'), 500).then(function () {
console.log('TEXT2');
})
browser.wait(EC.textToBePresentInElement(element, 'TEXT3'), 500).then(function () {
console.log('TEXT3');
})
browser.wait(EC.textToBePresentInElement(element, 'TEXT4'), 500).then(function () {
console.log('TEXT4');
})
})
return this;
}
thanks
I see two problems. first, not sure what 'constatus' is? you need to correct that. second, browser.wait will be throwing error/exceptions when it is not able to find matching condition and timeout expires, So, if your first condition doesn't meet, it will throw timeout exception and will never go to second one. Instead, try something like below
var section = "";
this.checkStatus = function () {
var element = $('.message')
browser.wait(EC.visibilityOf(element), 5000).then(function () {
browser.wait(()=>{
if(EC.textToBePresentInElement(element, 'TEXT1')){
section = "Text1";
}
else if(EC.textToBePresentInElement(element, 'TEXT2')) {
section = "Text2";
}
else if(EC.textToBePresentInElement(element, 'TEXT3')) {
section = "Text3";
}
else if(EC.textToBePresentInElement(element, 'TEXT4')) {
section = "Text4";
}
if(section !== "")
return true;
}, 5000).then(()=>{
<here you can do anything based on 'section'>
}
Note - I haven't verified compilation errors.. so check for that.
Not sure what are you up to, but you can join multiple expected conditions with "or":
var conStatus = $('.message');
var containsText1 = EC.textToBePresentInElement(conStatus, 'TEXT1');
var containsText2 = EC.textToBePresentInElement(conStatus, 'TEXT2');
var containsText3 = EC.textToBePresentInElement(conStatus, 'TEXT3');
var containsText4 = EC.textToBePresentInElement(conStatus, 'TEXT4');
browser.wait(EC.or(containsText1, containsText2, containsText3, containsText4), 5000);

How to deal with asyncronous javascript in loops?

I have a forloop like this:
for (var name in myperson.firstname){
var myphone = new phone(myperson, firstname);
myphone.get(function(phonenumbers){
if(myphone.phonearray){
myperson.save();
//Can I put a break here?;
}
});
}
What it does is that it searches for phone-numbers in a database based on various first-names. What I want to achieve is that once it finds a number associated with any of the first names, it performs myperson.save and then stops all the iterations, so that no duplicates get saved. Sometimes, none of the names return any phone-numbers.
myphone.get contains a server request and the callback is triggered on success
If I put a break inside the response, what will happen with the other iterations of the loop? Most likely the other http-requests have already been initiated. I don't want them to perform the save. One solution I have thought of is to put a variable outside of the forloop and set it to save, and then check when the other callbacks get's triggered, but I'm not sure if that's the best way to go.
You could write a helper function to restrict invocations:
function callUntilTrue(cb) {
var done = false;
return function () {
if (done) {
log("previous callback succeeded. not calling others.");
return;
}
var res = cb.apply(null, arguments);
done = !! res;
};
}
var myperson = {
firstname: {
"tom": null,
"jerry": null,
"micky": null
},
save: function () {
log("save " + JSON.stringify(this, null, 2));
}
};
var cb = function (myperson_, phonenumbers) {
if (myperson_.phonearray) {
log("person already has phone numbers. returning.");
return false;
}
if (phonenumbers.length < 1) {
log("response has no phone numbers. returning.");
return false;
}
log("person has no existing phone numbers. saving ", phonenumbers);
myperson_.phonearray = phonenumbers;
myperson_.save();
return true;
};
var restrictedCb = callUntilTrue(cb.bind(null, myperson));
for (var name in myperson.firstname) {
var myphone = new phone(myperson, name);
myphone.get(restrictedCb);
}
Sample Console:
results for tom-0 after 1675 ms
response has no phone numbers. returning.
results for jerry-1 after 1943 ms
person has no existing phone numbers. saving , [
"jerry-1-0-number"
]
save {
"firstname": {
"tom": null,
"jerry": null,
"micky": null
},
"phonearray": [
"jerry-1-0-number"
]
}
results for micky-2 after 4440 ms
previous callback succeeded. not calling others.
Full example in this jsfiddle with fake timeouts.
EDIT Added HTML output as well as console.log.
The first result callback will only ever happen after the loop, because of the single-threaded nature of javascript and because running code isn't interrupted if events arrive.
If you you still want requests to happen in parallel, you may use a flag
var saved = false;
for (var name in myperson.firstname){
var myphone = new phone(myperson, firstname /* name? */);
myphone.get(function(phonenumbers){
if (!saved && myphone.phonearray){
saved = true;
myperson.save();
}
});
}
This will not cancel any pending requests, however, just prevent the save once they return.
It would be better if your .get() would return something cancelable (the request itself, maybe).
var saved = false;
var requests = [];
for (var name in myperson.firstname){
var myphone = new phone(myperson, firstname /* name? */);
var r;
requests.push(r = myphone.get(function(phonenumbers){
// Remove current request.
requests = requests.filter(function(i) {
return r !== i;
});
if (saved || !myphone.phonearray) {
return;
}
saved = true;
// Kill other pending/unfinished requests.
requests.forEach(function(r) {
 r.abort();
});
myperson.save();
}));
}
Even better, don't start all requests at once. Instead construct an array of all possible combinations, have a counter (a semaphore) and only start X requests.
var saved = false;
var requests = [];
// Use requests.length as the implicit counter.
var waiting = []; // Wait queue.
for (var name in myperson.firstname){
var myphone = new phone(myperson, firstname /* name? */);
var r;
if (requests.length >= 4) {
// Put in wait queue instead.
waiting.push(myphone);
continue;
}
requests.push(r = myphone.get(function cb(phonenumbers){
// Remove current request.
requests = requests.filter(function(i) {
return r !== i;
});
if (saved) {
return;
}
if (!myphone.phonearray) {
// Start next request.
var w = waiting.shift();
if (w) {
requests.push(w.get(cb));
)
return;
}
saved = true;
// Kill other pending/unfinished requests.
requests.forEach(function(r) {
r.abort();
});
myperson.save();
}));
}

Categories

Resources