Skip to content
Skillv1.0.0

CDPWave Debugging

Advanced debugging with cdpwave. Breakpoints, step debugging, CPU profiling, heap snapshots, code coverage, DOM debugger.

by MathiasPaulenko(0) 0 installs
Free
Sign in to install

Free account. Installing gives you the manifest plus copy-paste snippets.

See reviews

About

Imported from MathiasPaulenko/stack-practices-web (.devin/skills/cdpwave-debugging/SKILL.md). Install upstream with npx skills add MathiasPaulenko/stack-practices-web --skill cdpwave-debugging. Copyright stays with the author.

CDPWave Debugging

Description

Advanced debugging with cdpwave — set breakpoints, step through JavaScript, capture CPU profiles, take heap snapshots, measure code coverage, and use the DOM debugger for event-level debugging.

When to Invoke

  • Setting breakpoints and step debugging JavaScript
  • Capturing and analyzing CPU profiles
  • Taking heap snapshots for memory leak detection
  • Measuring JS and CSS code coverage
  • Setting DOM and event breakpoints
  • Debugging JavaScript execution in automated tests

Prerequisites

  • pip install cdpwave
  • Chrome or Edge launched with --remote-debugging-port=9222
  • Basic familiarity with cdpwave (see cdpwave-testing skill)
  • Python 3.11+ with asyncio

Debugger Domain

The CDP Debugger domain provides full step-debugging capabilities over the DevTools Protocol.

Setting breakpoints

debug_set_breakpoint

Set a breakpoint by URL, function, or script:

import asyncio
from cdpwave import CDPSession

async def set_breakpoint():
    session = await CDPSession.connect("ws://localhost:9222")

    # Enable the Debugger domain
    await session.Debugger.enable()

    # Set breakpoint by URL and line number
    result = await session.Debugger.set_breakpoint_by_url(
        url="https://example.com/app.js",
        line_number=42,
        column_number=0
    )
    breakpoint_id = result["breakpointId"]

    # Set breakpoint by function name
    result = await session.Debugger.set_breakpoint_by_function(
        function_name="handleClick"
    )

    # Set breakpoint by script ID and line
    result = await session.Debugger.set_breakpoint(
        location={
            "scriptId": "script123",
            "lineNumber": 10,
            "columnNumber": 0
        }
    )

    await session.close()

Conditional breakpoints

await session.Debugger.set_breakpoint_by_url(
    url="https://example.com/app.js",
    line_number=42,
    condition="x > 100"
)

Logpoints (breakpoints that log without pausing)

await session.Debugger.set_breakpoint_by_url(
    url="https://example.com/app.js",
    line_number=42,
    condition="console.log('hit line 42, x=', x); false"
)

Pausing and resuming

debug_pause

Pause JavaScript execution:

await session.Debugger.pause()

debug_resume

Resume execution after a pause:

await session.Debugger.resume()

Step debugging

Method Description
Debugger.step_over Step over the next function call
Debugger.step_into Step into the next function call
Debugger.step_out Step out of the current function
Debugger.resume Resume execution until next breakpoint
async def step_debug(session):
    # Wait for pause event
    paused = await session.Debugger.wait_for_pause()

    print(f"Paused at: {paused['callFrames'][0]['url']}")
    print(f"Line: {paused['callFrames'][0]['location']['lineNumber']}")

    # Step over
    await session.Debugger.step_over()
    paused = await session.Debugger.wait_for_pause()

    # Step into
    await session.Debugger.step_into()
    paused = await session.Debugger.wait_for_pause()

    # Step out
    await session.Debugger.step_out()
    paused = await session.Debugger.wait_for_pause()

    # Resume
    await session.Debugger.resume()

Removing breakpoints

# Remove a specific breakpoint
await session.Debugger.remove_breakpoint(breakpoint_id=breakpoint_id)

# Remove all breakpoints
await session.Debugger.set_skip_all_breakpoints(skip=True)

Call frames and scope

When paused, inspect call frames and scope chains:

