mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-12 11:50:22 +03:00
chore(tracing): simplify resource treatment (#6571)
This commit is contained in:
parent
9b0aeeffae
commit
7b844c5fab
@ -56,13 +56,11 @@ export class Dialog extends SdkObject {
|
||||
assert(!this._handled, 'Cannot accept dialog which is already handled!');
|
||||
this._handled = true;
|
||||
await this._onHandle(true, promptText);
|
||||
this._page.emit(Page.Events.InternalDialogClosed, this);
|
||||
}
|
||||
|
||||
async dismiss() {
|
||||
assert(!this._handled, 'Cannot dismiss dialog which is already handled!');
|
||||
this._handled = true;
|
||||
await this._onHandle(false);
|
||||
this._page.emit(Page.Events.InternalDialogClosed, this);
|
||||
}
|
||||
}
|
||||
|
@ -97,7 +97,6 @@ export class Page extends SdkObject {
|
||||
Crash: 'crash',
|
||||
Console: 'console',
|
||||
Dialog: 'dialog',
|
||||
InternalDialogClosed: 'internaldialogclosed',
|
||||
Download: 'download',
|
||||
FileChooser: 'filechooser',
|
||||
DOMContentLoaded: 'domcontentloaded',
|
||||
|
@ -66,35 +66,6 @@ export type FrameSnapshotTraceEvent = {
|
||||
snapshot: FrameSnapshot,
|
||||
};
|
||||
|
||||
export type DialogOpenedEvent = {
|
||||
timestamp: number,
|
||||
type: 'dialog-opened',
|
||||
pageId: string,
|
||||
dialogType: string,
|
||||
message?: string,
|
||||
};
|
||||
|
||||
export type DialogClosedEvent = {
|
||||
timestamp: number,
|
||||
type: 'dialog-closed',
|
||||
pageId: string,
|
||||
dialogType: string,
|
||||
};
|
||||
|
||||
export type NavigationEvent = {
|
||||
timestamp: number,
|
||||
type: 'navigation',
|
||||
pageId: string,
|
||||
url: string,
|
||||
sameDocument: boolean,
|
||||
};
|
||||
|
||||
export type LoadEvent = {
|
||||
timestamp: number,
|
||||
type: 'load',
|
||||
pageId: string,
|
||||
};
|
||||
|
||||
export type TraceEvent =
|
||||
ContextCreatedTraceEvent |
|
||||
PageCreatedTraceEvent |
|
||||
@ -102,8 +73,4 @@ export type TraceEvent =
|
||||
ScreencastFrameTraceEvent |
|
||||
ActionTraceEvent |
|
||||
ResourceSnapshotTraceEvent |
|
||||
FrameSnapshotTraceEvent |
|
||||
DialogOpenedEvent |
|
||||
DialogClosedEvent |
|
||||
NavigationEvent |
|
||||
LoadEvent;
|
||||
FrameSnapshotTraceEvent;
|
||||
|
@ -21,9 +21,7 @@ import yazl from 'yazl';
|
||||
import { calculateSha1, createGuid, mkdirIfNeeded, monotonicTime } from '../../../utils/utils';
|
||||
import { Artifact } from '../../artifact';
|
||||
import { BrowserContext } from '../../browserContext';
|
||||
import { Dialog } from '../../dialog';
|
||||
import { ElementHandle } from '../../dom';
|
||||
import { Frame, NavigationEvent } from '../../frames';
|
||||
import { helper, RegisteredListener } from '../../helper';
|
||||
import { CallMetadata, InstrumentationListener, SdkObject } from '../../instrumentation';
|
||||
import { Page } from '../../page';
|
||||
@ -188,51 +186,6 @@ export class Tracing implements InstrumentationListener {
|
||||
page.setScreencastOptions({ width: 800, height: 600, quality: 90 });
|
||||
|
||||
this._eventListeners.push(
|
||||
helper.addEventListener(page, Page.Events.Dialog, (dialog: Dialog) => {
|
||||
const event: trace.DialogOpenedEvent = {
|
||||
timestamp: monotonicTime(),
|
||||
type: 'dialog-opened',
|
||||
pageId,
|
||||
dialogType: dialog.type(),
|
||||
message: dialog.message(),
|
||||
};
|
||||
this._appendTraceEvent(event);
|
||||
}),
|
||||
|
||||
helper.addEventListener(page, Page.Events.InternalDialogClosed, (dialog: Dialog) => {
|
||||
const event: trace.DialogClosedEvent = {
|
||||
timestamp: monotonicTime(),
|
||||
type: 'dialog-closed',
|
||||
pageId,
|
||||
dialogType: dialog.type(),
|
||||
};
|
||||
this._appendTraceEvent(event);
|
||||
}),
|
||||
|
||||
helper.addEventListener(page.mainFrame(), Frame.Events.Navigation, (navigationEvent: NavigationEvent) => {
|
||||
if (page.mainFrame().url() === 'about:blank')
|
||||
return;
|
||||
const event: trace.NavigationEvent = {
|
||||
timestamp: monotonicTime(),
|
||||
type: 'navigation',
|
||||
pageId,
|
||||
url: navigationEvent.url,
|
||||
sameDocument: !navigationEvent.newDocument,
|
||||
};
|
||||
this._appendTraceEvent(event);
|
||||
}),
|
||||
|
||||
helper.addEventListener(page, Page.Events.Load, () => {
|
||||
if (page.mainFrame().url() === 'about:blank')
|
||||
return;
|
||||
const event: trace.LoadEvent = {
|
||||
timestamp: monotonicTime(),
|
||||
type: 'load',
|
||||
pageId,
|
||||
};
|
||||
this._appendTraceEvent(event);
|
||||
}),
|
||||
|
||||
helper.addEventListener(page, Page.Events.ScreencastFrame, params => {
|
||||
const sha1 = calculateSha1(createGuid()); // no need to compute sha1 for screenshots
|
||||
const event: trace.ScreencastFrameTraceEvent = {
|
||||
@ -248,7 +201,6 @@ export class Tracing implements InstrumentationListener {
|
||||
await fsWriteFileAsync(path.join(this._resourcesDir!, sha1), params.buffer).catch(() => {});
|
||||
});
|
||||
}),
|
||||
|
||||
helper.addEventListener(page, Page.Events.Close, () => {
|
||||
const event: trace.PageDestroyedTraceEvent = {
|
||||
timestamp: monotonicTime(),
|
||||
|
@ -34,18 +34,10 @@ export class TraceModel {
|
||||
appendEvents(events: trace.TraceEvent[], snapshotStorage: SnapshotStorage) {
|
||||
for (const event of events)
|
||||
this.appendEvent(event);
|
||||
const actions: ActionEntry[] = [];
|
||||
const actions: trace.ActionTraceEvent[] = [];
|
||||
for (const page of this.contextEntry!.pages)
|
||||
actions.push(...page.actions);
|
||||
|
||||
const resources = snapshotStorage.resources().reverse();
|
||||
actions.reverse();
|
||||
|
||||
for (const action of actions) {
|
||||
while (resources.length && resources[0].timestamp > action.timestamp)
|
||||
action.resources.push(resources.shift()!);
|
||||
action.resources.reverse();
|
||||
}
|
||||
this.contextEntry!.resources = snapshotStorage.resources();
|
||||
}
|
||||
|
||||
appendEvent(event: trace.TraceEvent) {
|
||||
@ -56,6 +48,7 @@ export class TraceModel {
|
||||
endTime: Number.MIN_VALUE,
|
||||
created: event,
|
||||
pages: [],
|
||||
resources: []
|
||||
};
|
||||
break;
|
||||
}
|
||||
@ -64,7 +57,7 @@ export class TraceModel {
|
||||
created: event,
|
||||
destroyed: undefined as any,
|
||||
actions: [],
|
||||
interestingEvents: [],
|
||||
events: [],
|
||||
screencastFrames: [],
|
||||
};
|
||||
this.pageEntries.set(event.pageId, pageEntry);
|
||||
@ -82,20 +75,14 @@ export class TraceModel {
|
||||
case 'action': {
|
||||
const metadata = event.metadata;
|
||||
const pageEntry = this.pageEntries.get(metadata.pageId!)!;
|
||||
const action: ActionEntry = {
|
||||
actionId: metadata.id,
|
||||
resources: [],
|
||||
...event,
|
||||
};
|
||||
pageEntry.actions.push(action);
|
||||
pageEntry.actions.push(event);
|
||||
break;
|
||||
}
|
||||
case 'dialog-opened':
|
||||
case 'dialog-closed':
|
||||
case 'navigation':
|
||||
case 'load': {
|
||||
const pageEntry = this.pageEntries.get(event.pageId)!;
|
||||
pageEntry.interestingEvents.push(event);
|
||||
case 'event': {
|
||||
const metadata = event.metadata;
|
||||
const pageEntry = this.pageEntries.get(metadata.pageId!);
|
||||
if (pageEntry)
|
||||
pageEntry.events.push(event);
|
||||
break;
|
||||
}
|
||||
case 'resource-snapshot':
|
||||
@ -105,8 +92,10 @@ export class TraceModel {
|
||||
this._snapshotStorage.addFrameSnapshot(event.snapshot);
|
||||
break;
|
||||
}
|
||||
this.contextEntry!.startTime = Math.min(this.contextEntry!.startTime, event.timestamp);
|
||||
this.contextEntry!.endTime = Math.max(this.contextEntry!.endTime, event.timestamp);
|
||||
if (event.type === 'action' || event.type === 'event') {
|
||||
this.contextEntry!.startTime = Math.min(this.contextEntry!.startTime, event.metadata.startTime);
|
||||
this.contextEntry!.endTime = Math.max(this.contextEntry!.endTime, event.metadata.endTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,15 +104,14 @@ export type ContextEntry = {
|
||||
endTime: number;
|
||||
created: trace.ContextCreatedTraceEvent;
|
||||
pages: PageEntry[];
|
||||
resources: ResourceSnapshot[];
|
||||
}
|
||||
|
||||
export type InterestingPageEvent = trace.DialogOpenedEvent | trace.DialogClosedEvent | trace.NavigationEvent | trace.LoadEvent;
|
||||
|
||||
export type PageEntry = {
|
||||
created: trace.PageCreatedTraceEvent;
|
||||
destroyed: trace.PageDestroyedTraceEvent;
|
||||
actions: ActionEntry[];
|
||||
interestingEvents: InterestingPageEvent[];
|
||||
actions: trace.ActionTraceEvent[];
|
||||
events: trace.ActionTraceEvent[];
|
||||
screencastFrames: {
|
||||
sha1: string,
|
||||
timestamp: number,
|
||||
@ -132,11 +120,6 @@ export type PageEntry = {
|
||||
}[]
|
||||
}
|
||||
|
||||
export type ActionEntry = trace.ActionTraceEvent & {
|
||||
actionId: string;
|
||||
resources: ResourceSnapshot[]
|
||||
};
|
||||
|
||||
export class PersistentSnapshotStorage extends BaseSnapshotStorage {
|
||||
private _resourcesDir: string;
|
||||
|
||||
|
@ -14,17 +14,17 @@
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { ActionEntry } from '../../../server/trace/viewer/traceModel';
|
||||
import './actionList.css';
|
||||
import './tabbedPane.css';
|
||||
import * as React from 'react';
|
||||
import { ActionTraceEvent } from '../../../server/trace/common/traceEvents';
|
||||
|
||||
export interface ActionListProps {
|
||||
actions: ActionEntry[],
|
||||
selectedAction: ActionEntry | undefined,
|
||||
highlightedAction: ActionEntry | undefined,
|
||||
onSelected: (action: ActionEntry) => void,
|
||||
onHighlighted: (action: ActionEntry | undefined) => void,
|
||||
actions: ActionTraceEvent[],
|
||||
selectedAction: ActionTraceEvent | undefined,
|
||||
highlightedAction: ActionTraceEvent | undefined,
|
||||
onSelected: (action: ActionTraceEvent) => void,
|
||||
onHighlighted: (action: ActionTraceEvent | undefined) => void,
|
||||
}
|
||||
|
||||
export const ActionList: React.FC<ActionListProps> = ({
|
||||
@ -68,16 +68,16 @@ export const ActionList: React.FC<ActionListProps> = ({
|
||||
}}
|
||||
ref={actionListRef}
|
||||
>
|
||||
{actions.map(actionEntry => {
|
||||
const { metadata, actionId } = actionEntry;
|
||||
const selectedSuffix = actionEntry === selectedAction ? ' selected' : '';
|
||||
const highlightedSuffix = actionEntry === highlightedAction ? ' highlighted' : '';
|
||||
{actions.map(action => {
|
||||
const { metadata } = action;
|
||||
const selectedSuffix = action === selectedAction ? ' selected' : '';
|
||||
const highlightedSuffix = action === highlightedAction ? ' highlighted' : '';
|
||||
return <div
|
||||
className={'action-entry' + selectedSuffix + highlightedSuffix}
|
||||
key={actionId}
|
||||
onClick={() => onSelected(actionEntry)}
|
||||
onMouseEnter={() => onHighlighted(actionEntry)}
|
||||
onMouseLeave={() => (highlightedAction === actionEntry) && onHighlighted(undefined)}
|
||||
key={metadata.id}
|
||||
onClick={() => onSelected(action)}
|
||||
onMouseEnter={() => onHighlighted(action)}
|
||||
onMouseLeave={() => (highlightedAction === action) && onHighlighted(undefined)}
|
||||
>
|
||||
<div className={'action-error codicon codicon-issues'} hidden={!metadata.error} />
|
||||
<div className='action-title'>{metadata.apiName || metadata.method}</div>
|
||||
|
@ -14,18 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ActionEntry } from '../../../server/trace/viewer/traceModel';
|
||||
import * as React from 'react';
|
||||
import './logsTab.css';
|
||||
import { ActionTraceEvent } from '../../../server/trace/common/traceEvents';
|
||||
|
||||
export const LogsTab: React.FunctionComponent<{
|
||||
actionEntry: ActionEntry | undefined,
|
||||
}> = ({ actionEntry }) => {
|
||||
action: ActionTraceEvent | undefined,
|
||||
}> = ({ action }) => {
|
||||
let logs: string[] = [];
|
||||
if (actionEntry) {
|
||||
logs = actionEntry.metadata.log || [];
|
||||
if (actionEntry.metadata.error)
|
||||
logs = [actionEntry.metadata.error, ...logs];
|
||||
if (action) {
|
||||
logs = action.metadata.log || [];
|
||||
if (action.metadata.error)
|
||||
logs = [action.metadata.error, ...logs];
|
||||
}
|
||||
return <div className='logs-tab'>{
|
||||
logs.map((logLine, index) => {
|
||||
|
@ -14,18 +14,25 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ActionEntry } from '../../../server/trace/viewer/traceModel';
|
||||
import { ActionTraceEvent } from '../../../server/trace/common/traceEvents';
|
||||
import { ContextEntry } from '../../../server/trace/viewer/traceModel';
|
||||
import './networkTab.css';
|
||||
import * as React from 'react';
|
||||
import { NetworkResourceDetails } from './networkResourceDetails';
|
||||
import { ResourceSnapshot } from '../../../server/snapshot/snapshotTypes';
|
||||
|
||||
export const NetworkTab: React.FunctionComponent<{
|
||||
actionEntry: ActionEntry | undefined,
|
||||
}> = ({ actionEntry }) => {
|
||||
context: ContextEntry,
|
||||
action: ActionTraceEvent | undefined,
|
||||
nextAction: ActionTraceEvent | undefined,
|
||||
}> = ({ context, action, nextAction }) => {
|
||||
const [selected, setSelected] = React.useState(0);
|
||||
|
||||
const resources: ResourceSnapshot[] = context.resources.filter(resource => {
|
||||
return action && resource.timestamp > action.metadata.startTime && (!nextAction || resource.timestamp < nextAction.metadata.startTime);
|
||||
});
|
||||
return <div className='network-tab'>{
|
||||
(actionEntry ? actionEntry.resources : []).map((resource, index) => {
|
||||
resources.map((resource, index) => {
|
||||
return <NetworkResourceDetails resource={resource} key={index} index={index} selected={selected === index} setSelected={setSelected} />;
|
||||
})
|
||||
}</div>;
|
||||
|
@ -14,23 +14,23 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ActionEntry } from '../../../server/trace/viewer/traceModel';
|
||||
import { Size } from '../geometry';
|
||||
import './snapshotTab.css';
|
||||
import './tabbedPane.css';
|
||||
import * as React from 'react';
|
||||
import { useMeasure } from './helpers';
|
||||
import type { Point } from '../../../common/types';
|
||||
import { ActionTraceEvent } from '../../../server/trace/common/traceEvents';
|
||||
|
||||
export const SnapshotTab: React.FunctionComponent<{
|
||||
actionEntry: ActionEntry | undefined,
|
||||
action: ActionTraceEvent | undefined,
|
||||
snapshotSize: Size,
|
||||
}> = ({ actionEntry, snapshotSize }) => {
|
||||
}> = ({ action, snapshotSize }) => {
|
||||
const [measure, ref] = useMeasure<HTMLDivElement>();
|
||||
let [snapshotIndex, setSnapshotIndex] = React.useState(0);
|
||||
|
||||
const snapshotMap = new Map<string, { title: string, snapshotName: string }>();
|
||||
for (const snapshot of actionEntry?.metadata.snapshots || [])
|
||||
for (const snapshot of action?.metadata.snapshots || [])
|
||||
snapshotMap.set(snapshot.title, snapshot);
|
||||
const actionSnapshot = snapshotMap.get('action') || snapshotMap.get('after');
|
||||
const snapshots = [actionSnapshot ? { ...actionSnapshot, title: 'action' } : undefined, snapshotMap.get('before'), snapshotMap.get('after')].filter(Boolean) as { title: string, snapshotName: string }[];
|
||||
@ -44,12 +44,12 @@ export const SnapshotTab: React.FunctionComponent<{
|
||||
return;
|
||||
let snapshotUri = undefined;
|
||||
let point: Point | undefined = undefined;
|
||||
if (actionEntry) {
|
||||
if (action) {
|
||||
const snapshot = snapshots[snapshotIndex];
|
||||
if (snapshot && snapshot.snapshotName) {
|
||||
snapshotUri = `${actionEntry.metadata.pageId}?name=${snapshot.snapshotName}`;
|
||||
snapshotUri = `${action.metadata.pageId}?name=${snapshot.snapshotName}`;
|
||||
if (snapshot.snapshotName.includes('action'))
|
||||
point = actionEntry.metadata.point;
|
||||
point = action.metadata.point;
|
||||
}
|
||||
}
|
||||
const snapshotUrl = snapshotUri ? `${window.location.origin}/snapshot/${snapshotUri}` : 'data:text/html,<body style="background: #ddd"></body>';
|
||||
@ -57,7 +57,7 @@ export const SnapshotTab: React.FunctionComponent<{
|
||||
(iframeRef.current.contentWindow as any).showSnapshot(snapshotUrl, { point });
|
||||
} catch (e) {
|
||||
}
|
||||
}, [actionEntry, snapshotIndex]);
|
||||
}, [action, snapshotIndex]);
|
||||
|
||||
const scale = Math.min(measure.width / snapshotSize.width, measure.height / snapshotSize.height);
|
||||
const scaledSize = {
|
||||
|
@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ActionEntry } from '../../../server/trace/viewer/traceModel';
|
||||
import * as React from 'react';
|
||||
import { useAsyncMemo } from './helpers';
|
||||
import './sourceTab.css';
|
||||
@ -23,6 +22,7 @@ import { StackFrame } from '../../../common/types';
|
||||
import { Source as SourceView } from '../../components/source';
|
||||
import { StackTraceView } from './stackTrace';
|
||||
import { SplitView } from '../../components/splitView';
|
||||
import { ActionTraceEvent } from '../../../server/trace/common/traceEvents';
|
||||
|
||||
type StackInfo = string | {
|
||||
frames: StackFrame[];
|
||||
@ -30,22 +30,22 @@ type StackInfo = string | {
|
||||
};
|
||||
|
||||
export const SourceTab: React.FunctionComponent<{
|
||||
actionEntry: ActionEntry | undefined,
|
||||
}> = ({ actionEntry }) => {
|
||||
const [lastAction, setLastAction] = React.useState<ActionEntry | undefined>();
|
||||
action: ActionTraceEvent | undefined,
|
||||
}> = ({ action }) => {
|
||||
const [lastAction, setLastAction] = React.useState<ActionTraceEvent | undefined>();
|
||||
const [selectedFrame, setSelectedFrame] = React.useState<number>(0);
|
||||
const [needReveal, setNeedReveal] = React.useState<boolean>(false);
|
||||
|
||||
if (lastAction !== actionEntry) {
|
||||
setLastAction(actionEntry);
|
||||
if (lastAction !== action) {
|
||||
setLastAction(action);
|
||||
setSelectedFrame(0);
|
||||
setNeedReveal(true);
|
||||
}
|
||||
|
||||
const stackInfo = React.useMemo<StackInfo>(() => {
|
||||
if (!actionEntry)
|
||||
if (!action)
|
||||
return '';
|
||||
const { metadata } = actionEntry;
|
||||
const { metadata } = action;
|
||||
if (!metadata.stack)
|
||||
return '';
|
||||
const frames = metadata.stack;
|
||||
@ -53,7 +53,7 @@ export const SourceTab: React.FunctionComponent<{
|
||||
frames,
|
||||
fileContent: new Map(),
|
||||
};
|
||||
}, [actionEntry]);
|
||||
}, [action]);
|
||||
|
||||
const content = useAsyncMemo<string>(async () => {
|
||||
let value: string;
|
||||
@ -80,6 +80,6 @@ export const SourceTab: React.FunctionComponent<{
|
||||
|
||||
return <SplitView sidebarSize={100} orientation='vertical'>
|
||||
<SourceView text={content} language='javascript' highlight={[{ line: targetLine, type: 'running' }]} revealLine={targetLine}></SourceView>
|
||||
<StackTraceView actionEntry={actionEntry} selectedFrame={selectedFrame} setSelectedFrame={setSelectedFrame}></StackTraceView>
|
||||
<StackTraceView action={action} selectedFrame={selectedFrame} setSelectedFrame={setSelectedFrame}></StackTraceView>
|
||||
</SplitView>;
|
||||
};
|
||||
|
@ -14,16 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ActionEntry } from '../../../server/trace/viewer/traceModel';
|
||||
import * as React from 'react';
|
||||
import './stackTrace.css';
|
||||
import { ActionTraceEvent } from '../../../server/trace/common/traceEvents';
|
||||
|
||||
export const StackTraceView: React.FunctionComponent<{
|
||||
actionEntry: ActionEntry | undefined,
|
||||
action: ActionTraceEvent | undefined,
|
||||
selectedFrame: number,
|
||||
setSelectedFrame: (index: number) => void
|
||||
}> = ({ actionEntry, setSelectedFrame, selectedFrame }) => {
|
||||
const frames = actionEntry?.metadata.stack || [];
|
||||
}> = ({ action, setSelectedFrame, selectedFrame }) => {
|
||||
const frames = action?.metadata.stack || [];
|
||||
return <div className='stack-trace'>{
|
||||
frames.map((frame, index) => {
|
||||
return <div
|
||||
|
@ -71,10 +71,9 @@
|
||||
position: absolute;
|
||||
height: 9px;
|
||||
top: 11px;
|
||||
background-color: red;
|
||||
border-radius: 2px;
|
||||
min-width: 3px;
|
||||
--action-color: 'transparent';
|
||||
--action-color: gray;
|
||||
background-color: var(--action-color);
|
||||
}
|
||||
|
||||
@ -83,46 +82,54 @@
|
||||
box-shadow: 0 0 0 1px var(--action-color);
|
||||
}
|
||||
|
||||
.timeline-bar.click,
|
||||
.timeline-bar.dblclick,
|
||||
.timeline-bar.hover,
|
||||
.timeline-bar.check,
|
||||
.timeline-bar.uncheck,
|
||||
.timeline-bar.tap {
|
||||
.timeline-bar.frame_click,
|
||||
.timeline-bar.frame_dblclick,
|
||||
.timeline-bar.frame_hover,
|
||||
.timeline-bar.frame_check,
|
||||
.timeline-bar.frame_uncheck,
|
||||
.timeline-bar.frame_tap {
|
||||
--action-color: var(--green);
|
||||
}
|
||||
|
||||
.timeline-bar.fill,
|
||||
.timeline-bar.press,
|
||||
.timeline-bar.type,
|
||||
.timeline-bar.selectOption,
|
||||
.timeline-bar.setInputFiles {
|
||||
.timeline-bar.page_load,
|
||||
.timeline-bar.page_domcontentloaded,
|
||||
.timeline-bar.frame_fill,
|
||||
.timeline-bar.frame_press,
|
||||
.timeline-bar.frame_type,
|
||||
.timeline-bar.frame_selectoption,
|
||||
.timeline-bar.frame_setinputfiles {
|
||||
--action-color: var(--orange);
|
||||
}
|
||||
|
||||
.timeline-bar.goto,
|
||||
.timeline-bar.setContent,
|
||||
.timeline-bar.goBack,
|
||||
.timeline-bar.goForward,
|
||||
.timeline-bar.frame_loadstate {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.timeline-bar.frame_goto,
|
||||
.timeline-bar.frame_setcontent,
|
||||
.timeline-bar.frame_goback,
|
||||
.timeline-bar.frame_goforward,
|
||||
.timeline-bar.reload {
|
||||
--action-color: var(--blue);
|
||||
}
|
||||
|
||||
.timeline-bar.evaluateExpression {
|
||||
.timeline-bar.frame_evaluateexpression {
|
||||
--action-color: var(--yellow);
|
||||
}
|
||||
|
||||
.timeline-bar.dialog {
|
||||
top: 22px;
|
||||
.timeline-bar.frame_dialog {
|
||||
--action-color: var(--transparent-blue);
|
||||
}
|
||||
|
||||
.timeline-bar.navigation {
|
||||
top: 22px;
|
||||
.timeline-bar.frame_navigated {
|
||||
--action-color: var(--blue);
|
||||
}
|
||||
|
||||
.timeline-bar.waitForEventInfo {
|
||||
.timeline-bar.event {
|
||||
top: 22px;
|
||||
}
|
||||
|
||||
.timeline-bar.frame_waitforeventinfo {
|
||||
bottom: inherit;
|
||||
top: 0;
|
||||
--action-color: var(--gray);
|
||||
|
@ -15,7 +15,8 @@
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { ContextEntry, InterestingPageEvent, ActionEntry, trace } from '../../../server/trace/viewer/traceModel';
|
||||
import { ActionTraceEvent } from '../../../server/trace/common/traceEvents';
|
||||
import { ContextEntry } from '../../../server/trace/viewer/traceModel';
|
||||
import './timeline.css';
|
||||
import { Boundaries } from '../geometry';
|
||||
import * as React from 'react';
|
||||
@ -24,24 +25,24 @@ import { msToString } from '../../uiUtils';
|
||||
import { FilmStrip } from './filmStrip';
|
||||
|
||||
type TimelineBar = {
|
||||
entry?: ActionEntry;
|
||||
event?: InterestingPageEvent;
|
||||
action?: ActionTraceEvent;
|
||||
event?: ActionTraceEvent;
|
||||
leftPosition: number;
|
||||
rightPosition: number;
|
||||
leftTime: number;
|
||||
rightTime: number;
|
||||
type: string;
|
||||
label: string;
|
||||
priority: number;
|
||||
className: string;
|
||||
};
|
||||
|
||||
export const Timeline: React.FunctionComponent<{
|
||||
context: ContextEntry,
|
||||
boundaries: Boundaries,
|
||||
selectedAction: ActionEntry | undefined,
|
||||
highlightedAction: ActionEntry | undefined,
|
||||
onSelected: (action: ActionEntry) => void,
|
||||
onHighlighted: (action: ActionEntry | undefined) => void,
|
||||
selectedAction: ActionTraceEvent | undefined,
|
||||
highlightedAction: ActionTraceEvent | undefined,
|
||||
onSelected: (action: ActionTraceEvent) => void,
|
||||
onHighlighted: (action: ActionTraceEvent | undefined) => void,
|
||||
}> = ({ context, boundaries, selectedAction, highlightedAction, onSelected, onHighlighted }) => {
|
||||
const [measure, ref] = useMeasure<HTMLDivElement>();
|
||||
const [previewPoint, setPreviewPoint] = React.useState<{ x: number, clientY: number } | undefined>();
|
||||
@ -61,64 +62,36 @@ export const Timeline: React.FunctionComponent<{
|
||||
if (entry.metadata.method === 'goto')
|
||||
detail = entry.metadata.params.url || '';
|
||||
bars.push({
|
||||
entry,
|
||||
action: entry,
|
||||
leftTime: entry.metadata.startTime,
|
||||
rightTime: entry.metadata.endTime,
|
||||
leftPosition: timeToPosition(measure.width, boundaries, entry.metadata.startTime),
|
||||
rightPosition: timeToPosition(measure.width, boundaries, entry.metadata.endTime),
|
||||
label: entry.metadata.apiName + ' ' + detail,
|
||||
type: entry.metadata.method,
|
||||
priority: 0,
|
||||
type: entry.metadata.type + '.' + entry.metadata.method,
|
||||
className: `${entry.metadata.type}_${entry.metadata.method}`.toLowerCase()
|
||||
});
|
||||
}
|
||||
let lastDialogOpened: trace.DialogOpenedEvent | undefined;
|
||||
for (const event of page.interestingEvents) {
|
||||
if (event.type === 'dialog-opened') {
|
||||
lastDialogOpened = event;
|
||||
continue;
|
||||
}
|
||||
if (event.type === 'dialog-closed' && lastDialogOpened) {
|
||||
bars.push({
|
||||
event,
|
||||
leftTime: lastDialogOpened.timestamp,
|
||||
rightTime: event.timestamp,
|
||||
leftPosition: timeToPosition(measure.width, boundaries, lastDialogOpened.timestamp),
|
||||
rightPosition: timeToPosition(measure.width, boundaries, event.timestamp),
|
||||
label: lastDialogOpened.message ? `${event.dialogType} "${lastDialogOpened.message}"` : event.dialogType,
|
||||
type: 'dialog',
|
||||
priority: -1,
|
||||
});
|
||||
} else if (event.type === 'navigation') {
|
||||
bars.push({
|
||||
event,
|
||||
leftTime: event.timestamp,
|
||||
rightTime: event.timestamp,
|
||||
leftPosition: timeToPosition(measure.width, boundaries, event.timestamp),
|
||||
rightPosition: timeToPosition(measure.width, boundaries, event.timestamp),
|
||||
label: `navigated to ${event.url}`,
|
||||
type: event.type,
|
||||
priority: 1,
|
||||
});
|
||||
} else if (event.type === 'load') {
|
||||
bars.push({
|
||||
event,
|
||||
leftTime: event.timestamp,
|
||||
rightTime: event.timestamp,
|
||||
leftPosition: timeToPosition(measure.width, boundaries, event.timestamp),
|
||||
rightPosition: timeToPosition(measure.width, boundaries, event.timestamp),
|
||||
label: `load`,
|
||||
type: event.type,
|
||||
priority: 1,
|
||||
});
|
||||
}
|
||||
|
||||
for (const event of page.events) {
|
||||
const startTime = event.metadata.startTime;
|
||||
bars.push({
|
||||
event,
|
||||
leftTime: startTime,
|
||||
rightTime: startTime,
|
||||
leftPosition: timeToPosition(measure.width, boundaries, startTime),
|
||||
rightPosition: timeToPosition(measure.width, boundaries, startTime),
|
||||
label: event.metadata.method,
|
||||
type: event.metadata.type + '.' + event.metadata.method,
|
||||
className: `${event.metadata.type}_${event.metadata.method}`.toLowerCase()
|
||||
});
|
||||
}
|
||||
}
|
||||
bars.sort((a, b) => a.priority - b.priority);
|
||||
return bars;
|
||||
}, [context, boundaries, measure.width]);
|
||||
|
||||
const hoveredBar = hoveredBarIndex !== undefined ? bars[hoveredBarIndex] : undefined;
|
||||
let targetBar: TimelineBar | undefined = bars.find(bar => bar.entry === (highlightedAction || selectedAction));
|
||||
let targetBar: TimelineBar | undefined = bars.find(bar => bar.action === (highlightedAction || selectedAction));
|
||||
targetBar = hoveredBar || targetBar;
|
||||
|
||||
const findHoveredBarIndex = (x: number) => {
|
||||
@ -150,7 +123,7 @@ export const Timeline: React.FunctionComponent<{
|
||||
setPreviewPoint({ x, clientY: event.clientY });
|
||||
setHoveredBarIndex(index);
|
||||
if (typeof index === 'number')
|
||||
onHighlighted(bars[index].entry);
|
||||
onHighlighted(bars[index].action);
|
||||
};
|
||||
|
||||
const onMouseLeave = () => {
|
||||
@ -167,7 +140,7 @@ export const Timeline: React.FunctionComponent<{
|
||||
const index = findHoveredBarIndex(x);
|
||||
if (index === undefined)
|
||||
return;
|
||||
const entry = bars[index].entry;
|
||||
const entry = bars[index].action;
|
||||
if (entry)
|
||||
onSelected(entry);
|
||||
};
|
||||
@ -183,7 +156,7 @@ export const Timeline: React.FunctionComponent<{
|
||||
<div className='timeline-lane timeline-labels'>{
|
||||
bars.map((bar, index) => {
|
||||
return <div key={index}
|
||||
className={'timeline-label ' + bar.type + (targetBar === bar ? ' selected' : '')}
|
||||
className={'timeline-label ' + bar.className + (targetBar === bar ? ' selected' : '')}
|
||||
style={{
|
||||
left: bar.leftPosition + 'px',
|
||||
width: Math.max(1, bar.rightPosition - bar.leftPosition) + 'px',
|
||||
@ -196,7 +169,7 @@ export const Timeline: React.FunctionComponent<{
|
||||
<div className='timeline-lane timeline-bars'>{
|
||||
bars.map((bar, index) => {
|
||||
return <div key={index}
|
||||
className={'timeline-bar ' + bar.type + (targetBar === bar ? ' selected' : '')}
|
||||
className={'timeline-bar ' + (bar.action ? 'action ' : '') + (bar.event ? 'event ' : '') + bar.className + (targetBar === bar ? ' selected' : '')}
|
||||
style={{
|
||||
left: bar.leftPosition + 'px',
|
||||
width: Math.max(1, bar.rightPosition - bar.leftPosition) + 'px',
|
||||
|
@ -14,7 +14,8 @@
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { ActionEntry, ContextEntry } from '../../../server/trace/viewer/traceModel';
|
||||
import { ActionTraceEvent } from '../../../server/trace/common/traceEvents';
|
||||
import { ContextEntry } from '../../../server/trace/viewer/traceModel';
|
||||
import { ActionList } from './actionList';
|
||||
import { TabbedPane } from './tabbedPane';
|
||||
import { Timeline } from './timeline';
|
||||
@ -33,20 +34,21 @@ export const Workbench: React.FunctionComponent<{
|
||||
debugNames: string[],
|
||||
}> = ({ debugNames }) => {
|
||||
const [debugName, setDebugName] = React.useState(debugNames[0]);
|
||||
const [selectedAction, setSelectedAction] = React.useState<ActionEntry | undefined>();
|
||||
const [highlightedAction, setHighlightedAction] = React.useState<ActionEntry | undefined>();
|
||||
const [selectedAction, setSelectedAction] = React.useState<ActionTraceEvent | undefined>();
|
||||
const [highlightedAction, setHighlightedAction] = React.useState<ActionTraceEvent | undefined>();
|
||||
|
||||
let context = useAsyncMemo(async () => {
|
||||
return (await fetch(`/context/${debugName}`).then(response => response.json())) as ContextEntry;
|
||||
}, [debugName], emptyContext);
|
||||
|
||||
const actions = React.useMemo(() => {
|
||||
const actions: ActionEntry[] = [];
|
||||
const { actions, nextAction } = React.useMemo(() => {
|
||||
const actions: ActionTraceEvent[] = [];
|
||||
for (const page of context.pages)
|
||||
actions.push(...page.actions);
|
||||
actions.sort((a, b) => a.timestamp - b.timestamp);
|
||||
return actions;
|
||||
}, [context]);
|
||||
const nextAction = selectedAction ? actions[actions.indexOf(selectedAction) + 1] : undefined;
|
||||
return { actions, nextAction };
|
||||
}, [context, selectedAction]);
|
||||
|
||||
const snapshotSize = context.created.viewportSize || { width: 1280, height: 720 };
|
||||
const boundaries = { minimum: context.startTime, maximum: context.endTime };
|
||||
@ -77,11 +79,11 @@ export const Workbench: React.FunctionComponent<{
|
||||
</div>
|
||||
<SplitView sidebarSize={300} orientation='horizontal' sidebarIsFirst={true}>
|
||||
<SplitView sidebarSize={300} orientation='horizontal'>
|
||||
<SnapshotTab actionEntry={selectedAction} snapshotSize={snapshotSize} />
|
||||
<SnapshotTab action={selectedAction} snapshotSize={snapshotSize} />
|
||||
<TabbedPane tabs={[
|
||||
{ id: 'logs', title: 'Log', render: () => <LogsTab actionEntry={selectedAction} /> },
|
||||
{ id: 'source', title: 'Source', render: () => <SourceTab actionEntry={selectedAction} /> },
|
||||
{ id: 'network', title: 'Network', render: () => <NetworkTab actionEntry={selectedAction} /> },
|
||||
{ id: 'logs', title: 'Log', render: () => <LogsTab action={selectedAction} /> },
|
||||
{ id: 'source', title: 'Source', render: () => <SourceTab action={selectedAction} /> },
|
||||
{ id: 'network', title: 'Network', render: () => <NetworkTab context={context} action={selectedAction} nextAction={nextAction}/> },
|
||||
]}/>
|
||||
</SplitView>
|
||||
<ActionList
|
||||
@ -110,5 +112,6 @@ const emptyContext: ContextEntry = {
|
||||
viewportSize: { width: 1280, height: 800 },
|
||||
debugName: '<empty>',
|
||||
},
|
||||
pages: []
|
||||
pages: [],
|
||||
resources: []
|
||||
};
|
||||
|
@ -147,7 +147,7 @@ DEPS['src/cli/driver.ts'] = DEPS['src/inprocess.ts'] = DEPS['src/browserServerIm
|
||||
// Tracing is a client/server plugin, nothing should depend on it.
|
||||
DEPS['src/web/recorder/'] = ['src/common/', 'src/web/', 'src/web/components/', 'src/server/supplements/recorder/recorderTypes.ts'];
|
||||
DEPS['src/web/traceViewer/'] = ['src/common/', 'src/web/'];
|
||||
DEPS['src/web/traceViewer/ui/'] = ['src/common/', 'src/web/traceViewer/', 'src/web/', 'src/server/trace/viewer/', 'src/server/trace/', 'src/server/trace/common/', 'src/server/snapshot/snapshotTypes.ts'];
|
||||
DEPS['src/web/traceViewer/ui/'] = ['src/common/', 'src/web/traceViewer/', 'src/web/', 'src/server/trace/viewer/', 'src/server/trace/', 'src/server/trace/common/', 'src/server/snapshot/snapshotTypes.ts', 'src/protocol/channels.ts'];
|
||||
// The service is a cross-cutting feature, and so it depends on a bunch of things.
|
||||
DEPS['src/remote/'] = ['src/client/', 'src/debug/', 'src/dispatchers/', 'src/server/', 'src/server/supplements/', 'src/server/electron/', 'src/server/trace/'];
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user