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.
| Tool | What for | Note |
|---|---|---|
| console.log | checking a simple value or the flow | unreadable for arrays of objects |
| console.table(arr, ['id','status']) | shows an array of objects as a table | ideal for inspecting API responses |
| console.dir(obj, { depth: null }) | expands the full nested structure | Node truncates depth in a plain log |
| console.time / timeEnd | measures how long a step takes | for finding why a test is slow |
| breakpoint (VS Code) | pausing execution and seeing every variable | far faster than logging |
| Playwright trace viewer | DOM, network and console per step | the 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.logeffect 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
- Chrome DevTools: JavaScript debuggingofficialdeveloper.chrome.com
- Node.js debuggingofficialnodejs.org