async def inspect_pause(session):
    paused = await session.Debugger.wait_for_pause()

    for frame in paused["callFrames"]:
        print(f"Function: {frame['functionName']}")
        print(f"URL: {frame['url']}")
        print(f"Line: {frame['location']['lineNumber']}")

        for scope in frame["scopeChain"]:
            print(f"  Scope: {scope['type']}")  # global, local, closure, catch, block, script, with
            obj = await session.Runtime.get_properties(
                object_id=scope["object"]["objectId"],
                own_properties=True
            )
            for prop in obj["result"]:
                print(f"    {prop['name']} = {prop.get('value', {}).get('value', 'N/A')}")

HeapProfiler Domain

Capture heap snapshots for memory leak detection and analysis.

Taking a heap snapshot

async def heap_snapshot(session):
    await session.HeapProfiler.enable()

    # Capture heap snapshot
    snapshot_data = []
    async for event in session.HeapProfiler.take_heap_snapshot():
        snapshot_data.append(event)

    # Save snapshot
    with open("heap.heapsnapshot", "w") as f:
        json.dump(snapshot_data, f)

Heap sampling

async def heap_sampling(session):
    await session.HeapProfiler.enable()

    # Start sampling
    await session.HeapProfiler.start_sampling()

    # ... run page interactions ...

    # Stop sampling and get results
    profile = await session.HeapProfiler.stop_sampling()

    with open("heap-sampling.json", "w") as f:
        json.dump(profile, f)

Tracking heap object allocations

async def track_allocations(session):
    await session.HeapProfiler.enable()

    # Start tracking
    await session.HeapProfiler.start_tracking_heap_objects()

    # ... run page interactions ...

    # Stop tracking and get snapshot
    await session.HeapProfiler.stop_tracking_heap_objects()

    # Get tracked objects
    objects = await session.HeapProfiler.get_heap_object_id()

Heap snapshot analysis

import json

def analyze_heap(snapshot_path):
    with open(snapshot_path) as f:
        snapshot = json.load(f)

    nodes = snapshot["nodes"]
    strings = snapshot["strings"]

    # Count nodes by type
    type_counts = {}
    for i in range(0, len(nodes), 7):
        type_idx = nodes[i]
        type_name = strings[type_idx]
        type_counts[type_name] = type_counts.get(type_name, 0) + 1

    for type_name, count in sorted(type_counts.items(), key=lambda x: -x[1]):
        print(f"{type_name}: {count}")

Profiler Domain

Capture CPU profiles to identify performance bottlenecks.

CPU profiling

async def cpu_profile(session):
    await session.Profiler.enable()

    # Start profiling
    await session.Profiler.start()

    # ... run page interactions ...

    # Stop profiling and get profile
    profile = await session.Profiler.stop()

    with open("cpu-profile.json", "w") as f:
        json.dump(profile, f)

Profiling with precision

async def precise_profile(session):
    await session.Profiler.enable()

    # Set sampling interval (default is 1000us = 1ms)
    await session.Profiler.set_sampling_interval(interval=100)  # 100us = 0.1ms

    await session.Profiler.start()

    # ... run interactions ...

    profile = await session.Profiler.stop()

CPU profile analysis

import json

def analyze_cpu_profile(profile_path):
    with open(profile_path) as f:
        profile = json.load(f)

    nodes = profile["profile"]["nodes"]
    samples = profile["profile"]["samples"]
    time_deltas = profile["profile"]["timeDeltas"]

    # Find hot functions (most samples)
    sample_counts = {}
    for sample in samples:
        sample_counts[sample] = sample_counts.get(sample, 0) + 1

    # Map node IDs to function names
    node_map = {n["id"]: n for n in nodes}

    for node_id, count in sorted(sample_counts.items(), key=lambda x: -x[1])[:10]:
        node = node_map[node_id]
        func = node["callFrame"]["functionName"] or "(anonymous)"
        url = node["callFrame"]["url"]
        line = node["callFrame"]["lineNumber"]
        print(f"{func} at {url}:{line}{count} samples")

DOMDebugger Domain

Set breakpoints on DOM events and DOM modifications.

Event breakpoints

async def event_breakpoints(session):
    await session.DOMDebugger.enable()

    # Break on all click events
    await session.DOMDebugger.set_event_breakpoint(
        event_name="click"
    )

    # Break on XHR load
    await session.DOMDebugger.set_event_breakpoint(
        event_name="load",
        target_name="XMLHttpRequest"
    )

    # Break on specific event
    await session.DOMDebugger.set_event_breakpoint(
        event_name="submit"
    )

