Is it possible to keep executing test if element wasn't found with Cypress - cypress

I have a case when I need to wait for element (advertising), if it's visible then needs to click it, but if element wasn't found after timeout then needs to keep executing a test.
How to handle the situation with Cypress ?

The way Cypress says to check for a conditional element is Element existence
cy.get('body').then(($body) => {
const modal = $body.find('modal')
if (modal.length) {
modal.click()
}
})
Most likely you put that at the top of the test, and it runs too soon (there's no retry timeoout).
You can add a wait say 30 seconds, but the test is delayed every time.
Better to call recursively
const clickModal = (selector, attempt = 0) => {
if (attempt === 100) return // whole 30 seconds is up
cy.get('body').then(($body) => {
const modal = $body.find('modal')
if (!modal.length) {
cy.wait(300) // wait in small chunks
clickModal(selector, ++attempt)
}
})
return // done, exit
}
cy.get('body')
.then($body => clickModal('modal'))
Intercept the advert
Best is if you can find the url for the advert in network tab, use cy.intercept() to catch it and stub it out to stop the modal displaying.

I tried the above solution, but seems that in some cases parameter $body could not contain necessary element, cause it was not loaded when we invoked cy.get('body'). So, I found another solution, using jQuery via Cypress, here is it:
let counter = 0;
const timeOut: number = Cypress.config('defaultCommandTimeout');
const sleep = (milliseconds) => {
const date = Date.now();
let currentDate = null;
do {
currentDate = Date.now();
} while (currentDate - date < milliseconds);
};
while (true) {
if (Cypress.$(element).length > 0 && Cypress.$(element).is(':visible')) {
Cypress.$(element).click();
break;
} else {
sleep(500);
counter = counter + 500;
if (counter >= timeOut) {
cy.log(elementName+ ' was not found after timeout');
break;
}
}
}

Related

Dart component progress bar only updating at the end of loop

I'm developing an app in angular dart and trying to animate a progress bar which shows progress of a file upload. I'm simply parsing the JSON from the file, and sending them off to a service with a _service.create(....) method.
I've put all of this code in an async method which is called when my submit button is clicked:
void uploadFile() async {
submitButton.attributes.addAll({ 'disabled': '' });
progress.value = 0;
FileList files = input.files;
if (files.isEmpty) {
_handleError("No file selected");
//handle error, probably a banner
return;
}
File file = files.item(0);
FileReader reader = new FileReader();
//reader.result
reader.onLoadEnd.listen((e) async {
Map map = json.decode(reader.result);
var combinations = map['combinations'];
progress.max = combinations.length;
int loopCount = 0;
combinations.forEach((e) async {
await _service.create(VJCombination.fromJSON(e)).then((_) {
combinationCount++;
progress.value++;
loopCount++;
if (loopCount == combinations.length) {
submitButton.attributes.remove('disabled');
}
});
});
isLoadSuccessful = true;
});
reader.onError.listen((evt) => print(evt));
reader.readAsText(file);
progress.value = 10;
}
I'm getting the progress and the submitButton elements with the #ViewChild annotations:
#ViewChild('progress')
ProgressElement progress;
#ViewChild('submit')
ButtonElement submitButton;
The code works. The progress bar starts off empty, and after the file is read and the service gets the data, the progress bar is full.
My issue is that the UI is only updated after all of the combinations have been sent to the _service. So it seemingly goes from empty to full in one frame.
This code
combinations.forEach((e) async {
does not make sense, because forEach does not care about the returned Future
Rather use
for(var e in combinations) {
Not sure if this fixes your problem, but such code definitely needs to be changed.

ReactiveUI: When using ReactiveCommand.CreateFromObservable() the UI does not update until the command completes

I'm using ReactiveUI 7.0 with WPF and .NET 4.5.2.
I'm trying to create a ReactiveCommand from an Observable. The code DOES work, however, the UI doesn't update until the command is completed. I have a progress bar and a progress window that I want to update as the command runs. In addition, the UI is unresponsive while the ReactiveCommand is executing (I can't click on a cancel button or anything else for that matter). I'm hoping it's something I am overlooking and obvious to somebody smarter than me. Or maybe I'm just doing it wrong.
Thanks for looking.
Here is the relevant code:
My ViewModel declaration:
public ReactiveCommand<Unit, string> PerformSelectedOperationCommand { get; set; }
private StringBuilder sb;
Within my ViewModel constructor:
PerformSelectedOperationCommand = ReactiveCommand.CreateFromObservable(PerformOperationObservable,
this.WhenAnyValue(x => x.SelectedPath, x => x.TotalFilesSelected,
(x, y) => x != null && y > 0));
// listen to the messages and append to output
PerformSelectedOperationCommand.Subscribe(s =>
{
sb.AppendLine(s);
ProgressWindowOutput = sb.ToString();
});
And here is the Observable contained in my ViewModel which runs when clicking on the Go button (Notice it's modifying properties of my ViewModel):
private IObservable<string> PerformOperationObservable()
{
sb.Clear();
return Observable.Create<string>((o) =>
{
using (cts = new CancellationTokenSource())
{
// this creates a copy of the file list which will keep us safe
// if the user is clicking around on the UI and changing the list.
var selectedFiles = FileList.Where(x => x.IsChecked).ToArray();
int total = selectedFiles.Length;
int count = 0;
foreach (var file in selectedFiles)
{
ProgressBarMessage = $"Processing {count + 1} of {total}";
o.OnNext($"Processing file {file.Name}");
SelectedFileOperation.PerformOperation(file.FullPath);
count++;
PercentComplete = (int)(((double)count / total) * 100);
if (cts.IsCancellationRequested)
{
PercentComplete = 0;
ProgressBarMessage = string.Empty;
break;
}
}
ProgressBarMessage = string.Empty;
}
o.OnCompleted();
return Disposable.Empty;
});
}
Observable are inherently single threaded, you need to specify where to do the work. You could do this, I believe:
PerformSelectedOperationCommand
= ReactiveCommand.CreateFromObservable(
PerformOperationObservable.SubscribeOn(RxApp.TaskPoolScheduler), // Move subscription to task pool
this.WhenAnyValue(
x => x.SelectedPath,
x => x.TotalFilesSelected,
(x, y) => x != null && y > 0
)
);
// listen to the messages and append to output
PerformSelectedOperationCommand
.ObserveOnDispather() // Return to UI thread
.Subscribe(s =>
{
sb.AppendLine(s);
ProgressWindowOutput = sb.ToString();
});
You explicitly subscribe to the observable on the task pool, moving the work of the UI thread. Right before using the output, you return to the UI thread to be able to do updates on the UI.

Transitioning from Session to ReactiveVar or ReactiveDict (Meteor)

So, I'm making an audio player and I've got a bit of code to get the audio duration of the selected file, for the timer. It was working really well with Session, but then, as I might want more than one player per page, I decided to switch to ReactiveVar or ReactiveDict and I don't think I quite grasped how they work, because my code broke. Can you help me? What am I doing wrong?
This is the code as it was with Session.
Template.audioplayer.onRendered(
function() {
audio = $("audio").get(0);
}
);
Template.audioplayer.helpers({
audioduration: function() {
if (!Session.get("audioduration")) {
audioLenght = Meteor.setInterval(function() {
var totaltime = parseInt(audio.duration, 10);
var mins = Math.floor(audio.duration / 60, 10);
var secs = totaltime - mins * 60;
var gimmethetime = mins + ':' + (secs > 9 ? secs : '0' + secs);
Session.set("audioduration", gimmethetime);
return Session.get("audioduration");
}, 500);
} else {
Meteor.clearInterval(audioLenght);
return Session.get("audioduration");
}
}
});
This is my latest attempt at the same result with ReactiveVar. It came out with "TypeError: Cannot read property 'get' of undefined".
Template.audioplayer.onCreated(
function() {
audio = $("audio").get(0);
this.audioduration = new ReactiveVar();
}
);
Template.audioplayer.helpers({
audioduration: function() {
if (!Template.audioduration.get()) {
audioLenght = Meteor.setInterval(function() {
var totaltime = parseInt(audio.duration, 10);
var mins = Math.floor(audio.duration / 60, 10);
var secs = totaltime - mins * 60;
var gimmethetime = mins + ':' + (secs > 9 ? secs : '0' + secs);
Template.instance().audioduration.set(gimmethetime);
return gimmethetime;
}, 500);
} else {
Meteor.clearInterval(audioLenght);
return Template.instance().audioduration.get();
}
}
});
Thanks in advance!
I would add some information before answering your question. I have seen a lot of resources over the Internet using the Session variable to store temporary data and this stunned me a little. You must understand that the Session lifecycle start when you load the page and ends when you reload the page or close your browser. Let's say you store all your temporary data inside the session, if I take time to got through all the application then my session will be polute with data from all these pages. You are not faulty I just wanted to add some information on this. Anyone correct me if I'm wrong.
Now comes the place to answer.
In your helper, you have a test. In this test you try to access the reactiveVar with Template.audioduration. But as you have made for all the other calls it should be Template.instance().audioduration.get()
Template.audioplayer.onCreated(
function() {
audio = $("audio").get(0);
this.audioduration = new ReactiveVar();
}
);
Template.audioplayer.helpers({
audioduration: function() {
if (!Template.instance().audioduration.get()) {
audioLenght = Meteor.setInterval(function() {
var totaltime = parseInt(audio.duration, 10);
var mins = Math.floor(audio.duration / 60, 10);
var secs = totaltime - mins * 60;
var gimmethetime = mins + ':' + (secs > 9 ? secs : '0' + secs);
Template.instance().audioduration.set(gimmethetime);
return gimmethetime;
}, 500);
} else {
Meteor.clearInterval(audioLenght);
return Template.instance().audioduration.get();
}
}
});
Like this it should work...
EDIT :
You had a second error, I put it here for later reference and explanation.
"Exception in setInterval callback: TypeError: Cannot read property 'audioduration' of null". In order to correct it, I told you to create a variable in the helper scope with Template.instance().
This is a typical JS error. In JS, all the diffulty is to understand what is the scope of a function. In this case, it is possible to access the Template variable in the helper but it cannot be accessed inside the callback. If you reference the template instance inside the context of the helper then it can be accessed from the callback. I don't know much about how are linked the different contexts in this particular case. But it is quite usual that the this object cannot be accessed by callbacks, we are forced to user var that = this;. `See this post.

How to retry failures with $q.all

I have some code that saves data using Breeze and reports progress over multiple saves that is working reasonably well.
However, sometimes a save will timeout, and I'd like to retry it once automatically. (Currently the user is shown an error and has to retry manually)
I am struggling to find an appropriate way to do this, but I am confused by promises, so I'd appreciate some help.
Here is my code:
//I'm using Breeze, but because the save takes so long, I
//want to break the changes down into chunks and report progress
//as each chunk is saved....
var surveys = EntityQuery
.from('PropertySurveys')
.using(manager)
.executeLocally();
var promises = [];
var fails = [];
var so = new SaveOptions({ allowConcurrentSaves: false});
var count = 0;
//...so I iterate through the surveys, creating a promise for each survey...
for (var i = 0, len = surveys.length; i < len; i++) {
var query = EntityQuery.from('AnsweredQuestions')
.where('PropertySurveyID', '==', surveys[i].ID)
.expand('ActualAnswers');
var graph = manager.getEntityGraph(query)
var changes = graph.filter(function (entity) {
return !entity.entityAspect.entityState.isUnchanged();
});
if (changes.length > 0) {
promises.push(manager
.saveChanges(changes, so)
.then(function () {
//reporting progress
count++;
logger.info('Uploaded ' + count + ' of ' + promises.length);
},
function () {
//could I retry the fail here?
fails.push(changes);
}
));
}
}
//....then I use $q.all to execute the promises
return $q.all(promises).then(function () {
if (fails.length > 0) {
//could I retry the fails here?
saveFail();
}
else {
saveSuccess();
}
});
Edit
To clarify why I have been attempting this:
I have an http interceptor that sets a timeout on all http requests. When a request times out, the timeout is adjusted upwards, the user is displayed an error message, telling them they can retry with a longer wait if they wish.
Sending all the changes in one http request is looking like it could take several minutes, so I decided to break the changes down into several http requests, reporting progress as each request succeeds.
Now, some requests in the batch might timeout and some might not.
Then I had the bright idea that I would set a low timeout for the http request to start with and automatically increase it. But the batch is sent asynchronously with the same timeout setting and the time is adjusted for each failure. That is no good.
To solve this I wanted to move the timeout adjustment after the batch completes, then also retry all requests.
To be honest I'm not so sure an automatic timeout adjustment and retry is such a great idea in the first place. And even if it was, it would probably be better in a situation where http requests were made one after another - which I've also been looking at: https://stackoverflow.com/a/25730751/150342
Orchestrating retries downstream of $q.all() is possible but would be very messy indeed. It's far simpler to perform retries before aggregating the promises.
You could exploit closures and retry-counters but it's cleaner to build a catch chain :
function retry(fn, n) {
/*
* Description: perform an arbitrary asynchronous function,
* and, on error, retry up to n times.
* Returns: promise
*/
var p = fn(); // first try
for(var i=0; i<n; i++) {
p = p.catch(function(error) {
// possibly log error here to make it observable
return fn(); // retry
});
}
return p;
}
Now, amend your for loop :
use Function.prototype.bind() to define each save as a function with bound-in parameters.
pass that function to retry().
push the promise returned by retry().then(...) onto the promises array.
var query, graph, changes, saveFn;
for (var i = 0, len = surveys.length; i < len; i++) {
query = ...; // as before
graph = ...; // as before
changes = ...; // as before
if (changes.length > 0) {
saveFn = manager.saveChanges.bind(manager, changes, so); // this is what needs to be tried/retried
promises.push(retry(saveFn, 1).then(function() {
// as before
}, function () {
// as before
}));
}
}
return $q.all(promises)... // as before
EDIT
It's not clear why you might want to retry downsteam of $q.all(). If it's a matter of introducing some delay before retrying, the simplest way would be to do within the pattern above.
However, if retrying downstream of $q.all() is a firm requirement, here's a cleanish recursive solution that allows any number of retries, with minimal need for outer vars :
var surveys = //as before
var limit = 2;
function save(changes) {
return manager.saveChanges(changes, so).then(function () {
return true; // true signifies success
}, function (error) {
logger.error('Save Failed');
return changes; // retry (subject to limit)
});
}
function saveChanges(changes_array, tries) {
tries = tries || 0;
if(tries >= limit) {
throw new Error('After ' + tries + ' tries, ' + changes_array.length + ' changes objects were still unsaved.');
}
if(changes_array.length > 0) {
logger.info('Starting try number ' + (tries+1) + ' comprising ' + changes_array.length + ' changes objects');
return $q.all(changes_array.map(save)).then(function(results) {
var successes = results.filter(function() { return item === true; };
var failures = results.filter(function() { return item !== true; }
logger.info('Uploaded ' + successes.length + ' of ' + changes_array.length);
return saveChanges(failures), tries + 1); // recursive call.
});
} else {
return $q(); // return a resolved promise
}
}
//using reduce to populate an array of changes
//the second parameter passed to the reduce method is the initial value
//for memo - in this case an empty array
var changes_array = surveys.reduce(function (memo, survey) {
//memo is the return value from the previous call to the function
var query = EntityQuery.from('AnsweredQuestions')
.where('PropertySurveyID', '==', survey.ID)
.expand('ActualAnswers');
var graph = manager.getEntityGraph(query)
var changes = graph.filter(function (entity) {
return !entity.entityAspect.entityState.isUnchanged();
});
if (changes.length > 0) {
memo.push(changes)
}
return memo;
}, []);
return saveChanges(changes_array).then(saveSuccess, saveFail);
Progress reporting is slightly different here. With a little more thought it could be made more like in your own answer.
This is a very rough idea of how to solve it.
var promises = [];
var LIMIT = 3 // 3 tris per promise.
data.forEach(function(chunk) {
promises.push(tryOrFail({
data: chunk,
retries: 0
}));
});
function tryOrFail(data) {
if (data.tries === LIMIT) return $q.reject();
++data.tries;
return processChunk(data.chunk)
.catch(function() {
//Some error handling here
++data.tries;
return tryOrFail(data);
});
}
$q.all(promises) //...
Two useful answers here, but having worked through this I have concluded that immediate retries is not really going to work for me.
I want to wait for the first batch to complete, then if the failures are because of timeouts, increase the timeout allowance, before retrying failures.
So I took Juan Stiza's example and modified it to do what I want. i.e. retry failures with $q.all
My code now looks like this:
var surveys = //as before
var successes = 0;
var retries = 0;
var failedChanges = [];
//The saveChanges also keeps a track of retries, successes and fails
//it resolves first time through, and rejects second time
//it might be better written as two functions - a save and a retry
function saveChanges(data) {
if (data.retrying) {
retries++;
logger.info('Retrying ' + retries + ' of ' + failedChanges.length);
}
return manager
.saveChanges(data.changes, so)
.then(function () {
successes++;
logger.info('Uploaded ' + successes + ' of ' + promises.length);
},
function (error) {
if (!data.retrying) {
//store the changes and resolve the promise
//so that saveChanges can be called again after the call to $q.all
failedChanges.push(data.changes);
return; //resolved
}
logger.error('Retry Failed');
return $q.reject();
});
}
//using map instead of a for loop to call saveChanges
//and store the returned promises in an array
var promises = surveys.map(function (survey) {
var changes = //as before
return saveChanges({ changes: changes, retrying: false });
});
logger.info('Starting data upload');
return $q.all(promises).then(function () {
if (failedChanges.length > 0) {
var retries = failedChanges.map(function (data) {
return saveChanges({ changes: data, retrying: true });
});
return $q.all(retries).then(saveSuccess, saveFail);
}
else {
saveSuccess();
}
});

how to retry if cannot find element in Nightwatch

I want to retry if nightwatch does not click on an element. How can I do that?
I have the following code:
this.browser.isVisible('.signUp', function (result) {
if (result.value && result.length) {
for(var i = 0; i < 3; i += 1) {
this.browser.click(this.element.login.signInBtn);
var check = this.browser.url(function(result) {
// on login page
console.log(result);
if (result.value.indexOf("#login") !== -1) {
return false;
} else {
return true;
}
});
if (check) {
break;
}
}
}
}.bind(this));
-retries n will not run before and after function so this will not work as expected. You should try --suiteRetries n.
You can retry test cases with --retries command-line option. Example nightwatch --retries 2 will retry failed test case two additional times.
However I will not recommend you using that. I would first explore other options to make sure that element can be clicked. Like example waiting for it to become visible with waitForElementVisible
this.browser.waitForElementVisible(this.element.login.signInBtn, 1000);

Resources