fix(snapshots): define dummy custom elements (#21131)
For all custom elements defined in the page, we preserve their names and define them in the rendered snapshot. This makes things like `:defined` css pseudo work. Fixes #21030.
This commit is contained in:
parent
55c95a4463
commit
2718123d30
|
|
@ -45,6 +45,7 @@ export function frameSnapshotStreamer(snapshotStreamer: string) {
|
|||
const kScrollLeftAttribute = '__playwright_scroll_left_';
|
||||
const kStyleSheetAttribute = '__playwright_style_sheet_';
|
||||
const kTargetAttribute = '__playwright_target__';
|
||||
const kCustomElementsAttribute = '__playwright_custom_elements__';
|
||||
|
||||
// Symbols for our own info on Nodes/StyleSheets.
|
||||
const kSnapshotFrameId = Symbol('__playwright_snapshot_frameid_');
|
||||
|
|
@ -296,6 +297,8 @@ export function frameSnapshotStreamer(snapshotStreamer: string) {
|
|||
if (document.documentElement)
|
||||
findElementsToRestoreScrollPositionRecursively(document.documentElement);
|
||||
|
||||
const definedCustomElements = new Set<string>();
|
||||
|
||||
const visitNode = (node: Node | ShadowRoot): { equals: boolean, n: NodeSnapshot } | undefined => {
|
||||
const nodeType = node.nodeType;
|
||||
const nodeName = nodeType === Node.DOCUMENT_FRAGMENT_NODE ? 'template' : node.nodeName;
|
||||
|
|
@ -385,6 +388,8 @@ export function frameSnapshotStreamer(snapshotStreamer: string) {
|
|||
|
||||
if (nodeType === Node.ELEMENT_NODE) {
|
||||
const element = node as Element;
|
||||
if (element.localName.includes('-') && window.customElements?.get(element.localName))
|
||||
definedCustomElements.add(element.localName);
|
||||
if (nodeName === 'INPUT' || nodeName === 'TEXTAREA') {
|
||||
const value = (element as HTMLInputElement).value;
|
||||
expectValue(kValueAttribute);
|
||||
|
|
@ -453,6 +458,14 @@ export function frameSnapshotStreamer(snapshotStreamer: string) {
|
|||
attrs[name] = value;
|
||||
}
|
||||
|
||||
// Process custom elements before bailing out since they depend on JS, not the DOM.
|
||||
if (nodeName === 'BODY' && definedCustomElements.size) {
|
||||
const value = [...definedCustomElements].join(',');
|
||||
expectValue(kCustomElementsAttribute);
|
||||
expectValue(value);
|
||||
attrs[kCustomElementsAttribute] = value;
|
||||
}
|
||||
|
||||
// We can skip attributes comparison because nothing else has changed,
|
||||
// and mutation observer didn't tell us about the attributes.
|
||||
if (equals && data.attributesCached && !shadowDomNesting)
|
||||
|
|
|
|||
|
|
@ -229,6 +229,15 @@ function snapshotScript() {
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
const body = root.querySelector(`body[__playwright_custom_elements__]`);
|
||||
if (body && window.customElements) {
|
||||
const customElements = (body.getAttribute('__playwright_custom_elements__') || '').split(',');
|
||||
for (const elementName of customElements)
|
||||
window.customElements.define(elementName, class extends HTMLElement {});
|
||||
}
|
||||
}
|
||||
|
||||
for (const element of root.querySelectorAll(`template[__playwright_shadow_root_]`)) {
|
||||
const template = element as HTMLTemplateElement;
|
||||
const shadowRoot = template.parentElement!.attachShadow({ mode: 'open' });
|
||||
|
|
|
|||
|
|
@ -526,6 +526,36 @@ test('should handle src=blob', async ({ page, server, runAndTrace, browserName }
|
|||
expect(size).toBe(10);
|
||||
});
|
||||
|
||||
test('should register custom elements', async ({ page, server, runAndTrace }) => {
|
||||
const traceViewer = await runAndTrace(async () => {
|
||||
page.on('console', console.log);
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
await page.evaluate(() => {
|
||||
customElements.define('my-element', class extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
const shadow = this.attachShadow({ mode: 'open' });
|
||||
const span = document.createElement('span');
|
||||
span.textContent = 'hello';
|
||||
shadow.appendChild(span);
|
||||
shadow.appendChild(document.createElement('slot'));
|
||||
}
|
||||
});
|
||||
});
|
||||
await page.setContent(`
|
||||
<style>
|
||||
:not(:defined) {
|
||||
visibility: hidden;
|
||||
}
|
||||
</style>
|
||||
<MY-element>world</MY-element>
|
||||
`);
|
||||
});
|
||||
|
||||
const frame = await traceViewer.snapshotFrame('page.setContent');
|
||||
await expect(frame.getByText('worldhello')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should highlight target elements', async ({ page, runAndTrace, browserName }) => {
|
||||
const traceViewer = await runAndTrace(async () => {
|
||||
await page.setContent(`
|
||||
|
|
|
|||
Loading…
Reference in a new issue