can we use cypress wait without then block? - cypress

https://docs.cypress.io/api/commands/wait - cy.wait() is used to wait for an arbitrary number of milliseconds.
What's the difference in the following two snippets
// snippet-1
cy.wait(10*1000);
// other code
// snippet - 2
cy.wait(10*1000).then(()=>{
// other code
})
I tried to run the following code to test how it is working and i got the output mentioned in the below image.
cy.wait(10 * 1000).then(() =>
cy.log("This is inside wait()" + new Date().toLocaleTimeString())
);
cy.log("This is after wait()" + new Date().toLocaleTimeString());
These are the log results:
From the above image, it looks like we should add our code inside .then because that will be executed after cy.wait . I also have seen people writing their test cases just like snippet-1
Which one is correct and which one we should use?

If you want a better log, use a custom command to make a version that defers evaluating the value.
By providing a callback, the new Date().toLocaleTimeString() expression evaluates when you expect it to.
Cypress.Commands.add('logLazy', (thingToLog) => {
if (typeof thingToLog === 'function') {
cy.log(thingToLog())
} else {
cy.log(thingToLog)
}
})
cy.wait(3_000).then(() =>
cy.log("This is inside wait()" + new Date().toLocaleTimeString())
);
cy.log("This is after wait()" + new Date().toLocaleTimeString());
cy.wait(3_000).then(() =>
cy.logLazy("This is LAZY LOG inside wait()" + new Date().toLocaleTimeString())
);
cy.logLazy(() => "This is LAZY LOG after wait()" + new Date().toLocaleTimeString());

Cypress commands use the current value of variables and resolve function values when enqueuing commands. So, in your example, the commands are enqueued as .wait() and then the outer .log(). The value of the date string is calculated when the command is enqueued. The inner .log() is enqueued within the .then(), and thus has a later date string.
Either snippet is fine to use, but be aware of how the subsequent commands in your test will react. Are you manipulating some variable during your .wait(), and you would not want the original value to be used for your Cypress command? Probably a good idea to place that command within a .then() or some other Cypress command. Are you just interacting with the website and need to wait 10 seconds? You probably don't need to worry about placing commands inside .then().

Related

Cypress: wrap chained commands in one

