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
Related
I am writing a test case which requires me to reload the page N number of times, and compare its title for a value, if that value does not exists then break the while loop without rising error.
Below is a demo program, similar to the one that I am looking to implement.
/// <reference types='cypress' />
it("Visiting Google",function(){
var webUrl = 'https://html5test.com/'
cy.visit(webUrl)
var loop_iter = 0
while(loop_iter < 5)
{
cy.get('body:nth-child(2) div:nth-child(2) div.header h1:nth-child(1) > em:nth-child(2)').then(($text_data) =>{
if($text_data.text().contains('HTML123'))
{
cy.log(" --> ITERATION = ",loop_iter)
cy.reload()
}
else
{
cy.log("Unknown website")
loop_iter = 10
}
})
loop_iter += 1
}
})
I need a way to break from the while loop when the else part is executed, without rising any error.
The if condition when false returns AssertionError, in such case it should execute else part.
cy.title() is asynchronous (proof is, you need to use .then()), so, the entire while loop ends even before the first .then() triggers. That's how asynchronism works.
You need another approach :
it("Visiting Google", async function () {
var webUrl = 'https://html5test.com/'
cy.visit(webUrl)
for (let i = 0; i < 5; i++) { // You can't await in a 'while' loop
const $text_data = await cy.title();
if ($text_data.includes('HTML')) {
cy.log(" --> ITERATION = ", i)
cy.reload()
}
else {
cy.log("Unknown website")
break;
}
}
})
Please take a look at the sample recipe Page reloads. It uses recursion as suggested in comments.
This is your code adapted to the pattern,
it('reload until "HTML" disappears', () => {
// our utility function
const checkAndReload = (recurse_level = 0) => {
cy.title().then(title => {
if (title.includes('HTML') && recurse_level < 5) {
cy.log(" --> ITERATION = ", recurse_level)
cy.wait(500, { log: false }) // just breathe here
cy.reload() // reload
checkAndReload(recurse_level + 1) // check again
} else {
cy.log("Unknown website")
}
})
}
cy.visit('https://html5test.com/') // start the test by visiting the page
checkAndReload() // and kicking off the first check
})
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();
}
I am running tests using Cypress.
I have an array of Litecoin addresses. I am trying to set first in the input. Then submit the form.
If the address is duplicate then a notification is displayed and submit button will be not visible. The same I want to set for the second element and so on till end of the array.
I tried recursive function:
function runTillElementFound (totalCount, currentCount, litecoin_addresses)
{
var self = this;
if (currentCount < totalCount) {
return cy.get('body').then(($body) =>
{
if ($body.find(dash_page.save_wallet_circle_btn)) {
//if there is save button then set address and submit form
cy.log('taken address: ' + litecoin_addresses[ currentCount ]);
dashact.fill_wallet(litecoin_addresses[ currentCount ]);
cy.log('address is filled');
dashact.submit_wallet(true, 0);
self.runTillElementFound(totalCount, currentCount++);
}
});
} else {
return false; //if element not present after Max count reached.
}
I try to call it:
it('Set wallet', () =>
{
cy.log('this is array length: ' + litecoin_addresses);
runTillElementFound(20, 0, litecoin_addresses);
/* comact.submit_form(true, 1);
let ltc_address = promisify(dashact.get_wallet_value());
cy.log('this is address: ' + ltc_address);
//close popup and check that it is closed:
popact.submit_payment(); */
});
However I receive undefined:
I have also tried non recursive function:
for (var i = 0; i < litecoin_addresses.length; i++) {
cy.log('taken address: ' + litecoin_addresses[ i ])
if (litecoin_addresses[ i ] == wallet_before_edit || litecoin_addresses[ i ].length == 0 || litecoin_addresses[ i ].startsWith('ltc')) {
continue;
}
else {
cy.log('this is curent i: ' + i);
dashact.fill_wallet(litecoin_addresses[ i ]);
dashact.submit_wallet(true, null);
cy.get('body').then(($body) =>
{
// synchronously query from body
// to find which element was created
if ($body.find(com_page.not_message).length) {
// error was found, do something else here
cy.log('error was found');
}
else {
cy.log('error not found');
// input was not found, do something else here
i = litecoin_addresses.length;
cy.log('current i value: ' + i);
}
})
}
However it for sure, does not work, as i inside promise has one valued but in the loop it still remains the same.
If you use a specific array in your test code, you can easily get the length of the array by using the .lenght and access its elements by using the for loop.
I have a 100 or so Word Open XML (.xml, not .docx, saved as "Word XML Document")documents (components) stored on SharePoint.
I use AJAX to load these by selection, as xml, 1 to many into an array, in which I also manage the selection sequence.
Once the user has selected the "components" they can then insert them into Word, the insertion is done via an array traversal (there is probably a better way to do this - but for now it does work),
wordBuild does the loading
function writeDocSync(){
// run through nameXMLArray to find the right sequence
var x = 0;
var countXMLAdds = 0;
//debugger;
toggleWriteButton("disable");
$('.progress-button').progressInitialize("Building Word");
toggleProgressBar(true);
// only run if we have data present
if(nameXMLArray.length > 0){
// increment through sequentially until we have all values
while (countXMLAdds <= checkedList.length){
// repeatedly traverse the array to get the next in sequence
while (x < nameXMLArray.length){
if (Number(nameXMLArray[x].position) === countXMLAdds && nameXMLArray[x].useStatus === true){
progHold = countXMLAdds;
wordBuild(nameXMLArray[x].xml, nameXMLArray[x].filename, countXMLAdds);
}
x++;
}
x=0;
countXMLAdds ++;
}
document.getElementById("showCheck").className = "results";
writeSelections("<b>You just built your proposal using<br/>the following components:</b><br/>");
toggleWriteButton("enable");
}
}
xxxxxxxxx
function wordBuild(xmlBody, nameDoc, progress){
var aryLN = checkedList.length;
var progPCT = (progress/aryLN)*100;
progressMeter.progressSet(progPCT);
Word.run(function (context) {
var currentDoc = context.document;
var body = currentDoc.body;
body.insertOoxml(xmlBody, Word.InsertLocation.end);
body.insertBreak(Word.BreakType.page, Word.InsertLocation.end);
return context.sync().then(function () {
showNotification("Written " + nameDoc);
});
})
.catch(function (error) {
showNotification('Error: ' + nameDoc + ' :' + JSON.stringify(error));
if (error instanceof OfficeExtension.Error) {
showNotification('Debug info: ' + JSON.stringify(error.debugInfo));
}
});
}
All the documents will load singly, and all will load in batches of say 10 - 30 or more.
The problem comes when I load the entire set (I have a "check all" option).
Sometimes 50 will build before I get an exception, sometimes 60, rarely more than 60, but very occasionally I get a gap where the exception doesn't occur, then it continues later.
The exception (which is repeated for each file) is:
Debug info: {}
Error: componentABC.xml :{"name":"OfficeExtension.Error","code":"GeneralException","message":"An internal error has occurred.","traceMessages":[],"debugInfo":{},"stack":"GeneralException: An internal error has occurred.\n at Anonymous function (https://customerportal.sharepoint.com/sites/components/Shared%20Documents/componentAssembler/Scripts/Office/1/word-win32-16.00.js:19:150094)\n at yi (https://customerportal.sharepoint.com/sites/components/Shared%20Documents/componentAssembler/Scripts/Office/1/word-win32-16.00.js:19:163912)\n at st (https://customerportal.sharepoint.com/sites/components/Shared%20Documents/componentAssembler/Scripts/Office/1/word-win32-16.00.js:19:163999)\n at d (https://customerportal.sharepoint.com/sites/components/Shared%20Documents/componentAssembler/Scripts/Office/1/word-win32-16.00.js:19:163819)\n at c (https://customerportal.sharepoint.com/sites/components/Shared%20Documents/componentAssembler/Scripts/Office/1/word-win32-16.00.js:19:162405)"}
Any help with what might cause this would be hugely appreciated.
Oh I should also say, the files where the exception is raised don't get inserted into Word. But in smaller batches - they work without issue.
Word.run() is an asynchronous call, and there's a limit to the number of concurrent Word.run() calls you can make. Since you're executing Word.run() inside a while loop, all of them get kicked off at the same time and run simultaneously.
There are a few ways to work around this.
Put everything inside one Word.run() call. This puts everything in one giant batch, avoiding multiple roundtrip calls to Word.
if (nameXMLArray.length > 0 {
Word.run(function(context) {
//...
while(...) {
wordBuild(context, nameXMLArray[x].xml, nameXMLArray[x].filename, countXMLAdds);
//...
}
return context.sync();
});
}
function wordBuild(context, xmlBoxy, nameDoc, progress) {
//everything as it currently is, except without the Word.run and the context.sync
}
Implement wordBuild as a promise, and use AngularJS’s $q service to chain the promises, something vaguely like this:
function wordBuild(...) {
var deferred = $q.defer();
Word.run( function(context) {
// current code
return context.sync().then(function() {
deferred.resolve();
});
});
return deferred.promise;
}
//Somewhere else
for (var x…)
{
promises.add(wordBuild);
}
$q.all(promises);
https://docs.angularjs.org/api/ng/service/$q
Angularjs $q.all
Chain the wordBuild calls yourself, as something like this:
var x = 0;
var context;
function (wordBuild() {
if (x >= nameXMLArray.length)
return;
else {
context.document.body.insertOoxml(ooxml, Word.InsertLocation.end);
x++;
return context.sync().then(wordBuild);
}
});
Word.run(function (ctx) {
context = ctx;
return wordBuild();
}
This sort of approach is difficult to maintain, but it could work.
Incidentally, the progress meter in your original code only updates when the call to Word starts, not when it actually returns. You might want to move the progress meter update code into the callback.
I ended up using jQuery deferreds, I was already using jQuery for treeview and checkboxes etc. so it made sense.
This is a mix of Geoffrey's suggestions and my own! I cannot claim it to be good code, only that is does work. (If it is good code or not will take me more time to understand!)
I run batches of 49 xml doc inserts, at 51 the Async call "Word.run" failed in tests, and inserts of 80 or so documents in one Word.run caused Word to freeze, so although not proven 49 inserts within 1 Word.run seems like a good starter for 10! 50 inserts of 49 pieces allows for 2450 inserts, which is way beyond anything I can see being needed, and would probably break Word!
To get the deferreds and sent variables to keep their values once launched as asynch deferreds I had to create a variable to transfer both new deferreds, and values, so I could use the "bind" command.
As Word async returns context.sync() I check the count of the batch, when the batch is completed, I then call the next batch - inside the context.sync()
A sort of recursive call, still a combination of Geoffrey's suggestion, and batches. This has a theoretical limit of 50 batches of 49 document sections. So far this has worked in all tests.
The progress meter exists in its own timed call, but as JavaScript prioritises code over UI it does hop. For example 120 documents it will hop just below half way fairly quickly, then a while later jump to almost complete, then complete (effectively 3 hops of a massively fast sequential percentage increases, various tricks suggested have zero effect (forceRepaint() is the latest experiment!).
function startUILock(){
// batch up in groups of 49 documents (51 and more were shown to fail, 49 gives manouvre room)
toggleProgressBar(true);
$('.progress-button').progressInitialize("Building Word");
progressMeter.progressSet(1);
$.blockUI({message: "Building word..."});
setTimeout(forceRepaint, 3000);
}
function forceRepaint(){
var el = document.getElementById('progDiv');
el.style.cssText += ';-webkit-transform:rotateZ(0deg)';
el.offsetHeight;
el.style.cssText += ';-webkit-transform:none';
}
function UIUnlock(insertedCount){
debugger;
var pct = (insertedCount/checkedList.length)*100
//showNotification('Progress percent is: ' + pct);
if (insertedCount !== checkedList.length ){
progressMeter.progressSet(pct);
forceRepaint();
} else {
$.unblockUI();
progressMeter.progressSet(100);
}
}
function writeDocDeffered(){
insertedCounter = 0;
var lastBatch = 0;
var x = 49;
var z = checkedList.length + 1;
if(x > z){
x=z;
}
deferreds = buildDeferredBatch(x, lastBatch);
$.when(deferreds).done(function () {
return;
})
.fail(function () {
//showNotification('One of our promises failed');
});
}
function buildDeferredBatch(batch, lastBatch) {
// this ensures the variables remain as issued - allows use of "bind"
var deferredsa = [];
var docSender = {
defr : $.Deferred(),
POSITION: batch,
LASTPOSITION: lastBatch,
runMe : function(){
this.defr.resolve(writeDocBatchedDeferred(this.POSITION, this.LASTPOSITION, this.defr));
}
}
// small timeout might not be required
deferredsa.push(setTimeout(docSender.runMe.bind(docSender), 10));
return deferredsa;
}
function writeDocBatchedDeferred(batch, lastBatch, defr){
// write the batches using deferred and promises
var x;
var countXMLAdds = lastBatch;
x = 0;
var fileName;
debugger;
// only run if we have data present
if(nameXMLArray.length > 0){
var aryLN = checkedList.length;
// increment through sequentially until we have all values
Word.run(function (context) {
var currentDoc = context.document;
var body = currentDoc.body;
while (countXMLAdds <= batch){
// repeatedly traverse the array to get the next in sequence
while (x < nameXMLArray.length){
if (Number(nameXMLArray[x].position) === countXMLAdds && nameXMLArray[x].useStatus === true){
fileName = nameXMLArray[x].filename;
body.insertOoxml(nameXMLArray[x].xml, Word.InsertLocation.end);
body.insertBreak(Word.BreakType.page, Word.InsertLocation.end);
insertedCounter = countXMLAdds;
var latest = insertedCounter;
var timerIt = {
LATEST: latest,
runMe : function(){
UIUnlock(this.LATEST);
}
}
setTimeout(timerIt.runMe.bind(timerIt),1000);
}
x++;
}
x=0;
countXMLAdds ++;
}
return context.sync().then(function () {
if(countXMLAdds = batch){
var lastBatch = batch + 1;
// set for next batch
var nextBatch = batch + 50;
var totalBatch = checkedList.length + 1;
// do not exceed the total batch
if(nextBatch > totalBatch){
nextBatch=totalBatch;
}
// any left to process keep going
if (nextBatch <= totalBatch && lastBatch < nextBatch){
deferreds = deferreds.concat(buildDeferredBatch(nextBatch, lastBatch));
}
// this batch done
defr.done();
}
});
})
.catch(function (error) {
showNotification('Error: ' + nameXMLArray[x].filename + " " + JSON.stringify(error));
if (error instanceof OfficeExtension.Error) {
showNotification('Debug info: ' + JSON.stringify(error.debugInfo));
}
});
document.getElementById("showCheck").className = "results";
writeSelections("<b>You just built your document using<br/>the following components:</b><br/>");
}
return defr.promise;
}
I have minimal knowledge of Javascript, but would like to use this code to enhance my GMail experience. It works, but I also get errors. When I run the debugger in Google Spreadsheet, two functions appear to malfunction:
TypeError: Cannot call method "getThreads" of null. (line 59)
With the following Execution transcript
GmailApp.getUserLabelByName([FollowUp])
GmailApp.getUserLabelByName([FollowUp/1undefined])
Inserted comment: there is some information about the GMail API call getThreads (and others) here: https://developers.google.com/apps-script/class_gmaillabel#getThreads
What I don't get is why it is calling Followup/1undefined -> why is it undefined? It should be Followup/1days
And another error with another function:
Cannot find method moveThreadsToInbox(. (line 26)
With nothing in the Execution transcript
The entire code is:
// Adapted from:
// http://gmailblog.blogspot.com/2011/07/gmail-snooze-with-apps-script.html
//
// To setup:
// - From the |Run| menu select |setup|
// - if prompted to authorize, do so, and then repeat this step.
//
// - Verify the script is set to be triggered to run
// - |Triggers| menu |Current script's triggers...|
// - 3 triggers should exist to call e.g.
// - dailyUpdate, Time Driven, Daily
function getLabelName(i, labelSuffixString) {
return "FollowUp/" + i + labelSuffixString;
}
function setup() {
for (var i = 1; i <= 7; ++i) {
GmailApp.createLabel(getLabelName(i, "days"));
GmailApp.createLabel(getLabelName(i, "weeks"));
}
GmailApp.createLabel("FollowUp");
}
function moveToInbox(page) {
GmailApp.moveThreadsToInbox(page);
GmailApp.markThreadsImportant(page);
}
function cleanOldFollowUpLabels() {
var searchString = "-label:inbox label:FollowUp";
for (var i = 1; i <= 7; ++i) {
searchString += " -label:" + getLabelName(i, "days");
searchString += " -label:" + getLabelName(i, "weeks");
}
searchString = searchString.replace(RegExp("/", "g"), "-");
Logger.log("cleanOldFollowUpLabels() Search String:");
Logger.log(searchString);
var followUpLabel = GmailApp.getUserLabelByName("FollowUp");
var page = null;
// Get threads in "pages" of 100 at a time
while(!page || page.length == 100) {
page = GmailApp.search(searchString, 0, 100);
Logger.log("found: " + page.length);
if (page.length > 0)
followUpLabel.removeFromThreads(page);
}
}
function update(labelSuffixString) {
var oldLabel, newLabel, page;
var followUpLabel = GmailApp.getUserLabelByName("FollowUp");
for (var i = 1; i <= 7; ++i) {
newLabel = oldLabel;
oldLabel = GmailApp.getUserLabelByName(getLabelName(i, labelSuffixString));
page = null;
// Get threads in "pages" of 100 at a time
while(!page || page.length == 100) {
page = oldLabel.getThreads(0, 100);
if (page.length > 0) {
followUpLabel.addToThreads(page);
if (newLabel) {
// Move the threads into "today’s" label
newLabel.addToThreads(page);
} else {
moveToInbox(page);
}
// Move the threads out of "yesterday’s" label
oldLabel.removeFromThreads(page);
}
}
}
}
function dailyUpdate() {
update("days");
}
function weeklyUpdate() {
update("weeks");
}
Also here: http://pastie.org/4790086.js
mm.. with the help of a colleague, we found the answer to my own problem. I had three triggers running: A daily trigger, a weekly trigger, and the update trigger. Now the update trigger was not necessary, because it was called upon by the daily and weekly trigger, and without any input. That caused the error. Now I have to wait and see if there are any errors and if the script works.