diff --git a/src/server/snapshot/snapshot.ts b/src/server/snapshot/snapshot.ts new file mode 100644 index 0000000000..5e1fda86e8 --- /dev/null +++ b/src/server/snapshot/snapshot.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 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 }, +}; diff --git a/src/server/trace/viewer/frameSnapshot.ts b/src/server/snapshot/snapshotRenderer.ts similarity index 87% rename from src/server/trace/viewer/frameSnapshot.ts rename to src/server/snapshot/snapshotRenderer.ts index 97e9b719f1..3abc3036d5 100644 --- a/src/server/trace/viewer/frameSnapshot.ts +++ b/src/server/snapshot/snapshotRenderer.ts @@ -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; + +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 = `` + 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') { diff --git a/src/server/trace/viewer/snapshotServer.ts b/src/server/snapshot/snapshotServer.ts similarity index 94% rename from src/server/trace/viewer/snapshotServer.ts rename to src/server/snapshot/snapshotServer.ts index 91826f0974..57dfab7f77 100644 --- a/src/server/trace/viewer/snapshotServer.ts +++ b/src/server/snapshot/snapshotServer.ts @@ -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; } diff --git a/src/server/trace/recorder/snapshotter.ts b/src/server/snapshot/snapshotter.ts similarity index 93% rename from src/server/trace/recorder/snapshotter.ts rename to src/server/snapshot/snapshotter.ts index 14b4a961df..87c97c687a 100644 --- a/src/server/trace/recorder/snapshotter.ts +++ b/src/server/snapshot/snapshotter.ts @@ -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, diff --git a/src/server/trace/recorder/snapshotterInjected.ts b/src/server/snapshot/snapshotterInjected.ts similarity index 99% rename from src/server/trace/recorder/snapshotterInjected.ts rename to src/server/snapshot/snapshotterInjected.ts index 8ce1555f07..903b8eb67c 100644 --- a/src/server/trace/recorder/snapshotterInjected.ts +++ b/src/server/snapshot/snapshotterInjected.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { NodeSnapshot } from '../common/traceEvents'; +import { NodeSnapshot } from './snapshot'; export type SnapshotData = { doctype?: string, diff --git a/src/server/trace/common/traceEvents.ts b/src/server/trace/common/traceEvents.ts index 1279342e23..c47f4927da 100644 --- a/src/server/trace/common/traceEvents.ts +++ b/src/server/trace/common/traceEvents.ts @@ -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 }, -}; diff --git a/src/server/trace/recorder/tracer.ts b/src/server/trace/recorder/tracer.ts index 651031e417..a7bb4ef26a 100644 --- a/src/server/trace/recorder/tracer.ts +++ b/src/server/trace/recorder/tracer.ts @@ -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', diff --git a/src/server/trace/viewer/screenshotGenerator.ts b/src/server/trace/viewer/screenshotGenerator.ts index 90f589d971..2b9d7ac4d6 100644 --- a/src/server/trace/viewer/screenshotGenerator.ts +++ b/src/server/trace/viewer/screenshotGenerator.ts @@ -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)); diff --git a/src/server/trace/viewer/traceModel.ts b/src/server/trace/viewer/traceModel.ts index c23440da69..02a8693783 100644 --- a/src/server/trace/viewer/traceModel.ts +++ b/src/server/trace/viewer/traceModel.ts @@ -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; - export type InterestingPageEvent = trace.DialogOpenedEvent | trace.DialogClosedEvent | trace.NavigationEvent | trace.LoadEvent; export type PageEntry = { diff --git a/src/server/trace/viewer/traceViewer.ts b/src/server/trace/viewer/traceViewer.ts index 1e26f2704a..03e20ddfa4 100644 --- a/src/server/trace/viewer/traceViewer.ts +++ b/src/server/trace/viewer/traceViewer.ts @@ -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!); diff --git a/utils/check_deps.js b/utils/check_deps.js index 0cc7030981..6e4cb70ccd 100644 --- a/utils/check_deps.js +++ b/utils/check_deps.js @@ -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);