Skip to content

Commit

Permalink
feat(inspector): render params and durations in log (#5489)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelfeldman committed Feb 18, 2021
1 parent da135c2 commit 15833ee
Show file tree
Hide file tree
Showing 14 changed files with 129 additions and 51 deletions.
1 change: 1 addition & 0 deletions src/dispatchers/dispatcher.ts
Expand Up @@ -219,6 +219,7 @@ export class DispatcherConnection {
rewriteErrorMessage(e, e.message + formatLogRecording(callMetadata.log) + kLoggingNote);
this.onmessage({ id, error: serializeError(e) });
} finally {
callMetadata.endTime = monotonicTime();
if (sdkObject)
await sdkObject.instrumentation.onAfterCall(sdkObject, callMetadata);
}
Expand Down
2 changes: 2 additions & 0 deletions src/server/instrumentation.ts
Expand Up @@ -34,6 +34,8 @@ export type CallMetadata = {
id: number;
startTime: number;
endTime: number;
pauseStartTime?: number;
pauseEndTime?: number;
type: string;
method: string;
params: any;
Expand Down
1 change: 0 additions & 1 deletion src/server/progress.ts
Expand Up @@ -109,7 +109,6 @@ export class ProgressController {
throw e;
} finally {
clearTimeout(timer);
this.metadata.endTime = monotonicTime();
}
}

Expand Down
5 changes: 4 additions & 1 deletion src/server/supplements/inspectorController.ts
Expand Up @@ -72,12 +72,15 @@ export class InspectorController implements InstrumentationListener {
const info = metadata.params.info;
switch (info.phase) {
case 'before':
metadata.endTime = 0;
// Fall through.
case 'log':
return;
case 'after':
metadata = this._waitOperations.get(info.waitId)!;
const originalMetadata = this._waitOperations.get(info.waitId)!;
originalMetadata.endTime = metadata.endTime;
this._waitOperations.delete(info.waitId);
metadata = originalMetadata;
break;
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/server/supplements/recorder/recorderTypes.ts
Expand Up @@ -36,6 +36,11 @@ export type CallLog = {
status: 'in-progress' | 'done' | 'error' | 'paused';
error?: string;
reveal?: boolean;
duration?: number;
params: {
url?: string,
selector?: string,
};
};

export type SourceHighlight = {
Expand Down
24 changes: 21 additions & 3 deletions src/server/supplements/recorderSupplement.ts
Expand Up @@ -31,7 +31,7 @@ import { RecorderApp } from './recorder/recorderApp';
import { CallMetadata, internalCallMetadata, SdkObject } from '../instrumentation';
import { Point } from '../../common/types';
import { CallLog, EventData, Mode, Source, UIState } from './recorder/recorderTypes';
import { isUnderTest } from '../../utils/utils';
import { isUnderTest, monotonicTime } from '../../utils/utils';

type BindingSource = { frame: Frame, page: Page };

Expand Down Expand Up @@ -210,6 +210,7 @@ export class RecorderSupplement {
this._pausedCallsMetadata.set(metadata, f);
});
this._recorderApp!.setPaused(true);
metadata.pauseStartTime = monotonicTime();
this._updateUserSources();
this.updateCallLog([metadata]);
return result;
Expand All @@ -219,8 +220,11 @@ export class RecorderSupplement {
this._pauseOnNextStatement = step;
this._recorderApp?.setPaused(false);

for (const callback of this._pausedCallsMetadata.values())
const endTime = monotonicTime();
for (const [metadata, callback] of this._pausedCallsMetadata) {
metadata.pauseEndTime = endTime;
callback();
}
this._pausedCallsMetadata.clear();

this._updateUserSources();
Expand Down Expand Up @@ -418,7 +422,21 @@ export class RecorderSupplement {
status = 'paused';
if (metadata.error)
status = 'error';
logs.push({ id: metadata.id, messages: metadata.log, title, status, error: metadata.error });
const params = {
url: metadata.params?.url,
selector: metadata.params?.selector,
};
let duration = metadata.endTime ? metadata.endTime - metadata.startTime : undefined;
if (duration && metadata.pauseStartTime && metadata.pauseEndTime)
duration -= (metadata.pauseEndTime - metadata.pauseStartTime);
logs.push({
id: metadata.id,
messages: metadata.log,
title, status,
error: metadata.error,
params,
duration
});
}
this._recorderApp?.updateCallLogs(logs);
}
Expand Down
13 changes: 13 additions & 0 deletions src/web/recorder/callLog.css
Expand Up @@ -76,3 +76,16 @@
.call-log .codicon-error {
color: red;
}

.call-log-url {
color: var(--blue);
}

.call-log-selector {
color: var(--orange);
}

.call-log-time {
margin-left: 4px;
color: var(--gray);
}
19 changes: 15 additions & 4 deletions src/web/recorder/callLog.example.ts
Expand Up @@ -22,15 +22,21 @@ export function exampleCallLog(): CallLog[] {
'id': 3,
'messages': [],
'title': 'newPage',
'status': 'done'
'status': 'done',
'duration': 100,
'params': {}
},
{
'id': 4,
'messages': [
'navigating to "https://github.com/microsoft", waiting until "load"',
],
'title': 'goto',
'status': 'done'
'status': 'done',
'params': {
'url': 'https://github.com/microsoft'
},
'duration': 1100,
},
{
'id': 5,
Expand All @@ -47,7 +53,10 @@ export function exampleCallLog(): CallLog[] {
' performing click action'
],
'title': 'click',
'status': 'paused'
'status': 'paused',
'params': {
'selector': 'input[aria-label="Find a repository…"]'
},
},
{
'id': 6,
Expand All @@ -56,7 +65,9 @@ export function exampleCallLog(): CallLog[] {
],
'error': 'Error occured',
'title': 'error',
'status': 'error'
'status': 'error',
'params': {
},
},
];
}
4 changes: 4 additions & 0 deletions src/web/recorder/callLog.tsx
Expand Up @@ -17,6 +17,7 @@
import './callLog.css';
import * as React from 'react';
import type { CallLog } from '../../server/supplements/recorder/recorderTypes';
import { msToString } from '../uiUtils';

export interface CallLogProps {
log: CallLog[]
Expand Down Expand Up @@ -46,7 +47,10 @@ export const CallLogView: React.FC<CallLogProps> = ({
setExpandOverrides(newOverrides);
}}></span>
{ callLog.title }
{ callLog.params.url ? <span>(<span className='call-log-url'>{callLog.params.url}</span>)</span> : undefined }
{ callLog.params.selector ? <span>(<span className='call-log-selector'>{callLog.params.selector}</span>)</span> : undefined }
<span className={'codicon ' + iconClass(callLog)}></span>
{ typeof callLog.duration === 'number' ? <span className='call-log-time'>{msToString(callLog.duration)}</span> : undefined}
</div>
{ (isExpanded ? callLog.messages : []).map((message, i) => {
return <div className='call-log-message' key={i}>
Expand Down
26 changes: 0 additions & 26 deletions src/web/traceViewer/ui/helpers.tsx
Expand Up @@ -72,29 +72,3 @@ export const Expandable: React.FunctionComponent<{
{ expanded && <div style={{ display: 'flex', flex: 'auto', margin: '5px 0 5px 20px' }}>{body}</div> }
</div>;
};

export function msToString(ms: number): string {
if (!isFinite(ms))
return '-';

if (ms === 0)
return '0';

if (ms < 1000)
return ms.toFixed(0) + 'ms';

const seconds = ms / 1000;
if (seconds < 60)
return seconds.toFixed(1) + 's';

const minutes = seconds / 60;
if (minutes < 60)
return minutes.toFixed(1) + 'm';

const hours = minutes / 60;
if (hours < 24)
return hours.toFixed(1) + 'h';

const days = hours / 24;
return days.toFixed(1) + 'd';
}
3 changes: 2 additions & 1 deletion src/web/traceViewer/ui/snapshotTab.tsx
Expand Up @@ -18,7 +18,8 @@ import { ActionEntry } from '../../../cli/traceViewer/traceModel';
import { Boundaries, Size } from '../geometry';
import './snapshotTab.css';
import * as React from 'react';
import { msToString, useMeasure } from './helpers';
import { useMeasure } from './helpers';
import { msToString } from '../../uiUtils';

export const SnapshotTab: React.FunctionComponent<{
actionEntry: ActionEntry | undefined,
Expand Down
3 changes: 2 additions & 1 deletion src/web/traceViewer/ui/timeline.tsx
Expand Up @@ -19,7 +19,8 @@ import { ContextEntry, InterestingPageEvent, ActionEntry, trace } from '../../..
import './timeline.css';
import { Boundaries } from '../geometry';
import * as React from 'react';
import { msToString, useMeasure } from './helpers';
import { useMeasure } from './helpers';
import { msToString } from '../../uiUtils';

type TimelineBar = {
entry?: ActionEntry;
Expand Down
41 changes: 41 additions & 0 deletions src/web/uiUtils.ts
@@ -0,0 +1,41 @@
/*
Copyright (c) Microsoft Corporation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

export function msToString(ms: number): string {
if (!isFinite(ms))
return '-';

if (ms === 0)
return '0';

if (ms < 1000)
return ms.toFixed(0) + 'ms';

const seconds = ms / 1000;
if (seconds < 60)
return seconds.toFixed(1) + 's';

const minutes = seconds / 60;
if (minutes < 60)
return minutes.toFixed(1) + 'm';

const hours = minutes / 60;
if (hours < 24)
return hours.toFixed(1) + 'h';

const days = hours / 24;
return days.toFixed(1) + 'd';
}
33 changes: 19 additions & 14 deletions test/pause.spec.ts
Expand Up @@ -146,8 +146,8 @@ describe('pause', (suite, { mode }) => {
await recorderPage.click('[title="Resume"]');
await recorderPage.waitForSelector('.source-line-paused:has-text("page.pause(); // 2")');
expect(await sanitizeLog(recorderPage)).toEqual([
'pause',
'click',
'pause- XXms',
'click(button)- XXms',
'pause',
]);
await recorderPage.click('[title="Resume"]');
Expand All @@ -168,9 +168,9 @@ describe('pause', (suite, { mode }) => {
await recorderPage.click('[title="Resume"]');
await recorderPage.waitForSelector('.source-line-paused:has-text("page.pause(); // 2")');
expect(await sanitizeLog(recorderPage)).toEqual([
'pause',
'waitForEvent()',
'click',
'pause- XXms',
'waitForEvent(console)- XXms',
'click(button)- XXms',
'pause',
]);
await recorderPage.click('[title="Resume"]');
Expand All @@ -187,10 +187,10 @@ describe('pause', (suite, { mode }) => {
await recorderPage.click('[title="Resume"]');
await recorderPage.waitForSelector('.source-line-error');
expect(await sanitizeLog(recorderPage)).toEqual([
'pause',
'isChecked',
'pause- XXms',
'isChecked(button)- XXms',
'checking \"checked\" state of \"button\"',
'selector resolved to <button onclick=\"console.log()\">Submit</button>',
'selector resolved to <button onclick=\"console.log(1)\">Submit</button>',
'Not a checkbox or radio button',
]);
const error = await scriptPromise;
Expand All @@ -199,10 +199,15 @@ describe('pause', (suite, { mode }) => {
});

async function sanitizeLog(recorderPage: Page): Promise<string[]> {
const text = await recorderPage.innerText('.call-log');
return text.split('\n').filter(l => {
return l !== 'element is not stable - waiting...';
}).map(l => {
return l.replace(/\(.*\)/, '()');
});
const results = [];
for (const entry of await recorderPage.$$('.call-log-call')) {
const header = await (await (await entry.$('.call-log-call-header')).textContent()).replace(/— \d+(ms|s)/, '- XXms');
results.push(header);
results.push(...await entry.$$eval('.call-log-message', ee => ee.map(e => e.textContent)));
const errorElement = await entry.$('.call-log-error');
const error = errorElement ? await errorElement.textContent() : undefined;
if (error)
results.push(error);
}
return results;
}

0 comments on commit 15833ee

Please sign in to comment.