Debugging methodology: worlds / verification / SPA selectors
The three most common traps when debugging injected apps: misjudging which world a script runs in, "verifying" interactions with unreliable methods, and relying on unstable selectors in SPAs. This section gives a reusable methodology.
World semantics: where does the script actually run
| Execution path | World | Visibility |
|---|---|---|
Installed app / liveDebug on | USER_SCRIPT (isolated) | Shares the page DOM but not JS globals: page-framework window variables are invisible to you, and yours to the page |
run --tab <target> (default) | MAIN (page world) | Same environment as page JS; cannot see the liveDebug app's globals living in the USER_SCRIPT world |
run --tab <target> --world user-script | USER_SCRIPT (same world as liveDebug) | Can directly probe the liveDebug app's globals and listener state |
LFRuntime.runInMainWorld(fn) | USER_SCRIPT → MAIN | Read page-framework data (e.g. React/Vue internals) from inside the app |
The most common misdiagnosis
Probing "did my app mount?" with plain run --tab, seeing window.__myPanel === undefined, and concluding the injection failed — while the script is running fine in the USER_SCRIPT world where you cannot see it from MAIN. Probing liveDebug app state requires --world user-script.
Both worlds share the same DOM: DOM nodes and DOM events (including dispatchEvent) cross world boundaries. Only JS globals, prototype patches, and closures stay isolated.
Verification methodology: observe → act → assert
Observe: snapshot before touching anything
Run
snapshotText --tab <t>to get an accessibility snapshot with@Nrefs; confirm the target element exists with the expected role and text. Never write selectors from memory.Act: trusted input, not synthetic events
input hover/click/fill --ref @Ndispatches trusted input via CDP: it triggers CSS:hover, framework-native event delegation, and browser default behavior.dispatchEvent(new MouseEvent(...))is synthetic (isTrusted: false), does not trigger:hoverstyles, and some frameworks ignore it. Verifying hover-revealed buttons requiresinput hover.Assert: check output, not "no error"
After acting, run
snapshotTextagain (or useinput ... --expect ...) to confirm the UI reached the expected state. "Command succeeded" does not mean "the page changed as expected".
Hypothesis triage: when behavior is off, run one minimal probe to distinguish "not injected" / "injected but not mounted" / "mounted but selector missed" / "matched but event never fired" before editing code. Verify one hypothesis per round.
SPA selector guidance
- Class names are usually build-time hashes (e.g.
feed-active-video-xK3f) and break on every release; prefer semantic attributes (role/aria-*/data-*), stable text, orsnapshotText@Nrefs - The same list may use different classes per state (e.g. active
feed-active-videovs plainfeed-video); snapshot each state and compare before writing a selector - SPA route changes (
history.pushState) keep the same document: liveDebug does not reinject, so your MutationObserver / event delegation must cover newly rendered nodes; useLFDOM.waitForElement/LFDOM.locator(auto-wait and retry) instead of a one-shotquerySelector - Virtual lists only render visible rows —
querySelectorAllcount ≠ total data; collect withLFDOM.findInVirtualList - Hover-revealed widgets (menus, tooltips) may be absent from the snapshot:
input hover --reffirst, then take a second snapshot