feat: Highlight elements when hovering selector in debugger view#1129
feat: Highlight elements when hovering selector in debugger view#1129
Conversation
- Add onHighlight callback through BrowserAction component tree - Create DebuggerHighlightContext to manage highlighted selector state - Expose Replayer instance from usePlayer hook - Create ReplayerHighlights component to query and display overlays - Implements issue #1063 Co-authored-by: Johan Suleiko Allansson <allansson@users.noreply.github.com>
…menu-b1e0 Co-authored-by: Johan Suleiko Allansson <allansson@users.noreply.github.com>
- Create shared findElementsBySelector utility using testing-library - Update debugger to use NodeSelector instead of CSS-only selectors - Support role, testId, alt, label, placeholder, text, title, and css selectors - Share logic between recorder and debugger highlighting - Update RemoteHighlights to use shared utility This ensures all selector types work consistently in both recorder and debugger views, using the same testing-library query functions. Co-authored-by: Johan Suleiko Allansson <allansson@users.noreply.github.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: ResizeObserver observes wrong element in cross-frame context
- Changed ResizeObserver to observe the element parameter instead of hardcoded document.body, ensuring it watches the correct document context (iframe or main page).
- ✅ Fixed: Scroll offset uses wrong window for iframe elements
- Modified toBounds and getElementBounds to use the element's ownerDocument.defaultView instead of global window, ensuring correct scroll offsets for iframe elements.
Or push these changes by commenting:
@cursor push cb21d467f3
Preview (cb21d467f3)
diff --git a/extension/src/frontend/view/TextSelectionPopover.hooks.ts b/extension/src/frontend/view/TextSelectionPopover.hooks.ts
--- a/extension/src/frontend/view/TextSelectionPopover.hooks.ts
+++ b/extension/src/frontend/view/TextSelectionPopover.hooks.ts
@@ -7,8 +7,11 @@
import { TextSelection } from './TextSelectionPopover.types'
function measureRange(range: Range) {
+ const ownerWindow = range.startContainer.ownerDocument?.defaultView ?? window
return {
- highlights: Array.from(range.getClientRects()).map(toBounds),
+ highlights: Array.from(range.getClientRects()).map((rect) =>
+ toBounds(rect, ownerWindow)
+ ),
bounds: getElementBounds(range),
}
}
diff --git a/src/components/Browser/ElementHighlights.hooks.ts b/src/components/Browser/ElementHighlights.hooks.ts
--- a/src/components/Browser/ElementHighlights.hooks.ts
+++ b/src/components/Browser/ElementHighlights.hooks.ts
@@ -65,7 +65,7 @@
})
})
- observer.observe(document.body)
+ observer.observe(element)
return () => {
observer.disconnect()
diff --git a/src/components/Browser/utils.ts b/src/components/Browser/utils.ts
--- a/src/components/Browser/utils.ts
+++ b/src/components/Browser/utils.ts
@@ -1,18 +1,22 @@
import { Bounds } from './types'
-export function toBounds(rect: DOMRect): Bounds {
+export function toBounds(rect: DOMRect, ownerWindow: Window): Bounds {
// `getBoundingClientRect` returns the coordinates relative to the viewport
// and not the document, so we add the scroll position so that the element
// is relative to the page instead. This means that content will stay in place
// when scrolling.
return {
- top: rect.top + window.scrollY,
- left: rect.left + window.scrollX,
+ top: rect.top + ownerWindow.scrollY,
+ left: rect.left + ownerWindow.scrollX,
width: rect.width,
height: rect.height,
}
}
export function getElementBounds(element: Element | Range): Bounds {
- return toBounds(element.getBoundingClientRect())
+ const ownerWindow =
+ element instanceof Range
+ ? (element.startContainer.ownerDocument?.defaultView ?? window)
+ : (element.ownerDocument?.defaultView ?? window)
+ return toBounds(element.getBoundingClientRect(), ownerWindow)
}You can send follow-ups to this agent here.
| }) | ||
| }) | ||
|
|
||
| observer.observe(document.body) |
There was a problem hiding this comment.
ResizeObserver observes wrong element in cross-frame context
Medium Severity
The ResizeObserver is hardcoded to observe document.body, but useHighlightedElements is now a shared hook that can receive an element from a different document context. When used from the debugger via SelectorHighlights, the element is the rrweb iframe's documentElement, but the observer watches the main app's document.body instead. Layout changes inside the replay iframe won't trigger highlight repositioning. The element parameter (already available and used in the first useEffect) needs to be used here as well.
Additional Locations (1)
| // when scrolling. | ||
| return { | ||
| top: rect.top + window.scrollY, | ||
| left: rect.left + window.scrollX, |
There was a problem hiding this comment.
Scroll offset uses wrong window for iframe elements
Low Severity
toBounds adds window.scrollY and window.scrollX to convert viewport-relative coordinates to document-relative ones. This is correct in the extension context (where window is the page's window), but when getElementBounds is called on elements inside the rrweb iframe from the main app (via SelectorHighlights → useHighlightedElements), window refers to the main app's window, not the iframe's. Any non-zero main-window scroll would offset highlight overlays from the correct position.
Additional Locations (1)
e-fisher
left a comment
There was a problem hiding this comment.
LGTM
Would be nice if replay would automatically rewind to the time when selector existed when clicked, but perhaps that's already planned for the future



Description
Implements element highlighting in the debugger view when hovering over selectors, with full support for all selector types (role, testId, alt, label, placeholder, text, title, and css).
Key Changes:
findElementsBySelectorutility using testing-library's query functionsNodeSelectorinstead of CSS-only selectorsWhen you hover over any locator in the browser actions list, the corresponding element(s) in the replay iframe are highlighted using the appropriate testing-library query function (e.g.,
queryByRole,queryByTestId, etc.).How to Test
You also need to test that hover work in the recorder:
Checklist
Related PR(s)/Issue(s)
Resolves #1063