feat(inspector): remove snapshots (#6909)

This commit is contained in:
Dmitry Gozman 2021-06-04 10:47:19 -07:00 committed by GitHub
parent a96491cbbb
commit 233f1874da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 21 additions and 117 deletions

View File

@ -50,11 +50,8 @@ export class Recorder {
private _actionPoint: Point | undefined; private _actionPoint: Point | undefined;
private _actionSelector: string | undefined; private _actionSelector: string | undefined;
private _params: { isUnderTest: boolean; }; private _params: { isUnderTest: boolean; };
private _snapshotIframe: HTMLIFrameElement | undefined;
private _snapshotUrl: string | undefined;
private _snapshotBaseUrl: string;
constructor(injectedScript: InjectedScript, params: { isUnderTest: boolean, snapshotBaseUrl: string }) { constructor(injectedScript: InjectedScript, params: { isUnderTest: boolean }) {
this._params = params; this._params = params;
this._injectedScript = injectedScript; this._injectedScript = injectedScript;
this._outerGlassPaneElement = document.createElement('x-pw-glass'); this._outerGlassPaneElement = document.createElement('x-pw-glass');
@ -66,7 +63,6 @@ export class Recorder {
this._outerGlassPaneElement.style.zIndex = '2147483647'; this._outerGlassPaneElement.style.zIndex = '2147483647';
this._outerGlassPaneElement.style.pointerEvents = 'none'; this._outerGlassPaneElement.style.pointerEvents = 'none';
this._outerGlassPaneElement.style.display = 'flex'; this._outerGlassPaneElement.style.display = 'flex';
this._snapshotBaseUrl = params.snapshotBaseUrl;
this._tooltipElement = document.createElement('x-pw-tooltip'); this._tooltipElement = document.createElement('x-pw-tooltip');
this._actionPointElement = document.createElement('x-pw-action-point'); this._actionPointElement = document.createElement('x-pw-action-point');
@ -160,28 +156,6 @@ export class Recorder {
document.documentElement.appendChild(this._outerGlassPaneElement); document.documentElement.appendChild(this._outerGlassPaneElement);
} }
private _createSnapshotIframeIfNeeded(): HTMLIFrameElement | undefined {
if (this._snapshotIframe)
return this._snapshotIframe;
if (window.top === window) {
this._snapshotIframe = document.createElement('iframe');
this._snapshotIframe.src = this._snapshotBaseUrl;
this._snapshotIframe.style.background = '#ff000060';
this._snapshotIframe.style.position = 'fixed';
this._snapshotIframe.style.top = '0';
this._snapshotIframe.style.right = '0';
this._snapshotIframe.style.bottom = '0';
this._snapshotIframe.style.left = '0';
this._snapshotIframe.style.border = 'none';
this._snapshotIframe.style.width = '100%';
this._snapshotIframe.style.height = '100%';
this._snapshotIframe.style.zIndex = '2147483647';
this._snapshotIframe.style.visibility = 'hidden';
document.documentElement.appendChild(this._snapshotIframe);
}
return this._snapshotIframe;
}
private async _pollRecorderMode() { private async _pollRecorderMode() {
const pollPeriod = 1000; const pollPeriod = 1000;
if (this._pollRecorderModeTimer) if (this._pollRecorderModeTimer)
@ -192,7 +166,7 @@ export class Recorder {
return; return;
} }
const { mode, actionPoint, actionSelector, snapshotUrl } = state; const { mode, actionPoint, actionSelector } = state;
if (mode !== this._mode) { if (mode !== this._mode) {
this._mode = mode; this._mode = mode;
this._clearHighlight(); this._clearHighlight();
@ -221,18 +195,6 @@ export class Recorder {
this._updateHighlight(); this._updateHighlight();
this._actionSelector = actionSelector; this._actionSelector = actionSelector;
} }
if (snapshotUrl !== this._snapshotUrl) {
this._snapshotUrl = snapshotUrl;
const snapshotIframe = this._createSnapshotIframeIfNeeded();
if (snapshotIframe) {
if (!snapshotUrl) {
snapshotIframe.style.visibility = 'hidden';
} else {
snapshotIframe.style.visibility = 'visible';
snapshotIframe.contentWindow?.postMessage({ snapshotUrl }, '*');
}
}
}
this._pollRecorderModeTimer = setTimeout(() => this._pollRecorderMode(), pollPeriod); this._pollRecorderModeTimer = setTimeout(() => this._pollRecorderMode(), pollPeriod);
} }

View File

@ -19,7 +19,7 @@ import { Point } from '../../../common/types';
export type Mode = 'inspecting' | 'recording' | 'none'; export type Mode = 'inspecting' | 'recording' | 'none';
export type EventData = { export type EventData = {
event: 'clear' | 'resume' | 'step' | 'pause' | 'setMode' | 'selectorUpdated' | 'callLogHovered'; event: 'clear' | 'resume' | 'step' | 'pause' | 'setMode' | 'selectorUpdated';
params: any; params: any;
}; };
@ -27,7 +27,6 @@ export type UIState = {
mode: Mode; mode: Mode;
actionPoint?: Point; actionPoint?: Point;
actionSelector?: string; actionSelector?: string;
snapshotUrl?: string;
}; };
export type CallLogStatus = 'in-progress' | 'done' | 'error' | 'paused'; export type CallLogStatus = 'in-progress' | 'done' | 'error' | 'paused';
@ -44,11 +43,6 @@ export type CallLog = {
url?: string, url?: string,
selector?: string, selector?: string,
}; };
snapshots: {
before: boolean,
action: boolean,
after: boolean,
}
}; };
export type SourceHighlight = { export type SourceHighlight = {

View File

@ -17,7 +17,7 @@
import { CallMetadata } from '../../instrumentation'; import { CallMetadata } from '../../instrumentation';
import { CallLog, CallLogStatus } from './recorderTypes'; import { CallLog, CallLogStatus } from './recorderTypes';
export function metadataToCallLog(metadata: CallMetadata, status: CallLogStatus, snapshots: Set<string>): CallLog { export function metadataToCallLog(metadata: CallMetadata, status: CallLogStatus): CallLog {
const title = metadata.apiName || metadata.method; const title = metadata.apiName || metadata.method;
if (metadata.error) if (metadata.error)
status = 'error'; status = 'error';
@ -38,23 +38,6 @@ export function metadataToCallLog(metadata: CallMetadata, status: CallLogStatus,
error: metadata.error, error: metadata.error,
params, params,
duration, duration,
snapshots: {
before: showBeforeSnapshot(metadata) && snapshots.has(`before@${metadata.id}`),
action: showActionSnapshot(metadata) && snapshots.has(`action@${metadata.id}`),
after: showAfterSnapshot(metadata) && snapshots.has(`after@${metadata.id}`),
}
}; };
return callLog; return callLog;
} }
function showBeforeSnapshot(metadata: CallMetadata): boolean {
return metadata.method === 'close';
}
function showActionSnapshot(metadata: CallMetadata): boolean {
return ['click', 'dblclick', 'check', 'uncheck', 'fill', 'press'].includes(metadata.method);
}
function showAfterSnapshot(metadata: CallMetadata): boolean {
return ['goto', 'click', 'dblclick', 'dblclick', 'check', 'uncheck', 'fill', 'press'].includes(metadata.method);
}

View File

@ -33,7 +33,6 @@ import { CallMetadata, InstrumentationListener, internalCallMetadata, SdkObject
import { Point } from '../../common/types'; import { Point } from '../../common/types';
import { CallLog, CallLogStatus, EventData, Mode, Source, UIState } from './recorder/recorderTypes'; import { CallLog, CallLogStatus, EventData, Mode, Source, UIState } from './recorder/recorderTypes';
import { isUnderTest } from '../../utils/utils'; import { isUnderTest } from '../../utils/utils';
import { InMemorySnapshotter } from '../snapshot/inMemorySnapshotter';
import { metadataToCallLog } from './recorder/recorderUtils'; import { metadataToCallLog } from './recorder/recorderUtils';
import { Debugger } from './debugger'; import { Debugger } from './debugger';
@ -56,9 +55,6 @@ export class RecorderSupplement implements InstrumentationListener {
private _currentCallsMetadata = new Map<CallMetadata, SdkObject>(); private _currentCallsMetadata = new Map<CallMetadata, SdkObject>();
private _recorderSources: Source[]; private _recorderSources: Source[];
private _userSources = new Map<string, Source>(); private _userSources = new Map<string, Source>();
private _snapshotter: InMemorySnapshotter;
private _hoveredSnapshot: { callLogId: string, phase: 'before' | 'after' | 'action' } | undefined;
private _snapshots = new Set<string>();
private _allMetadatas = new Map<string, CallMetadata>(); private _allMetadatas = new Map<string, CallMetadata>();
private _debugger: Debugger; private _debugger: Debugger;
@ -129,14 +125,12 @@ export class RecorderSupplement implements InstrumentationListener {
}); });
} }
this._generator = generator; this._generator = generator;
this._snapshotter = new InMemorySnapshotter(context);
} }
async install() { async install() {
const recorderApp = await RecorderApp.open(this._context); const recorderApp = await RecorderApp.open(this._context);
this._recorderApp = recorderApp; this._recorderApp = recorderApp;
recorderApp.once('close', () => { recorderApp.once('close', () => {
this._snapshotter.dispose().catch(() => {});
this._recorderApp = null; this._recorderApp = null;
}); });
recorderApp.on('event', (data: EventData) => { recorderApp.on('event', (data: EventData) => {
@ -150,13 +144,6 @@ export class RecorderSupplement implements InstrumentationListener {
this._refreshOverlay(); this._refreshOverlay();
return; return;
} }
if (data.event === 'callLogHovered') {
this._hoveredSnapshot = undefined;
if (this._debugger.isPaused() && data.params.callLogId)
this._hoveredSnapshot = data.params;
this._refreshOverlay();
return;
}
if (data.event === 'step') { if (data.event === 'step') {
this._debugger.resume(true); this._debugger.resume(true);
return; return;
@ -202,26 +189,18 @@ export class RecorderSupplement implements InstrumentationListener {
(source: BindingSource, action: actions.Action) => this._recordAction(source.frame, action), 'utility'); (source: BindingSource, action: actions.Action) => this._recordAction(source.frame, action), 'utility');
await this._context.exposeBinding('_playwrightRecorderState', false, source => { await this._context.exposeBinding('_playwrightRecorderState', false, source => {
let snapshotUrl: string | undefined;
let actionSelector = this._highlightedSelector; let actionSelector = this._highlightedSelector;
let actionPoint: Point | undefined; let actionPoint: Point | undefined;
if (this._hoveredSnapshot) {
const metadata = this._allMetadatas.get(this._hoveredSnapshot.callLogId)!;
snapshotUrl = `${metadata.pageId}?name=${this._hoveredSnapshot.phase}@${this._hoveredSnapshot.callLogId}`;
actionPoint = this._hoveredSnapshot.phase === 'action' ? metadata?.point : undefined;
} else {
for (const [metadata, sdkObject] of this._currentCallsMetadata) { for (const [metadata, sdkObject] of this._currentCallsMetadata) {
if (source.page === sdkObject.attribution.page) { if (source.page === sdkObject.attribution.page) {
actionPoint = metadata.point || actionPoint; actionPoint = metadata.point || actionPoint;
actionSelector = actionSelector || metadata.params.selector; actionSelector = actionSelector || metadata.params.selector;
} }
} }
}
const uiState: UIState = { const uiState: UIState = {
mode: this._mode, mode: this._mode,
actionPoint, actionPoint,
actionSelector, actionSelector,
snapshotUrl,
}; };
return uiState; return uiState;
}, 'utility'); }, 'utility');
@ -236,8 +215,7 @@ export class RecorderSupplement implements InstrumentationListener {
this._debugger.resume(false); this._debugger.resume(false);
}, 'main'); }, 'main');
const snapshotBaseUrl = await this._snapshotter.initialize() + '/snapshot/'; await this._context.extendInjectedScript('utility', recorderSource.source, { isUnderTest: isUnderTest() });
await this._context.extendInjectedScript('utility', recorderSource.source, { isUnderTest: isUnderTest(), snapshotBaseUrl });
await this._context.extendInjectedScript('main', consoleApiSource.source); await this._context.extendInjectedScript('main', consoleApiSource.source);
if (this._debugger.isPaused()) if (this._debugger.isPaused())
@ -391,18 +369,9 @@ export class RecorderSupplement implements InstrumentationListener {
this._generator.signal(pageAlias, page.mainFrame(), { name: 'dialog', dialogAlias: String(++this._lastDialogOrdinal) }); this._generator.signal(pageAlias, page.mainFrame(), { name: 'dialog', dialogAlias: String(++this._lastDialogOrdinal) });
} }
_captureSnapshot(sdkObject: SdkObject, metadata: CallMetadata, phase: 'before' | 'after' | 'action') {
if (sdkObject.attribution.page) {
const snapshotName = `${phase}@${metadata.id}`;
this._snapshots.add(snapshotName);
this._snapshotter.captureSnapshot(sdkObject.attribution.page, snapshotName);
}
}
async onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata) { async onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata) {
if (this._mode === 'recording') if (this._mode === 'recording')
return; return;
this._captureSnapshot(sdkObject, metadata, 'before');
this._currentCallsMetadata.set(metadata, sdkObject); this._currentCallsMetadata.set(metadata, sdkObject);
this._allMetadatas.set(metadata.id, metadata); this._allMetadatas.set(metadata.id, metadata);
this._updateUserSources(); this._updateUserSources();
@ -416,7 +385,6 @@ export class RecorderSupplement implements InstrumentationListener {
async onAfterCall(sdkObject: SdkObject, metadata: CallMetadata) { async onAfterCall(sdkObject: SdkObject, metadata: CallMetadata) {
if (this._mode === 'recording') if (this._mode === 'recording')
return; return;
this._captureSnapshot(sdkObject, metadata, 'after');
if (!metadata.error) if (!metadata.error)
this._currentCallsMetadata.delete(metadata); this._currentCallsMetadata.delete(metadata);
this._updateUserSources(); this._updateUserSources();
@ -458,9 +426,6 @@ export class RecorderSupplement implements InstrumentationListener {
} }
async onBeforeInputAction(sdkObject: SdkObject, metadata: CallMetadata) { async onBeforeInputAction(sdkObject: SdkObject, metadata: CallMetadata) {
if (this._mode === 'recording')
return;
this._captureSnapshot(sdkObject, metadata, 'action');
} }
async onCallLog(logName: string, message: string, sdkObject: SdkObject, metadata: CallMetadata): Promise<void> { async onCallLog(logName: string, message: string, sdkObject: SdkObject, metadata: CallMetadata): Promise<void> {
@ -479,7 +444,7 @@ export class RecorderSupplement implements InstrumentationListener {
status = 'in-progress'; status = 'in-progress';
if (this._debugger.isPaused(metadata)) if (this._debugger.isPaused(metadata))
status = 'paused'; status = 'paused';
logs.push(metadataToCallLog(metadata, status, this._snapshots)); logs.push(metadataToCallLog(metadata, status));
} }
this._recorderApp?.updateCallLogs(logs); this._recorderApp?.updateCallLogs(logs);
} }

View File

@ -48,6 +48,7 @@
.call-log-call .codicon { .call-log-call .codicon {
padding: 0 4px; padding: 0 4px;
flex: none;
} }
.call-log .codicon-check { .call-log .codicon-check {
@ -66,6 +67,12 @@
color: red; color: red;
} }
.call-log-details {
flex: 0 1 auto;
overflow-x: hidden;
text-overflow: ellipsis;
}
.call-log-url { .call-log-url {
color: var(--blue); color: var(--blue);
} }
@ -75,6 +82,7 @@
} }
.call-log-time { .call-log-time {
flex: none;
margin-left: 4px; margin-left: 4px;
color: var(--gray); color: var(--gray);
} }

View File

@ -21,12 +21,10 @@ import { msToString } from '../uiUtils';
export interface CallLogProps { export interface CallLogProps {
log: CallLog[], log: CallLog[],
onHover: (callLog: CallLog | undefined, phase?: 'before' | 'after' | 'action') => void
} }
export const CallLogView: React.FC<CallLogProps> = ({ export const CallLogView: React.FC<CallLogProps> = ({
log, log,
onHover,
}) => { }) => {
const messagesEndRef = React.createRef<HTMLDivElement>(); const messagesEndRef = React.createRef<HTMLDivElement>();
const [expandOverrides, setExpandOverrides] = React.useState<Map<string, boolean>>(new Map()); const [expandOverrides, setExpandOverrides] = React.useState<Map<string, boolean>>(new Map());
@ -47,14 +45,10 @@ export const CallLogView: React.FC<CallLogProps> = ({
setExpandOverrides(newOverrides); setExpandOverrides(newOverrides);
}}></span> }}></span>
{ callLog.title } { callLog.title }
{ callLog.params.url ? <span>(<span className='call-log-url'>{callLog.params.url}</span>)</span> : undefined } { callLog.params.url ? <span className='call-log-details'>(<span className='call-log-url' title={callLog.params.url}>{callLog.params.url}</span>)</span> : undefined }
{ callLog.params.selector ? <span>(<span className='call-log-selector'>{callLog.params.selector}</span>)</span> : undefined } { callLog.params.selector ? <span className='call-log-details'>(<span className='call-log-selector' title={callLog.params.selector}>{callLog.params.selector}</span>)</span> : undefined }
<span className={'codicon ' + iconClass(callLog)}></span> <span className={'codicon ' + iconClass(callLog)}></span>
{ typeof callLog.duration === 'number' ? <span className='call-log-time'> {msToString(callLog.duration)}</span> : undefined} { typeof callLog.duration === 'number' ? <span className='call-log-time'> {msToString(callLog.duration)}</span> : undefined}
{ <div style={{flex: 'auto'}}></div> }
<span className={'codicon codicon-vm-outline preview' + (callLog.snapshots.before ? '' : ' invisible')} onMouseEnter={() => onHover(callLog, 'before')} onMouseLeave={() => onHover(undefined)}></span>
<span className={'codicon codicon-vm-running preview' + (callLog.snapshots.action ? '' : ' invisible')} onMouseEnter={() => onHover(callLog, 'action')} onMouseLeave={() => onHover(undefined)}></span>
<span className={'codicon codicon-vm-active preview' + (callLog.snapshots.after ? '' : ' invisible')} onMouseEnter={() => onHover(callLog, 'after')} onMouseLeave={() => onHover(undefined)}></span>
</div> </div>
{ (isExpanded ? callLog.messages : []).map((message, i) => { { (isExpanded ? callLog.messages : []).map((message, i) => {
return <div className='call-log-message' key={i}> return <div className='call-log-message' key={i}>

View File

@ -121,9 +121,7 @@ export const Recorder: React.FC<RecorderProps> = ({
window.dispatch({ event: 'selectorUpdated', params: { selector: event.target.value } }); window.dispatch({ event: 'selectorUpdated', params: { selector: event.target.value } });
}} /> }} />
</Toolbar> </Toolbar>
<CallLogView log={Array.from(log.values())} onHover={(callLog, phase) => { <CallLogView log={Array.from(log.values())}/>
window.dispatch({ event: 'callLogHovered', params: { callLogId: callLog?.id, phase } });
}}/>
</div> </div>
</SplitView> </SplitView>
</div>; </div>;