chore: extract snapshotter from trace viewer (#5618)

This commit is contained in:
Pavel Feldman 2021-02-25 09:33:32 -08:00 committed by GitHub
parent af89ab7a6f
commit 2ff6d54f26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 95 additions and 77 deletions

View File

@ -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 },
};

View File

@ -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') {

View File

@ -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;
}

View File

@ -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,

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { NodeSnapshot } from '../common/traceEvents';
import { NodeSnapshot } from './snapshot';
export type SnapshotData = {
doctype?: string,

View File

@ -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 },
};

View File

@ -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',

View File

@ -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));

View File

@ -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 = {

View File

@ -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!);

View File

@ -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);