Using Nightwatch for demo - slow down assertions by config - javascript

my team and me are using Nightwatch to write end-to-end-acceptance tests for a microservice oriented architecture with a total of five systems.
After putting in some work to set it up and wiring together our services with docker-compose, it works great now and all tests are clicked through on the UI in a browser (not headless).
We got the idea to use this for demos, too (initial sprint demo etc) and wondered if there is some kind of setting (which we didn't found until now) or other possibility to simple add some artificial delay between the clicks/tests/assertions and everything.
Does someone have an idea?

You can add pauses in your suite wherever you want by using:
.pause(5000) // a pause for 5 seconds
//or alternately
.pause(this.timeout)
this.timeout can be set in your base-test-case.js
var timeout = 5000; // in your variable declarations
and then in that same file, on your base Class prototype you want:
before: function (client) {
this.timeout = timeout;

browser.pause between clicks or setValue to have nice delay, anything between 100-300 miliseconds is good
http://nightwatchjs.org/api#pause

Related

How do I use cypress with components that are prefetched or preloaded with webpack?

I'm using Cypress 7.7.0 (also tested on 8.0.0), and I'm running into an interesting race condition. I'm testing a page where one of the first interactions that Cypress does is click a button to open a modal. To keep bundle sizes small, I split the modal into its own prefetched webpack chunk. My Cypress test starts with cy.get('#modal-button').click() but this doesn't load the modal because the modal hasn't finished downloading/loading. It does nothing instead (doesn't even throw any errors to the console). In other words, Cypress interacts with the page too quickly. This was also reproduced with manual testing (I clicked on the button super fast after page load). I have tried setting the modal to be preloaded instead, but that didn't work either.
I am able to solve the problem by introducing more delay between page load and button interaction. For example, inserting any Cypress command (even a cy.wait(0)) before I click on the button fixes the solution. Cypress, however, is known for not needing to insert these brittle solutions. Is there a good way to get around this? I'd like to keep the modal in its own chunk.
FYI: I'm using Vue as my front end library and am using a simple defineAsyncComponent(() => import(/* webpackPrefetch: true */ './my-modal.vue')) to load the modal component. I figure that this problem is general to Cypress though.
There's nothing wrong with cy.wait(0).
All you are doing is handing control from the test to the next process in the JS queue, in this case it's the app's startup script which is presumably waiting to add the click handler to the button.
I recently found that this is also needed in a React hooks app to allow the hook to complete it's process. You will likely also come across that in Vue 3, since they have introduced a hook-like feature.
If you want to empirically test that the event handler has arrived, you can use the method given here (modified for click()) - When Can The Test Start?
let appHasStarted
function spyOnAddEventListener (win) {
const addListener = win.EventTarget.prototype.addEventListener
win.EventTarget.prototype.addEventListener = function (name) {
if (name === 'click') {
appHasStarted = true
win.EventTarget.prototype.addEventListener = addListener // restore original listener
}
return addListener.apply(this, arguments)
}
}
function waitForAppStart() {
return new Cypress.Promise((resolve, reject) => {
const isReady = () => {
if (appHasStarted) {
return resolve()
}
setTimeout(isReady, 0) // recheck "appHasStarted" variable
}
isReady()
})
}
it('greets', () => {
cy.visit('app.html', {
onBeforeLoad: spyOnAddEventListener
}).then(waitForAppStart)
cy.get('#modal-button').click()
})
But note setTimeout(isReady, 0) will probably just achieve the same as cy.wait(0) in your app, i.e you don't really need to poll for the event handler, you just need the app to take a breath.
It seems like your problem is that you're already rendering a button before the code backing it is loaded. As you noticed, this isn't only an issue for fast automated bots, but even a "regular" user.
In short, the solution is to not display the button early, but show a loading dialog instead. Cypress allows waiting for a DOM element to be visible with even a timeout option. This is more robust than a brittle random wait.
I ended up going with waiting for the network to be idle, although there were several options available to me.
The cypress function I used to do this was the following which was heavily influenced by this solution for waiting on the network:
Cypress.Commands.add('waitForIdleNetwork', () => {
const idleTimesInit = 3
let idleTimes = idleTimesInit
let resourcesLengthPrevious
cy.window().then(win =>
cy.waitUntil(() => {
const resourcesLoaded = win.performance.getEntriesByType('resource')
if (resourcesLoaded.length === resourcesLengthPrevious) {
idleTimes--
} else {
idleTimes = idleTimesInit
resourcesLengthPrevious = resourcesLoaded.length
}
return !idleTimes
})
)
})
Here are the pros and cons of the solution I went with:
pros: no need to increase bundle size or modify client code when the user will likely never run into this problem
cons: technically still possible to have a race condition where the click event happens after the assets were downloaded, but before they could all execute and render their contents, but very unlikely, not as efficient as waiting on the UI itself for indication of when it is ready
This was the way I chose solve it but the following solutions would have also worked:
creating lightweight placeholder components to take the place of asychronous components while they download and having cypress wait for the actual component to render (e.g. a default modal that just has a spinner being shown while the actual modal is downloaded in the background)
pros: don't have to wait on network resources, avoids all race conditions if implemented properly
cons: have to create a component the user may never see, increases bundle size
"sleeping" an arbitrary amount (although this is brittle) with cy.wait(...)
pros: easy to implement
cons: brittle, not recommended to use this directly by Cypress, will cause linter problems if using eslint-plugin-cypress (you can disable eslint on the line that you use this on, but it "feels ugly" to me (no hate on anyone who programs that way)

Meteor Collection.find() blocks the entire application during working

I try to display a loading alert on Meteor with modal package during loading of data.
'change .filterPieChart': function(evt){
Modal.show('loadingModal');
/* a little bit of work */
var data = MyCollection.find().fetch(); // takes 3 or 4 seconds
/* lot of work */
Modal.hide('loadingModal');
}
Normally, the alert is displayed at the beginning of the function, and disappears at the end. But here, the alert appears only after the loading time of the MyCollection.find(), and then disappears just behind. How to display it at the beginning of the function ??
I tried to replace Modal.show with reactive variable, and the result is the same, the changing value of reactive variable is detect at the end of the function.
From what you describe, what probably happens is that the JS engine is busy doing your computation (searching through the collection), and indeed blocks the UI, whether your other reactive variable has already been detected or not.
A simple workaround would be to give some time for the UI to show your modal by delaying the collection search (or any other intensive computation), typically with a setTimeout:
Modal.show('loadingModal');
setTimeout(function () {
/* a little bit of work */
var data = MyCollection.find().fetch(); // takes 3 or 4 seconds
/* lot of work */
Modal.hide('loadingModal');
}, 500); // delay in ms
A more complex approach could be to decrease the delay to the bare minimum by using requestAnimationFrame
I think you need to use template level subscription + reactiveVar. It is more the meteor way and your code looks consistent. As i can see you do some additional work ( retrive some data ) on the change event. Make sense to actually really retrive the data on the event instead of simulation this.
Template.TemplateName.onCreated(function () {
this.subsVar = new RelativeVar();
this.autorun( () => {
let subsVar = this.subsVar.get();
this.subscribe('publicationsName', this.subsVar);
})
})
Template.TemplateName.events({
'change .filterPieChart': function(evt){
Template.instance().collectionDate.subsVar.set('value');
Modal.show('loadingModal');
MyCollection.find().fetch();
Modal.hide('loadingModal');
}
})
Please pay attention that i didn't test this code. And you need to use the es6 arrow function.

WebSpeech Speech Synthesis: Pausing utterance1, playing another utterance2, and resuming utterance1 - possible?

I am using WebSpeech's speechSynthesis module to have a web application speak. However, it seems that you can only add utterances to a queue and then pause(), resume(), and cancel() the entire queue.
I have a situation where I want to have two utterances:
utterance1 = new SpeechSynthesisUtterance(text1);
utterance2 = new SpeechSynthesisUtterance(text2);
I would like to have utterance1 play, then pause it in the middle, have utterance2 play, and then resume utterance1. In code, it would look like this:
speechSynthesis.speak(utterance1);
// ... after a while
speechSyntehsis.pause(utterance1);
speechSynthesis.speak(utterance2);
// ... after a long while
speechSynthesis.resume(utterance1);
Unfortunately, speechSynthesis' methods pause(), resume(), and cancel() do not take any argument and act on the entire speech utterance queue. Is there any way to achieve this behavior?
If I could have multiple speechSynthesis objects, then I could create one for each utterance, but I believe I can only have one.
If I could keep track of where in the string the utterance has "been uttered to" then I could cancel it and then create a new utterance with the remainder of the text, but I don't know if that is possible.
Any suggestions?
I have already work in the speechSynthesis for a couple of months with my library Artyom.js , and according to the documentation (and all the tests that i've made ) pause a single synthesis instance and reanudate another is not possible because all the instances are related to the window.speechSynthesis (if someday the API changes, that will be another great step in the speechSynthesis). When you call the pause method of the speechSynthesis "instance", it will apply for all the queue and there's no other way.
According to the documentation :
// the only solution would be if the speechSynthesis official API had a constructor like
// and a real NEW instance be created
// var synthRealInstance = new speechSynthesis();
// but till the date ... nope :(
var synthA = window.speechSynthesis;
var synthB = window.speechSynthesis;
var utterance1 = new SpeechSynthesisUtterance('How about we say this now? This is quite a long sentence to say.');
var utterance2 = new SpeechSynthesisUtterance('We should say another sentence too, just to be on the safe side.');
synthA.speak(utterance1);
synthB.speak(utterance2);
synthA.pause();
// or synthB will anyway stop the synthesis of the queue
There is a property on the utterance (onmark) however is not well documented and probably will not work as this api still experimental.
The mark event is fired when a ‘mark’ tag is reached in a Speech Synthesis Markup Language (SSML) file. Just know that it’s possible to pass your speech data to an utterance using an XML-based SSML document. The main advantage of this being that it makes it easier to manage speech content when building applications that have large amount of text that need to be synthesised.
Read more about here.

youtube repeat oneliner

I am trying to make a oneliner to repeat videos.
I started looking for that replay button to trigger another round:
$("[title='Replay']")[0].click();
A self calling function to loop in the background while the vid is running.
rpt=function(){setTimeout(function(){alert("blarg");rpt()},5000)}
putting those two things together and adding a tiny bit of fluff:
$ = jQuery to initialize shiny $()-syntax and finally rpt();to get things rolling:
$=jQuery,rpt=function(){setTimeout(function(){$("[title='Replay']")[0].click(),rpt()},100)};rpt();
alas, the parts work but not the whole thing
I noticed that the console is printing an error message if I enter the final line before the video has finished; since the button is not found yet and therefore a call to .click() on undefined isn't working.
Shouldn't the function still loop in the background and trigger during a later call, as soon as the replay button is there for jQuery to grab?
I'm using chrome 44.0.2403.130 and jQuery: 1.10.1
Add a verification to make sure there is a replay element available.
Use setInterval() instead of setTimeout().
By default (and unless you use other conflicting libraries), jQuery is assigned to the $ variable on its initialization.
setInterval(function(){if($("[title='Replay']").length)$("[title='Replay']").trigger('click');},100);
one line repeat + number of repeat appended in title:
var title=$('#eow-title').innerHTML; var replaysN = 1; setInterval(function(){if($('.ytp-play-button').title == 'Replay') {$('.ytp-play-button').click();$('#eow-title').innerHTML = title+' (x'+replaysN+')';replaysN++;} }, 1000);
..after some weeks after making this post, there was a new feature in youtube :D
After right-click on video -> Loop
So every customization is useless :)

Lengthening MIDI.js piano note duration

I'm using MIDI.js to build a music app that allows users to play piano through their keyboard.
Everything is working, but the problem I'm having is that the notes (called with MIDI.noteOn) only last 3 seconds, then just cut off. I'm trying to find a way to:
1- Make the note last for longer.
2- Make the note fade to nothing as opposed to just cutting off.
Could anyone point me in the right direction with there? There is so little documentation & discussion on MIDI.js.
Thanks in advance!
EDIT: The instrument name is acoustic_grand_piano
In theory, you need to call noteOff at the proper time.
In practice, MIDI.Plugin.js has this:
// FIX: needs some way to fade out smoothly..
root.noteOff = function (channel, note, delay) {
// var source = sources[channel+""+note];
// if (!source) return;
// source.noteOff(delay || 0);
// return source;
};

Categories