Skip to content

Commit be2a3f9

Browse files
author
DavertMik
committed
added shadow dom support
1 parent 6adfb39 commit be2a3f9

File tree

3 files changed

+46
-1
lines changed

3 files changed

+46
-1
lines changed

lib/helper/Playwright.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4096,6 +4096,11 @@ function buildLocatorString(locator) {
40964096
if (locator.isXPath()) {
40974097
return `xpath=${locator.value}`
40984098
}
4099+
if (locator.isShadow()) {
4100+
// Convert shadow locator to CSS with >>> deep descendant combinator
4101+
// { shadow: ['my-app', 'recipe-hello', 'button'] } => 'my-app >>> recipe-hello >>> button'
4102+
return locator.value.join(' >>> ')
4103+
}
40994104
return locator.simplify()
41004105
}
41014106

lib/helper/Puppeteer.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2900,10 +2900,17 @@ async function findElements(matcher, locator) {
29002900
if (isReactLocator) return findReactElements.call(this, locator)
29012901

29022902
locator = new Locator(locator, 'css')
2903-
2903+
29042904
// Check if locator is a role locator and call findByRole
29052905
if (locator.isRole()) return findByRole.call(this, matcher, locator)
29062906

2907+
// Handle shadow DOM locators with >>> deep descendant combinator
2908+
// { shadow: ['my-app', 'recipe-hello', 'button'] } => 'my-app >>> recipe-hello >>> button'
2909+
if (locator.isShadow()) {
2910+
const shadowSelector = locator.value.join(' >>> ')
2911+
return matcher.$$(shadowSelector)
2912+
}
2913+
29072914
// Use proven legacy approach - Puppeteer Locator API doesn't have .all() method
29082915
if (!locator.isXPath()) return matcher.$$(locator.simplify())
29092916

@@ -2967,6 +2974,13 @@ async function findElement(matcher, locator) {
29672974
return elements[0]
29682975
}
29692976

2977+
// Handle shadow DOM locators with >>> deep descendant combinator
2978+
if (locator.isShadow()) {
2979+
const shadowSelector = locator.value.join(' >>> ')
2980+
const elements = await matcher.$$(shadowSelector)
2981+
return elements[0]
2982+
}
2983+
29702984
// Use proven legacy approach - Puppeteer Locator API doesn't have .first() method
29712985
if (!locator.isXPath()) {
29722986
const elements = await matcher.$$(locator.simplify())

test/helper/webapi.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,32 @@ export function tests() {
517517
})
518518
})
519519

520+
describe('#shadow DOM', () => {
521+
it('should click button inside shadow DOM', async () => {
522+
await I.amOnPage('/form/shadow_dom')
523+
await I.click({ shadow: ['my-button', 'button'] })
524+
await I.see('my-button > button', '#clicked-element')
525+
})
526+
527+
it('should click button in nested shadow DOM', async () => {
528+
await I.amOnPage('/form/shadow_dom')
529+
await I.click({ shadow: ['my-app', 'my-form', 'button'] })
530+
await I.see('my-app > my-form > button', '#clicked-element')
531+
})
532+
533+
it('should fill field inside nested shadow DOM', async () => {
534+
await I.amOnPage('/form/shadow_dom')
535+
await I.fillField({ shadow: ['my-app', 'my-form', 'input'] }, 'Shadow Test')
536+
await I.see('Shadow Test', '#input-value')
537+
})
538+
539+
it('should work with shadow locator as JSON string', async () => {
540+
await I.amOnPage('/form/shadow_dom')
541+
await I.click('{"shadow": ["my-button", "button"]}')
542+
await I.see('my-button > button', '#clicked-element')
543+
})
544+
})
545+
520546
describe('#executeScript', () => {
521547
it('should execute synchronous script', async () => {
522548
await I.amOnPage('/')

0 commit comments

Comments
 (0)