Skip to content

Commit

Permalink
chore: update trace event on action merge (#32860)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelfeldman committed Sep 28, 2024
1 parent 908b0de commit 1101414
Show file tree
Hide file tree
Showing 12 changed files with 67 additions and 49 deletions.
2 changes: 1 addition & 1 deletion packages/playwright-core/src/server/recorder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export class Recorder implements InstrumentationListener, IRecorder {
});
this._contextRecorder.on(ContextRecorder.Events.Change, (data: { sources: Source[], actions: actions.ActionInContext[] }) => {
this._recorderSources = data.sources;
recorderApp.setActions(data.actions);
recorderApp.setActions(data.actions, data.sources);
this._pushAllSources();
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ export class ContextRecorder extends EventEmitter {
name: 'closePage',
signals: [],
},
timestamp: monotonicTime()
startTime: monotonicTime()
});
this._pageAliases.delete(page);
});
Expand All @@ -195,7 +195,7 @@ export class ContextRecorder extends EventEmitter {
url: page.mainFrame().url(),
signals: [],
},
timestamp: monotonicTime()
startTime: monotonicTime()
});
}
}
Expand Down Expand Up @@ -232,7 +232,7 @@ export class ContextRecorder extends EventEmitter {
frame: frameDescription,
action,
description: undefined,
timestamp: monotonicTime()
startTime: monotonicTime()
};
await this._delegate.rewriteActionInContext?.(this._pageAliases, actionInContext);
return actionInContext;
Expand Down
4 changes: 2 additions & 2 deletions packages/playwright-core/src/server/recorder/recorderApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class EmptyRecorderApp extends EventEmitter implements IRecorderApp {
async setSelector(selector: string, userGesture?: boolean): Promise<void> {}
async updateCallLogs(callLogs: CallLog[]): Promise<void> {}
async setSources(sources: Source[]): Promise<void> {}
async setActions(actions: actions.ActionInContext[]): Promise<void> {}
async setActions(actions: actions.ActionInContext[], sources: Source[]): Promise<void> {}
}

export class RecorderApp extends EventEmitter implements IRecorderApp {
Expand Down Expand Up @@ -155,7 +155,7 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
}
}

async setActions(actions: actions.ActionInContext[]): Promise<void> {
async setActions(actions: actions.ActionInContext[], sources: Source[]): Promise<void> {
}

async setSelector(selector: string, userGesture?: boolean): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,12 @@ export class RecorderCollection extends EventEmitter {
this._fireChange();
const error = await callback?.(callMetadata).catch((e: Error) => e);
callMetadata.endTime = monotonicTime();
actionInContext.endTime = callMetadata.endTime;
callMetadata.error = error ? serializeError(error) : undefined;
// Do not wait for onAfterCall so that performAction returned immediately after the action.
mainFrame.instrumentation.onAfterCall(mainFrame, callMetadata).catch(() => {});
mainFrame.instrumentation.onAfterCall(mainFrame, callMetadata).then(() => {
this._fireChange();
}).catch(() => {});
}

signal(pageAlias: string, frame: Frame, signal: Signal) {
Expand All @@ -94,7 +97,7 @@ export class RecorderCollection extends EventEmitter {
generateGoto = true;
else if (lastAction.action.name !== 'click' && lastAction.action.name !== 'press')
generateGoto = true;
else if (timestamp - lastAction.timestamp > signalThreshold)
else if (timestamp - lastAction.startTime > signalThreshold)
generateGoto = true;

if (generateGoto) {
Expand All @@ -108,7 +111,8 @@ export class RecorderCollection extends EventEmitter {
url: frame.url(),
signals: [],
},
timestamp
startTime: timestamp,
endTime: timestamp,
});
}
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export interface IRecorderApp extends EventEmitter {
setSelector(selector: string, userGesture?: boolean): Promise<void>;
updateCallLogs(callLogs: CallLog[]): Promise<void>;
setSources(sources: Source[]): Promise<void>;
setActions(actions: actions.ActionInContext[]): Promise<void>;
setActions(actions: actions.ActionInContext[], sources: Source[]): Promise<void>;
}

export type IRecorderAppFactory = (recorder: IRecorder) => Promise<IRecorderApp>;
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ export class RecorderInTraceViewer extends EventEmitter implements IRecorderApp
}
}

async setActions(actions: actions.ActionInContext[]): Promise<void> {
this._transport.deliverEvent('setActions', { actions });
async setActions(actions: actions.ActionInContext[], sources: Source[]): Promise<void> {
this._transport.deliverEvent('setActions', { actions, sources });
}
}

