checkly/headless-recorder

Support custom elements with Shadow DOM

elf-pavlik opened this issue ยท 11 comments

related issue: puppeteer/puppeteer#4171

When I record interaction with this plugin, it doesn't seem to see anything in shadow dom of custom elements and just records events on parent in the top level dom.

I have the same issue. Things are working fine with a native input, but when I type in a web component it creates a page.type call but the text is undefined.
Definitely shadow DOM related: things work as expected if I turn shadow DOM off in the same component.

...
await page.type('body > div > input', 'Hello')
await page.type('body #my-component', 'undefined')
...

@elf-pavlik @mrtnmgs thanks for reporting. It would really help if I have a reproducable tests case. Either a small project or live web page to test this out on.

for example https://demo.vaadin.com/invoice-editor-app/ has each input inside different shadow dom

If we have a JavaScript context here https://github.com/checkly/puppeteer-recorder/blob/920f786695a844ff07e04cadf2f017b79215faca/src/content-scripts/EventRecorder.js#L96 then we could get the composedPath() of the event. At that point, we can recurse over the path, and anywhere we find a shadow root, we can interpolate some magic string like /PUPPETEER_RECORDER_DEEP/ into the selector.

Later, in the puppeteer script, when we find a selectors that include that string, we can split it and drill down into the shadow root

@bennypowers this sounds like an interesting direction. The most important thing would be it doesn't break current behaviour, as shadow DOM is probably not as widely used as "plain old DOM"

The most important thing would be it doesn't break current behaviour

Seems legit to rely on our own magic string in the selector. For extra security, you could recursively walk up the DOM through the composedPath() and check for the existence of shadowRoots first

while (host = host.getRootNode().host) this.#shadowHosts.push(host);
// pseudocode
if (!this.#shadowHosts.length) normalSelector()
else shadowSelector()

then while replaying

// pseudocode
if (selector.includes(MAGIC_SHADOW_DOM_STRING)) sneakThroughTheShadows(selector)
else processNormally(selector)

Maybe recording could provide jsPath as suggested in puppeteer/puppeteer#858 (comment) and later in puppeteer/puppeteer#4171

Any update on this?

@sghsri no yet.
We recently released a new version of HR (new design, bug fixes and UX improvements).
I will start researching on this and find possible workarounds

Hello, in a project that I am part of, we write our UI apps in stencil JS web components. We seek for quiet a while for a solution that records user interactions for automation purposes. It will be great if you solve this.