Browser DevTools overview

An overview and exploration of DevTools features

Return Home

Browser dev tools are a set of tools built directly into the browser that let's you inspect, diagnose, and edit web-pages on-the-fly. Some core workflows include

  • Debugging CSS
  • Prototyping CSS
  • Debugging JavaScript
  • Analyzing load performance

Most browsers are accessible by right-clicking a page and selecting "Inspect". This will open the "Elements" panel focused on the HTML element that you right-clicked.

The fastest way to get to dev-tools, however, is through keyboard shortcuts:

  • MacOS: cmd+option+I
  • Linux: ctrl+shift+I

This document provides an overview of a lot of dev-tooling features. To learn more about any topic the official Chrome DevTools docs have far more details.

Everyday Tooling

For general-purposes, the following panels provide the functionality described in each section below.

Elements

Inspecting Nodes

There are three ways to target a node for inspection

  • Right-click the content in the page you want to inspect, and select "Inspect" from the drop-down.
  • Click the picker icon (leftmost on the tab bar) and choose the content in the page. This will add more contextual information as you move your mouse around the page prior to making your selection.
  • Select the HTML node from the tree displayed in the Elements panel.

If you're struggling to find the element in the page you can also search by text-content, XPath, or query-selector. You can do this by focusing on the Elements panel and pressing cmd+F. If you search for text, such as "Hello, world" then you can seek through all elements containing that text. If you search for a selector, such as input[type=text] then you can seek through all elements matching that selector.

The keyboard arrows are also useful for quickly jumping around the tree.

Modifying Nodes

The elements view is useful for quickly prototyping and experimenting with HTML and CSS content. You can double-click on an elements tag, attribute, or text-content to edit that particular aspect of a node. The least tedious way to edit a node's aspect is to right-click it and choose what you'd like to edit, including the HTML itself.

You can delete/copy/duplicate an element directly through the keyboard. First select the node, then press one of the following

  • Delete: del
  • Copy: cmd+C
  • Move: cmd+up/down
  • Duplicate: option+shift+up/down

Node Information

In the panel beside the elements tree, once can view and edit data contextual to the currently selected element.

Styling

In the "Styles" tab, one can see fragments of the stylesheets actively applied to the current element. This is great for prototyping by doing the following

  • Edit inline-styles on the fly, through the element.style section
  • Add to a fragment's CSS properties by clicking beside an existing property/selector
  • Toggle an existing property
  • Edit an existing property's name/value

You can also emulate certain behaviour which may be difficult to do so in the page manually

  • Toggle pseudo-selectors through the :hov dropdown
  • Toggle/add classes through the .cls dropdown
  • Toggle dark-mode through the paint-brush dropdown

Or, you can even navigate and temporarily edit the CSS directly by clicking the link displayed next to a selector.

Computed Styles

For a clearer view into exactly what values are applied to an element you can use the "Computed" tab. This will show the box-sizing of an element as well as a list of each property's absolute value. Then, to find the source of a computed value, you can hover over it and click the blue arrow that appears.

Events

Often-times it can be tough to find out what event listeners have been attached to an element or what an event listener is doing. In the "Event Listeners" tab you can get a list of all the event that will fire when you do some interaction with an element.

Some common workflows I tend to use is as follows

  • For finding the source code of an event I already know of, I'll look through the list and click the link to the JS file.
  • For finding a problematic event which I don't know the cause of, I'll try deleting the listeners one by one until the problem goes away, taking note of which event caused the problem.

DOM Breakpoints

DOM breakpoints can be tricky to set up. When you can do so it's a powerful way to find out why an element has a specific attribute, child-node, or deletion that's not part of the source HTML from the server. The best way to set up a DOM breakpoint is to right-click on the element you want to listen to and select an option under "Break on". Sometimes these breakpoints can be lost between reloads, so oftentimes it's useful to set up DOM breakpoints before any JavaScript runs, this can be done through the "Script First Statement" event listener breakpoint (See the "Global Breakpoints" section).

JavaScript Properties

Through the "Properties" panel you can see what values are available to you through JavaScript. This is essential equivalent to printing $0 in the console. This is useful for quickly finding what data has been added to the element by the page's or the user-agent's JavaScript. Just keep in mind, this data is not always consistent between user-agents.