Expand Down
5 changes: 3 additions & 2 deletions packages/playwright-core/src/server/recorder/recorderUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,11 @@ export function callMetadataForAction(pageAliases: Map<Page, string>, actionInCo

const callMetadata: CallMetadata = {
id: `call@${createGuid()}`,
stepId: `recorder@${createGuid()}`,
apiName: 'page.' + method,
objectId: mainFrame.guid,
pageId: mainFrame._page.guid,
frameId: mainFrame.guid,
startTime: actionInContext.timestamp,
startTime: actionInContext.startTime,
endTime: 0,
type: 'Frame',
method,
Expand All @@ -102,7 +101,9 @@ export function collapseActions(actions: actions.ActionInContext[]): actions.Act
result.push(action);
continue;
}
const startTime = result[result.length - 1].startTime;
result[result.length - 1] = action;
result[result.length - 1].startTime = startTime;
}
return result;
}
3 changes: 2 additions & 1 deletion packages/recorder/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,5 +153,6 @@ export type ActionInContext = {
frame: FrameDescription;
description?: string;
action: Action;
timestamp: number;
startTime: number;
endTime?: number;
};
7 changes: 3 additions & 4 deletions packages/trace-viewer/src/ui/inspectorTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,17 @@
import { CodeMirrorWrapper } from '@web/components/codeMirrorWrapper';
import type { Language } from '@web/components/codeMirrorWrapper';
import { ToolbarButton } from '@web/components/toolbarButton';
import { copy, useSetting } from '@web/uiUtils';
import { copy } from '@web/uiUtils';
import * as React from 'react';
import './sourceTab.css';

export const InspectorTab: React.FunctionComponent<{
showScreenshot: boolean,
sdkLanguage: Language,
setIsInspecting: (isInspecting: boolean) => void,
highlightedLocator: string,
setHighlightedLocator: (locator: string) => void,
}> = ({ sdkLanguage, setIsInspecting, highlightedLocator, setHighlightedLocator }) => {
const [showScreenshot] = useSetting('screenshot-instead-of-snapshot', false);

}> = ({ showScreenshot, sdkLanguage, setIsInspecting, highlightedLocator, setHighlightedLocator }) => {
return <div className='vbox' style={{ backgroundColor: 'var(--vscode-sideBar-background)' }}>
<div style={{ margin: '10px 0px 10px 10px', color: 'var(--vscode-editorCodeLens-foreground)', flex: 'none' }}>Locator</div>
<div style={{ margin: '0 10px 10px', flex: 'auto' }}>
Expand Down
22 changes: 8 additions & 14 deletions packages/trace-viewer/src/ui/recorder/backendContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@ export const BackendProvider: React.FunctionComponent<React.PropsWithChildren<{
}>> = ({ guid, children }) => {
const [connection, setConnection] = React.useState<Connection | undefined>(undefined);
const [mode, setMode] = React.useState<Mode>('none');
const [actions, setActions] = React.useState<actionTypes.ActionInContext[]>([]);
const [sources, setSources] = React.useState<Source[]>([]);
const callbacks = React.useRef({ setMode, setActions, setSources });
const [actions, setActions] = React.useState<{ actions: actionTypes.ActionInContext[], sources: Source[] }>({ actions: [], sources: [] });
const callbacks = React.useRef({ setMode, setActions });

React.useEffect(() => {
const wsURL = new URL(`../${guid}`, window.location.toString());
Expand All @@ -40,8 +39,8 @@ export const BackendProvider: React.FunctionComponent<React.PropsWithChildren<{
}, [guid]);

const backend = React.useMemo(() => {
return connection ? { mode, actions, sources, connection } : undefined;
}, [actions, mode, sources, connection]);
return connection ? { mode, actions: actions.actions, sources: actions.sources, connection } : undefined;
}, [actions, mode, connection]);

return <BackendContext.Provider value={backend}>
{children}
Expand All @@ -56,8 +55,7 @@ export type Backend = {

type ConnectionCallbacks = {
setMode: (mode: Mode) => void;
setActions: (actions: actionTypes.ActionInContext[]) => void;
setSources: (sources: Source[]) => void;
setActions: (data: { actions: actionTypes.ActionInContext[], sources: Source[] }) => void;
};

class Connection {
Expand Down Expand Up @@ -111,14 +109,10 @@ class Connection {
const { mode } = params as { mode: Mode };
this._options.setMode(mode);
}
if (method === 'setSources') {
const { sources } = params as { sources: Source[] };
this._options.setSources(sources);
(window as any).playwrightSourcesEchoForTest = sources;
}
if (method === 'setActions') {
const { actions } = params as { actions: actionTypes.ActionInContext[] };
this._options.setActions(actions.filter(a => a.action.name !== 'openPage' && a.action.name !== 'closePage'));
const { actions, sources } = params as { actions: actionTypes.ActionInContext[], sources: Source[] };
this._options.setActions({ actions: actions.filter(a => a.action.name !== 'openPage' && a.action.name !== 'closePage'), sources });
(window as any).playwrightSourcesEchoForTest = sources;
}
}
}
50 changes: 34 additions & 16 deletions packages/trace-viewer/src/ui/recorder/recorderView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,25 @@ export const Workbench: React.FunctionComponent = () => {
const backend = React.useContext(BackendContext);
const model = React.useContext(ModelContext);
const [fileId, setFileId] = React.useState<string | undefined>();
const [selectedCallTime, setSelectedCallTime] = React.useState<number | undefined>(undefined);
const [selectedStartTime, setSelectedStartTime] = React.useState<number | undefined>(undefined);
const [isInspecting, setIsInspecting] = React.useState(false);
const [highlightedLocator, setHighlightedLocator] = React.useState<string>('');
const [highlightedLocatorInProperties, setHighlightedLocatorInProperties] = React.useState<string>('');
const [highlightedLocatorInTrace, setHighlightedLocatorInTrace] = React.useState<string>('');
const [traceCallId, setTraceCallId] = React.useState<string | undefined>();

const setSelectedAction = React.useCallback((action: actionTypes.ActionInContext | undefined) => {
setSelectedCallTime(action?.timestamp);
setSelectedStartTime(action?.startTime);
}, []);

const selectedAction = React.useMemo(() => {
return backend?.actions.find(a => a.timestamp === selectedCallTime);
}, [backend?.actions, selectedCallTime]);
return backend?.actions.find(a => a.startTime === selectedStartTime);
}, [backend?.actions, selectedStartTime]);