DOM breakpoints

async def dom_breakpoints(session):
    await session.DOMDebugger.enable()

    # Get a DOM node first
    document = await session.DOM.get_document()
    node_id = document["root"]["children"][1]["children"][0]["nodeId"]

    # Break on subtree modification
    await session.DOMDebugger.set_dom_breakpoint(
        node_id=node_id,
        type="subtree-modified"  # subtree-modified, attribute-modified, node-removed
    )

    # Break on attribute modification
    await session.DOMDebugger.set_dom_breakpoint(
        node_id=node_id,
        type="attribute-modified"
    )

    # Break on node removal
    await session.DOMDebugger.set_dom_breakpoint(
        node_id=node_id,
        type="node-removed"
    )

Removing DOM breakpoints

# Remove specific DOM breakpoint
await session.DOMDebugger.remove_dom_breakpoint(
    node_id=node_id,
    type="subtree-modified"
)

# Remove event breakpoint
await session.DOMDebugger.remove_event_breakpoint(
    event_name="click"
)

Code Coverage

Measure JS and CSS code coverage to find unused code.

JS coverage

async def js_coverage(session):
    await session.Profiler.enable()
    await session.Profiler.start_precise_coverage(
        call_count=True,
        detailed=True
    )

    # ... run page interactions ...

    # Get coverage results
    result = await session.Profiler.take_precise_coverage()

    for script in result["result"]:
        url = script["url"]
        functions = script["functions"]
        for func in functions:
            func_name = func["functionName"] or "(anonymous)"
            for range in func["ranges"]:
                start = range["startOffset"]
                end = range["endOffset"]
                count = range["count"]
                if count == 0:
                    print(f"UNUSED: {func_name} in {url} [{start}:{end}]")

    await session.Profiler.stop_precise_coverage()

CSS coverage

async def css_coverage(session):
    await session.CSS.enable()
    await session.CSS.start_rule_usage_tracking()

    # ... run page interactions ...

    # Get coverage results
    result = await session.CSS.stop_rule_usage_tracking()

    for rule in result["ruleUsage"]:
        style_sheet_id = rule["styleSheetId"]
        start = rule["startOffset"]
        end = rule["endOffset"]
        used = rule["used"]
        if not used:
            print(f"UNUSED CSS: sheet={style_sheet_id} [{start}:{end}]")

Combined Debug Session

A complete debug session combining breakpoints, profiling, and heap analysis:

import asyncio
import json
from cdpwave import CDPSession

async def debug_session():
    session = await CDPSession.connect("ws://localhost:9222")

    # Enable all required domains
    await session.Debugger.enable()
    await session.Profiler.enable()
    await session.HeapProfiler.enable()

    # Set breakpoint
    bp = await session.Debugger.set_breakpoint_by_url(
        url="https://example.com/app.js",
        line_number=42,
        condition="x > 100"
    )

    # Start CPU profiling
    await session.Profiler.start()

    # Navigate to trigger the breakpoint
    await session.Page.enable()
    await session.Page.navigate(url="https://example.com")

    # Wait for pause
    paused = await session.Debugger.wait_for_pause()
    print(f"Paused at line {paused['callFrames'][0]['location']['lineNumber']}")

    # Inspect call frames
    for frame in paused["callFrames"][:3]:
        print(f"  {frame['functionName']} at {frame['url']}:{frame['location']['lineNumber']}")

    # Resume
    await session.Debugger.resume()

    # Wait for page load
    await session.Page.wait_for_load()

    # Stop CPU profiling
    cpu_profile = await session.Profiler.stop()
    with open("cpu-profile.json", "w") as f:
        json.dump(cpu_profile, f)

    # Take heap snapshot
    snapshot = []
    async for chunk in session.HeapProfiler.take_heap_snapshot():
        snapshot.append(chunk)
    with open("heap.heapsnapshot", "w") as f:
        json.dump(snapshot, f)

    # Clean up
    await session.Debugger.remove_breakpoint(breakpoint_id=bp["breakpointId"])
    await session.close()

asyncio.run(debug_session())

