71 lines
1.6 KiB
Markdown
71 lines
1.6 KiB
Markdown
# Shadow DOM & Web Components Testing
|
|
|
|
## Challenges
|
|
|
|
- CSS encapsulation breaks selectors
|
|
- Elements hidden from DOM queries
|
|
- XPath doesn't penetrate shadow boundaries
|
|
|
|
## Tool Support
|
|
|
|
| Tool | Support | Method |
|
|
|------|---------|--------|
|
|
| Playwright | Native | `>>` piercing selector |
|
|
| Cypress | Good | `.shadow()` command |
|
|
| Selenium | Limited | JS execution |
|
|
| Axe | v5.7+ | API support |
|
|
|
|
## Playwright Shadow Piercing
|
|
|
|
```javascript
|
|
const input = page.locator('my-component >> .internal-input');
|
|
const button = page.locator('comp-a >> comp-b >> button');
|
|
const el = page.locator('custom-element >> button:has-text("Click me")');
|
|
```
|
|
|
|
## Cypress Shadow DOM
|
|
|
|
```javascript
|
|
cy.get('my-component').shadow().find('.internal-button').click();
|
|
|
|
// Enable globally: { includeShadowDom: true }
|
|
```
|
|
|
|
## Selenium Workaround
|
|
|
|
```javascript
|
|
const shadowHost = driver.findElement(By.css('my-component'));
|
|
const shadowRoot = driver.executeScript('return arguments[0].shadowRoot', shadowHost);
|
|
const button = shadowRoot.findElement(By.css('button'));
|
|
```
|
|
|
|
## Page Object Pattern
|
|
|
|
```typescript
|
|
export class MyComponentPO {
|
|
constructor(private page: Page) {}
|
|
|
|
async fillEmail(email: string) {
|
|
await this.page.locator('my-form >> input[type="email"]').fill(email);
|
|
}
|
|
|
|
async submit() {
|
|
await this.page.locator('my-form >> button[type="submit"]').click();
|
|
}
|
|
}
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. Request `open` shadow roots when possible
|
|
2. Encapsulate shadow traversal in page objects
|
|
3. Avoid deep nesting (increases complexity)
|
|
|
|
## Debugging
|
|
|
|
```javascript
|
|
const contents = await page.evaluate(() => {
|
|
return document.querySelector('my-component').shadowRoot.innerHTML;
|
|
});
|
|
```
|