fix(trace viewer): clear old highlighted elements upon change (#32917)

When the list of highlighted elements changes over time, we should
update the elements marked as `__playwright_target__` in the snapshot.

A good example is an `expect(locator).toHaveText([...])` where the list
of elements changes from 4 items to 3 after clicking a "Delete" button.
This commit is contained in:
Dmitry Gozman 2024-10-02 23:48:26 -07:00 committed by GitHub
parent 616425a0fb
commit 3c5967d4f5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 40 additions and 5 deletions

View File

@ -65,6 +65,7 @@ export class InjectedScript {
readonly isUnderTest: boolean;
private _sdkLanguage: Language;
private _testIdAttributeNameForStrictErrorAndConsoleCodegen: string = 'data-testid';
private _markedElements?: { callId: string, elements: Set<Element> };
// 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<Element>, 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() {

View File

@ -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) {

View File

@ -761,7 +761,7 @@ test('should highlight target elements', async ({ page, runAndTrace, browserName
await page.setContent(`
<div>t1</div>
<div>t2</div>
<div>t3</div>
<div id=div3>t3</div>
<div>t4</div>
<div>t5</div>
<div>t6</div>
@ -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 }) => {