From 81d4b4334b43f5884d562db3e36fb6835783aafe Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Fri, 25 Jun 2021 23:08:45 -0700 Subject: [PATCH] cherry-pick(release-1.12): account for last child node removal (#7335) PR #7332 SHA 02538fb5873ff527aa1cb61f9a7dcb711abbf722 Co-authored-by: Pavel Feldman --- src/server/snapshot/snapshotterInjected.ts | 18 ++++++++++-------- tests/snapshotter.spec.ts | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/server/snapshot/snapshotterInjected.ts b/src/server/snapshot/snapshotterInjected.ts index 50823160dd120..526cce8dddff3 100644 --- a/src/server/snapshot/snapshotterInjected.ts +++ b/src/server/snapshot/snapshotterInjected.ts @@ -44,6 +44,7 @@ export function frameSnapshotStreamer(snapshotStreamer: string) { // Symbols for our own info on Nodes/StyleSheets. const kSnapshotFrameId = Symbol('__playwright_snapshot_frameid_'); const kCachedData = Symbol('__playwright_snapshot_cache_'); + const kEndOfList = Symbol('__playwright_end_of_list_'); type CachedData = { cached?: any[], // Cached values to determine whether the snapshot will be the same. ref?: [number, number], // Previous snapshotNumber and nodeIndex. @@ -355,6 +356,7 @@ export function frameSnapshotStreamer(snapshotStreamer: string) { } for (let child = node.firstChild; child; child = child.nextSibling) visitChild(child); + expectValue(kEndOfList); let documentOrShadowRoot = null; if (node.ownerDocument!.documentElement === node) documentOrShadowRoot = node.ownerDocument; @@ -363,20 +365,19 @@ export function frameSnapshotStreamer(snapshotStreamer: string) { if (documentOrShadowRoot) { for (const sheet of (documentOrShadowRoot as any).adoptedStyleSheets || []) visitChildStyleSheet(sheet); + expectValue(kEndOfList); } } // Process iframe src attribute before bailing out since it depends on a symbol, not the DOM. if (nodeName === 'IFRAME' || nodeName === 'FRAME') { const element = node as Element; - for (let i = 0; i < element.attributes.length; i++) { - const frameId = (element as any)[kSnapshotFrameId]; - const name = 'src'; - const value = frameId ? `/snapshot/${frameId}` : ''; - expectValue(name); - expectValue(value); - attrs[name] = value; - } + const frameId = (element as any)[kSnapshotFrameId]; + const name = 'src'; + const value = frameId ? `/snapshot/${frameId}` : ''; + expectValue(name); + expectValue(value); + attrs[name] = value; } // We can skip attributes comparison because nothing else has changed, @@ -409,6 +410,7 @@ export function frameSnapshotStreamer(snapshotStreamer: string) { expectValue(value); attrs[name] = value; } + expectValue(kEndOfList); } if (result.length === 2 && !Object.keys(attrs).length) diff --git a/tests/snapshotter.spec.ts b/tests/snapshotter.spec.ts index 6c1b344ba29d4..0063f75767c39 100644 --- a/tests/snapshotter.spec.ts +++ b/tests/snapshotter.spec.ts @@ -75,6 +75,26 @@ it.describe('snapshots', () => { expect(distillSnapshot(snapshot2)).toBe(''); }); + it('should respect node removal', async ({ page, toImpl, snapshotter }) => { + page.on('console', console.log); + await page.setContent('
'); + const snapshot1 = await snapshotter.captureSnapshot(toImpl(page), 'snapshot1'); + expect(distillSnapshot(snapshot1)).toBe('
'); + await page.evaluate(() => document.getElementById('button2').remove()); + const snapshot2 = await snapshotter.captureSnapshot(toImpl(page), 'snapshot2'); + expect(distillSnapshot(snapshot2)).toBe('
'); + }); + + it('should respect attr removal', async ({ page, toImpl, snapshotter }) => { + page.on('console', console.log); + await page.setContent('
'); + const snapshot1 = await snapshotter.captureSnapshot(toImpl(page), 'snapshot1'); + expect(distillSnapshot(snapshot1)).toBe('
'); + await page.evaluate(() => document.getElementById('div').removeAttribute('attr2')); + const snapshot2 = await snapshotter.captureSnapshot(toImpl(page), 'snapshot2'); + expect(distillSnapshot(snapshot2)).toBe('
'); + }); + it('should have a custom doctype', async ({page, server, toImpl, snapshotter}) => { await page.goto(server.EMPTY_PAGE); await page.setContent('hi');