fix(trace viewer): make red dot to the center of the target element (#26825)

Also make sure red dot is visible in the popout tab.

Fixes #24532.
This commit is contained in:
Dmitry Gozman 2023-08-31 16:52:54 -07:00 committed by GitHub
parent fa286de0b3
commit f3c02a5b4f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 34 additions and 30 deletions

View file

@ -197,6 +197,7 @@ function snapshotScript(...targetIds: (string | undefined)[]) {
function applyPlaywrightAttributes(unwrapPopoutUrl: (url: string) => string, ...targetIds: (string | undefined)[]) { function applyPlaywrightAttributes(unwrapPopoutUrl: (url: string) => string, ...targetIds: (string | undefined)[]) {
const scrollTops: Element[] = []; const scrollTops: Element[] = [];
const scrollLefts: Element[] = []; const scrollLefts: Element[] = [];
const targetElements: Element[] = [];
const visit = (root: Document | ShadowRoot) => { const visit = (root: Document | ShadowRoot) => {
// Collect all scrolled elements for later use. // Collect all scrolled elements for later use.
@ -223,6 +224,7 @@ function snapshotScript(...targetIds: (string | undefined)[]) {
const style = (target as HTMLElement).style; const style = (target as HTMLElement).style;
style.outline = '2px solid #006ab1'; style.outline = '2px solid #006ab1';
style.backgroundColor = '#6fa8dc7f'; style.backgroundColor = '#6fa8dc7f';
targetElements.push(target);
} }
} }
@ -231,10 +233,8 @@ function snapshotScript(...targetIds: (string | undefined)[]) {
if (!src) { if (!src) {
iframe.setAttribute('src', 'data:text/html,<body style="background: #ddd"></body>'); iframe.setAttribute('src', 'data:text/html,<body style="background: #ddd"></body>');
} else { } 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)); 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. // We can be loading iframe from within iframe, reset base to be absolute.
const index = url.pathname.lastIndexOf('/snapshot/'); const index = url.pathname.lastIndexOf('/snapshot/');
if (index !== -1) if (index !== -1)
@ -284,23 +284,25 @@ function snapshotScript(...targetIds: (string | undefined)[]) {
element.removeAttribute('__playwright_scroll_left_'); 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; 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); const onDOMContentLoaded = () => visit(document);

View file

@ -42,23 +42,24 @@ export const SnapshotTab: React.FunctionComponent<{
const [measure, ref] = useMeasure<HTMLDivElement>(); const [measure, ref] = useMeasure<HTMLDivElement>();
const [snapshotTab, setSnapshotTab] = React.useState<'action'|'before'|'after'>('action'); const [snapshotTab, setSnapshotTab] = React.useState<'action'|'before'|'after'>('action');
type Snapshot = { action: ActionTraceEvent, snapshotName: string, showPoint?: boolean };
const { snapshots } = React.useMemo(() => { const { snapshots } = React.useMemo(() => {
if (!action) if (!action)
return { snapshots: {} }; return { snapshots: {} };
// if the action has no beforeSnapshot, use the last available afterSnapshot. // 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; let a = action;
while (!beforeSnapshot && a) { while (!beforeSnapshot && a) {
a = prevInList(a); a = prevInList(a);
beforeSnapshot = a?.afterSnapshot ? { action: a, snapshotName: a?.afterSnapshot } : undefined; beforeSnapshot = a?.afterSnapshot ? { action: a, snapshotName: a?.afterSnapshot } : undefined;
} }
const afterSnapshot = action.afterSnapshot ? { action, snapshotName: action.afterSnapshot } : beforeSnapshot; const afterSnapshot: Snapshot | undefined = action.afterSnapshot ? { action, snapshotName: action.afterSnapshot } : beforeSnapshot;
const actionSnapshot = action.inputSnapshot ? { action, snapshotName: action.inputSnapshot } : afterSnapshot; const actionSnapshot: Snapshot | undefined = action.inputSnapshot ? { action, snapshotName: action.inputSnapshot, showPoint: !!action.point } : afterSnapshot;
return { snapshots: { action: actionSnapshot, before: beforeSnapshot, after: afterSnapshot } }; return { snapshots: { action: actionSnapshot, before: beforeSnapshot, after: afterSnapshot } };
}, [action]); }, [action]);
const { snapshotInfoUrl, snapshotUrl, pointX, pointY, popoutUrl } = React.useMemo(() => { const { snapshotInfoUrl, snapshotUrl, popoutUrl } = React.useMemo(() => {
const snapshot = snapshots[snapshotTab]; const snapshot = snapshots[snapshotTab];
if (!snapshot) if (!snapshot)
return { snapshotUrl: kBlankSnapshotUrl }; return { snapshotUrl: kBlankSnapshotUrl };
@ -66,16 +67,18 @@ export const SnapshotTab: React.FunctionComponent<{
const params = new URLSearchParams(); const params = new URLSearchParams();
params.set('trace', context(snapshot.action).traceUrl); params.set('trace', context(snapshot.action).traceUrl);
params.set('name', snapshot.snapshotName); 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 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 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(); const popoutParams = new URLSearchParams();
popoutParams.set('r', snapshotUrl); popoutParams.set('r', snapshotUrl);
popoutParams.set('trace', context(snapshot.action).traceUrl); 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(); 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]); }, [snapshots, snapshotTab]);
const iframeRef0 = React.useRef<HTMLIFrameElement>(null); const iframeRef0 = React.useRef<HTMLIFrameElement>(null);
@ -111,12 +114,11 @@ export const SnapshotTab: React.FunctionComponent<{
iframe.addEventListener('load', loadedCallback); iframe.addEventListener('load', loadedCallback);
iframe.addEventListener('error', loadedCallback); iframe.addEventListener('error', loadedCallback);
const newUrl = snapshotUrl + (pointX === undefined ? '' : `&pointX=${pointX}&pointY=${pointY}`);
// Try preventing history entry from being created. // Try preventing history entry from being created.
if (iframe.contentWindow) if (iframe.contentWindow)
iframe.contentWindow.location.replace(newUrl); iframe.contentWindow.location.replace(snapshotUrl);
else else
iframe.src = newUrl; iframe.src = snapshotUrl;
await loadedPromise; await loadedPromise;
} catch { } catch {
@ -132,7 +134,7 @@ export const SnapshotTab: React.FunctionComponent<{
loadingRef.current.visibleIframe = newVisibleIframe; loadingRef.current.visibleIframe = newVisibleIframe;
setSnapshotInfo(newSnapshotInfo); setSnapshotInfo(newSnapshotInfo);
})(); })();
}, [snapshotUrl, snapshotInfoUrl, pointX, pointY]); }, [snapshotUrl, snapshotInfoUrl]);
const windowHeaderHeight = 40; const windowHeaderHeight = 40;
const snapshotContainerSize = { const snapshotContainerSize = {