Art of debugging with Chrome DevTools

Chrome DevTools come with an array of features that help developers debug their apps effectively, and therefore find and fix the bugs faster. There are a lot of power tools in devtools which are not quite well-known amongst the developers. Through this medium, I want to share some of the tips and tricks that I use in devtools for efficient debugging and shed light on some of these little-known power tools in Chrome devtools.

From this point on, I will use devtools to mean Chrome devtools for brevity.

Before we get started, a couple of things to set yourself up.

Use the Canary build

If you want to stay on the bleeding edge of Chrome and devtools, you might want to check out the canary build of Chrome and even make it your default dev browser. The canary build of Chrome is a developer build that gets frequent updates for early adopters. It may have broken build occasionally, but mostly it’s okay and you get to dabble with the latest and greatest of Chrome and devtools.

Enable Developer Tools experiments

You can do so by going to chrome://flags and making sure Developer Tools experiments flag is turned on. Once you have that enabled, you can go to your settings in the devtools. There you will find the “experiments” tab. If some of the features that I show here are not available in your devtools, you probably need to enable that feature in this experiments tab.

Super experimental features

If the feature that I show here is not even available in the experiments list, then it's probably one of the WIP features that you can already use but is hidden away in the devtools. While on the experiments tab, hit the shift key 6 times to show the WIP experiments on devtools.

Console

While debugging our apps, we spend quite some time in the Console panel. We sprinkle our code with a bunch of console logs, we go to the Console panel and inspect the variables in the logs. Because we spend so much time in this panel, it's important to know all the APIs and shortcuts the devtools provide us in the console panel via its console and the command-line APIs. Let’s take a look at them.

Always log objects

My first tip is actually not devtools related, but a trick that I use all the time while using console.log(); Instead of just logging variables, log objects by surrounding the variable names with {}. This will make the devtools nicely log the names of the variables together with the values. Now you will never lose the context of what variables are being logged in the console.

Use console.table to log tabular data

If the variable you are logging is an array of objects, instead of using traditional console.log(), make use of console.table() that gives you a nice tabular representation of the data that is much easier to consume.

Use colors to make your logs stand out

It's easy to get a lot of noise in the logs. It may be your own logs, logs from third-party widgets or the browser’s logs. Besides using filters, you can make your logs stand out by using colors.

$ and $$

If you are in the console and you are not using any library that assigns the $ and $$ global variables like jQuery, you can use the $ and $$ as shortcuts for document.querySelector() and document.querySelectorAll() respectively.

Besides saving keystrokes, $$ also returns a true array instead of an array-like NodeList. So you can use array methods like map, reduce and filter on the result immediately.

E.g. you can check for a broken link in your page using $$ like below

$0…$4

If all you want is a reference to a DOM element, use $0 shortcut. $0 gives you the reference to the currently selected element in the Elements panel. If $0 is the currently selected element, $1 is the previously selected element. That goes all the way to $4.

$_

Use $_ to get hold of the result of last evaluated expression in the console.

getEventListeners()

getEventListeners(domElement) gives you an object with all the registered event listeners on that DOM element. E.g. if you want a reference to a click handler on a button, remove it or modify it in the runtime in the production environment, this allows you to do so.

You may have noticed in the GIF above, how the expression that was typed in the console was immediately being evaluated. I did not have to press enter to get the value, the value was eagerly displayed in the console. That’s a new feature available on Canary right now called “Eager evaluation”.

debug(fn)

If in the example above, all you want is to pause execution when the button is clicked, you can use the debug method. debug(fn) takes in a function reference as an argument and everytime that function is invoked, the debugger will then pause the execution on the first line of that function.

Imagine you have to debug an issue with a button click. You have no idea where in the source code lies the handler for the button. Instead of digging through the source code, use the getEventListeners method as displayed above to get the reference to the click handler of the button and pass that function reference to the debug method. Then, if you click the button, devtools will pause the execution on the first line of the click handler.

copy(obj)

copy(anything) is a handy utility in the console that allows you to put anything into the system clipboard.

Give copy some mangled JSON. It will pretty print it for you.

Top-level await

async/await makes handling async operations super easy and understandable. The only downside, await -ing needs to be done in an async function. If we want to use async/await in the devtools console, we would have to wrap it in an Immediately Invoked Async Function Expression (IIAFE). Not convenient. Which is why devtools now supports top-level await so there is no needs to wrap your awaits inside an IIAFE.

Debugging in the Sources panel

