Supercharged Debugging with Conditional Breakpoints
Samuel Rouse
Posted on July 26, 2024
You might be accustomed to setting breakpoints to troubleshoot or debug your code, but you can supercharge your debugging and testing process with conditional breakpoints. Let's dive in.
Conditional Breakpoints
Conditional breakpoints execute an expression in the context where the breakpoint is set. If the outcome is true (truthy, actually), the breakpoint will pause execution. Otherwise, the code continues to run. These were introduced in Safari's WebKit engine way back in 2009 and Chrome shortly thereafter, as well as Firefox 19 a few years later as they replaced the FireBug extension. I've found reference to support in Internet Explorer Developer Tools as well, but I didn't find a timeline on that.
Basic Usage
The appearance and interaction for conditional breakpoints varies somewhat by browser platform.
Chromium – Chrome, Edge, Opera, Arc…
If you left-click the line number in the Sources panel – or the dash where a line number would be if the code is prettified – you get a regular breakpoint. If, instead, you right-click, you are presented with options, one of which is a "Add conditional breakpoint…"
Selecting this will present you with the ability to enter an expression.
Once completed, a conditional breakpoint will appear orange.
To change this, you can right-click on the conditional breakpoint and choose "Edit breakpoint..."
You can also edit other breakpoint types and convert them to a conditional breakpoint.
Gecko – Firefox, IceCat, K-Meleon
If you right-click on a line number in the Debugger panel, you can choose "Add condition".
Once selected, you are presented with a temporary inline field below the selected line to enter your condition.
When complete, it turns into an orange conditional breakpoint.
These can be edited by right-clicking and choosing "Edit condition".
WebKit – Safari
Safari is a bit different. Any breakpoint may have conditions, so start by left-clicking to create a breakpoint, and then right-click it to bring up the context menu and choose "Edit Breakpoint..."
You can then enter a condition used to trigger the breakpoint.
Editing a condition is the same as the first edit.
Breakpoint Position
It's important to remember that breakpoints stop at the specific location in that line. With a standard breakpoint at a line, it stops before that code is run. Knowing the line you are on hasn't run can be important when trying to debug. Stopping one line too early may mean variables or data you need aren't available, yet. You may need to adjust the position of a breakpoint to ensure
Inline Breakpoints
Chrome and Firefox both support even more specific inline breakpoint. Once the line's breakpoint is set, you can select a more specific point for breaking. This can be very helpful when dealing with callbacks or inline arrow functions.
When you create the inline breakpoint, do not remove the full-size breakpoint or all breakpoints within that line will be removed. You can remove the smaller breakpoint earlier in the line.
In both Chrome and Firefox these inline breakpoints can be made conditional.
At this time Firefox will not properly display an inline conditional breakpoint – it will remain blue – but it will function correctly. You will not be able to edit the condition, but can replace it each time.
Supercharging
Now that we know how to create conditional breakpoints, let's look at how we can use them to improve development and troubleshooting.
The Revelation
When it comes down to it, conditional breakpoints are blobs of code executed in the context where they are set. This means they have access to the variables and functions in that context. It also means conditional breakpoints can change data.
In this case .add()
returns no value, so not only does this conditional breakpoint change something, the result is falsy, so it doesn't stop execution.
We just performed a live code injection!
What's more, it doesn't stop execution, so it allows us to make changes without worrying about significantly changing execution times or experiencing new race conditions caused by completing network requests or events while the browser is paused.
We get most of the benefit of directly changing source code, without a build or deploy step.
Complex Operations
What if we want to perform an action that returns a truthy value? Or we want to perform multiple actions? You can use semi-colons to make separate statements, or use commas to chain statements together.
t.classList.add("submitting"); console.log("It's a breakpoint!"); false;
t.classList.add("submitting"), console.log("It's a breakpoint!"), false
This gives us the ability to do virtually anything in a condition. We can:
- call functions
- update values
- iterate over objects
- replace properties or methods
- make network requests
- define new variables to be accessed by later conditional breakpoints!
All of this from inside a breakpoint which doesn't even have to stop the code.
Complex conditions
Because we have complete control, we can even use conditional statements inside our conditional breakpoint to make specific changes.
Dynamic data
One of the most convenient uses is modifying a network response. By injecting changes as the data is returned, we can test updates or configuration changes without having to modify the application source, affect other consumers of the API, or restart services after changes.
// Change a configuration flag
response.data.config.someConfigurationFlag = true;
false;
// Change data formats
response.data.rows = response.data.rows.map(row => ({
...row,
value: row.type === 'date'
? moment(row.value, 'MM/DD/YYYY').format('YYYY-MM-DD')
: row.value,
});
false;
If you need, you can even completely replace the response object to test different response scenarios without the effort to create a scenario. This can be useful for replaying known responses and errors which would normally require a time-consuming setup process.
This can be a very powerful tool, but be sure we don't exclude proper validation. Static changes can become stale and incorrectly represent the proper server response.
Trace It
A standard breakpoint provides a wealth of information, but it comes at the risk of disrupting the normal execution flow. When this becomes a concern, I like to fall back to console.trace()
. While you can insert this directly into your code, tossing console.trace()
into a conditional breakpoint will log the stack trace out and keep running.
Complex loggers
One of my favorite uses of complex conditional breakpoints is creating complex loggers. The console.trace()
can be really useful, but it makes finding information in the console difficult. Wrapping them in a console.groupCollapsed()
gives a great balance, where you can locate the title and expand the group to see the detail.
I use this trick inside state management where the function calls that might not end up triggering updates can be logged to get a more complete picture of what's happening.
console.groupCollapsed('Update:', key, oldValue, newValue);
console.trace();
console.groupEnd();
Conclusion
Conditional Breakpoints are a great addition to your developer toolkit. They open up a huge range of possibilities, from making it convenient to pause in often-executed loops to my favorite way to live-patch code for testing, troubleshooting, or tinkering.
Posted on July 26, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.