A11y

The "Accessibility" panel is useful for viewing an accessibility tree instead of the DOM tree and seeing what data is available to screen readers for each node. In Firefox this is in a dedicated tab, and in my opinion is actually better than in Chrome.

In Firefox you can emulate visual deficiencies from the accessibility tab. In Chrome this feature is under the "Rendering" tab.

Console

The console gives you access to the current scope and logs. If you're not currently at a breakpoint this will be the global scope. The console is fairly basic, mostly used for running arbitrary JavaScript but it does have some overlooked capabilities.

  • The $0 variable will always be the element you've selected in the "Elements" tab.
  • The eye icon can be used to create a "live expression", which are essentially the same as a "watch" in the debugger. Some useful examples I often used include:
    • document.activeElement.textContent to check what the currently focused/tabbed element is.
  • When at a breakpoint, you can modify variables and log them for future reference.
  • You can create a bookmarklet (i.e. a bookmark with the URL javascript:void ...) to save useful debugging shortcuts/methods.

The log-levels can be filtered to verbose, info, warnings, and/or errors. Firefox also has additional filtering for logs related to CSS, which is useful for spotting CSS issues that may otherwise go unnoticed.

Sources/Debugger

The "Sources" tab (or "Debugger" in Firefox) provides an IDE-like environment for all the resources that have been loaded by the current page. It consists of three panels: The context, the file view, and the debugger.

Navigator

The left panel shows the context of the currently loaded page. Chrome by default displays a huge contiguous list of files, so I recommend enabling "Group by folder" in the drop-down menu for a more navigable view. From here you can view any resource, including media, HTML, and JavaScript. Debugging, however, is only possible in JavaScript files and <script> blocks in HTML files.

Files can be opened and viewed by clicking on it in the file tree or by using cmd+P to search by name. Note that searching by file content is different between Chrome and Firefox. In Firefox it's easy, you can search by content with cmd+shift+F. In Chrome you can open the tab menu and under "More tools" open "Search" and search for content there.

Overrides

Chrome has a unique feature here called "Overrides". This lets you save modifications to files locally so that those changes can be persisted between reloads. This tends to be useful for when you want to test modification to resources from places you don't have access to (like third party scripts or production environments).

File View

Once you've opened a file you'll see its contents displayed in the center panel. If the content is minified you can toggle pretty-printing with the {} button on the bottom bar.

Breakpoints

Setting in-file breakpoints is an essential part of debugging. You can set a breakpoint either with the debugger statement in JavaScript, or by clicking on the line number in the browser. While the browser is executing JavaScript, if it encounters a breakpoint it will pause execution and allow the developer to explore the state of the application. Once you set a breakpoint for a line there will appear some blue chevrons beside each operation in that line. You can use these to toggle the breakpoint for each specific operation within that line.

There are also some variations over the basic breakpoint type. By right-clicking a breakpoint and selecting "Edit breakpoint" you can set a breakpoint to the following:

  • Breakpoint: A standard breakpoint which will always stop execution
  • Conditional Breakpoint: A breakpoint which will only activate when the given expression is truthy.
  • Logpoint: Instead of stopping execution the given expression will be printed to the console.

Source Mapping

Sometimes the file you're viewing is the source code; not actually what's being run by the browser. This is useful for debugging a file as if it was the non-minified, un-polyfilled, pre-transpiled file you work with in your editor. Modifications to source-maps will have no effect (e.g. when using overrides).

When debugging, the active line or breakpoint may not always land where you expect it, this is because the browser makes a best effort to link up the source file to the actual file. If you have problems with a source map you can switch to the original file by clicking the lint to it in the bottom bar (e.g. From <file>.js in Chrome or To <file>.js in Firefox).

Editing

There are several ways of modifying files and it's easy to lose the changes you make, so it's good to be aware of the following. Changes can be caused by:

  • Modifying CSS selectors and properties via "Elements > Style"
  • Modifying files in "Sources"
  • Loading overrides which may be set in "Sources" or "Network"

If you want to find a change you made you can enter the tab menu and under "More tools" select "Changes" which will show a diff view for all the changes you've made.

Debugger