Sources panel is where the true debugging happens. Using breakpoints, stepping-into, stepping-over etc. you can get a lot of info on the state of things in your application that can guide you towards the source of an issue. I am not going to write about how to use the debugger, and going to assume that you already have that knowledge. Instead, I am going to share some other tips and tricks that I like to use in the Sources panel.

Enable auto-pretty print

Now on Canary, behind an experimental flag, you can make sure that all the code that you see in the sources panel is automatically pretty-printed. So that’s one less thing you have to worry about.

Inject console logs in prod with conditional breakpoints

Breakpoints are great. Even better, conditional breakpoints! Conditional breakpoints are the breakpoints that only pause execution if a condition evaluates to true. So devtools won’t pause on you every single time, but only when you want it to. Learn more on conditional breakpoints here.

I like to use conditional breakpoints to inject console.logs in production code when I cannot modify the source code. If my breakpoint condition is just a console.log, devtools will never break on that breakpoint because console.log returns undefined, which is falsy. But it will evaluate my expression and therefore log my variable in the console.

Why not just use a regular breakpoint and inspect the variable? Sometimes, I don’t want that. E.g. when trying to inspect code that gets executed in high frequency like a touch or a scroll handler. I don’t want the debugger to pause every single time, but I do want to log my variables. Now, I can just inject my console.log even in production code.

Freeze UI to inspect elements that appear only on hover

It’s hard to inspect an element that appears only on hover. E.g. how do you inspect a tooltip? By the time you right-click and inspect, the element is no longer being hovered and the tooltip is gone. Is there a way to freeze the UI as is and then inspect any element on the page.

The trick I like to use is

  1. Open up the sources panel
  2. Display the tooltip
  3. Use the keyboard shortcut to pause script execution. (Hover over the pause icon to find out the keyboard shortcut)
  4. When the script execution is paused, go back to the Elements panel and inspect the tooltip as you are used to

XHR breakpoints

When trying to figure out why a certain request is made and who made it, use the XHR breakpoint in the sources panel.

Use devtools as your IDE via Workspace

DevTools sources panel is a pretty advanced editor. You can easily fuzzy-search for a file, go to a line, go to a symbol, run a snippet, use multiple cursors etc. This has been documented well in this medium post by Addy Osmani.

But, why not bring the entire development workflow into the devtools so you don’t waste time switching applications and contexts. You can do so using Workspace.

E.g. if you have a project that you built with create-react-app or vue-cli, you can just drag the entire folder into the Sources panel. Devtools will automagically map the network resources into the local ones. So you can start editing the files in the devtools and view the changes right away. This makes for a very efficient feedback loop while debugging applications.

Use network overrides to tinker with the production code

If you are battling with a production bug and you just want to tinker with the production code without having to set things up locally, use the “network overrides” feature.

It allows you to easily map any resource that comes down over the network to a local copy which you can then edit in the devtools or your own editor at your own will. And devtools will always serve that local copy. Even when the cache is cleared or the resource changes on the server or when the service worker is involved.

This allows for some super easy debugging on production or some nifty performance experiments.

Nodejs debugging

If you want to use the same devtools debugger to debug your node applications, you can easily do so by invoking your node script with --inspect-brk flag.

node --inspect-brk script.js

Go to chrome://inspect. Your node session should appear as one of the remote targets that you can inspect.

Also, in devtools you will see a green node icon, clicking on which will fire up the chrome debugger for the node session.

If you wanted to debug your unit tests e.g. with devtools debugger, you’d have to do something like

node --inspect-brk ./node_modules/.bin/jest

It is kind of awkward having to figure out the link to the actual executable. GoogleChromeLabs recently released a tool to make this infinitely easier. It’s called ndb.

With ndb, all you need to do is

ndb npx jest

If you have a custom npm script, you can use devtools debugger for that too very easily.

ndb npm run unit

Even better, if you run ndb in a project with package.json, it will automatically find all the scripts from the package.json which you can then debug using devtools.

Use snippets for debugging utilities

Devtools come with a tool to create and save snippets of code that you can run anywhere. I like to use snippets to extend the debugger with some additional utility methods like

lodashify — to add lodash to any app so I have the entire Lodash utility belt with me all the time

Another example is a utility method that I have that can augment any object property so that whenever it is accessed or modified, it gives me a nice trace of who accessed that property or who changed it. Very useful when debugging state that was changed by something in the code somewhere.

There are lots of useful devtools snippets available that you can just grab and use to your advantage.

That’s it for now. This post will be followed up by another post with a focus on how to use devtools to debug performance issues. If you have additional tips and tricks, feel free to share in the comments section below. If you found it useful, please share it so others can benefit too.

Like what you read? Give Prashant Palikhe a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.