diff --git a/packages/playwright-core/src/server/injected/injectedScript.ts b/packages/playwright-core/src/server/injected/injectedScript.ts index 69fe959f81..53cf647cfb 100644 --- a/packages/playwright-core/src/server/injected/injectedScript.ts +++ b/packages/playwright-core/src/server/injected/injectedScript.ts @@ -65,6 +65,7 @@ export class InjectedScript { readonly isUnderTest: boolean; private _sdkLanguage: Language; private _testIdAttributeNameForStrictErrorAndConsoleCodegen: string = 'data-testid'; + private _markedElements?: { callId: string, elements: Set }; // eslint-disable-next-line no-restricted-globals readonly window: Window & typeof globalThis; readonly document: Document; @@ -1081,14 +1082,33 @@ export class InjectedScript { } markTargetElements(markedElements: Set, callId: string) { - const customEvent = new CustomEvent('__playwright_target__', { + if (this._markedElements?.callId !== callId) + this._markedElements = undefined; + const previous = this._markedElements?.elements || new Set(); + + const unmarkEvent = new CustomEvent('__playwright_unmark_target__', { bubbles: true, cancelable: true, detail: callId, composed: true, }); - for (const element of markedElements) - element.dispatchEvent(customEvent); + for (const element of previous) { + if (!markedElements.has(element)) + element.dispatchEvent(unmarkEvent); + } + + const markEvent = new CustomEvent('__playwright_mark_target__', { + bubbles: true, + cancelable: true, + detail: callId, + composed: true, + }); + for (const element of markedElements) { + if (!previous.has(element)) + element.dispatchEvent(markEvent); + } + + this._markedElements = { callId, elements: markedElements }; } private _setupGlobalListenersRemovalDetection() { diff --git a/packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts b/packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts index e881d52313..47385c8323 100644 --- a/packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts +++ b/packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts @@ -139,12 +139,19 @@ export function frameSnapshotStreamer(snapshotStreamer: string, removeNoScript: } private _refreshListeners() { - (document as any).addEventListener('__playwright_target__', (event: CustomEvent) => { + (document as any).addEventListener('__playwright_mark_target__', (event: CustomEvent) => { if (!event.detail) return; const callId = event.detail as string; (event.composedPath()[0] as any).__playwright_target__ = callId; }); + (document as any).addEventListener('__playwright_unmark_target__', (event: CustomEvent) => { + if (!event.detail) + return; + const callId = event.detail as string; + if ((event.composedPath()[0] as any).__playwright_target__ === callId) + delete (event.composedPath()[0] as any).__playwright_target__; + }); } private _interceptNativeMethod(obj: any, method: string, cb: (thisObj: any, result: any) => void) { diff --git a/tests/library/trace-viewer.spec.ts b/tests/library/trace-viewer.spec.ts index 99117c2fe9..f369051a69 100644 --- a/tests/library/trace-viewer.spec.ts +++ b/tests/library/trace-viewer.spec.ts @@ -761,7 +761,7 @@ test('should highlight target elements', async ({ page, runAndTrace, browserName await page.setContent(`
t1
t2
-
t3
+
t3
t4
t5
t6
@@ -778,6 +778,11 @@ test('should highlight target elements', async ({ page, runAndTrace, browserName await page.mouse.move(123, 234); await page.getByText(/^t\d$/).click().catch(() => {}); await expect(page.getByText(/t3|t4/)).toBeVisible().catch(() => {}); + + const expectPromise = expect(page.getByText(/t3|t4/)).toHaveText(['t4']); + await page.waitForTimeout(1000); + await page.evaluate(() => document.querySelector('#div3').textContent = 'changed'); + await expectPromise; }); async function highlightedDivs(frameLocator: FrameLocator) { @@ -825,6 +830,9 @@ test('should highlight target elements', async ({ page, runAndTrace, browserName const frameExpectStrictViolation = await traceViewer.snapshotFrame('expect.toBeVisible'); await expect.poll(() => highlightedDivs(frameExpectStrictViolation)).toEqual(['t3', 't4']); + + const frameUpdatedListOfTargets = await traceViewer.snapshotFrame('expect.toHaveText', 2); + await expect.poll(() => highlightedDivs(frameUpdatedListOfTargets)).toEqual(['t4']); }); test('should highlight target element in shadow dom', async ({ page, server, runAndTrace }) => {