The debugger will be active whenever execution is paused. You can disable breakpoints in the top bar of this panel. Execution can be paused manually by clicking the pause button. When active there are five methods of resuming execution:

  • Resume: Will resume execution until the next breakpoint/manual pause
  • Step over: Will resume execution until the next function-return/next line
  • Step into: Will resume execution until the next function call/next line
  • Step out: Will resume execution until the function returns
  • Step: Will resume execution until any of the above step types occur

Aside from the debugger controls, there are also several sections for monitoring execution and controlling breakpoints.

Watch

The watch allows you to assign expressions results that will be updated each time the debugger context changes.

Breakpoints

The breakpoints section provides a list of assigned breakpoints with links to their sources. From here breakpoints can be toggled and their types modified.

The breakpoint section also has options to break on all caught/uncaught exceptions.

Scope

The scope section lists the variables of the current context and each scope it belongs to. It's worth reading You Don't Know JS's chapter on scopes & closures for more information on this subject. Variables displayed in scope are also available for modification by using the console. If a variable is overwritten by another scope you can right-click and "Store as global variable" for modification. Storing a variable as global also allows you to continue using and modifying that variable once the debugger has resumed.

Call Stack

The call stack gives you access to jump through the list of function calls that led to the current point of execution. This is useful for finding the source of an event listener or DOM breakpoint. Oftentimes this is less useful when using a framework, which can often obfuscate the cause of execution. In cases like this, framework specific dev tool extensions would provide the features you need for debugging.

Global Breakpoints

Finally there are several sections that cover global breakpoints. These are breakpoints which are assigned to some browser behaviour rather than a specific line of code.

  • XHR/fetch can be paused when a URL pattern is detected
  • DOM changes can be paused as discussed earlier through the "Elements" tab
  • Global Listeners can be used to find sources of window events for setting breakpoints
  • Event Listener can be paused when some specific event listener type is activated. Some useful ones include:
    • DOM Mutation > DOMAttrModified: Can be toggle to break whenever any element attribute changes. This may be useful in cases where it's difficult to assign a breakpoint via the "Elements" tab.
    • DOM Mutation > DOMContentLoaded: Can be toggled for when any DOM content is parsed and loaded. This usually will run very early, so can be useful for testing before any JavaScript or DOM modifications occur.
    • Parse > Set innerHTML: Can be toggled for when insecure HTML modification are made. Useful for security testing.
    • Script > Script First Statement: Breaks as soon as any script file starts execution. This will always fire before any JavaScript runs, so is similarly useful to DOMContentLoaded.

Network

The network tab provides a log of all outgoing requests made by the page. Aside from the log itself, there's some additional controls over network conditions and filtering.

Conditions can be controlled with the "Disable cache" toggle and the throttling dropdown can be set to modes such as "No throttling", "Fast 4G", etc. Alongside the controls are filters. There's a textbox to filter by URL or toggleable tabs to filter by types (e.g. "HTML", "CSS", etc.).

In the main section is the log of requests. Each request can be selected to display a detailed view of the request.

Then on the bottom bar is a summary with notable information such as the total size, number, and time of resources loaded.

Details

In the details view you can drill down into more useful information for each specific request.

  • Headers: Lists the start line and headers of HTTP request and response.
  • Preview: Displays the response body as a file
  • Response: Displays the response body as text
  • Initiator: Displays the call stack of the request, with links to sources
  • Timing: Displays the duration of each process of the request

External Applications

Oftentimes you want to be able to debug a network request. To do so you can right-click a request and under "Copy" select "Copy as cURL".

From this the Curl command can be used in the terminal or imported by Postman to be modified and re-requested ad-hoc.

Application/Storage

The application tab provides access to all data saved to disk by the page for reuse between sessions. Chrome's tooling is best for this, but Firefox has the basics covered too. The storage tab consists of a list of storage resources and a panel to display details for each resource. Note that in Firefox the Application/Storage resources are in separate tabs. Chrome additionally has a "Storage" overview which is useful for clearing site data easily. Chrome also has tools for viewing and testing application background services.

The most useful of this tab is the cookies, session storage, and local storage tables. Here you can view, modify, and clear the stored values.

Extensions

