Sources
Pause the practice score in Sources.
A small function on this page builds a personalization score from an hour and a viewport width. The offer should appear. It does not. The Chrome Sources panel is where you pause that function, step into the helpers, and watch the value change.
The shortcuts are for Chrome on Mac and on Windows. On a Mac laptop, F8, F9, F10, and F11 often need Fn as well, unless the keyboard uses the F keys as standard function keys. The Command shortcuts do not need Fn. Hover a toolbar button to confirm the shortcut in the Chrome you have open.
Open DevTools and the Sources panel
Click the page once so the keyboard shortcut reaches the browser, then open DevTools.
MacCommand + Option + IWindowsF12 or Control + Shift + IDevTools reopens the panel you used last. If that panel is not Sources, click the Sources tab. The Command Menu can open it too: run Show Sources.
MacCommand + Shift + PWindowsControl + Shift + PCommand + ] on Mac, or Control + ] on Windows, focuses the next panel. The Console has its own shortcut if you want that drawer instead: Command + Option + J on Mac, Control + Shift + J on Windows.
Two sidebars matter once you are paused. The left one is the file tree. The right one holds the call stack, scope, and watches.
MacCommand + Shift + YWindowsControl + Shift + YThat pair shows or hides the file tree.
MacCommand + Shift + HWindowsControl + Shift + HThat pair shows or hides the debugger sidebar.
The practice score
The file is personalization-score.js. The function is buildPracticeBeacon. It asks four helpers for an hour band, a width band, a score, and a slot. The button always passes the same sample, so every browser hits the same bug.
The hour cuts are the ones the rest of the site uses: morning from 5, afternoon from 12, evening from 17, and night otherwise. The width cuts are the same device cuts: mobile under 760, tablet under 1100, and desktop from 1100. The offer starts at 40, the same line as the intent lab.
Afternoon
Hour 12 up to 17. 10 points. Hour 14, the sample, lands here.
Desktop
Width from 1100. 30 points. 1280, the sample, lands here.
The other hours
Morning is 15, evening is 20, night is 5.
The other widths
Mobile is 10. Tablet is 20.
10 + 30 is 40, so this sample should take the offer slot. Run it, then use the steps to see why the panel disagrees.
This tab only
Run the practice score
The button calls buildPracticeBeacon with hour 14 and a 1280px-wide viewport. The return value is drawn below. It is not posted.
- Sample hour
- 14 · 2:00 p.m. · afternoon · 10 points
- Sample width
- 1280px · desktop · 30 points
No run yet.
- Wait until the button reads Run the practice score. That means /debug/personalization-score.js has loaded.
- Open the file. Command + O or Command + P on Mac. Control + O or Control + P on Windows. Type personalization-score and open that script. In the file tree it sits under this host, in debug.
- Jump to the function. Command + Shift + O on Mac, Control + Shift + O on Windows, type buildPracticeBeacon, and press Enter.
- Click the line number beside const hourBand = bandForHour(signals.hour). The number turns blue. With the cursor on that line, Command + B on Mac or Control + B on Windows toggles the same breakpoint. DevTools pauses before the line runs.
- Leave the debugger checkbox off. Click Run the practice score. The page pauses on that line.
- In the Call stack, select buildPracticeBeacon if another frame is selected. Under Scope → Local, signals is hour 14 and viewportWidth 1280. hourBand is not set yet.
- Step into with F11. You land in bandForHour. Step over with F10 until it returns afternoon. Step out with Shift + F11 if you are still inside the helper. Back in buildPracticeBeacon, hourBand is afternoon.
- Step into bandForWidth. Step over until it returns desktop, then step out. widthBand is desktop.
- Step into scoreBands. Step into pointsForHour. Local band is afternoon, and the return is 10. Step out. hourPoints is 10. Step into pointsForWidth. The return is 30. Step out. widthPoints is 30.
- In Watch, click + and add hourPoints + widthPoints. It reads 40. Add hourPoints + widthBand. It reads "10desktop". Hover widthPoints and widthBand in the editor. The number and the word are both in scope. The return uses the word.
- Step over the return. Step into slotForScore. Scope → Closure shows OFFER_AT as 40. Local score is the string. The comparison fails, so the slot stays default.
- Resume with F8. The panel under the button shows the object that came back. Open The line that is wrong if you want the one-line account, including how to patch it in the editor and run it again.
Finding files, including source maps
Command + P (Mac) or Control + P (Windows) searches files the page has loaded. Command + O and Control + O do the same. Command + Shift + O, or Control + Shift + O, jumps to a function in the open file. Type scoreBands to land on the helper the return lives in.
Search across every loaded file with Command + Option + F on Mac, or Control + Shift + F on Windows. Control + G, on both, goes to a line in the open file. You can also open the Command Menu and type a colon and the line number.
App code on this site is bundled. A bundle is often one long line, and the names are short. The {} button at the bottom left of the editor pretty-prints that line. If Chrome has already wrapped it, a dash in the line-number gutter marks the continuation. Pretty-print makes the text readable. It does not restore the original function names.
A source map is a sidecar the browser can download. The script points at it with a sourceMappingURL comment. When Enable JavaScript source maps is on (Settings → Preferences → Sources; it starts on), DevTools can show the original files. The file-tree menu has Group by Authored/Deployed: Authored is the mapped source, Deployed is the file the browser ran. Known frameworks can also list third-party paths under the source map field ignoreList, which feeds the ignore list below.
This site does not publish a browser source map for the Next.js bundles. The practice file is served on its own, unminified, at /debug/personalization-score.js, so the walkthrough matches production. If Command + P cannot find it, reload after the button has finished loading.
Line breakpoints
A line breakpoint pauses before that line runs. Click the line number, or put the cursor on the line and press Command + B (Mac) or Control + B (Windows). The marker is blue. Click it again to remove it.
The Breakpoints list in the debugger sidebar groups them by file. The checkbox beside a row disables that breakpoint without deleting it. Command + F8 on Mac, or Control + F8 on Windows, deactivates every breakpoint so the button can finish. Press it again to turn them back on.
While paused, hold Command (Mac) or Control (Windows) and click a later line to continue to that line. Right-click a line number and choose Continue to here for the same jump.
Conditional breakpoints and logpoints
A conditional breakpoint is a line breakpoint that pauses only when an expression is truthy. Right-click the line number on the return inside scoreBands and choose Add conditional breakpoint. Command-click the line number on Mac, or Control-click it on Windows. With the cursor on the line, Command + Option + B or Control + Alt + B opens the same editor.
Use widthBand === "desktop". The marker turns orange. Remove the plain blue breakpoint on hourBand, or that one still pauses on every run. Click the button. You stop on the return, with hourPoints and widthPoints already filled in.
A logpoint writes to the Console and does not pause. Command + Shift + click a line number on Mac, or Control + Shift + click on Windows, and choose the logpoint field. On the return { line inside buildPracticeBeacon, enter score, slot. The marker is pink. Resume, click the button, and the console prints the string score and the default slot. slot is safe to log on that line because the line above has already assigned it. A logpoint on the assignment line itself would run before the assignment.
DOM, XHR/fetch, and event listener breakpoints
A DOM breakpoint pauses in whatever code changes a node. Open Elements (Command + Shift + C on Mac, Control + Shift + C on Windows, then click the result box). You can also switch to Elements and find #practice-beacon-result. Right-click that node, then Break on → Subtree modifications. Deactivate line breakpoints with Command + F8 or Control + F8 so you do not stop earlier. Click the button. DevTools pauses in the code that writes the result. That pause is after buildPracticeBeacon has returned, and it often lands in a bundled file. Attribute modifications watch attributes on that node. Node removal watches the node being removed. Subtree modifications are the ones that see the result text appear.
An XHR/fetch breakpoint pauses when a request URL contains the text you type. In Sources, open XHR/fetch Breakpoints (some Chrome versions say XHR Breakpoints), click +, and enter collect. The practice button does not send a request. With consent on, this site posts /api/collect for a page view, and the Google tag requests /g/collect. Either URL contains collect, so the breakpoint can pause there. The Collection Lab is the page that shows the beacon.
An event listener breakpoint pauses in the listener that runs after an event. In Sources, open Event Listener Breakpoints, expand Mouse, and check click. Click Run the practice score. The first pause may be a framework listener on the page, not buildPracticeBeacon. That is the moment for the ignore list: right-click that call-stack frame and choose Add script to ignore list. Resume, and keep the line breakpoint in the practice file if you still want to land there.
The debugger statement
debugger is a line breakpoint that lives in the source. Check Pause on the debugger statement, deactivate other breakpoints, and click the button with DevTools open. Execution stops on that line inside buildPracticeBeacon, before the helpers run. With DevTools closed, the statement does nothing.
Uncheck the box when you are done. A debugger statement left in a shipped function pauses every visitor who has DevTools open. This one runs only when the box is checked, and only in the practice file.
Stepping
The five buttons sit at the top of Sources once the page is paused. Both Step and Step into skip scripts on the ignore list. Take a script off the list when you need to enter it.
Resume
Run until the next breakpoint.
MacF8 or Command + \WindowsF8 or Control + \Step over
Run this line. If it calls a function, run the whole function, then pause on the next line.
MacF10 or Command + 'WindowsF10 or Control + 'Step into
Go inside the function this line calls. Also pause after an await, on the line that runs when the promise settles.
MacF11 or Command + ;WindowsF11 or Control + ;Step out
Finish the function you are in and pause in the caller.
MacShift + F11 or Command + Shift + ;WindowsShift + F11 or Control + Shift + ;Step
Go inside the function this line calls, and do not pause on the far side of an await. The practice helpers are synchronous, so Step and Step into land on the same line.
MacF9WindowsF9
Call stack and scope
The Call stack lists the frames that got you here, newest first. Click a frame to show its source and its scope. The selected frame is the one the console and the watches use. Control + . and Control + , move down and up the stack on both Mac and Windows.
While paused inside pointsForWidth, the stack is that helper, then scoreBands, then buildPracticeBeacon, then the click that started it. Frames from ignored scripts are hidden. Check Show ignore-listed frames under the Call stack to see them grayed out.
Scope is the same sidebar. Local is the function you selected: in scoreBands, that is hourBand, widthBand, hourPoints, and widthPoints. Closure is the surrounding scope the function actually uses. In slotForScore, Closure shows OFFER_AT. Global is the global object. bbpDebugPractice is the only name this file adds there. It holds the function. It is not a beacon, and it is not sent.
Double-click a value in Scope to edit it for the rest of this pause. Stop on the if inside slotForScore, change score from the string to the number 40, and step over. The slot becomes offer. The comparison was willing. The score that arrived was not a number. The edit lasts for this call. The next click runs the file again.
Watch expressions
Watch evaluates an expression every time you pause or step, in the selected frame. Click + in Watch, or select hourPoints + widthPoints in the editor and press Command + Shift + A (Mac) or Control + Shift + A (Windows).
Names belong to a frame. hourPoints exists in scoreBands. If the selected frame is buildPracticeBeacon, that watch is not available. Click the scoreBands frame and it reads 40 again. Delete a watch from the hover control on its row.
Hovering and the console while paused
While paused, hover a variable to see its value. Chrome also prints the current value beside the declaration. Hover widthBand on the return line and you get the word desktop. Hover widthPoints and you get 30.
Escape toggles the drawer. The console, while paused, evaluates in the selected call frame. In scoreBands, type hourPoints + widthPoints and typeof (hourPoints + widthBand). The first is the number 40. The second is "string". Select the expression in the editor and press Command + Shift + E (Mac) or Control + Shift + E (Windows) to run that selection in the console.
From the console you can also call the function yourself: bbpDebugPractice.buildPracticeBeacon({ hour: 14, viewportWidth: 1280 }). That still stays in this tab.
Blackboxing and the ignore list
Chrome calls this the ignore list. Older articles call it blackboxing. An ignored script stays out of the way: its frames are hidden on the call stack, and stepping does not enter it. The practice file should stay off the list. The framework bundle you hit from a click breakpoint, or a tag script, is the kind of file the list is for.
Add a script from any of these places:
- Right-click the file or its folder in the file tree, then Add script to ignore list or Add directory to ignore list.
- Right-click inside the open file and choose Add script to ignore list.
- Right-click a frame in the Call stack and choose Add script to ignore list.
Settings is F1, or Fn + F1 on a Mac laptop. Open the Ignore List tab. Enable Ignore Listing is the main switch. Add content scripts to ignore list skips extension scripts. Automatically add known third-party scripts to ignore list uses the ignoreList field in source maps. Custom exclusion rules start with /node_modules/ and /bower_components/. Add pattern takes a name or a regular expression, for example googletagmanager\.com.
The file tree grays out ignored sources. Its menu can hide them. Show ignore-listed frames puts the gray frames back on the call stack when you want the full path.
The line that is wrong
Show the return inside scoreBands
scoreBands computes widthPoints, then returns hourPoints + widthBand. widthBand is the word desktop. JavaScript joins a number and a string, so the score is "10desktop". slotForScore compares that string with 40. The comparison is false, so the slot stays default. The return that matches the table is hourPoints + widthPoints.
To try the fix in this tab, pause on that return, change widthBand to widthPoints, and save with Command + S (Mac) or Control + S (Windows). Chrome patches the function and restarts it. Resume. If a line breakpoint pauses the page again, press Command + F8 or Control + F8 to deactivate breakpoints, then click the button once more. The slot should read offer. Reload when you want the file from the server back. The save applies to the copy in this tab.
What this page stores
The score stays in this tab. The practice controls are marked so a click there is not recorded. Opening this lab still follows the same consent switch as the other pages: a page view is stored only after you accept. The practice file does not call fetch. The Calls lab is the follow-up: a second practice file that you edit so it calls this site’s public read-only APIs. Consent shows what that switch changes, and the Collection Lab shows a beacon when one is sent.
Recommended next
Why these picksReading path transitions…