diff --git a/packages/browser/src/client/tester/trace.ts b/packages/browser/src/client/tester/trace.ts index 28cca3921..e5df35302 100644 --- a/packages/browser/src/client/tester/trace.ts +++ b/packages/browser/src/client/tester/trace.ts @@ -55,14 +55,15 @@ interface TraceSnapshot { pseudoClassIds: Record } -// rrweb-snapshot rewrites pseudo-class selectors in serialized styles so replay can -// reproduce snapshot-time states. For example: +// Dynamic pseudo-class state isn't preserved in the serialized DOM. rrweb-snapshot +// rewrites user-action selectors in serialized styles. For example: // some-selector:hover { ... } // becomes: // some-selector:hover, some-selector.\:hover { ... } // Vitest side integration then adds matching pseudo-state classes in the replay DOM. // rrweb-snapshot only handles `:hover` upstream, so we patch it locally for the -// other user-action pseudo-classes as well. +// other user-action pseudo-classes as well. Native states such as `:popover-open` +// are restored through their DOM API instead. // https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/Pseudo-classes#user_action_pseudo-classes const PSEUDO_CLASS_NAMES = [ ':hover', @@ -70,6 +71,7 @@ const PSEUDO_CLASS_NAMES = [ ':focus', ':focus-visible', ':focus-within', + ':popover-open', ] as const type PseudoClassName = (typeof PSEUDO_CLASS_NAMES)[number] diff --git a/packages/ui/client/components/trace/TraceView.vue b/packages/ui/client/components/trace/TraceView.vue index 744debad2..303adbbd8 100644 --- a/packages/ui/client/components/trace/TraceView.vue +++ b/packages/ui/client/components/trace/TraceView.vue @@ -83,8 +83,11 @@ watch([selectedStep, iframeEl], ([step, iframe]) => { }) for (const [className, ids] of Object.entries(pseudoClassIds)) { for (const id of ids) { - const el = mirror.getNode(id) as Element | null - if (el?.classList) { + const el = mirror.getNode(id) as HTMLElement | null + if (className === ':popover-open') { + el?.showPopover?.() + } + else if (el?.classList) { el.classList.add(className) } } diff --git a/test/ui/fixtures/trace/basic.test.ts b/test/ui/fixtures/trace/basic.test.ts index 173f2f731..46b5dbd1d 100644 --- a/test/ui/fixtures/trace/basic.test.ts +++ b/test/ui/fixtures/trace/basic.test.ts @@ -18,6 +18,15 @@ test('switch-target', async () => { await page.getByRole('button').mark('Render switch target') }) +test('popover', async () => { + document.body.innerHTML = '
Popover content
' + const popover = document.querySelector('[popover]')! + const popoverContent = page.getByText('Popover content') + await popoverContent.mark('Render closed popover') + popover.showPopover() + await popoverContent.mark('Render open popover') +}) + test('pseudo-state', async () => { document.body.innerHTML = `