Working as a QA I need to fill in a lot of applications through a web form.
Idea is to have the personal data in some xls/txt/whatever file, read the file and use Powershell to feed data to the browser.
When I use the code below to fill in the form in IE, even though it seems to work fine, I get an error when submitting the form that no data was entered.
Any ideas or suggestions how to get past this would be much appreciated
Sadly my resources are limited to Powershell 2.0. Selenium or any other "more sophisticated" tools are out of question at least for now.
validation error here
$ie = New-Object -com InternetExplorer.Application
$ie.Navigate("MyURL")
$ie.visible = $true
while ($ie.ReadyState -ne 4){sleep -m 100}
Function ClickById($id) {
$ie.document.getElementById($id).Click()
}
### Základní údaje
$FnId = 'personalData.firstName'
$LnId = 'personalData.lastName'
$PhoneId = 'personalData.mobilePhone'
$EmailId = 'personalData.email'
$DataAgreementCheckBox = 'application.personalDataAgreement'
$SubmitfwdId = 'forward'
$Values = "Ublala", "Pung", "222333444", "ublala#pung.com"
$Ds1Elements = $FnId, $LnId, $PhoneId, $EmailId
$j = 0
foreach ($El in $Ds1Elements) {
$ie.document.getElementById($El).value = $values[$j]
$j++
}
ClickById $DataAgreementCheckBox
ClickById $SubmitfwdId
Thanks for the suggestion but it did not work as the form seemes to be stupid in many ways.
Anyway I used your advice for the focus when going with SendKeys method and it did the trick.
At the beginning I needed to load this assembly
[void] [System.Reflection.Assembly]::LoadWithPartialName("'System.Windows.Forms")
and then changed the loop accordingly to use the SendKeys method
$j = 0
foreach ($El in $Ds1Elements) {
$ie.document.getElementById($El).focus()
[System.Windows.Forms.SendKeys]::Sendwait($values[$j]);
$j++
}
And tradaaaa the form is filled and nobody is complaining :)
Sometimes website forms will wait until the input box has lost focus to pre-validate the value before submitting the form. The text boxes may never get focus if you are manually setting the values in the background so the form believes that you haven't actually entered any values.
You may be able to get around this by manually focusing each text box in turn, and then focusing the submit button before clicking it. Each element in the $ie.Document should have a focus() method you can use.
Related
I have over 3200 rows in a Google Sheet. I need a dropdown with each value on a web app.
I have this in Apps Script:
function doGet(e) {
var htmlOutput = HtmlService.createTemplateFromFile('CensusWebApp2');
var streets = getStreets();
var businessNames = getbusinessNames();
htmlOutput.message = '';
htmlOutput.streets = streets;
htmlOutput.businessNames = businessNames;
return htmlOutput.evaluate();
}
function getbusinessNames(){
var ss= SpreadsheetApp.getActiveSpreadsheet();
var StreetDataSheet = ss.getSheetByName("businessNames");
var getLastRow = StreetDataSheet.getLastRow();
var return_array = [];
return_array= StreetDataSheet.getRange(2,1,getLastRow-1,1).getValues();
return return_array;
}
This is the HTML code
<select type="select" name="IntestazioneTari" id="IntestazioneTari" class="form-control" >r>
<option value="" ></option>
<? for(var i = 0; i < businessNames.length; i++) { ?>
<option value="<?= businessNames[i] ?>" ><?= businessNames[i] ?></option>
<? } ?>
</select><be>
I'm creating an app similar to surveys forms, but this dropdown will be the same for every entry.
Is there a way to load this only once and not every time the form is submitted and got again for a new survey entry? (from the same operator/device)
I believe your goal as follows.
You want to use the value of businessNames retrieved from Google Spreadsheet at HTML side.
The value of businessNames is not changed. So you want to load the value only one time.
In this case, how about declaring the value in the tag of <script> as a global? When this point is reflected to your script it becomes as follows.
Modified script:
In this case, your HTML side is modified.
<select type="select" name="IntestazioneTari" id="IntestazioneTari" class="form-control" >r>
<option value="" ></option>
<? for(var i = 0; i < businessNames.length; i++) { ?>
<option value="<?= businessNames[i] ?>" ><?= businessNames[i] ?></option>
<? } ?>
</select>
<input type="button" value="ok" onclick="test()">
<script>
const value = JSON.parse(<?= JSON.stringify(businessNames) ?>); // Here, the value of "businessNames" is retrieved.
function test() {
console.log(value);
}
</script>
In this modification, when the HTML is loaded, the value of businessNames is added to the HTML by evaluate() method. At that time, businessNames is given to HTML and Javascript. By this, const value = JSON.parse(<?= JSON.stringify(businessNames) ?>); has the value of businessNames. In order to confirm this value, when you click a sample button of <input type="button" value="ok" onclick="test()">, you can see the value at the console. By this, you can use the value of businessNames at the Javascript side after HTML is loaded.
Reference:
HTML Service: Templated HTML
As the values from the spreadsheet won't change, I created a really long text row with all the options and pasted them directly in the HTML.
I made the same with the other information. Load time decreased enormously.
This is the code I use to generate the values:
return_array= "<option>" + businessNamesSheet.getRange(2,1,getLastRow-1,1).getValues().join("</option><option>")+"</option>";
We are talking about performance, and there are 3 things you need to do when doing so:
Make measurements
Make measurements again
And make some more measurements
A change in the code could have a negative impact for a reason that you didn't expect (it's hard to keep every single little detail in mind). When making a Google Apps Script web app, you have 3 reported times:
The timings in your browser. How much did it really take to load the entire page
The running time on Google Apps Script execution log.
Small timing inside your application using console.time (reference), console.timeLog (reference) and console.timeEnd (reference) (collectively called console timers).
Note that the first 2 may change without you changing a thing, probably because of the inner working in Google.
So let's start doing what I said: measuring. I'd measure:
The entire doGet function
The getStreets()
The getbusinessNames()
The template.evaluate()
How much time it takes to load the page (browser)
This will give me a rough idea on what takes most of the time. Knowing that, you can try the following ideas.
Note that I don't have your code so I can't tell how it will effect your times, so your mileage may vary. Also note that most ideas could be implemented simultaneously, this doesn't mean it's a good idea and can even slow what a single idea could have achieved.
Idea 1: Copy the generated options into the template
If you don't need to load the options from somewhere (like I suppose you are doing), you could generate the template once, copy the generated options and paste it to the HTML. This will obviously avoid the problem of having the request the list of options and evaluating them every time, but you lose flexibility.
Idea 2: Having the options in code instead of somewhere else
If the options won't change or you will be changing them, you could add them into your code:
const BUSINESS_NAMES = `
business 1
hello world
another one
and another one
`
function getbusinessNames() {
return BUSINESS_NAMES
.split('\n')
.filter(v => !!v) // remove empty string
}
It's similar to idea 1 but it's easier to change the values when needed, specially when using the V8 support for multi line strings.
Idea 3: Use a Google Apps Script cache
If what's taking time is querying the options, you could use CacheService (see reference). This would allow you to only query the options every X seconds (up to 6 hours) instead of every time.
function doGet(e) {
// [...]
const cache = CacheService.getScriptCache()
let businessNames = cache.get('businessNames')
if (businessNames == null) {
businessNames = getbusinessNames()
cache.put('businessNames', businessNames, 6*60*60)
}
// use businessNames
// [...]
}
In this case I've only done it with businessName but it can also be use in streets.
having a 6 hour cache means that it could take up to 6 hours for a change in the list to propagate. If you add the options manually you could add a function to force the reloading it:
function forcecacheRealod() {
cache.put('streets', getStreets(), 6*60*60)
cache.put('businessNames', getbusinessNames(), 6*60*60)
}
Idea 4: Improve how you load the data
Is very common for new Google Apps Script users to iterate the rows one by one getting the value. It's way more efficient to get the proper range with all the rows and columns and call getValues (reference).
Idea 5: do a fetch instead of submitting the form
If what is happening is that it takes time to load after sending the data, it might be a good idea to use google.script.run (reference) instead of making a form and submitting it, since it could prevent reloading the entire page again.
Idea 6: SPA web app
The result of doubling down on the last idea. Same benefits and you could load the necessary data in the background while the user lands on the home page.
Idea 7: Load the options dynamically
Use google.script.run (reference) to load the options once the page has already been loaded. May actually be slower but you can give faster feedback to the user.
Idea 8: Save the options in localStorage
Requires idea #7. Save the dynamically loaded options into localStorage(see reference) so the user only needs to wait once. You may need to load them once in a while to make sure they are up-to-date.
References
Console timer (MDN)
CacheService (Google Apps Script reference)
Range.getValues() (Google Apps Script reference)
Class google.script.run (Client-side API) (Google Apps Script reference)
Window.localStorage (MDN)
I'm a beginning learner of InDesign scripting and would like to help myself with debugging, but my attempts seem to run into walls. Hope someone has some insights that will help me going forward.
I'm working on a little project that loops through some selected tables, puts the 3 tables into an array/variable (accomplished that) and then loops through the content of those tables to find a GREP match and store those in an array/variable (for further uses I won't get into now)
My main objective at this point: See exactly what text characters the .findGrep(); function is catching and display those in the Javascript Console of the ExtendScript Toolkit app.
So here's a bit of the journey up to this point, including codes tried and suggestions from others. (All of my attempted uses of these has failed...why I'm here now... and why this is long; my apologies)
Initial try.
var myTables = []; (in Data Browser this shows values of [object Table], [object Table], [object Table]
var myFinds = [];
var myTest = [];
var myCharacters = [];
app.findGrepPreferences = null;
app.findGrepPreferences.findWhat = "\"";
for (x = 0; x < myTables.length; x++) {
var myFinds = myTables[x].findGrep();
$.writeln(myFinds);
};
Notes on this code: Because not every table has the characters in the findWhat, sometimes in this loop myFinds has nothing, but when it does have something, it shows this in console [object Character],[object Character],[object Character]
So someone (firstHelp) gave me this: And it did not work... error thrown on .contents.toString(); *"undefined is not an object" which I thought, "ok, yes I see at times in the loop myFinds has nothing in it... more on this later"
var stringArray = [];
for( var n=0; n<myFinds.length; n++ ) {
stringArray[n] = myFinds[n].contents.toString();
};
$.writeln(myFinds.join("\r"));
Code revamp Gave up on the $.writeln(myFinds); within the loop and tried this in order to gather Grep finds in a variable/array that could be dealt with outside of loop.
for (x = 0; x < myTables.length; x++) {
$.writeln(myTables[x].cells.firstItem().texts[0].contents[0]);
myFinds.push(myTables[x].findGrep());
};
$.writeln(myFinds);
ExtendScript Toolkit console now showing this for myFinds:
*myFinds = [Array], [object Character], [object Character], [object...
+ (object symbol) 0 =
+ (object symbol) 1 = [object Character], [object Character], [object Character]
+ (object symbol) 2 =
+ (object symbol) _proto_ =*
*again tried the .contents.toString(); on the myFinds and still the same error, "undefined..." including targeting the array when it clearly had something in it.
**So then I get this tipoff...(but no helpful code to apply to what I already have)
"you are dealing with arrays of arrays mixed with texts.
So you have to check with each item of the result array if it is text
or another array of texts.
If it is an array loop that array."
And later this bit of code that is supposed to "flatten" my array... a = [].concat.apply([],a);
Replacing a with myFinds like this, myFinds = [].concat.apply([],myFinds); did absolutely nothing. The array and its contents showed no change in the console... and I have no idea how to loop through each item of this array within an array, find out if it's text or another array and then show its real contents to console.
Really...how many loops and if/thens etc do I need to run on one array to show its actual contents in the console? But I know I struggle with breaking down every little step I want, to its minute scripting granularity and so my ignorance regularly impedes me. I welcome any suggestions/tips to move me closer to my **main objective" as stated above. Thanks
Regarding the first help. The real reason why you get an error while accessing content property is that you don’t check the type of the object and presume it will be a Text object. As the findGrep may not find a Text occurrence, you actually get an empty array. And Array.prototype.contents doesn’t exist hence the error.
Then $.writeln is legacy of Adobe ExtendScript toolkit, the IDE for ExtendScript. This product is no longer de eloped and maintained by Adobe. You should consider using other logging techniques such as the Visual Studio ExtendScript plugin which will allow you to use breakpoints and everything you need.
So I have been searching for an answer for this for a long time, and have come up with a temporary solution which i'll write below, but I was wondering if theres a more elegant one?
In the application im working on there is a weblist which can contain one of four values which are: 10,25,50,100. When a value is selected the web table below should show 10,25,50 or 100 results depending on the value selected of course.
Now when I call the standard Obj.Select "100" for example it changes the list box to 100 but nothing else happens. No event is triggered so the web table below remains the same. If I manually select 100 the web table updates to display 100 records.
I tried firing different events to the web list but none of them seemed to update the web table!
In the end I settled on the solution below:
Public Function CustomSelect(obj, strValue)
Dim intCounter, strProperty, boolItemInList, strEnabledOrDisabled, arrAllItems, strAllItems
Dim xCoord, yCoord
If strValue = "##" Then
Call AddComment("Passed in ##, skipping set function")
Exit Function
End If
Reporter.Filter = rfEnableErrorsOnly
strProperty= obj.GetTOProperty("name")
If strProperty= "" Then
strProperty= obj.GetTOProperty("html id")
End If
Reporter.Filter = rfEnableAll
If obj.exist(5) Then
XCoord = obj.GetROProperty("abs_x")
YCoord = obj.GetROProperty("abs_y")
strEnabledOrDisabled = obj.GetROProperty("disabled")
If strEnabledOrDisabled = 0 Or strEnabledOrDisabled = "0" Then
strAllItems = obj.GetROProperty("all items")
arrAllItems = split(strAllItems,";")
For intCounter = LBound(arrAllItems) to Ubound(arrAllItems)
'Obj.SendKeys "{DOWN}"
Obj.Select "#" & intCounter
If arrAllItems(intCounter) = strValue Then
Exit For
End If
Next
Else
Call ReportExpectedVsActual("Weblist is disabled: " & strProperty, False, True)
End If
Else
Call ReportExpectedVsActual("Weblist doesnt exist: " & strProperty, True, False)
End If
End Function
RegisterUserFunc "WebList", "CustomSelect", "CustomSelect"
I know it looks a bit messy but its all I can think of to get it working at the moment. Does anyone have any other ideas of what to try?
Cheers
Nick
There are at least two things to try:
Try to look into the source of the webpage to see how an action is triggered. It could be something like onchange='RefreshIt();' or you'll see an EventHandler attached to it, then you have to dig somewhat deeper.
If you have indentified the event that will update your page, try firing that event after updating the list with WebListObject.FireEvent {yourEvent}. yourEvent could be a string containing onchange, onclick, ondblclick, onblur, onfocus, onmousedown, onmouseup, onmouseover, onmouseout, onsubmit, onreset or onpropertychange.
Another way that is even simpler (but giving you less control) is worth a try. This is done through the settings: Go to Tools > Options and in the tree view, select Web > Advanced. Try what happens when changing the checkbox for Run only click and the radiobuttons for Replay type.
I have the following snippet code hooked up to a FormIt email form:
$tv = "taken" . (int)$hook->getValue('datetime');
$docID = $modx->resource->get('id'); //get the page id
$page = $modx->getObject('modResource', $docID);
$current = (int)$page->getTVValue($tv);
if (!$page->setTVValue($tv, $current + 1)) {
$modx->log(xPDO::LOG_LEVEL_ERROR, 'There was a problem saving your TV...');
}
$modx->setPlaceholder('successMessage','<h2 class="success">'.$current.'</h2>');
return true;`
It increments a template variable every time it is run and outputs a success message (although right now I'm using that functionality to output a debug message instead). The problem is, it only increments the TV once after saving the snippet, thereby refreshing the cache. Normally I would call the snippet without cache by appending ! to its name, but that doesn't appear to work for FormIt hooks. How can I get this code to work? Right now I'm running the entire page as uncacheable, but that is obviously suboptimal. Perhaps, there's a way to hook a snippet in an uncached manner? Call a snippet from within a snippet as uncached?
I'm doing something similar - but to count page loads, it looks to me like you are missing the last little bit: $current->save();
<?php
$docID = $modx->resource->get('id');
$tvIdm = 32;
$tvm = $modx->getObject('modTemplateVar',$tvIdm );
$tvm->setValue($docID, $tvm->getValue($docID) + 1 );
$tvm->save();
Try add this before you save $tv object
$tv->_processed = false;
It's derived from modElement's property it extends.
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: