Debugging Client & Catalog Client Scripts in ServiceNow

Intro

When dealing with server-side scripts, the ServiceNow Debugger makes debugging relatively easy (most of the time), as you can actually see into the call stack, and the contents of your server-side variables as you step through your code, line-by-line. When available, that tool is incredibly useful; but unfortunately, it does not work with client scripts.

So, how do you troubleshoot client-side scripts in ServiceNow? Well, since those scripts execute inside the user’s browser, you’re going to have to use some browser-magic to make that happen. The good news is, modern browsers already have an incredible debugger that’s at least as good as the server-side script debugger in ServiceNow, built right in!

The question then becomes: “How do I trigger the client-side debugger? I can’t easily put breakpoints in my code that runs client-side, especially if it runs on-load; right?”
In fact, you can! Better yet, you can put calls to the debugger directly in your code!
In this short article, we’re going to see exactly how to do that, using a not-very-smart Client Script that runs on the Incident form. Join me after the jump, for a walk-through!


TLDR

Put the statement debugger; in your client-side code, wherever you want the debugger to take over and simulate a breakpoint.

How-to

I usually find the answer by way of example to be most effective, so let’s look at an example where we might need to troubleshoot a Client Script.

Note: I’m going to use a regular ol’ Client Script here, but this simple method works with Catalog Client Scripts, UI Policy scripts, UI Scripts, and even client-side scripts in the Service Portal! Basically, anything that executes as-written, in the user’s browser.

Let’s say we work for a company whose management is all about the micro. They seem adamant that for some unknowable reason, they need to be added to the "watch list” on every single Incident that one of their direct reports is assigned to.
”Okay, sure” you say, as you die a little inside; but you go to build this functionality for them.

From a previous ‘enhancement’, you’ve already got the “Manager” field on the form, which is auto-populated whenever the assigned_to field changes. You reckon you can use that field.
You create a new Client Script on the Incident table, you have it run “onChange”, and trigger it by changes to the “Assigned to” field.
Then, you write a bit of code. This code does the following:

  1. Gets the value of the “manager” field.

  2. Gets the current value of the watch list, as an array (or a blank array if the watch list is empty).

  3. If the manager field is blank, or the manager is already on the watch list, halt and do nothing.

  4. Add the manager to the watch list array, then update the value of the watch list field.

All pretty straight-forward. Here’s what that Client Script looks like once you’re finished:

However, when we trigger this code, nothing seems to actually happen! What’s going wrong here?

We could add a bunch of log statements into our code and faff about with it until we figure out what the problem is, and then hopefully remember how to get our code back into the state we need it in and remove all of our debug log code… Or, we could add just one line of code, such as on line 8 in the below snippet:

function onChange(control, oldValue, newValue, isLoading, isTemplate) {
    if (isLoading || newValue === '') {
        return;
    }
    
    var assigneeMgrID = g_form.getValue('manager');
    var watchList = g_form.getValue('watch_list');
    debugger;
    //If watch list has anything in it, split it into an array;
    // otherwise, replace with a blank array.
    watchList = (watchList ? watchList.split(',') : []);
    
    //If manager is empty or already on watch list, halt and do nothing.
    if (!assigneeMgrID || watchList.indexOf(assigneeMgrID) >= 0) {
        return;
    }
    
    watchList.push(assigneeMgrID); //Add manager to watch list array
    g_form.setValue('watch_list', watchList.join(',')); //update watch list field
}

All we did here, was add a line with the statement debugger; on line 8, but now if we press F12 to open the console and then re-trigger our code, we get something more like this (the Google Chrome debugger)…

As you can see, the debugger has halted execution on the line in our code that has the debugger; statement. We can see the call stack, and loads of other stuff.

This can all look pretty overwhelming at first, but the main thing to pay attention to, is the Scope section. By default (in Chrome), this will be just below the Call Stack section on the right side of the debugger. Collapse Call Stack, and you’ll see Scope. In the Scope section, Local is usually going to be the most relevant bit. that’ll show you the values of the variables in your code at the moment the code halted.

As you can see in the image to the right, the variables in the local function scope in my code look as you’d expect, except for assigneeMgrID. Since the code paused after that variable was assigned, we would expect that variable to have a value!

I can use the buttons at the top-right of the debugger to step line-by-line through my code, or even into or out of a given function-call, or I can use step over to remain at the current level of the call stack (meaning, keep the debugger on my code).
By advancing step-by-step through my code, I can see that it’s evaluating my if block to be true, because assigneeMgrID is blank. This is causing the script to return early, and not do what I expected it to do.

Now that the debugger has helped us figure this out, we might go and investigate why this is. In this case, the issue is being caused by the fact that the Manager field is a dot-walked field. This means that my g_form.getValue(‘manager’) line would not work.

I could change that line to g_form.getValue(‘assigned_to.manager’), but that wouldn’t work either because the client script triggers when the Assigned to field is changed, not when the dot-walked Manager field is changed. (You can’t trigger a client script on change of a dot-walked/derived field). The manager field is updated asynchronously when the Assigned to field is updated, so when our logic runs, the assigned_to.manager field has not yet been populated.

Pro-tip: If you need to debug a callback function, adding debugger; will halt execution, but your current state may be lost, and the code won’t be visible. You can work around this though, by simply adding a second debugger; line in the code that runs synchronously, and just pressing the ‘continue’ button in the console debugger UI when it’s hit. That will allow your callback function to be debugged effectively!

In order to get this code to work, we’ll need to do an asynchronous query of some sort to get the user’s manager. This highlights another of the benefits to the debugger; statement: You can even put it in your callback function, to debug code in higher-order functions!

That’s all for this pro-tip. Thanks for reading!
If you have any questions, leave them in the comments below.
If you haven’t yet subscribed… are you okay? - Get subscribed below!