Sparround

Debugging techniques

Debugging is not scattering random console.log calls — it is narrowing the search space step by step. When a test fails, answer these in order:

1. What failed? An assertion, or the code itself? An assertion error means "the product differs from what was expected"; a TypeError/undefined is a problem in the test code. 2. Where did it fail? The topmost stack trace line inside YOUR code — skip the framework lines. 3. With what data? What were the variables actually holding at that moment — print them or set a breakpoint. 4. Does it always fail? Re-run it (--repeat-each). Failing every time is a real bug; failing sometimes is flakiness (timing, ordering, shared state). 5. Does it fail everywhere? Passing locally but failing in CI means the environment differs: timezone, locale, viewport, speed, data.

Saying this sequence out loud in an interview is itself a strong answer — it shows systematic thinking.

ToolWhat forNote
console.logchecking a simple value or the flowunreadable for arrays of objects
console.table(arr, ['id','status'])shows an array of objects as a tableideal for inspecting API responses
console.dir(obj, { depth: null })expands the full nested structureNode truncates depth in a plain log
console.time / timeEndmeasures how long a step takesfor finding why a test is slow
breakpoint (VS Code)pausing execution and seeing every variablefar faster than logging
Playwright trace viewerDOM, network and console per stepthe primary tool for a CI failure

Breakpoints and debuggers. In VS Code you click to the left of a line number to set a breakpoint, then run the test in debug mode — execution stops on that line and you see the real value of every variable, the call stack, and can even evaluate expressions in the console.

Useful varieties:

  • Conditional breakpoint — stops only when a condition holds (orderId === '1042'); priceless inside a 500-item loop
  • Logpoint — prints a message without stopping; a console.log effect without touching the code
  • Caught/uncaught exception breakpoints — stop at the moment an error is thrown

The general Node mechanism: node --inspect-brk script.js pauses the process at startup and waits for a debugger; you attach via chrome://inspect or VS Code's "Attach to Node Process". Playwright's own shortcuts are npx playwright test --debug (the Inspector) and await page.pause() in the code.

Reading a stack trace is one of the highest-return skills and is frequently probed in interviews. The rule: the first line is the error TYPE and message; the lines below are the call chain — top to bottom: where it blew up → who called it. Skip the node_modules lines and look at the first line inside YOUR files — the problem is almost always there.

In async code a stack trace can look short and useless (at processTicksAndRejections), because the error happens in a different tick from the original call. The most common cause is a forgotten await: adding it both fixes the bug and makes the trace readable. Enriching errors with new Error(msg, { cause: originalError }) also preserves the context.

📚 Sources and documentation