I create a test entry at the beginning of my test, but sometimes it doesn't load by the time the page is accessed in the test. Wanted to poll for the entry, and if not present reload the page then check again. Tried using the recursive function example for cy.get() in the docs, but getting an error:
Error: TypeError: move.find(...).contains is not a function
Function:
function pollForTestMove() {
cy.get('div[class="consumer-name-wrap"]')
.then((move) => {
if (
move.find('span')
.contains("test" + localStorage.getItem('randomNameString') + " user" + localStorage.getItem('randomNameString') + " (TEST)", { "matchCase": true })
)
return
cy.reload
pollForTestMove()
})
}
To fix this particular error, I think using cy.wrap should be enough:
cy.wrap(move).find('span')...
However, I don't think the code with the conditional test will work as you expect it to work. Please take a look at Cypress documentation about conditional testing.
Hope it helps. If you have any questions let me know
You need something like this. Then call in your page pollForElement(name of the text from the element)
pollForElement(name) {
var count = 0;
function pollElement() {
return cy.get('body').then($body => {
if ($body.text().includes(name)) {
console.log('item has been found');
return $body;
}
else {
count += 1;
cy.wait(2000);
cy.reload();
console.log('Polling for element...');
}
if(count === 10 ) {
console.log('Element not found');
assert.isFalse(true, 'Element not found')
}
return pollElement();
});
}
return pollElement();
}
Related
I am trying to check my all 4 images is uploaded to server without any error, then redirect to another page so i am trying to perform some sync checking in my code (I have total 4 images in my imgResultAfterCompress array). below is my code:
if(Boolean(this.updateImage(data.AddId))===true)
{
this.router.navigate(['/job-in-hotels-india-abroad']);
}
updateImage(AddId:number):Observable<boolean>
{
this.cnt=0;
this.uploadingMsg='Uploading Images...';
this.imgResultAfterCompress.forEach( (value, key) => {
if(value!=='')
{
this.itemService.updateImage(this.employer.ID,AddId,key,value).subscribe(data=>{
if(data && data.status == 'success') {
this.uploadingMsg=this.uploadingMsg+'<br>Image No - '+(key+1)+' Uploaded.';
this.cnt++;
}
else
this.alertService.error(data.message);
});
}
if(this.cnt==4)
this.uploadingDone= true;
else
this.uploadingDone= false
});
return this.uploadingDone;
}
Every time i am getting cnt value is 0, i want its value = 4 (completely uploaded all images) then redirection will occurred.
The easier way is to wrap your observables into a single one, using zip operator
https://rxjs-dev.firebaseapp.com/api/index/function/zip
Thus once every request is finished successfully your zipped Observable will be fulfilled.
UPDATE:
This is how I think it should look like. I could miss something specific, but the global idea should be clear
redirect() {
this.updateImages(data.AddId).subscribe(
() => this.router.navigate(['/job-in-hotels-india-abroad']),
error => this.alertService.error(error.message)
)
}
updateImages(AddId: number): Observable<boolean[]> {
this.uploadingMsg = 'Uploading Images...';
const requests: Observable<boolean>[] = [];
this.imgResultAfterCompress.forEach((value, key) => {
if (!value) {
return;
}
requests.push(
this.itemService.updateImage(this.employer.ID, AddId, key, value)
.pipe(
tap(() => this.uploadingMsg = this.uploadingMsg + '<br>Image No - ' + (key + 1) + ' Uploaded.'),
switchMap((data) => {
if (data && data.status == 'success') {
return of(true)
} else {
throwError(new Error('Failed to upload image'));
}
})
)
)
});
return zip(...requests);
}
Finally got the desire result by using forkJoin
Service.ts:
public requestDataFromMultipleSources(EmpId: number,AddId:number,myFiles:any): Observable<any[]> {
let response: any[] = [];
myFile.forEach(( value, key ) => {
response.push(this.http.post<any>(this.baseUrl + 'furniture.php', {EmpId: EmpId, AddId:AddId,ImgIndex:key,option: 'updateAdImg', myFile:value}));
});
// Observable.forkJoin (RxJS 5) changes to just forkJoin() in RxJS 6
return forkJoin(response);
}
my.component.ts
let resCnt=0;
this.itemService.requestDataFromMultipleSources(this.employer.ID,AddId,this.imgResultAfterCompress).subscribe(responseList => {
responseList.forEach( value => {
if(value.status=='success')
{
resCnt++;
this.uploadingMsg=this.uploadingMsg+'<br>Image No - '+(value.ImgIndex+1)+' Uploaded.';
}
else
this.uploadingMsg=this.uploadingMsg+'<br>Problem In Uploading Image No - '+(value.ImgIndex+1)+', Please choose another one.';
});
if(resCnt === this.imgResultAfterCompress.length)
{
this.alertService.success('Add Posted Successfully');
this.router.navigate(['/job-in-hotels-india-abroad']);
}
else
this.alertService.error('Problem In Uploading Your Images');
});
You shouldn't try to make sync call within a loop. It is possible using async/await, but it's bad for app performance, and it is a common anti-pattern.
Look into Promise.all(). You could wrap each call into promise and redirect when all promises are resolved.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
So I query a set of nodes in firebase in order to get a value depends if the id inputted is exists, problem here is after the function returns the for loop stops looping though the return will exits a loop. So what would be the best way on querying data synchronously without stopping the loop after a return called. Hope someone helps snippet of the code attached below
loginIdentification = ['bfp','ndrmmc','pnp','rta'];
for (var counter = 0; counter < this.loginIdentification.length; counter++) { // loop the possible place of the data
console.log('loop',counter);
return this.loginFiredatabase.ref('users/' + this.loginIdentification[counter] + '/' + authData.uid).on('value', (snapshot) => { // authdata.uid is the id of the user
console.log('await');
if(snapshot.exists()) {
console.log(JSON.stringify(snapshot));
return snapshot;
}
else {
console.log('not exists');
}
});
}
I think you're trying to find the first user identification in the array that already exists in the database. If that's the case, it'd be something like this:
function findMatch(ids, callback) {
var id = ids.shift;
this.loginFiredatabase.ref('users/' + this.loginIdentification[counter] + '/' + authData.uid)
.once('value', function(snapshot) {
if (snapshot.exists()) {
callback(snapshot);
else if (ids.length > 0) {
findMatch(ids, callback);
}
else {
callback();
}
});
}
The trick about this function is that it checks one ID from the array at a time. Since data is loaded from Firebase asynchronously, it waits until it gets a result before trying the next item.
You'd call it like:
findMatch(['bfp','ndrmmc','pnp','rta'], function(result) {
if (result) {
console.log("Found match: "+result.val());
}
else {
console.log("No match found");
}
}
I need help with loop beaking.
For my check I did the simple test:
while(i < 10) {
element(by.xpath("//a[contains(#id, 'ma-apply')]")).isPresent().then(function(result) {
if(!result) {
helper.one_page_ahead();
} else {
console.log('there is on the page');
break;
}
});
i++;
};
This code leads to the error.
I tried to follow advice via StackOverflow and changed break to return.
But this leads to full loop execution (up to 10).
Here is the output:
[14:17:46] I/launcher - Running 1 instances of WebDriver Started user
skills: AJAX there is on the page there is on the page there is on the
page there is on the page there is on the page there is on the page
there is on the page there is on the page .
1 spec, 0 failures Finished in 37.93 seconds
I tried the same with for loop like
for(i = 0; i < 10; i++) {
//code
break;
}
Would be glad to find the answer.
This is some commentary about why the while statement does not work: When you call isPresent you are returned a webdriver.promise.Promise<boolean>. Since you are in the webdriver control flow, you'll need to throw an error,
browser.get('http://angularjs.org');
var i = 0;
var running = true;
while(i < 3 && running) {
console.log('while: ' + running + ' ' + i);
element(by.model('username')).isPresent().then((result) => {
console.log('element: ' + running + ' ' + i);
if (result) {
// huzzah we found it, so lets break the element after the first test
browser.get('https://docs.angularjs.org/tutorial');
} else {
running = false;
throw new Error('no username')
}
}).catch((err) => {
console.log(err);
});
i++;
}
This basically prints out:
[19:07:18] I/hosted - Using the selenium server at http://localhost:4444/wd/hub
[19:07:18] I/launcher - Running 1 instances of WebDriver
Started
while: true 0
while: true 1
while: true 2
element: true 3
[Error: no username]
element: false 3
[Error: no username]
element: false 3
[Error: no username]
So basically your while loop queues up items in the control flow to execute. These then will get executed asynchronously in order.
I like the suggestion by Sudharsan Selvaraj to do this recursively.
You need to implement a recursive method to achieve what you want, try the below piece of code,
function runTillElementFound(totalCount,currentCount){
var self = this;
var _element = element(by.xpath("//a[contains(#id, 'ma-apply')]"));
if(currentCount < totalCount){
return _element.isPresent().then(function(isElementPresent){
if(isElementPresent){
return;
}else{
helper.one_page_ahead();
self.runTillElementFound(totalCount,currentCount++);
}
})
}else{
return false; //if element not present after Max count reached.
}
}
this.runTillElementFound(10,0); //this will execute the method untill the required element found on the page.
If you want to avoid recursion you could modify the index variable inside the returned promised
while(i < 10) {
element(by.xpath("//a[contains(#id, 'ma-apply')]")).isPresent().then(function(result) {
if(!result) {
helper.one_page_ahead();
} else {
console.log('there is on the page');
i = 10;
}
});
i++;
};
And I would add a browser.sleep(x) in between each repetion to avoid the code to be run before the result from the promise is evaluated.
i = 10; is not effecting, Still loop iterating
I have coded a javascript file:
$(function() {
return $(".ajax-form").on("ajax:success", function(e, data, status, xhr) {
var model_name;
model_name = $(this).data('model-name');
console.log('ajax form success');
if (model_name === 'contact') {
return $('#modal-alert-contact').modal('show');
} else {
return $('#modal-alert-demo').modal('show');
}
}).bind("ajax:error", function(e, xhr, status, error) {
var elm, messages, model_name;
model_name = $(this).data('model-name');
console.log('ajax form error');
console.log(model_name);
if (model_name === 'contact') {
if (xhr.responseJSON["email"]) {
elm = $('.alert-contact-fields');
messages = [];
$.each(xhr.responseJSON, function(id, error_messages) {
return messages.push(("<li><strong class='titleize'>" + id + "</strong> - can't be blank</li>").replace(/_/g, " "));
});
elm.find('.messages').html(messages);
return elm.removeClass('hide');
} else {
elm = $('.alert-contact-fields');
return elm.addClass('hide');
}
} else {
if (xhr.responseJSON["company_name"]) {
elm = $('.alert-demo-fields');
messages = [];
$.each(xhr.responseJSON, function(id, error_messages) {
return messages.push(("<li><strong class='titleize'>" + id + "</strong> - can't be blank</li>").replace(/_/g, " "));
});
elm.find('.messages').html(messages);
return elm.removeClass('hide');
} else {
elm = $('.alert-demo-fields');
return elm.addClass('hide');
}
}
});
});
and I found it out messy, and repeating same codes.
What I'm want to do is this part:
messages = [];
$.each(xhr.responseJSON, function(id, error_messages) {
return messages.push(("<li><strong class='titleize'>" + id + "</strong> - can't be blank</li>").replace(/_/g, " "));
});
elm.find('.messages').html(messages);
return elm.removeClass('hide');
I want that part to be a function, and after I do that, I will call that function to use it on my function. Is it possible or there's some technique to improve my coding structure?
Thanks!
I think you want something like this:
$(function() {
var myform = $(".ajax-form");
var makeMessages = function(json) {
return $.map(json, function(error_messages, id) {
return ("<li><strong class='titleize'>" + id + "</strong> - can't be blank</li>").replace(/_/g, " ");
});
};
myform.on('ajax:success', function(e, data, status, xhr) {
var modal_to_show = ($(this).data('model-name') === 'contact') ? '#modal-alert-contact' : '#modal-alet-demo';
return $(modal_to_show).modal('show');
});
myform.on('ajax:error', function(e, xhr, status, error) {
var fields;
if ($(this).data('model-name') === 'contact') {
fields = $('.alert-contact-fields');
if (xhr.responseJSON["email"]) {
return fields.find('messages').html(makeMessages(xhr.responseJSON)).end().removeClass('hide');
}
return fields.addClass('hide');
}
fields = $('.alert-demo-fields');
if (xhr.responseJSON["company_name"]) {
return fields.find('.messages').html(makeMessages(xhr.responseJSON)).end().removeClass('hide');
}
return fields.addClass('hide');
});
});
makeMessages is a function that takes the json object and returns a set of strings; map() is a much better function for that than each(), because it requires no intermediate array to save the values.
The 'success' handler shows the use of the 'ternary operator', also known as a conditional expression. You want to know which modal to show: this is how you pick it, then have one and only one 'show' operation. It's way easier to debug.
For the 'error' handler, each time you set the messages, you just call makeMessages() with your JSON and get back the array of strings you want. Because you had to find the messages field inside the alert-*-fields, I call end() which pops the current jquery context back one search (from the 'find' to the initial $() call) and then call 'show' on it instead.
Since you call 'return' at the end of a chosen successful operation, there is no need at all for 'else' statements. They're noise. Either your code does its thing, or it falls through to the next stage.
You could remove my fields = set operations, since performance-wise each will only be called once, so it's harmless to have that repetition. But this makes explicit which region you're working in.
If you want to get insanely dense about what you're working on, put all the decision-making up top (what to work on, what to show), and make the rest of the code pure machinery, the 'how' part of your code. The 'error' handler becomes this:
myform.on('ajax:error', function(e, xhr, status, error) {
var handler = (($(this).data('model-name') === 'contact') ?
{ 'fieldclass': '.alert-contact-fields', 'objname': 'email' } :
{ 'fieldclass': '.alert-demo-fields', 'objname': 'company_name' });
var fields = $(handler.fieldclass);
if (xhr.responseJSON[handler.objname]) {
return fields.find('.messages').html(makeMessages(xhr.responseJSON)).end().removeClass('hide');
}
return fields.addClass('hide');
});
At which point 'makeMessages()' just becomes a nice, convenient function because it shows (and names! Good names are always important to maintenance) what you're doing with the JSON object.
One (well, two) last alternatives:
myform.on('ajax:error', function(e, xhr, status, error) {
var drawResults = function(fieldclass, objname) {
var fields = $(fieldclass);
if (xhr.responseJSON[objname]) {
return fields.find('messages').html(makeMessages(xhr.responseJSON)).end().removeClass('hide');
}
return fields.addClass('hide');
};
return ($(this).data('model-name') === 'contact' ?
drawResults('.alert-contact-fields', 'email') :
drawResults('.alert-data-fields', 'company_name'));
/* Absolutely minimal alternative, but some people find
* using the apply method obfuscating. */
return drawResults.apply(this, $(this).data('model-name') === 'contact' ?
['.alert-contact-fields', 'email'] :
['.alert-data-fields', 'company_name']);
});
Rather than use fields and decisions up front, this puts all the decision making at the end, and describes what will happen once the decision is made up front. This uses a more familiar syntax of calling a function. It's important to see that drawResults() has access to the xhr object already, so it isn't necessary to pass it in.
One last possible extraction is to turn $(this).data('model-name') === 'contact' into a function, like isDemo(), so that code only happens once and is also well-named.
Sorry for the lack of description in title, it's difficult to explain.
So I have a simple signup page and I made a bunch of functions in my code that check things such as the username length, make sure the passwords match, etc..
The problem is, if there is more than one error in the users input, it only displays one error at the bottom.
HEre is the JSfiddle: http://jsfiddle.net/LCBradley3k/xqcJS/19/
Javascript:
$('#join').on('click', function () {
var correct = true;
$('input[type="text"], input[type="password"]').each(function (indx) {
var $currentField = $(this);
if ($currentField.val() === '') {
$currentField.addClass('empty');
correct = false;
$currentField.one('keydown', function () {
$currentField.removeClass('empty');
});
} else {
$currentField.removeClass('empty');
}
});
function userLength() {
var x = $('input[name="user"]').val();
if (x.length < 6) {
$('#answer').html('Less than six characters.');
$('input[name="user"]').addClass('empty');
return false;
} else {
return true;
}
}
function passwordCheck() {
var x = $('input[name="password"]').val();
var y = $('input[name="passwordcheck"]').val();
if (x === y) {
return true;
} else {
$('#answer').html('Two different passwords');
$('input[name="password"], input[name="passwordcheck"]').addClass('empty');
return false;
}
}
function validateForm() {
var x = $('input[name="email"]').val();
if (x.indexOf('#') !== -1 && x.lastIndexOf(".") !== -1) {
return true;
} else {
$('#answer').html('Not a valid email');
$('input[name="email"]').addClass('empty');
return false;
}
}
if (correct) {
if (userLength()) {
if (passwordCheck()) {
if (validateForm()) {
$('#answer').html('Thank You!');
setTimeout(function () {
$('.inputs').hide("slide", {
direction: "up"
}, 1000);
}, 2000);
}
}
}
} else {
$('#answer').html('Please fill highlighted fields.');
}
});
You can see that all of them edit the #('#answer') div with .html(). But only one is displayed when there is more than one error. Once that error is fixed and the button is pressed, it will then display the next error. I want them all to be displayed in a list.
I created a fiddle that may be of some help. The idea is to create an array with the errors in it like so:
var errors = [];
errors.push("Error 1");
errors.push("Error 2");
As you step through the validation, every time an error is encountered you simply push the error string onto the array. When you get to the end of the validation you need to compile these errors into html like that can be appended to your $('#answer') element. In this case the items are compiled into an unordered list. You can change this to fit your needs.
var content = "<ul>";
for(var a = 0, len = errors.length; a < len; a++) {
content += "<li>" + errors[a] + "</li>";
}
content += "</ul>";
$('#answer').html(content);
The html is built dynamically and stored in the variable content. content is then appended to your html element that displays the errors (in your case answer).
You have 2 issues with doing what you want.
First, you are only continuing your checks if the first one passes, due to your nested if statements.
Second, you are replacing the #answer html with the message, which means even if you do each check, you will only see the results of the last one.
A simple fix would be to un-nest your if statements, and keep a variable that tracks the overall pass state. Secondly, instead of using .html(), use .append(), but make sure to clear out #answer before starting your checks.
correct &= checkFilled();
correct &= userLength();
correct &= passwordCheck();
correct &= validateForm();
if (correct) {
// ...
}
Fiddle: http://jsfiddle.net/jtbowden/9cFKW/
Note: I made your form filled check it's own function to work better with this method.
You can do some more fancy things, like pushing error messages on an array, and then checking the array for errors at the end and appending all of the messages, but this should get you started.