Best Practices

  • Enable domains before useDebugger.enable(), Profiler.enable(), etc.
  • Use conditional breakpoints — avoid pausing on every hit; filter by condition.
  • Set sampling interval for precision — lower intervals give more detail but more overhead.
  • Capture heap snapshots before and after — compare to find memory leaks.
  • Run coverage with realistic interactions — navigate and interact to exercise code paths.
  • Clean up breakpoints — remove breakpoints after debugging to avoid side effects.
  • Save profiles as files — CPU and heap profiles are large; save to disk for analysis.
  • Use logpoints for non-intrusive loggingcondition="console.log(...); false" doesn't pause.
  • Disable domains when doneDebugger.disable() etc. to reduce overhead.
  • Use wait_for_pause() in async code — avoids polling for pause events.

Common Pitfalls

  • Not enabling domains — methods fail silently if the domain isn't enabled.
  • Breakpoint line numbers are 0-indexed — line 42 in the editor is line 41 in CDP.
  • Heap snapshots are large — can be 50-200 MB for complex pages; save to disk.
  • CPU profiling overhead — profiling slows execution; use for diagnosis, not benchmarking.
  • Coverage without interactions — only code that executes is measured; interact with the page.
  • DOM breakpoints need node IDs — get them from DOM.get_document() or DOM.query_selector().
  • Event breakpoints pause on all instancesset_event_breakpoint("click") pauses on every click.
  • Forgetting to stop profilingProfiler.stop() must be called to get results.

References

Use it

Copy one of these into your project. Installing also returns the manifest and these snippets.

yaml
targets:
  - https://api.opensmartroute.ai/api/v1/registry/mathiaspaulenko-stack-practices-web-cdpwave-debugging/manifest   # or paste the manifest below

Manifest

An Open Capability Manifest: the router reads it to know what this does, what it costs and when to pick it.

mathiaspaulenko-stack-practices-web-cdpwave-debugging.ocm.jsonjson
{
  "ocm": "1",
  "id": "mathiaspaulenko-stack-practices-web-cdpwave-debugging",
  "kind": "skill",
  "name": "CDPWave Debugging",
  "description": "Advanced debugging with cdpwave. Breakpoints, step debugging, CPU profiling, heap snapshots, code coverage, DOM debugger.",
  "publisher": "MathiasPaulenko",
  "version": "1.0.0",
  "capabilities": {
    "domains": [
      "coding"
    ],
    "tags": [
      "skill-md",
      "debugging",
      "breakpoints",
      "profiling",
      "heap-snapshot",
      "github"
    ],
    "languages": [
      "en"
    ]
  },
  "quality_prior": 0.6,
  "examples": [
    "Advanced debugging with cdpwave. Breakpoints, step debugging, CPU profiling, heap snapshots, code coverage, DOM debugger."
  ],
  "primary": false,
  "metadata": {
    "source": {
      "provider": "github",
      "repository": "https://github.com/MathiasPaulenko/stack-practices-web",
      "path": ".devin/skills/cdpwave-debugging/SKILL.md",
      "ref": "dc25ba253187d4cb96f91f88787af947568dc80e",
      "url": "https://github.com/MathiasPaulenko/stack-practices-web/blob/dc25ba253187d4cb96f91f88787af947568dc80e/.devin/skills/cdpwave-debugging/SKILL.md",
      "key": "MathiasPaulenko/stack-practices-web/.devin/skills/cdpwave-debugging/SKILL.md"
    }
  },
  "instructions": "# CDPWave Debugging\n\n## Description\n\nAdvanced debugging with cdpwave — set breakpoints, step through JavaScript, capture CPU profiles, take heap snapshots, measure code coverage, and use the DOM debugger for event-level debugging.\n\n## When to Invoke\n\n- Setting breakpoints and step debugging JavaScript\n- Capturing and analyzing CPU profiles\n- Taking heap snapshots for memory leak detection\n- Measuring JS and CSS code coverage\n- Setting DOM and event breakpoints\n- Debugging JavaScript execution in automated tests\n\n## Prerequisites\n\n- `pip install cdpwave`\n- Chrome or Edge launched with `--remote",
  "cost": {
    "context_tokens": 3787
  }
}

Fetch it by URL: GET /api/v1/registry/mathiaspaulenko-stack-practices-web-cdpwave-debugging/manifest?version=1.0.0

Reviews

Star ratings from people who tried it. One review per account; edit yours any time.

No reviews yet. Install it, try it, and be the first to rate it.