There are also some useful extensions that can provide additional dev-tools. I won't go over them in details, but I recommend the following:

  • React Dev Tools
    • Components provides an experience similar to the "Elements" tab
    • Profiler provides an experience similar to the "Performance" tab
  • axe DevTools provides page accessibility analysis using Axe

Intermediate Tooling

There's some additional tooling available in dev-tools that provide workflows beyond everyday debugging needs. These generally explore the realm of performance, security, or specific web features. Most of these features are available only in Chrome.

Performance

Performance is can be a fairly general topic and depending on what problems you're seeing you may want to use different tools.

Performance

The performance tab provide fairly general purpose performance and profiling tools. By default Chrome will provide live metrics showing the core web vitals for the page as well as logs for page interaction timings and layout shifts. This view is quite useful for monitoring performance as you interact with a page without creating a detailed report.

In Chrome and Firefox you can click the record button and all execution will be sampled until you stop recording. If timing is particularly difficult to record, consider setting up Recorder (see the section under "Niche Tooling") and recording performance from there. Once recorded you'll see a dense view of the recording taken. The main things to focus on are the Flame Fraph and the Call Tree. Personally I find Firefox's profiler better to use despite not provide as much breadth of information.

Flame Graph

The flame graph shows a stack of rising segments based on the call stack and their timings. This is most useful for finding which functions are taking up the longest time and in proportion to their child calls.

Call Tree

The call tree is a ranking of function calls sorted by their running time. This is most useful for grouping and filtering out functions to make more informed decisions on where to put optimisation efforts.

Lighthouse

Lighthouse is a tool to measure performance, security, and accessibility scores. Lighthouse scores don't necessarily make your website good, but it will provide some easy wins that search engines will take into account.

Coverage

Coverage measures how much of the resources requested by the network actually get used by the browser. Ideally one should be lazy in requesting resources and should never request a byte until it actually needs to be used. By using coverage it will show you exactly what lines of code could be omitted to improve the time to load a webpage.

Rendering

Rending can be used to create some visual markers on the page to alert you of potential issues. The "Paint flashing" and "Scrolling performance issues" are useful for spotting unexpected issues during testing.

Benchmarking

Sometimes you might be experimenting on micro-optimisations and need an objective way to find the best approach. I find jsbenchmark to be useful for this.

Memory Inspector

When working with ArrayBuffer (e.g. for base-64, CBOR, WASM, or text-encoding) the memory inspector is useful for exploring an otherwise opaque data type. If you have an ArrayBuffer in the console or debugger you can right click it and select "Reveal in Memory Inspector panel". By default it will display content in hex and ASCII format. From here you'll have the option to view the data in various formats that may be more readable.

Niche Tooling

Some dev-tooling covers features that have value primarily towards people of specific professions or niches. These tools are accessible under "More tools" in the tab menu.

For QA and Testers: Recorder

If you're someone who regularly tests repeatable features you ought to use the Recorder. You can record some user interaction. It will then replay the interaction back as fast as possible, beating out any manual testing you may have otherwise been doing.

For testers using puppeteer, recordings can then be turned into automated end-to-end tests.

For Authentication Engineers: WebAuthn

For those working on WebAuthn authentication systems without access to a password manager or passkey the WebAuthn tab will allow you to emulate passkey providers.

Keep in mind that this is no true replacement for real passkeys, as passkey extensions can have slightly nuanced behaviour compared to those handled directly by the browser/operating system.

Additionally, by using the Memory Inspector as discussed in a previous section you can at a glance check the AAGUID and hash of the attestationObject.

For Animators: Animations

If you're working with a particularly complicated or problematic animation then use the Animations tab to slow down, scrub, and replay animations.

For Mobile Applications: Sensors

For emulating mobile-specific sensors, the sensors tab will allow you to control the following

  • Location
  • Orientation
  • Touch
  • Idle

For Performance Engineers: about://tracing

To get a "Performance" tab experience for non-web applications, you can navigate to the about://tracing page and upload some recording there. There are several ways to capture tracing data for applications.

  • Using the --generateTrace flag on tsc to generate performance tracing for TypeScript compilation. For more info check out the TypeScript GitHub wiki.
  • Using the --trace-event-categories flag on node. This can be used alongside the node:trace_events module for more targeted tracing. This sort of tracing is useful for finding issues with Jest performance.
  • The samply program can be useful for profiling native applications