I use this long line to check the selected value on any list on my page (using Ember power-select which is not a but a complex set of ), selector being the parent so I can target the list I want, and trimming being there so I can chain a .should('eq', expected_value)
cy.get(selector).find('span[class="ember-power-select-selected-item"]').invoke("text").then((text) => text.trim())
I would love to shorten all the commands after get in one, and be able to call something like
cy.get(selector).selected_value()
I've started reading on custom commands, wrap, invoke... but am too new on Cypress to understand the right way to do this.
To add a custom command, you may add the following inside cypress/support/commands.js file:
Cypress.Commands.add('selected_value', { prevSubject: true}, (subject) => {
return cy.wrap(subject).find('span[class="ember-power-select-selected-item"]').invoke("text").then((text) => text.trim())
})
Note that we use the prevSubject option to be able to chain our command from an initial cy.get(selector) one.
And now you are able to use the command:
cy.get(selector).selected_value().should('eq', expected_value)
Also, it's better to provide a type script definition file for the new command so IDE can be aware of it and provide the autocomplete and other features.
Adding a command might look a bit complicated for a beginner or annoying for an experienced user. There is Cypress Pro plugin (I'm the author) for the IntelliJ platform that can simplify creating and maintaining custom commands.
Creating a custom command can make your test more readable, encapsulate a group of commands that are repeated and need to be parameterized.
For your example testing the text of dropdown items, you can pass in the text and return the select so that chaining is possible
NOTES
In your code, .find().invoke("text") returns all the item text in one string, so I added .contains() to select an individual item.
If you're only interested in a partial match, the command chain can stop at .contain(text.trim())
Cypress.Commands.add('hasSelectedItemText',
{ prevSubject: true },
(subject, text) => {
cy.wrap(subject)
.find('span[class="ember-power-select-selected-item"]')
.contains(text.trim())
.invoke("text")
.should('eq', text.trim())
cy.wrap(subject) // returns the original select
}
)
cy.get(selector)
.hasSelectedItemText('one')
.hasSelectedItemText('two')
.hasSelectedItemText('three')
A more complicated example using the dual type command. Here the command can be parent or child, so the parameters have different meanings depending on usage
Cypress.Commands.add("dropdownItemText",
{ prevSubject: "optional" },
(subject, arg1, arg2) => {
if (subject) {
const text = arg1
cy.wrap(subject)
.find('span[class="ember-power-select-selected-item"]')
.contains(text.trim())
.invoke("text")
.should('eq', text.trim())
cy.wrap(subject) // returns the original select
} else {
const text = arg2
cy.get(arg1)
.find('span[class="ember-power-select-selected-item"]')
.contains(text.trim())
.invoke("text")
.should('eq', text.trim())
cy.get(arg1) // make select the returned "subject" for further chaining
}
}
)
cy.dropdownItemText(selector, 'one')
.dropdownItemText('two')
.dropdownItemText('three')

Does mixing javascript functions and Cypress commands contribute to test flakiness?

Among many, One of my test looks like
it("Admin is able to edit new group", () => {
cy.intercept("PUT", /\/api\/groups/).as("editGroupAPI");
cy.get("#groups").then(groups => {
const group = groups[0];
// go to edit page cancel and come back to groups page
app.groupsPage.card
.groupActionIcon(group.name, "modify")
.scrollIntoView()
.click();
app.commonElements
.toolBarTitle()
.should("have.text", "Edit Group");
app.groupsPage.groupDetailsForm.cancelButton().click();
app.commonElements.toolBarTitle().should("have.text", "Groups");
// edit group - 1
app.groupsPage.card
.groupActionIcon(group.name, "modify")
.scrollIntoView()
.click();
app.groupsPage.groupDetailsForm
.groupDescription()
.type(" edited");
app.groupsPage.groupDetailsForm.saveButton().click();
cy.wait("#editGroupAPI");
// validate that Groups page have loaded
app.commonElements.toolBarTitle().should("have.text", "Groups");
// validate whether group card description is reflected on card
app.groupsPage.card
.groupDescription(group.name)
.should("have.text", group.description + " edited");
});
});
app is top level parent obj, and this test uses Page Object Model.
One example of POM class is :
class CommonElements {
burgerMenu() {
return cy.get("#widgets-banner-appBanner-sideDrawerButton-content");
}
toolBarTitle() {
return cy.get("h1.app-toolbar__title__main-title");
}
toolBarTitleWithText(text) {
return cy.contains("h1.app-toolbar__title__main-title", text);
}
globalScopeButton() {
return cy.get("#global-scope-switch-toggleSwitch-button");
}
}
So as it is evident that, cy.wait() and then call to pageObjectModel function to grab title element:
cy.wait("#editGroupAPI");
// validate that Groups page have loaded
app.commonElements.toolBarTitle().should("have.text", "Groups");
Now sometimes this fails, so as I have seen in docs, plain js code get executed immediately, but since in this case whole test is wrapped in cy.get("alias"), will it still matter (or execute js immediately)?
This might sound very obvious, but I just want to confirm.
Final question: does mix usage of Page Object Model functions and cy.command contribute to test flakiness?
Short answer: no, mixing Cypress commands with Page Object model functions does not itself contribute to test flakiness.
Explanation: a Cypress command is never executed immediately. It does not matter if a Cypress command is called in any 'external' function (including a POM function) or directly in a test case function. Either way, Cypress commands are only enqueued when statements of a function are executed. And they later will be executed in the same order regardless whether they defined inside 'external' function or test case one.
This is also true for a command that was called inside a cypress synchronous block of code (in a then/should callback). Even in this case the command will not be executed immediately.
In a nutshell, using a POM function to call a Cypress command does not influence on how and when this command is executed and so using POM approach can not itself contribute to any test flakiness.
You can play and see the order of command execution using such a script:
You can either open Dev console to see the console output or use breakpoints to see the execution live.
The gif above shows the debugging directly from IDE (IntelliJ) using the Cypress Support Pro plugin

wait for element present without error

is there a way to call waitForElementPresent in nightwatch without erroring out if the element is not there?
Right now, if you pass in false as the third parameter, you still see an error. Ideally, I would like a function that waits for an element, and returns if it didn't find the element.
you can do it :
Browser.waitForElementPresent('#element',3000,false, function(result){
If(result.value === true){
// test if element present
}else{
// test if element not present
}
})
Since the day one, i did this and the problem with this code is nightwatch would count a failed test as a passed test,as you see above code handle both result value.
So i recommend let the nightwatch return error itself, write difference function for difference value.

How to debug Google Apps Script (aka where does Logger.log log to?)

In Google Sheets, you can add some scripting functionality. I'm adding something for the onEdit event, but I can't tell if it's working. As far as I can tell, you can't debug a live event from Google Sheets, so you have to do it from the debugger, which is pointless since the event argument passed to my onEdit() function will always be undefined if I run it from the Script Editor.
So, I was trying to use the Logger.log method to log some data whenever the onEdit function gets called, but this too seems like it only works when run from the Script Editor. When I run it from the Script Editor, I can view the logs by going to View->Logs...
I was hoping I'd be able to see the logs from when the event actually gets executed, but I can't figure it out.
How do I debug this stuff?
UPDATE:
As written in this answer,
Stackdriver Logging is the preferred method of logging now.
Use console.log() to log to Stackdriver.
Logger.log will either send you an email (eventually) of errors that have happened in your scripts, or, if you are running things from the Script Editor, you can view the log from the last run function by going to View->Logs (still in script editor). Again, that will only show you anything that was logged from the last function you ran from inside Script Editor.
The script I was trying to get working had to do with spreadsheets - I made a spreadsheet todo-checklist type thing that sorted items by priorities and such.
The only triggers I installed for that script were the onOpen and onEdit triggers. Debugging the onEdit trigger was the hardest one to figure out, because I kept thinking that if I set a breakpoint in my onEdit function, opened the spreadsheet, edited a cell, that my breakpoint would be triggered. This is not the case.
To simulate having edited a cell, I did end up having to do something in the actual spreadsheet though. All I did was make sure the cell that I wanted it to treat as "edited" was selected, then in Script Editor, I would go to Run->onEdit. Then my breakpoint would be hit.
However, I did have to stop using the event argument that gets passed into the onEdit function - you can't simulate that by doing Run->onEdit. Any info I needed from the spreadsheet, like which cell was selected, etc, I had to figure out manually.
Anyways, long answer, but I figured it out eventually.
EDIT:
If you want to see the todo checklist I made, you can check it out here
(yes, I know anybody can edit it - that's the point of sharing it!)
I was hoping it'd let you see the script as well. Since you can't see it there, here it is:
function onOpen() {
setCheckboxes();
};
function setCheckboxes() {
var checklist = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("checklist");
var checklist_data_range = checklist.getDataRange();
var checklist_num_rows = checklist_data_range.getNumRows();
Logger.log("checklist num rows: " + checklist_num_rows);
var coredata = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("core_data");
var coredata_data_range = coredata.getDataRange();
for(var i = 0 ; i < checklist_num_rows-1; i++) {
var split = checklist_data_range.getCell(i+2, 3).getValue().split(" || ");
var item_id = split[split.length - 1];
if(item_id != "") {
item_id = parseInt(item_id);
Logger.log("setting value at ("+(i+2)+",2) to " + coredata_data_range.getCell(item_id+1, 3).getValue());
checklist_data_range.getCell(i+2,2).setValue(coredata_data_range.getCell(item_id+1, 3).getValue());
}
}
}
function onEdit() {
Logger.log("TESTING TESTING ON EDIT");
var active_sheet = SpreadsheetApp.getActiveSheet();
if(active_sheet.getName() == "checklist") {
var active_range = SpreadsheetApp.getActiveSheet().getActiveRange();
Logger.log("active_range: " + active_range);
Logger.log("active range col: " + active_range.getColumn() + "active range row: " + active_range.getRow());
Logger.log("active_range.value: " + active_range.getCell(1, 1).getValue());
Logger.log("active_range. colidx: " + active_range.getColumnIndex());
if(active_range.getCell(1,1).getValue() == "?" || active_range.getCell(1,1).getValue() == "?") {
Logger.log("made it!");
var next_cell = active_sheet.getRange(active_range.getRow(), active_range.getColumn()+1, 1, 1).getCell(1,1);
var val = next_cell.getValue();
Logger.log("val: " + val);
var splits = val.split(" || ");
var item_id = splits[splits.length-1];
Logger.log("item_id: " + item_id);
var core_data = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("core_data");
var sheet_data_range = core_data.getDataRange();
var num_rows = sheet_data_range.getNumRows();
var sheet_values = sheet_data_range.getValues();
Logger.log("num_rows: " + num_rows);
for(var i = 0; i < num_rows; i++) {
Logger.log("sheet_values[" + (i) + "][" + (8) + "] = " + sheet_values[i][8]);
if(sheet_values[i][8] == item_id) {
Logger.log("found it! tyring to set it...");
sheet_data_range.getCell(i+1, 2+1).setValue(active_range.getCell(1,1).getValue());
}
}
}
}
setCheckboxes();
};
As far as I can tell, you can't debug a live event from google docs, so you have to do it from the debugger, which is pointless since the event argument passed to my onEdit() function will always be undefined if I run it from the Script Editor.
True - so define the event argument yourself for debugging. See How can I test a trigger function in GAS?
I was trying to use the Logger.log method to log some data whenever the onEdit function gets called, but this too seems like it only works when run from the Script Editor. When I run it from the Script Editor, I can view the logs by going to View->Logs...
True again, but there is help. Peter Hermann's BetterLog library will redirect all logs to a spreadsheet, enabling logging even from code that is not attached to an instance of the editor / debugger.
If you're coding in a spreadsheet-contained script, for example, you can add just this one line to the top of your script file, and all logs will go to a "Logs" sheet in the spreadsheet. No other code necessary, just use Logger.log() as you usually would:
Logger = BetterLog.useSpreadsheet();
2017 Update:
Stackdriver Logging is now available for Google Apps Script. From the menu bar in the script editor, goto:
View > Stackdriver Logging to view or stream the logs.
console.log() will write DEBUG level messages
Example onEdit() logging:
function onEdit (e) {
var debug_e = {
authMode: e.authMode,
range: e.range.getA1Notation(),
source: e.source.getId(),
user: e.user,
value: e.value,
oldValue: e. oldValue
}
console.log({message: 'onEdit() Event Object', eventObject: debug_e});
}
Then check the logs in the Stackdriver UI labeled onEdit() Event Object to see the output
I've gone through these posts and somehow ended up finding a simple answer, which I'm posting here for those how want short and sweet solutions:
Use console.log("Hello World") in your script.
Go to https://script.google.com/home/my and select your add-on.
Click on the ellipsis menu on Project Details, select Executions.
Click on the header of the latest execution and read the log.
A little hacky, but I created an array called "console", and anytime I wanted to output to console I pushed to the array. Then whenever I wanted to see the actual output, I just returned console instead of whatever I was returning before.
//return 'console' //uncomment to output console
return "actual output";
}
If you have the script editor open you will see the logs under View->Logs. If your script has an onedit trigger, make a change to the spreadsheet which should trigger the function with the script editor opened in a second tab. Then go to the script editor tab and open the log. You will see whatever your function passes to the logger.
Basically as long as the script editor is open, the event will write to the log and show it for you. It will not show if someone else is in the file elsewhere.
I am having the same problem, I found the below on the web somewhere....
Event handlers in Docs are a little tricky though. Because docs can handle multiple simultaneous edits by multiple users, the event handlers are handled server-side. The major issue with this structure is that when an event trigger script fails, it fails on the server. If you want to see the debug info you'll need to setup an explicit trigger under the triggers menu that emails you the debug info when the event fails or else it will fail silently.
It's far from elegant, but while debugging, I often log to the Logger, and then use getLog() to fetch its contents. Then, I either:
save the results to a variable (which can be inspected in the Google Scripts debugger—this works around cases where I can't set a breakpoint in some code, but I can set one in code that gets executed later)
write it to some temporary DOM element
display it in an alert
Essentially, it just becomes a JavaScript output issue.
It grossly lacks the functionality of modern console.log() implementations, but the Logger does still help debug Google Scripts.
Just as a notice. I made a test function for my spreadsheet. I use the variable google throws in the onEdit(e) function (I called it e). Then I made a test function like this:
function test(){
var testRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(GetItemInfoSheetName).getRange(2,7)
var testObject = {
range:testRange,
value:"someValue"
}
onEdit(testObject)
SpreadsheetApp.getActiveSpreadsheet().getSheetByName(GetItemInfoSheetName).getRange(2,6).setValue(Logger.getLog())
}
Calling this test function makes all the code run as you had an event in the spreadsheet. I just put in the possision of the cell i edited whitch gave me an unexpected result, setting value as the value i put into the cell.
OBS! for more variables googles gives to the function go here: https://developers.google.com/apps-script/guides/triggers/events#google_sheets_events
Currently you are confined to the container bound nature of using scripts within docs. If you create a new script inside outside of docs then you will be able to export information to a google spreadsheet and use it like a logging tool.
For example in your first code block
function setCheckboxes() {
// Add your spreadsheet data
var errorSheet = SpreadsheetApp.openById('EnterSpreadSheetIDHere').getSheetByName('EnterSheetNameHere');
var cell = errorSheet.getRange('A1').offset(errorSheet.getLastRow(),0);
// existing code
var checklist = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("checklist");
var checklist_data_range = checklist.getDataRange();
var checklist_num_rows = checklist_data_range.getNumRows();
// existing logger
Logger.log("checklist num rows: " + checklist_num_rows);
//We can pass the information to the sheet using cell.setValue()
cell.setValue(new Date() + "Checklist num rows: " + checklist_num_rows);
When I'm working with GAS I have two monitors ( you can use two windows ) set up with one containing the GAS environment and the other containing the SS so I can write information to and log.
The dev console will log errors thrown by the app script, so you can just throw an error to get it logged as a normal console.log. It will stop execution, but it might still be useful for step by step debugging.
throw Error('hello world!');
will show up in the console similarly to console.log('hello world')
For Apps Script projects that are tied to a single Sheet (or doc) — in 2022 — there is no View menu like other answers suggest. Instead you need to look in the Executions menu on the left sidebar to see the executions of your onSelectionChange function (or any other function), from there you can click REFRESH until your console.log messages appear.
just debug your spreadsheet code like this:
...
throw whatAmI;
...
shows like this:

setAttribute, onClick and cross browser compatibility

I have read a number of posts about this but none with any solid answer. Here is my code:
// button creation
onew = document.createElement('input');
onew.setAttribute("type", "button");
onew.setAttribute("value", "hosts");
onew.onclick = function(){fnDisplay_Computers("'" + alines[i] + "'"); }; // ie
onew.setAttribute("onclick", "fnDisplay_Computers('" + alines[i] + "')"); // mozilla
odiv.appendChild(onew);
Now, the setAttribute() method (with the mozilla comment) works fine in mozilla but only if it comes AFTER the line above it. So in other words it seems to just default to whichever gets set last. The .onclick method (with the ie comment) does not work in either case, am I using it incorrectly?
Either way I can't find a way to make this work at all in IE, let alone in both. I did change the function call when using the .onclick method and it worked fine using just a simple call to an alert function which is why I believe my syntax is incorrect.
Long story short, I can't get the onclick parameter to work consistently between IE/Mozilla.
-- Nicholas
onew.setAttribute("type", "button");
Never use setAttribute on HTML documents. IE gets it badly wrong in many cases, and the DOM-HTML properties are shorter, faster and easier to read:
onew.type= 'button';
onew.onclick = function(){fnDisplay_Computers("'" + alines[i] + "'"); }; // ie
What is ‘alines’? Why are you converting it to a string and surrounding it with single quotes? It looks like you are trying to do something heinous involving evaluating code in a string (which is what you're doing below in the ‘onew.setAttribute’ version). Evaluating JavaScript code in strings is almost always the Wrong Thing; avoid it like the plague. In the above case, IE should do the same as Firefox: it shouldn't work.
If ‘alines[i]’ is a string, I guess what you're trying to do is make it remember that string by constructing a code string that will evaluate in JavaScript to the original string. But:
"'" + alines[i] + "'"
is insufficient. What happens if ‘alines[i]’ has an apostrophe in, or a backslash?
'O'Reilly'
you've got a syntax error and possible security hole. Now, you could do something laborious and annoying like:
"'" + alines[i].split('\\').join('\\\\').split("'").join("\\'") + "'"
to try to escape the string, but it's ugly and won't work for other datatypes. You could even ask JavaScript to do it for you:
uneval(alines[i])
But not all objects can even be converted to evaluatable JavaScript source strings; basically the entire approach is doomed to failure.
The normal thing to do if you just want to have the onclick callback call a function with a parameter is to write the code in the straightforward way:
onew.onclick= function() {
fnDisplay_Computers(alines[i]);
};
Generally this will work and is what you want. There is, however, a slight wrinkle which you may have hit here, which could be what is confusing you into considering the wacky approach with the strings.
Namely, if ‘i’ in this case is the variable of an enclosing ‘for’ loop, the reference to ‘alines[i]’ won't do what you think it does. The ‘i’ will be accessed by the callback function when the click happens — which is after the loop has finished. At this point the ‘i’ variable will be left with whatever value it had at the end of the loop, so ‘alines[i]’ will always be the last element of ‘alines’, regardless of which ‘onew’ was clicked.
(See eg. How to fix closure problem in ActionScript 3 (AS3) for some discussion of this. It's one of the biggest causes of confusion with closures in both JavaScript and Python, and should really be fixed at a language level some day.)
You can get around the loop problem by encapsulating the closure in its own function, like this:
function callbackWithArgs(f, args) {
return function() { f.apply(window, args); }
}
// ...
onew.onclick= callbackWithArgs(fnDisplay_Computers, [alines[i]]);
And in a later version of JavaScript, you'll be able to say simply:
onew.onclick= fnDisplay_Computers.bind(window, alines[i]);
If you would like to be able to use ‘Function.bind()’ in browsers today, you can get an implementation from the Prototype framework, or just use:
if (!('bind' in Function.prototype)) {
Function.prototype.bind= function(owner) {
var that= this;
var args= Array.prototype.slice.call(arguments, 1);
return function() {
return that.apply(owner,
args.length===0? arguments : arguments.length===0? args :
args.concat(Array.prototype.slice.call(arguments, 0))
);
};
};
}
I usually use something like:
onew.onclick = new Function("fnDisplay_Computers('" + alines[i] + "')");
this should work both in IE e Firefox.
Use the addEventListener() function with "click" for the type argument for Mozilla-based browsers, and attachEvent() function with "onclick" as the sEvent argument for IE; I find it best to use a try/catch statement, for example:
try {
onew.attachEvent("onclick", //For IE
function(){fnDisplay_Computers("'" + alines[i] + "'"); });
}
catch(e) {
onew.addEventListener("click", //For Mozilla-based browsers
function(){fnDisplay_Computers("'" + alines[i] + "'"); },
false);
}
I think #3 protesteth too much. In a lot of situations I'm building a table dynamically and need to pass parameters to the callback function. It isn't a typesafe issue since my variable parm is an integer index to the table row in question. Here's code with both a variable and fixed parameter that seems to be cross-browser compliant:
for (i = 0; i < arrTableData.length; i++) {
eleTR = objTable.insertRow(i + 1);
cell = eleTR.insertCell(0);
cell.width = "21";
var myElement = document.createElement('img');
myElement.setAttribute('src', 'images/button_down.gif');
myElement.setAttribute('alt', 'Move item down');
myElement.onclick = new Function('moveItem(' + i + ',0)');
cell.appendChild(myElement);
}

Resources