React.useEffect(() => {
const callId = model?.actions.find(a => a.endTime && a.endTime === selectedAction?.endTime)?.callId;
if (callId)
setTraceCallId(callId);
}, [model, selectedAction]);

const source = React.useMemo(() => backend?.sources.find(s => s.id === fileId) || backend?.sources[0], [backend?.sources, fileId]);
const sourceLocation = React.useMemo(() => {
Expand Down Expand Up @@ -95,6 +103,17 @@ export const Workbench: React.FunctionComponent = () => {
return { boundaries };
}, [model]);

const locatorPickedInTrace = React.useCallback((locator: string) => {
setHighlightedLocatorInProperties(locator);
setHighlightedLocatorInTrace('');
setIsInspecting(false);
}, []);

const locatorTypedInProperties = React.useCallback((locator: string) => {
setHighlightedLocatorInTrace(locator);
setHighlightedLocatorInProperties(locator);
}, []);

const actionList = <ActionListView
sdkLanguage={sdkLanguage}
actions={backend?.actions || []}
Expand Down Expand Up @@ -129,25 +148,23 @@ export const Workbench: React.FunctionComponent = () => {
<SourceChooser fileId={fileId} sources={backend?.sources || []} setFileId={fileId => {
setFileId(fileId);
}} />
<ToolbarButton icon='clear-all' title='Clear' onClick={() => {
}}></ToolbarButton>
<ToolbarButton icon='color-mode' title='Toggle color mode' toggled={false} onClick={() => toggleTheme()}></ToolbarButton>
</Toolbar>;

const sidebarTabbedPane = <TabbedPane tabs={[actionsTab]} />;
const traceView = <TraceView
sdkLanguage={sdkLanguage}
callTime={selectedCallTime || 0}
callId={traceCallId}
isInspecting={isInspecting}
setIsInspecting={setIsInspecting}
highlightedLocator={highlightedLocator}
setHighlightedLocator={setHighlightedLocator} />;
highlightedLocator={highlightedLocatorInTrace}
setHighlightedLocator={locatorPickedInTrace} />;
const propertiesView = <PropertiesView
sdkLanguage={sdkLanguage}
boundaries={boundaries}
setIsInspecting={setIsInspecting}
highlightedLocator={highlightedLocator}
setHighlightedLocator={setHighlightedLocator}
highlightedLocator={highlightedLocatorInProperties}
setHighlightedLocator={locatorTypedInProperties}
sourceLocation={sourceLocation} />;

return <div className='vbox workbench'>
Expand Down Expand Up @@ -196,6 +213,7 @@ const PropertiesView: React.FunctionComponent<{
id: 'inspector',
title: 'Locator',
render: () => <InspectorTab
showScreenshot={false}
sdkLanguage={sdkLanguage}
setIsInspecting={setIsInspecting}
highlightedLocator={highlightedLocator}
Expand Down Expand Up @@ -240,23 +258,23 @@ const PropertiesView: React.FunctionComponent<{

const TraceView: React.FunctionComponent<{
sdkLanguage: Language,
callTime: number;
callId: string | undefined,
isInspecting: boolean;
setIsInspecting: (value: boolean) => void;
highlightedLocator: string;
setHighlightedLocator: (locator: string) => void;
}> = ({
sdkLanguage,
callTime,
callId,
isInspecting,
setIsInspecting,
highlightedLocator,
setHighlightedLocator,
}) => {
const model = React.useContext(ModelContext);
const action = React.useMemo(() => {
return model?.actions.find(a => a.startTime === callTime);
}, [model, callTime]);
return model?.actions.find(a => a.callId === callId);
}, [model, callId]);

const snapshot = React.useMemo(() => {
const snapshot = collectSnapshots(action);
Expand Down
1 change: 1 addition & 0 deletions packages/trace-viewer/src/ui/workbench.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ export const Workbench: React.FunctionComponent<{
id: 'inspector',
title: 'Locator',
render: () => <InspectorTab
showScreenshot={showScreenshot}
sdkLanguage={sdkLanguage}
setIsInspecting={setIsInspecting}
highlightedLocator={highlightedLocator}
Expand Down

0 comments on commit 1101414

Please sign in to comment.