chore: make trace server generic (#5616)

This commit is contained in:
Pavel Feldman 2021-02-25 08:25:52 -08:00 committed by GitHub
parent 1cd398e700
commit af89ab7a6f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 84 additions and 95 deletions

View file

@ -37,7 +37,6 @@ export type ContextCreatedTraceEvent = {
isMobile: boolean,
viewportSize?: { width: number, height: number },
debugName?: string,
snapshotScript: string,
};
export type ContextDestroyedTraceEvent = {

View file

@ -427,59 +427,3 @@ export function frameSnapshotStreamer() {
(window as any)[kSnapshotStreamer] = new Streamer();
}
export function snapshotScript() {
function applyPlaywrightAttributes(shadowAttribute: string, scrollTopAttribute: string, scrollLeftAttribute: string) {
const scrollTops: Element[] = [];
const scrollLefts: Element[] = [];
const visit = (root: Document | ShadowRoot) => {
// Collect all scrolled elements for later use.
for (const e of root.querySelectorAll(`[${scrollTopAttribute}]`))
scrollTops.push(e);
for (const e of root.querySelectorAll(`[${scrollLeftAttribute}]`))
scrollLefts.push(e);
for (const iframe of root.querySelectorAll('iframe')) {
const src = iframe.getAttribute('src') || '';
if (src.startsWith('data:text/html'))
continue;
// Rewrite iframes to use snapshot url (relative to window.location)
// instead of begin relative to the <base> tag.
const index = location.pathname.lastIndexOf('/');
if (index === -1)
continue;
const pathname = location.pathname.substring(0, index + 1) + src;
const href = location.href.substring(0, location.href.indexOf(location.pathname)) + pathname;
iframe.setAttribute('src', href);
}
for (const element of root.querySelectorAll(`template[${shadowAttribute}]`)) {
const template = element as HTMLTemplateElement;
const shadowRoot = template.parentElement!.attachShadow({ mode: 'open' });
shadowRoot.appendChild(template.content);
template.remove();
visit(shadowRoot);
}
};
visit(document);
const onLoad = () => {
window.removeEventListener('load', onLoad);
for (const element of scrollTops) {
element.scrollTop = +element.getAttribute(scrollTopAttribute)!;
element.removeAttribute(scrollTopAttribute);
}
for (const element of scrollLefts) {
element.scrollLeft = +element.getAttribute(scrollLeftAttribute)!;
element.removeAttribute(scrollLeftAttribute);
}
};
window.addEventListener('load', onLoad);
}
const kShadowAttribute = '__playwright_shadow_root_';
const kScrollTopAttribute = '__playwright_scroll_top_';
const kScrollLeftAttribute = '__playwright_scroll_left_';
return `\n(${applyPlaywrightAttributes.toString()})('${kShadowAttribute}', '${kScrollTopAttribute}', '${kScrollLeftAttribute}')`;
}

View file

@ -26,7 +26,6 @@ import { Snapshotter } from './snapshotter';
import { helper, RegisteredListener } from '../../helper';
import { Dialog } from '../../dialog';
import { Frame, NavigationEvent } from '../../frames';
import { snapshotScript } from './snapshotterInjected';
import { CallMetadata, InstrumentationListener, SdkObject } from '../../instrumentation';
const fsWriteFileAsync = util.promisify(fs.writeFile.bind(fs));
@ -102,7 +101,6 @@ class ContextTracer implements SnapshotterDelegate {
deviceScaleFactor: context._options.deviceScaleFactor || 1,
viewportSize: context._options.viewport || undefined,
debugName: context._options._debugName,
snapshotScript: snapshotScript(),
};
this._appendTraceEvent(event);
this._snapshotter = new Snapshotter(context, this);

View file

@ -15,7 +15,7 @@
*/
import * as trace from '../common/traceEvents';
import { ContextEntry, ContextResources } from './traceModel';
import { ContextResources } from './traceModel';
export * as trace from '../common/traceEvents';
export type SerializedFrameSnapshot = {
@ -26,13 +26,11 @@ export type SerializedFrameSnapshot = {
export class FrameSnapshot {
private _snapshots: trace.FrameSnapshotTraceEvent[];
private _index: number;
private _contextEntry: ContextEntry;
private _contextResources: ContextResources;
private _frameId: string;
constructor(frameId: string, contextEntry: ContextEntry, contextResources: ContextResources, events: trace.FrameSnapshotTraceEvent[], index: number) {
constructor(frameId: string, contextResources: ContextResources, events: trace.FrameSnapshotTraceEvent[], index: number) {
this._frameId = frameId;
this._contextEntry = contextEntry;
this._contextResources = contextResources;
this._snapshots = events;
this._index = index;
@ -82,7 +80,7 @@ export class FrameSnapshot {
let html = visit(snapshot.html, this._index);
if (snapshot.doctype)
html = `<!DOCTYPE ${snapshot.doctype}>` + html;
html += `<script>${this._contextEntry.created.snapshotScript}</script>`;
html += `<script>${snapshotScript}</script>`;
const resources: { [key: string]: { resourceId: string, sha1?: string } } = {};
for (const [url, contextResources] of this._contextResources) {
@ -125,3 +123,59 @@ function snapshotNodes(snapshot: trace.FrameSnapshot): trace.NodeSnapshot[] {
}
return (snapshot as any)._nodes;
}
export function snapshotScript() {
function applyPlaywrightAttributes(shadowAttribute: string, scrollTopAttribute: string, scrollLeftAttribute: string) {
const scrollTops: Element[] = [];
const scrollLefts: Element[] = [];
const visit = (root: Document | ShadowRoot) => {
// Collect all scrolled elements for later use.
for (const e of root.querySelectorAll(`[${scrollTopAttribute}]`))
scrollTops.push(e);
for (const e of root.querySelectorAll(`[${scrollLeftAttribute}]`))
scrollLefts.push(e);
for (const iframe of root.querySelectorAll('iframe')) {
const src = iframe.getAttribute('src') || '';
if (src.startsWith('data:text/html'))
continue;
// Rewrite iframes to use snapshot url (relative to window.location)
// instead of begin relative to the <base> tag.
const index = location.pathname.lastIndexOf('/');
if (index === -1)
continue;
const pathname = location.pathname.substring(0, index + 1) + src;
const href = location.href.substring(0, location.href.indexOf(location.pathname)) + pathname;
iframe.setAttribute('src', href);
}
for (const element of root.querySelectorAll(`template[${shadowAttribute}]`)) {
const template = element as HTMLTemplateElement;
const shadowRoot = template.parentElement!.attachShadow({ mode: 'open' });
shadowRoot.appendChild(template.content);
template.remove();
visit(shadowRoot);
}
};
visit(document);
const onLoad = () => {
window.removeEventListener('load', onLoad);
for (const element of scrollTops) {
element.scrollTop = +element.getAttribute(scrollTopAttribute)!;
element.removeAttribute(scrollTopAttribute);
}
for (const element of scrollLefts) {
element.scrollLeft = +element.getAttribute(scrollLeftAttribute)!;
element.removeAttribute(scrollLeftAttribute);
}
};
window.addEventListener('load', onLoad);
}
const kShadowAttribute = '__playwright_shadow_root_';
const kScrollTopAttribute = '__playwright_scroll_top_';
const kScrollLeftAttribute = '__playwright_scroll_left_';
return `\n(${applyPlaywrightAttributes.toString()})('${kShadowAttribute}', '${kScrollTopAttribute}', '${kScrollLeftAttribute}')`;
}

View file

@ -15,25 +15,22 @@
*/
import * as http from 'http';
import fs from 'fs';
import path from 'path';
import querystring from 'querystring';
import { TraceServer } from './traceServer';
import type { FrameSnapshot, SerializedFrameSnapshot } from './frameSnapshot';
import type { NetworkResourceTraceEvent } from '../common/traceEvents';
import type { FrameSnapshot, SerializedFrameSnapshot } from './frameSnapshot';
import { HttpServer } from '../../../utils/httpServer';
export interface SnapshotStorage {
resourceContent(sha1: string): Buffer;
resourceById(resourceId: string): NetworkResourceTraceEvent;
snapshotByName(snapshotName: string): FrameSnapshot | undefined;
}
export class SnapshotServer {
private _resourcesDir: string | undefined;
private _urlPrefix: string;
private _snapshotStorage: SnapshotStorage;
constructor(server: TraceServer, snapshotStorage: SnapshotStorage, resourcesDir: string | undefined) {
this._resourcesDir = resourcesDir;
constructor(server: HttpServer, snapshotStorage: SnapshotStorage) {
this._urlPrefix = server.urlPrefix();
this._snapshotStorage = snapshotStorage;
@ -212,9 +209,6 @@ export class SnapshotServer {
}
private _serveResource(request: http.IncomingMessage, response: http.ServerResponse): boolean {
if (!this._resourcesDir)
return false;
// - /resources/<resourceId>
// - /resources/<resourceId>/override/<overrideSha1>
const parts = request.url!.split('/');
@ -239,7 +233,7 @@ export class SnapshotServer {
const resource = this._snapshotStorage.resourceById(resourceId);
const sha1 = overrideSha1 || resource.responseSha1;
try {
const content = fs.readFileSync(path.join(this._resourcesDir, sha1));
const content = this._snapshotStorage.resourceContent(sha1);
response.statusCode = 200;
let contentType = resource.contentType;
const isTextEncoding = /^text\/|^application\/(javascript|json)/.test(contentType);

View file

@ -157,7 +157,7 @@ export class TraceModel {
const frameSnapshots = pageEntry.snapshotsByFrameId[frameId];
for (let index = 0; index < frameSnapshots.length; index++) {
if (frameSnapshots[index].snapshotId === snapshotId)
return new FrameSnapshot(frameId, contextEntry, this.contextResources.get(contextEntry.created.contextId)!, frameSnapshots, index);
return new FrameSnapshot(frameId, this.contextResources.get(contextEntry.created.contextId)!, frameSnapshots, index);
}
}
@ -170,7 +170,7 @@ export class TraceModel {
if (timestamp && snapshot.timestamp <= timestamp)
snapshotIndex = index;
}
return snapshotIndex >= 0 ? new FrameSnapshot(frameId, contextEntry, this.contextResources.get(contextEntry.created.contextId)!, frameSnapshots, snapshotIndex) : undefined;
return snapshotIndex >= 0 ? new FrameSnapshot(frameId, this.contextResources.get(contextEntry.created.contextId)!, frameSnapshots, snapshotIndex) : undefined;
}
}

View file

@ -22,7 +22,7 @@ import { ScreenshotGenerator } from './screenshotGenerator';
import { TraceModel } from './traceModel';
import { NetworkResourceTraceEvent, TraceEvent } from '../common/traceEvents';
import { SnapshotServer, SnapshotStorage } from './snapshotServer';
import { ServerRouteHandler, TraceServer } from './traceServer';
import { ServerRouteHandler, HttpServer } from '../../../utils/httpServer';
import { FrameSnapshot } from './frameSnapshot';
const fsReadFileAsync = util.promisify(fs.readFile.bind(fs));
@ -32,8 +32,6 @@ type TraceViewerDocument = {
model: TraceModel;
};
const emptyModel: TraceModel = new TraceModel();
class TraceViewer implements SnapshotStorage {
private _document: TraceViewerDocument | undefined;
@ -74,8 +72,17 @@ class TraceViewer implements SnapshotStorage {
// - "/snapshot/service-worker.js" - service worker that intercepts snapshot resources
// and translates them into "/resources/<resourceId>".
const server = new TraceServer(this._document ? this._document.model : emptyModel);
const snapshotServer = new SnapshotServer(server, this, this._document ? this._document.resourcesDir : undefined);
const server = new HttpServer();
const traceModelHandler: ServerRouteHandler = (request, response) => {
response.statusCode = 200;
response.setHeader('Content-Type', 'application/json');
response.end(JSON.stringify(Array.from(this._document!.model.contextEntries.values())));
return true;
};
server.routePath('/contexts', traceModelHandler);
const snapshotServer = new SnapshotServer(server, this);
const screenshotGenerator = this._document ? new ScreenshotGenerator(snapshotServer, this._document.resourcesDir, this._document.model) : undefined;
const traceViewerHandler: ServerRouteHandler = (request, response) => {
@ -145,6 +152,10 @@ class TraceViewer implements SnapshotStorage {
const snapshot = parsed.snapshotId ? traceModel.findSnapshotById(parsed.pageId, parsed.frameId, parsed.snapshotId) : traceModel.findSnapshotByTime(parsed.pageId, parsed.frameId, parsed.timestamp!);
return snapshot;
}
resourceContent(sha1: string): Buffer {
return fs.readFileSync(path.join(this._document!.resourcesDir, sha1));
}
}
export async function showTraceViewer(traceDir: string) {

View file

@ -17,27 +17,16 @@
import * as http from 'http';
import fs from 'fs';
import path from 'path';
import type { TraceModel } from './traceModel';
export type ServerRouteHandler = (request: http.IncomingMessage, response: http.ServerResponse) => boolean;
export class TraceServer {
private _traceModel: TraceModel;
export class HttpServer {
private _server: http.Server | undefined;
private _urlPrefix: string;
private _routes: { prefix?: string, exact?: string, needsReferrer: boolean, handler: ServerRouteHandler }[] = [];
constructor(traceModel: TraceModel) {
this._traceModel = traceModel;
constructor() {
this._urlPrefix = '';
const traceModelHandler: ServerRouteHandler = (request, response) => {
response.statusCode = 200;
response.setHeader('Content-Type', 'application/json');
response.end(JSON.stringify(Array.from(this._traceModel.contextEntries.values())));
return true;
};
this.routePath('/contexts', traceModelHandler);
}
routePrefix(prefix: string, handler: ServerRouteHandler, skipReferrerCheck?: boolean) {