I'm writing an automated script with webdriver.io to fill in inputs/selects on a web page and take screenshots. What if a user action (such as changing the value of a select) causes the page to load in the middle of the automation script's execution? Is there a way to pick up where you left off in the script (let's say some inputs were already filled in at that point and they retain their value via session data but you want to continue filling in the rest of them).
As far as I can see once that page reloads the script stops executing.
If there is no way to pick up where you left off, then how do you trigger the script from the beginning by detecting a page load and then re-executing the script?
Responding to comments: I don't think I need to paste the code (although I will) because the question is really just how to detect a page load via webdriver.io when the page load occurs in the middle of script execution and is NOT initiated by the script itself.
UPDATE!!: I managed to solve this using waitForExist() and simply waiting for each input/select to exist before I interact with it. It's not ideal, but it works. If anyone has a better solution please let me know!
Here's the code:
webdriverio = require('webdriverio');
var tester = {};
tester.iter = 0;
var options = {
desiredCapabilities: {
browserName: 'chrome'
}
};
var params = {
[[I've removed these because it's private info]]
};
var fields = {
deliveryCompany: 'ABC Inc',
deliveryFirstname: 'John',
deliveryLastname: 'Smith',
deliveryStreet1: '123 Main St.',
deliveryCity: 'Boston',
};
var execQueue = [];
var wrapFunction = function(fn, context, params) {
return function() {
fn.apply(context, params);
};
};
var takeScreenshot = function(){
console.log('take screenshot', this);
this.saveScreenshot('./checkout.png')
.click('input.CBSubmit').then(function(value) {
this.waitForExist('#mjPaymentReview').then(function(){
this.saveScreenshot('./review.png').then(function(){
this.click('input.CBSubmit').then(function(){
this.waitForExist('#mjCartFinal').then(function(){
this.saveScreenshot('./confirmation.png').then(function(){
this.end();
});
});//end wait for exists
});//end click.then
});// Save the screenshot to disk
});//end pause.then
});//end click.then
};//end takeScreenshot
function fillFields(driver, fields){
driver.elements('select + .chosen-container').then(function(result){
console.log('how many selects', result.value.length);
tester.totalSelects = result.value.length;
});
//loop through all selects and inputs
for(property in fields){
var p = property;
//closure to preserve value of property
(function(p){
//if chosen input then choose from list
driver.isExisting('div.' + p + ' .chosen-results').then(function(result){
if(result === true){
driver.elements('div.' + p + ' select').then(function(result){
//loop through each select (expiration date has two selections in one container)
for(var i=0;i<result.value.length;i++){
var s = result.value[i].ELEMENT;
//closure
(function(s){
//find the name of each select
driver.elementIdAttribute(s,'name').then(function(result){
//find the chosen container after select
var container = 'select[name=' + result.value + '] + .chosen-container';
var selectName = result.value;
//find corresponding a.chosen-single
//this.debug()
//click on a.chosen-single to activate chosen drop
var qfunction = function(link, value){
console.log('#################in qu function ###########', value);
driver.click(link).then(function(){
this.keys([value, '\uE007']).then(function(){
tester.iter++;
console.log('tester.iter', tester.iter);
console.log('tester.totalSelects', tester.totalSelects);
if(tester.iter == tester.totalSelects-1){
takeScreenshot.call(this, null);
}
execQueue[tester.iter]();
});//end keys.then
});//end click.then
}//end qfunction
execQueue.push(wrapFunction(qfunction, this, [container + ' a.chosen-single', fields[selectName]]));//end push
if(execQueue.length == tester.totalSelects - 1){
console.log('**********equal');
execQueue[tester.iter]();
}//end if equal
console.log('queue so far', execQueue.length);
});//end elementIdAttribute
})(s);//end closure
}//end for selects in container
});//end driver.elements
}else{
driver.addValue('input[name=' + p + ']', fields[p]);
}
})//end driver.isExisting
})(p);
}//end for each field
};//end fillFields
webdriverio
.remote(options)
.init()
.url('https://' + params.domain + '/' + params.clientId + '/?scope=' + params.scope + '&cart=' + params.cart + '&cfg=' + params.cfg + '&progress=' + params.progress + '&language=' + params.language + '¤cyId=' + params.currencyId) // navigate to the web page
.then(function(result) {
fillFields(this, fields);
});
Related
I have a situation where I am updating a the DOM with information from a 3rd party website at the same time that I am creating an object on the backend with ajax. Sometimes the ajax call returns slower than the separate job responds through ActionCable.
On the front-end I'm rendering the 3rd party results but when the DOM hasn't been updated I end up with an un-updatable DOM. Is there another way to handle a delay other than just with a setTimeout? Because if for some reason I don't wait long enough, the DOM update will still fail.
EDIT: Code of what I'm trying to do.
connectPosts: function connectPosts(){
var self = this;
if (!webSockets) { return }
App.cable.subscriptions.create({
channel_id: channel_id,
channel: 'PostsChannel'
},{
received: function(data){
var $element = $('#' + data['object_type'] + '_' + data['object_id'] + ' .container');
if($element.length == 0)
setTimeout(function(data){self.buildElement(data)}, 1000, data);
else
self.buildElement(data);
}
});
},
buildElement: function buildElement(data){
var $element = $('#' + data['object_type'] + '_' + data['object_id'] + ' .container');
var content = HandlebarsTemplates['posts/element'](data);
$element.html(content);
if($element.find('.author-' + self.currentUser.id)){
$element.find('a').show();
}
}
};
Working on a practice app with localStorage, but the stored data is getting cleared on page refresh. Based on answers to similar questions, I've used JSON.stringify(); on setItem, and JSON.parse(); on getItem, but still no luck. Am I using those methods in the wrong way? For reference, #petType and #petName are input IDs, and #name and #type are ul IDs. Thanks!
var animalArray = [];
var addPet = function(type,name) {
var type = $("#petType").val();
var name = $("#petName").val();
localStorage.setItem("petType", JSON.stringify(type));
localStorage.setItem("petName", JSON.stringify(name));
animalArray.push(type,name);
};
var logPets = function() {
animalArray.forEach( function(element,index) {
//empty array
animalArray.length = 0;
//empty input
$("input").val("");
var storedName = JSON.parse(localStorage.getItem("petName"));
var storedType = JSON.parse(localStorage.getItem("petType"));
//append localStorage values onto ul's
$("#name").append("<li>" + storedName + "</li>");
$("#type").append("<li>" + storedType + "</li>");
});
};
//click listPets button, call logPets function
$("#listPets").on("click", function() {
logPets();
$("#check").html("");
});
//click enter button, call addPet function
$("#enter").on("click", function() {
addPet(petType,petName);
$("#check").append("<i class='fa fa-check' aria-hidden='true'></i>");
});
It appears to clear because you are not loading data from it when the page loads. There are multiple bugs in the code:
It appears that you're only saving the last added pet to localStorage, which would create inconsistent behaviour
Setting animalArray.length to 0 is incorrect
animalArray.push(type, name); is probably not what you want, since it adds 2 items to the array, do something like animalArray.push({type: type, name: name});
logPets can just use the in memory array, since it's identical to the one saved
Fixed code:
var storedArray = localStorage.getItem("animalArray");
var animalArray = [];
if(storedArray) {
animalArray = JSON.parse(storedArray);
}
var addPet = function(type,name) {
var type = $("#petType").val();
var name = $("#petName").val();
animalArray.push({type: type, name: name});
localStorage.setItem("animalArray", JSON.stringify(animalArray));
};
var logPets = function() {
animalArray.forEach( function(element,index) {
//empty input
$("input").val("");
//append localStorage values onto ul's
$("#name").append("<li>" + element.name + "</li>");
$("#type").append("<li>" + element.type + "</li>");
});
};
//click listPets button, call logPets function
$("#listPets").on("click", function() {
logPets();
$("#check").html("");
});
//click enter button, call addPet function
$("#enter").on("click", function() {
addPet(petType,petName);
$("#check").append("<i class='fa fa-check' aria-hidden='true'></i>");
});
A quick fiddle to demo it: https://jsfiddle.net/rhnnvvL0/1/
I'm working on a twitter clone in JS + jQuery for a pre-course to a development program, so if this is an obvious fix - let me know!
I have a problem I'm unable to solve: "click on username, page returns with that user's last tweets".
The only thing I could come up with is, an event handler on the <a> tag filter the page. However I'm vastly inexperienced and unclear in how to proceed.
Any ideas?
note- I removed some code for brevity.
$(document).ready(function() {
var $body = $('body');
$body.html();
var stream = function() {
var index = streams.home.length - 1;
while (index >= 0) {
var tweet = streams.home[index];
var $tweet = $('<div class="tweetbody"></div>');
$tweet.text(': ' + tweet.message);
$tweet.appendTo($body);
index -= 1;
var link = $('<a>', {
text: tweet.user,
href: '#',
}).prop('outerHTML');
$tweet.html('#' + link + ': ' + tweet.message);
}
};
Here's the <a> tag event:
//click on a username to see that user's timeline.
$('a').on('click', function() {
console.log("a tag is clicked ");
console.log(this);
});
}();
}); //end document ready body
In the on click function you could perhaps do either of the two things, so go to another page, like redirecting to a new page
//click on a username to see that user's timeline.
$('a').on('click', function() {
//console.log("a tag is clicked ");
//console.log(this);
window.location = '/some_file.php?get_tweets='+tweet.user;
});
or, use an ajax call to do the same:
//click on a username to see that user's timeline.
$('a').on('click', function() {
//console.log("a tag is clicked ");
//console.log(this);
$.ajax({
type: "GET",
url: "/some_file.php",
data: {
'user' : tweet.user
},
success: function(msg) {
$body.html(msg);
});
});
In both cases some_file.php should format the content.
I'm using this jQuery script to show search results. Everything works fine, but when search results have more than one page and I'm browsing pages via paging then every page loading is gradually getting slower. Usually first cca 10 pages loads I get quickly, but next are getting avoiding loading delay. Whole website get frozen for a little while (also loader image), but browser is not yet. What should be the problem?
function editResults(def) {
$('.searchResults').html('<p class=\'loader\'><img src=\'images/loader.gif\' /></p>');
var url = def;
var url = url + "&categories=";
// Parse Categories
$('input[name=chCat[]]').each(function() {
if (this.checked == true) {
url = url + this.value + ",";
}
});
url = url + "&sizes=";
// Parse Sizes
$('input[name=chSize[]]').each(function() {
if (this.checked == true) {
url = url + this.value + ",";
}
});
url = url + "&prices=";
// Parse Prices
$('input[name=chPrice[]]').each(function() {
if (this.checked == true) {
url = url + this.value + ",";
}
});
$('.searchResults').load('results.php'+url);
$('.pageLinks').live("click", function() {
var page = this.title;
editResults("?page="+page);
});
}
$(document).ready(function(){
editResults("?page=1");
// Check All Categories
$('input[name=chCat[0]]').click(function() {
check_status = $('input[name=chCat[0]]').attr("checked");
$('input[name=chCat[]]').each(function() {
this.checked = check_status;
});
});
// Check All Sizes
$('input[name=chSize[0]]').click(function() {
check_status = $('input[name=chSize[0]]').attr("checked");
$('input[name=chSize[]]').each(function() {
this.checked = check_status;
});
});
// Edit Results
$('.checkbox').change(function() {
editResults("?page=1");
});
// Change Type
$(".sort").change(function() {
editResults("?page=1&sort="+$(this).val());
});
});
$('.pageLinks').live("click", function() {
var page = this.title;
editResults("?page="+page);
});
just a wild guess but... wouldn't this piece of code add a new event handler to the click event instead reaplacing the old one with a new one? causing the click to call all the once registered handlers.
you should make the event binding just once
var global_var = '1';
function editResults(def) {
// all your code
global_var = 2; // what ever page goes next
};
$(document).ready(function() {
// all your code ...
$('.pageLinks').live("click", function() {
var page = global_var;
editResults("?page="+page);
});
});
Hi guys this might be a really stupid error but im using jquery to add a formset to a page it also does other things such as updating the number of forms but that does not seem to be a issue.
http://jsfiddle.net/b5Y8f/
$(document).ready(function () {
function updateElementIndex(el, prefix, ndx) {
var id_regex = new RegExp('(' + prefix + '_set-\\d+-)');
var replacement = prefix + '_set-' + ndx + '-';
if ($(el).attr("for")) $(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
if (el.id) el.id = el.id.replace(id_regex, replacement);
if (el.name) el.name = el.name.replace(id_regex, replacement);
}
function changeDeleteForms(el, prefix, formid) {
var idstring = 'id_' + prefix + '_set-' + formid + '-DELETE';
//$('<input>').attr({type: 'hidden', id: 'id_' + idstring, name: idstring}).appendTo('.command-delete');
$('#' + idstring).prop('checked', true);
}
function deleteForm(btn, prefix) {
var formCount = parseInt($('#id_' + prefix + '_set-TOTAL_FORMS').val());
if (formCount > 1) {
// Delete the item/form
$(btn).parents('.command').hide();
$(btn).parents('.command').attr('class', 'command-delete');
var dc = $(".command-delete");
$(dc).children().children().children().each(function () {
var formid = this.id.match(/\d+/g);
changeDeleteForms(this, prefix, formid);
//$(this).val("");
});
var forms = $('.command'); // Get all the forms
var formsdelete = $('.command-delete'); // Get all the forms
var fl = parseInt(forms.length);
var fdl = parseInt(formsdelete.length);
var finalcount = fl + fdl
// Update the total number of forms (1 less than before)
//$('#id_' + prefix + '_set-TOTAL_FORMS').val(forms.length);
var i = 0;
} // End if
else {
alert("Please enter atleast 1 command for this item.");
}
return false;
}
function addForm(btn, prefix) {
var formCount = parseInt($('#id_' + prefix + '_set-TOTAL_FORMS').val());
var maxCount = parseInt($('#id_' + prefix + '_set-MAX_NUM_FORMS').val());
var forms = parseInt($('.command-delete').length); // Get all the forms
var newcount = formCount + forms;
// You can only submit a maximum of 10 todo items
if (newcount < maxCount) {
// Clone a form (without event handlers) from the first form
var row = $(".command:first").clone(false).get(0);
// Insert it after the last form
$(row).removeAttr('id').hide().insertAfter(".command:last").slideDown(300);
// Remove the bits we don't want in the new row/form
// e.g. error messages
$(".errorlist", row).remove();
$(row).children().removeClass("error");
// Relabel or rename all the relevant bits
$(row).children().children().children().children().each(function () {
updateElementIndex(this, prefix, newcount);
$(this).val("");
});
// Add an event handler for the delete item/form link
$(row).find(".delete").click(function () {
return deleteForm(this, prefix);
});
// Update the total form count
$("#id_" + prefix + "_set-TOTAL_FORMS").val(newcount + 1);
} // End if
else {
alert("Sorry, you can only enter a maximum of 1000 items.");
}
return false;
}
// Register the click event handlers
$("#add").click(function () {
return addForm(this, "itemcommands");
});
$(".delete").click(function () {
return deleteForm(this, "itemcommands");
});
$('.command input:checkbox').hide();
});
If you go to the link above you can see the code works perfectly fine it update the form count and add the new form with the new number in the id and everything however in production when you click the add command button for the first 3 times it does not show however the code has been enter into the page and the form is technically there but not shown.
on the fourth time you press the button it works and the row has been added after the last ('.command') in the element.
What could be causing it to work on JSFiddle but not on production?
-------------------UPDATE--------------------------
It seems if i remove the overflow hidden from the 3 that dont show when you press the button the first 3 times it will show them in the correct place.
Why would overflow no be removed from the first 3 form rows but the rest after fine?
----------------------UPDATE--------------------------
Think i have found the issue and its nothing to do with the JQUERY at all it seems to be bootstraps responsive layout hiding the forms i think if i add them specifically to their own rows i can fix this.
Thanks for the help though guys.
I don't see a src="*jQuery source*" in your file. Since JSFiddle already adds the source to the file, you may have forgotten to put it in.