diff --git a/packages/trace-viewer/src/snapshotRenderer.ts b/packages/trace-viewer/src/snapshotRenderer.ts index dcea1d9670..e156dcbaf9 100644 --- a/packages/trace-viewer/src/snapshotRenderer.ts +++ b/packages/trace-viewer/src/snapshotRenderer.ts @@ -197,6 +197,7 @@ function snapshotScript(...targetIds: (string | undefined)[]) { function applyPlaywrightAttributes(unwrapPopoutUrl: (url: string) => string, ...targetIds: (string | undefined)[]) { const scrollTops: Element[] = []; const scrollLefts: Element[] = []; + const targetElements: Element[] = []; const visit = (root: Document | ShadowRoot) => { // Collect all scrolled elements for later use. @@ -223,6 +224,7 @@ function snapshotScript(...targetIds: (string | undefined)[]) { const style = (target as HTMLElement).style; style.outline = '2px solid #006ab1'; style.backgroundColor = '#6fa8dc7f'; + targetElements.push(target); } } @@ -231,10 +233,8 @@ function snapshotScript(...targetIds: (string | undefined)[]) { if (!src) { iframe.setAttribute('src', 'data:text/html,'); } else { - // Append query parameters to inherit ?name= or ?time= values from parent. + // Retain query parameters to inherit name=, time=, showPoint= and other values from parent. const url = new URL(unwrapPopoutUrl(window.location.href)); - url.searchParams.delete('pointX'); - url.searchParams.delete('pointY'); // We can be loading iframe from within iframe, reset base to be absolute. const index = url.pathname.lastIndexOf('/snapshot/'); if (index !== -1) @@ -284,23 +284,25 @@ function snapshotScript(...targetIds: (string | undefined)[]) { element.removeAttribute('__playwright_scroll_left_'); } - const search = new URL(window.location.href).searchParams; - const pointX = search.get('pointX'); - const pointY = search.get('pointY'); - if (pointX) { - const pointElement = document.createElement('x-pw-pointer'); - pointElement.style.position = 'fixed'; - pointElement.style.backgroundColor = '#f44336'; - pointElement.style.width = '20px'; - pointElement.style.height = '20px'; - pointElement.style.borderRadius = '10px'; - pointElement.style.margin = '-10px 0 0 -10px'; - pointElement.style.zIndex = '2147483647'; - pointElement.style.left = pointX + 'px'; - pointElement.style.top = pointY + 'px'; - document.documentElement.appendChild(pointElement); - } document.styleSheets[0].disabled = true; + + const search = new URL(window.location.href).searchParams; + if (search.get('showPoint')) { + for (const target of targetElements) { + const pointElement = document.createElement('x-pw-pointer'); + pointElement.style.position = 'fixed'; + pointElement.style.backgroundColor = '#f44336'; + pointElement.style.width = '20px'; + pointElement.style.height = '20px'; + pointElement.style.borderRadius = '10px'; + pointElement.style.margin = '-10px 0 0 -10px'; + pointElement.style.zIndex = '2147483647'; + const box = target.getBoundingClientRect(); + pointElement.style.left = (box.left + box.width / 2) + 'px'; + pointElement.style.top = (box.top + box.height / 2) + 'px'; + document.documentElement.appendChild(pointElement); + } + } }; const onDOMContentLoaded = () => visit(document); diff --git a/packages/trace-viewer/src/ui/snapshotTab.tsx b/packages/trace-viewer/src/ui/snapshotTab.tsx index b6efbd211a..c3d4919336 100644 --- a/packages/trace-viewer/src/ui/snapshotTab.tsx +++ b/packages/trace-viewer/src/ui/snapshotTab.tsx @@ -42,23 +42,24 @@ export const SnapshotTab: React.FunctionComponent<{ const [measure, ref] = useMeasure(); const [snapshotTab, setSnapshotTab] = React.useState<'action'|'before'|'after'>('action'); + type Snapshot = { action: ActionTraceEvent, snapshotName: string, showPoint?: boolean }; const { snapshots } = React.useMemo(() => { if (!action) return { snapshots: {} }; // if the action has no beforeSnapshot, use the last available afterSnapshot. - let beforeSnapshot = action.beforeSnapshot ? { action, snapshotName: action.beforeSnapshot } : undefined; + let beforeSnapshot: Snapshot | undefined = action.beforeSnapshot ? { action, snapshotName: action.beforeSnapshot } : undefined; let a = action; while (!beforeSnapshot && a) { a = prevInList(a); beforeSnapshot = a?.afterSnapshot ? { action: a, snapshotName: a?.afterSnapshot } : undefined; } - const afterSnapshot = action.afterSnapshot ? { action, snapshotName: action.afterSnapshot } : beforeSnapshot; - const actionSnapshot = action.inputSnapshot ? { action, snapshotName: action.inputSnapshot } : afterSnapshot; + const afterSnapshot: Snapshot | undefined = action.afterSnapshot ? { action, snapshotName: action.afterSnapshot } : beforeSnapshot; + const actionSnapshot: Snapshot | undefined = action.inputSnapshot ? { action, snapshotName: action.inputSnapshot, showPoint: !!action.point } : afterSnapshot; return { snapshots: { action: actionSnapshot, before: beforeSnapshot, after: afterSnapshot } }; }, [action]); - const { snapshotInfoUrl, snapshotUrl, pointX, pointY, popoutUrl } = React.useMemo(() => { + const { snapshotInfoUrl, snapshotUrl, popoutUrl } = React.useMemo(() => { const snapshot = snapshots[snapshotTab]; if (!snapshot) return { snapshotUrl: kBlankSnapshotUrl }; @@ -66,16 +67,18 @@ export const SnapshotTab: React.FunctionComponent<{ const params = new URLSearchParams(); params.set('trace', context(snapshot.action).traceUrl); params.set('name', snapshot.snapshotName); + if (snapshot.showPoint) + params.set('showPoint', '1'); const snapshotUrl = new URL(`snapshot/${snapshot.action.pageId}?${params.toString()}`, window.location.href).toString(); const snapshotInfoUrl = new URL(`snapshotInfo/${snapshot.action.pageId}?${params.toString()}`, window.location.href).toString(); - const pointX = snapshotTab === 'action' ? snapshot.action.point?.x : undefined; - const pointY = snapshotTab === 'action' ? snapshot.action.point?.y : undefined; const popoutParams = new URLSearchParams(); popoutParams.set('r', snapshotUrl); popoutParams.set('trace', context(snapshot.action).traceUrl); + if (snapshot.showPoint) + popoutParams.set('showPoint', '1'); const popoutUrl = new URL(`snapshot.html?${popoutParams.toString()}`, window.location.href).toString(); - return { snapshots, snapshotInfoUrl, snapshotUrl, pointX, pointY, popoutUrl }; + return { snapshots, snapshotInfoUrl, snapshotUrl, popoutUrl }; }, [snapshots, snapshotTab]); const iframeRef0 = React.useRef(null); @@ -111,12 +114,11 @@ export const SnapshotTab: React.FunctionComponent<{ iframe.addEventListener('load', loadedCallback); iframe.addEventListener('error', loadedCallback); - const newUrl = snapshotUrl + (pointX === undefined ? '' : `&pointX=${pointX}&pointY=${pointY}`); // Try preventing history entry from being created. if (iframe.contentWindow) - iframe.contentWindow.location.replace(newUrl); + iframe.contentWindow.location.replace(snapshotUrl); else - iframe.src = newUrl; + iframe.src = snapshotUrl; await loadedPromise; } catch { @@ -132,7 +134,7 @@ export const SnapshotTab: React.FunctionComponent<{ loadingRef.current.visibleIframe = newVisibleIframe; setSnapshotInfo(newSnapshotInfo); })(); - }, [snapshotUrl, snapshotInfoUrl, pointX, pointY]); + }, [snapshotUrl, snapshotInfoUrl]); const windowHeaderHeight = 40; const snapshotContainerSize = {