chore: extract snapshotter from trace viewer (#5618)
This commit is contained in:
parent
af89ab7a6f
commit
2ff6d54f26
|
|
@ -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 type NodeSnapshot =
|
||||
// Text node.
|
||||
string |
|
||||
// Subtree reference, "x snapshots ago, node #y". Could point to a text node.
|
||||
// Only nodes that are not references are counted, starting from zero, using post-order traversal.
|
||||
[ [number, number] ] |
|
||||
// Just node name.
|
||||
[ string ] |
|
||||
// Node name, attributes, child nodes.
|
||||
// Unfortunately, we cannot make this type definition recursive, therefore "any".
|
||||
[ string, { [attr: string]: string }, ...any ];
|
||||
|
||||
|
||||
export type ResourceOverride = {
|
||||
url: string,
|
||||
sha1?: string,
|
||||
ref?: number
|
||||
};
|
||||
|
||||
export type FrameSnapshot = {
|
||||
doctype?: string,
|
||||
html: NodeSnapshot,
|
||||
resourceOverrides: ResourceOverride[],
|
||||
viewport: { width: number, height: number },
|
||||
};
|
||||
|
|
@ -14,34 +14,30 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as trace from '../common/traceEvents';
|
||||
import { ContextResources } from './traceModel';
|
||||
export * as trace from '../common/traceEvents';
|
||||
import { FrameSnapshot, NodeSnapshot } from './snapshot';
|
||||
|
||||
export type SerializedFrameSnapshot = {
|
||||
export type ContextResources = Map<string, { resourceId: string, frameId: string }[]>;
|
||||
|
||||
export type RenderedFrameSnapshot = {
|
||||
html: string;
|
||||
resources: { [key: string]: { resourceId: string, sha1?: string } };
|
||||
};
|
||||
|
||||
export class FrameSnapshot {
|
||||
private _snapshots: trace.FrameSnapshotTraceEvent[];
|
||||
export class SnapshotRenderer {
|
||||
private _snapshots: FrameSnapshot[];
|
||||
private _index: number;
|
||||
private _contextResources: ContextResources;
|
||||
private _frameId: string;
|
||||
|
||||
constructor(frameId: string, contextResources: ContextResources, events: trace.FrameSnapshotTraceEvent[], index: number) {
|
||||
constructor(frameId: string, contextResources: ContextResources, snapshots: FrameSnapshot[], index: number) {
|
||||
this._frameId = frameId;
|
||||
this._contextResources = contextResources;
|
||||
this._snapshots = events;
|
||||
this._snapshots = snapshots;
|
||||
this._index = index;
|
||||
}
|
||||
|
||||
traceEvent(): trace.FrameSnapshotTraceEvent {
|
||||
return this._snapshots[this._index];
|
||||
}
|
||||
|
||||
serialize(): SerializedFrameSnapshot {
|
||||
const visit = (n: trace.NodeSnapshot, snapshotIndex: number): string => {
|
||||
render(): RenderedFrameSnapshot {
|
||||
const visit = (n: NodeSnapshot, snapshotIndex: number): string => {
|
||||
// Text node.
|
||||
if (typeof n === 'string')
|
||||
return escapeText(n);
|
||||
|
|
@ -51,7 +47,7 @@ export class FrameSnapshot {
|
|||
// Node reference.
|
||||
const referenceIndex = snapshotIndex - n[0][0];
|
||||
if (referenceIndex >= 0 && referenceIndex < snapshotIndex) {
|
||||
const nodes = snapshotNodes(this._snapshots[referenceIndex].snapshot);
|
||||
const nodes = snapshotNodes(this._snapshots[referenceIndex]);
|
||||
const nodeIndex = n[0][1];
|
||||
if (nodeIndex >= 0 && nodeIndex < nodes.length)
|
||||
(n as any)._string = visit(nodes[nodeIndex], referenceIndex);
|
||||
|
|
@ -76,7 +72,7 @@ export class FrameSnapshot {
|
|||
return (n as any)._string;
|
||||
};
|
||||
|
||||
const snapshot = this._snapshots[this._index].snapshot;
|
||||
const snapshot = this._snapshots[this._index];
|
||||
let html = visit(snapshot.html, this._index);
|
||||
if (snapshot.doctype)
|
||||
html = `<!DOCTYPE ${snapshot.doctype}>` + html;
|
||||
|
|
@ -88,7 +84,7 @@ export class FrameSnapshot {
|
|||
if (contextResource)
|
||||
resources[url] = { resourceId: contextResource.resourceId };
|
||||
}
|
||||
for (const o of this.traceEvent().snapshot.resourceOverrides) {
|
||||
for (const o of snapshot.resourceOverrides) {
|
||||
const resource = resources[o.url];
|
||||
resource.sha1 = o.sha1;
|
||||
}
|
||||
|
|
@ -106,10 +102,10 @@ function escapeText(s: string): string {
|
|||
return s.replace(/[&<]/ug, char => (escaped as any)[char]);
|
||||
}
|
||||
|
||||
function snapshotNodes(snapshot: trace.FrameSnapshot): trace.NodeSnapshot[] {
|
||||
function snapshotNodes(snapshot: FrameSnapshot): NodeSnapshot[] {
|
||||
if (!(snapshot as any)._nodes) {
|
||||
const nodes: trace.NodeSnapshot[] = [];
|
||||
const visit = (n: trace.NodeSnapshot) => {
|
||||
const nodes: NodeSnapshot[] = [];
|
||||
const visit = (n: NodeSnapshot) => {
|
||||
if (typeof n === 'string') {
|
||||
nodes.push(n);
|
||||
} else if (typeof n[0] === 'string') {
|
||||
|
|
@ -16,14 +16,19 @@
|
|||
|
||||
import * as http from 'http';
|
||||
import querystring from 'querystring';
|
||||
import type { NetworkResourceTraceEvent } from '../common/traceEvents';
|
||||
import type { FrameSnapshot, SerializedFrameSnapshot } from './frameSnapshot';
|
||||
import { HttpServer } from '../../../utils/httpServer';
|
||||
import { SnapshotRenderer, RenderedFrameSnapshot } from './snapshotRenderer';
|
||||
import { HttpServer } from '../../utils/httpServer';
|
||||
|
||||
export type NetworkResponse = {
|
||||
contentType: string;
|
||||
responseHeaders: { name: string, value: string }[];
|
||||
responseSha1: string;
|
||||
};
|
||||
|
||||
export interface SnapshotStorage {
|
||||
resourceContent(sha1: string): Buffer;
|
||||
resourceById(resourceId: string): NetworkResourceTraceEvent;
|
||||
snapshotByName(snapshotName: string): FrameSnapshot | undefined;
|
||||
resourceById(resourceId: string): NetworkResponse;
|
||||
snapshotByName(snapshotName: string): SnapshotRenderer | undefined;
|
||||
}
|
||||
|
||||
export class SnapshotServer {
|
||||
|
|
@ -149,7 +154,7 @@ export class SnapshotServer {
|
|||
}
|
||||
if (request.mode === 'navigate') {
|
||||
const htmlResponse = await fetch(`/snapshot-data?snapshotName=${snapshotId}`);
|
||||
const { html, resources }: SerializedFrameSnapshot = await htmlResponse.json();
|
||||
const { html, resources }: RenderedFrameSnapshot = await htmlResponse.json();
|
||||
if (!html)
|
||||
return respondNotAvailable();
|
||||
snapshotResources.set(snapshotId, resources);
|
||||
|
|
@ -203,7 +208,7 @@ export class SnapshotServer {
|
|||
response.setHeader('Content-Type', 'application/json');
|
||||
const parsed: any = querystring.parse(request.url!.substring(request.url!.indexOf('?') + 1));
|
||||
const snapshot = this._snapshotStorage.snapshotByName(parsed.snapshotName);
|
||||
const snapshotData: any = snapshot ? snapshot.serialize() : { html: '' };
|
||||
const snapshotData: any = snapshot ? snapshot.render() : { html: '' };
|
||||
response.end(JSON.stringify(snapshotData));
|
||||
return true;
|
||||
}
|
||||
|
|
@ -14,15 +14,15 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { BrowserContext } from '../../browserContext';
|
||||
import { Page } from '../../page';
|
||||
import * as network from '../../network';
|
||||
import { helper, RegisteredListener } from '../../helper';
|
||||
import { debugLogger } from '../../../utils/debugLogger';
|
||||
import { Frame } from '../../frames';
|
||||
import { BrowserContext } from '../browserContext';
|
||||
import { Page } from '../page';
|
||||
import * as network from '../network';
|
||||
import { helper, RegisteredListener } from '../helper';
|
||||
import { debugLogger } from '../../utils/debugLogger';
|
||||
import { Frame } from '../frames';
|
||||
import { SnapshotData, frameSnapshotStreamer, kSnapshotBinding, kSnapshotStreamer } from './snapshotterInjected';
|
||||
import { calculateSha1 } from '../../../utils/utils';
|
||||
import { FrameSnapshot } from '../common/traceEvents';
|
||||
import { calculateSha1 } from '../../utils/utils';
|
||||
import { FrameSnapshot } from './snapshot';
|
||||
|
||||
export type SnapshotterResource = {
|
||||
pageId: string,
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { NodeSnapshot } from '../common/traceEvents';
|
||||
import { NodeSnapshot } from './snapshot';
|
||||
|
||||
export type SnapshotData = {
|
||||
doctype?: string,
|
||||
|
|
@ -15,18 +15,7 @@
|
|||
*/
|
||||
|
||||
import { StackFrame } from '../../../common/types';
|
||||
|
||||
export type NodeSnapshot =
|
||||
// Text node.
|
||||
string |
|
||||
// Subtree reference, "x snapshots ago, node #y". Could point to a text node.
|
||||
// Only nodes that are not references are counted, starting from zero, using post-order traversal.
|
||||
[ [number, number] ] |
|
||||
// Just node name.
|
||||
[ string ] |
|
||||
// Node name, attributes, child nodes.
|
||||
// Unfortunately, we cannot make this type definition recursive, therefore "any".
|
||||
[ string, { [attr: string]: string }, ...any ];
|
||||
import { FrameSnapshot } from '../../snapshot/snapshot';
|
||||
|
||||
export type ContextCreatedTraceEvent = {
|
||||
timestamp: number,
|
||||
|
|
@ -157,16 +146,3 @@ export type TraceEvent =
|
|||
NavigationEvent |
|
||||
LoadEvent |
|
||||
FrameSnapshotTraceEvent;
|
||||
|
||||
export type ResourceOverride = {
|
||||
url: string,
|
||||
sha1?: string,
|
||||
ref?: number
|
||||
};
|
||||
|
||||
export type FrameSnapshot = {
|
||||
doctype?: string,
|
||||
html: NodeSnapshot,
|
||||
resourceOverrides: ResourceOverride[],
|
||||
viewport: { width: number, height: number },
|
||||
};
|
||||
|
|
|
|||
|
|
@ -15,18 +15,19 @@
|
|||
*/
|
||||
|
||||
import { BrowserContext, Video } from '../../browserContext';
|
||||
import type { SnapshotterResource as SnapshotterResource, SnapshotterBlob, SnapshotterDelegate } from './snapshotter';
|
||||
import type { SnapshotterResource as SnapshotterResource, SnapshotterBlob, SnapshotterDelegate } from '../../snapshot/snapshotter';
|
||||
import * as trace from '../common/traceEvents';
|
||||
import path from 'path';
|
||||
import * as util from 'util';
|
||||
import fs from 'fs';
|
||||
import { createGuid, getFromENV, mkdirIfNeeded, monotonicTime } from '../../../utils/utils';
|
||||
import { Page } from '../../page';
|
||||
import { Snapshotter } from './snapshotter';
|
||||
import { Snapshotter } from '../../snapshot/snapshotter';
|
||||
import { helper, RegisteredListener } from '../../helper';
|
||||
import { Dialog } from '../../dialog';
|
||||
import { Frame, NavigationEvent } from '../../frames';
|
||||
import { CallMetadata, InstrumentationListener, SdkObject } from '../../instrumentation';
|
||||
import { FrameSnapshot } from '../../snapshot/snapshot';
|
||||
|
||||
const fsWriteFileAsync = util.promisify(fs.writeFile.bind(fs));
|
||||
const fsAppendFileAsync = util.promisify(fs.appendFile.bind(fs));
|
||||
|
|
@ -133,7 +134,7 @@ class ContextTracer implements SnapshotterDelegate {
|
|||
this._appendTraceEvent(event);
|
||||
}
|
||||
|
||||
onFrameSnapshot(frame: Frame, frameUrl: string, snapshot: trace.FrameSnapshot, snapshotId?: string): void {
|
||||
onFrameSnapshot(frame: Frame, frameUrl: string, snapshot: FrameSnapshot, snapshotId?: string): void {
|
||||
const event: trace.FrameSnapshotTraceEvent = {
|
||||
timestamp: monotonicTime(),
|
||||
type: 'snapshot',
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import path from 'path';
|
|||
import * as playwright from '../../../..';
|
||||
import * as util from 'util';
|
||||
import { ActionEntry, ContextEntry, TraceModel } from './traceModel';
|
||||
import { SnapshotServer } from './snapshotServer';
|
||||
import { SnapshotServer } from '../../snapshot/snapshotServer';
|
||||
|
||||
const fsReadFileAsync = util.promisify(fs.readFile.bind(fs));
|
||||
const fsWriteFileAsync = util.promisify(fs.writeFile.bind(fs));
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
import { createGuid } from '../../../utils/utils';
|
||||
import * as trace from '../common/traceEvents';
|
||||
import { FrameSnapshot } from './frameSnapshot';
|
||||
import { ContextResources, SnapshotRenderer } from '../../snapshot/snapshotRenderer';
|
||||
export * as trace from '../common/traceEvents';
|
||||
|
||||
export class TraceModel {
|
||||
|
|
@ -152,16 +152,16 @@ export class TraceModel {
|
|||
return { contextEntry, pageEntry };
|
||||
}
|
||||
|
||||
findSnapshotById(pageId: string, frameId: string, snapshotId: string): FrameSnapshot | undefined {
|
||||
findSnapshotById(pageId: string, frameId: string, snapshotId: string): SnapshotRenderer | undefined {
|
||||
const { pageEntry, contextEntry } = this.pageEntries.get(pageId)!;
|
||||
const frameSnapshots = pageEntry.snapshotsByFrameId[frameId];
|
||||
for (let index = 0; index < frameSnapshots.length; index++) {
|
||||
if (frameSnapshots[index].snapshotId === snapshotId)
|
||||
return new FrameSnapshot(frameId, this.contextResources.get(contextEntry.created.contextId)!, frameSnapshots, index);
|
||||
return new SnapshotRenderer(frameId, this.contextResources.get(contextEntry.created.contextId)!, frameSnapshots.map(fs => fs.snapshot), index);
|
||||
}
|
||||
}
|
||||
|
||||
findSnapshotByTime(pageId: string, frameId: string, timestamp: number): FrameSnapshot | undefined {
|
||||
findSnapshotByTime(pageId: string, frameId: string, timestamp: number): SnapshotRenderer | undefined {
|
||||
const { pageEntry, contextEntry } = this.pageEntries.get(pageId)!;
|
||||
const frameSnapshots = pageEntry.snapshotsByFrameId[frameId];
|
||||
let snapshotIndex = -1;
|
||||
|
|
@ -170,7 +170,7 @@ export class TraceModel {
|
|||
if (timestamp && snapshot.timestamp <= timestamp)
|
||||
snapshotIndex = index;
|
||||
}
|
||||
return snapshotIndex >= 0 ? new FrameSnapshot(frameId, this.contextResources.get(contextEntry.created.contextId)!, frameSnapshots, snapshotIndex) : undefined;
|
||||
return snapshotIndex >= 0 ? new SnapshotRenderer(frameId, this.contextResources.get(contextEntry.created.contextId)!, frameSnapshots.map(fs => fs.snapshot), snapshotIndex) : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -183,8 +183,6 @@ export type ContextEntry = {
|
|||
pages: PageEntry[];
|
||||
}
|
||||
|
||||
export type ContextResources = Map<string, { resourceId: string, frameId: string }[]>;
|
||||
|
||||
export type InterestingPageEvent = trace.DialogOpenedEvent | trace.DialogClosedEvent | trace.NavigationEvent | trace.LoadEvent;
|
||||
|
||||
export type PageEntry = {
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ import * as util from 'util';
|
|||
import { ScreenshotGenerator } from './screenshotGenerator';
|
||||
import { TraceModel } from './traceModel';
|
||||
import { NetworkResourceTraceEvent, TraceEvent } from '../common/traceEvents';
|
||||
import { SnapshotServer, SnapshotStorage } from './snapshotServer';
|
||||
import { ServerRouteHandler, HttpServer } from '../../../utils/httpServer';
|
||||
import { FrameSnapshot } from './frameSnapshot';
|
||||
import { SnapshotServer, SnapshotStorage } from '../../snapshot/snapshotServer';
|
||||
import { SnapshotRenderer } from '../../snapshot/snapshotRenderer';
|
||||
|
||||
const fsReadFileAsync = util.promisify(fs.readFile.bind(fs));
|
||||
|
||||
|
|
@ -146,7 +146,7 @@ class TraceViewer implements SnapshotStorage {
|
|||
return traceModel.resourceById.get(resourceId)!;
|
||||
}
|
||||
|
||||
snapshotByName(snapshotName: string): FrameSnapshot | undefined {
|
||||
snapshotByName(snapshotName: string): SnapshotRenderer | undefined {
|
||||
const traceModel = this._document!.model;
|
||||
const parsed = parseSnapshotName(snapshotName);
|
||||
const snapshot = parsed.snapshotId ? traceModel.findSnapshotById(parsed.pageId, parsed.frameId, parsed.snapshotId) : traceModel.findSnapshotByTime(parsed.pageId, parsed.frameId, parsed.timestamp!);
|
||||
|
|
|
|||
|
|
@ -158,8 +158,9 @@ DEPS['src/server/supplements/recorder/recorderApp.ts'] = ['src/common/', 'src/ut
|
|||
DEPS['src/utils/'] = ['src/common/'];
|
||||
|
||||
// Trace viewer
|
||||
DEPS['src/server/trace/recorder/'] = ['src/server/trace/common/', ...DEPS['src/server/']];
|
||||
DEPS['src/server/trace/viewer/'] = ['src/server/trace/common/', ...DEPS['src/server/']];
|
||||
DEPS['src/server/trace/common/'] = ['src/server/snapshot/', ...DEPS['src/server/']];
|
||||
DEPS['src/server/trace/recorder/'] = ['src/server/trace/common/', ...DEPS['src/server/trace/common/']];
|
||||
DEPS['src/server/trace/viewer/'] = ['src/server/trace/common/', ...DEPS['src/server/trace/common/']];
|
||||
|
||||
checkDeps().catch(e => {
|
||||
console.error(e && e.stack ? e.stack : e);
|
||||
|
|
|
|||
Loading…
Reference in New Issue