chore: read trace off zip file (#9377)
This commit is contained in:
parent
9164fc71ef
commit
2a628d0e0e
1
package-lock.json
generated
1
package-lock.json
generated
|
|
@ -47,6 +47,7 @@
|
||||||
"source-map-support": "^0.4.18",
|
"source-map-support": "^0.4.18",
|
||||||
"stack-utils": "^2.0.3",
|
"stack-utils": "^2.0.3",
|
||||||
"ws": "^7.4.6",
|
"ws": "^7.4.6",
|
||||||
|
"yauzl": "^2.10.0",
|
||||||
"yazl": "^2.5.1"
|
"yazl": "^2.5.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,7 @@
|
||||||
"source-map-support": "^0.4.18",
|
"source-map-support": "^0.4.18",
|
||||||
"stack-utils": "^2.0.3",
|
"stack-utils": "^2.0.3",
|
||||||
"ws": "^7.4.6",
|
"ws": "^7.4.6",
|
||||||
|
"yauzl": "^2.10.0",
|
||||||
"yazl": "^2.5.1"
|
"yazl": "^2.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ export class InMemorySnapshotter extends BaseSnapshotStorage implements Snapshot
|
||||||
this.addFrameSnapshot(snapshot);
|
this.addFrameSnapshot(snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceContent(sha1: string): Buffer | undefined {
|
async resourceContent(sha1: string): Promise<Buffer | undefined> {
|
||||||
return this._blobs.get(sha1);
|
return this._blobs.get(sha1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
import * as http from 'http';
|
import * as http from 'http';
|
||||||
import querystring from 'querystring';
|
import querystring from 'querystring';
|
||||||
import { HttpServer } from '../../utils/httpServer';
|
import { HttpServer } from '../../utils/httpServer';
|
||||||
import type { RenderedFrameSnapshot } from './snapshotTypes';
|
import type { RenderedFrameSnapshot, ResourceSnapshot } from './snapshotTypes';
|
||||||
import { SnapshotStorage } from './snapshotStorage';
|
import { SnapshotStorage } from './snapshotStorage';
|
||||||
import type { Point } from '../../common/types';
|
import type { Point } from '../../common/types';
|
||||||
|
|
||||||
|
|
@ -176,11 +176,19 @@ export class SnapshotServer {
|
||||||
const sha1 = resource.response.content._sha1;
|
const sha1 = resource.response.content._sha1;
|
||||||
if (!sha1)
|
if (!sha1)
|
||||||
return false;
|
return false;
|
||||||
|
(async () => {
|
||||||
|
this._innerServeResource(sha1, resource, response);
|
||||||
|
})().catch(() => {});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
private async _innerServeResource(sha1: string, resource: ResourceSnapshot, response: http.ServerResponse) {
|
||||||
const content = this._snapshotStorage.resourceContent(sha1);
|
const content = await this._snapshotStorage.resourceContent(sha1);
|
||||||
if (!content)
|
if (!content) {
|
||||||
return false;
|
response.statusCode = 404;
|
||||||
|
response.end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
response.statusCode = 200;
|
response.statusCode = 200;
|
||||||
let contentType = resource.response.content.mimeType;
|
let contentType = resource.response.content.mimeType;
|
||||||
const isTextEncoding = /^text\/|^application\/(javascript|json)/.test(contentType);
|
const isTextEncoding = /^text\/|^application\/(javascript|json)/.test(contentType);
|
||||||
|
|
@ -203,10 +211,6 @@ export class SnapshotServer {
|
||||||
response.setHeader('Content-Length', content.byteLength);
|
response.setHeader('Content-Length', content.byteLength);
|
||||||
response.setHeader('Cache-Control', 'public, max-age=31536000');
|
response.setHeader('Cache-Control', 'public, max-age=31536000');
|
||||||
response.end(content);
|
response.end(content);
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import { SnapshotRenderer } from './snapshotRenderer';
|
||||||
|
|
||||||
export interface SnapshotStorage {
|
export interface SnapshotStorage {
|
||||||
resources(): ResourceSnapshot[];
|
resources(): ResourceSnapshot[];
|
||||||
resourceContent(sha1: string): Buffer | undefined;
|
resourceContent(sha1: string): Promise<Buffer | undefined>;
|
||||||
snapshotByName(pageOrFrameId: string, snapshotName: string): SnapshotRenderer | undefined;
|
snapshotByName(pageOrFrameId: string, snapshotName: string): SnapshotRenderer | undefined;
|
||||||
snapshotByIndex(frameId: string, index: number): SnapshotRenderer | undefined;
|
snapshotByIndex(frameId: string, index: number): SnapshotRenderer | undefined;
|
||||||
}
|
}
|
||||||
|
|
@ -58,7 +58,7 @@ export abstract class BaseSnapshotStorage extends EventEmitter implements Snapsh
|
||||||
this.emit('snapshot', renderer);
|
this.emit('snapshot', renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract resourceContent(sha1: string): Buffer | undefined;
|
abstract resourceContent(sha1: string): Promise<Buffer | undefined>;
|
||||||
|
|
||||||
resources(): ResourceSnapshot[] {
|
resources(): ResourceSnapshot[] {
|
||||||
return this._resources.slice();
|
return this._resources.slice();
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,12 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import fs from 'fs';
|
|
||||||
import path from 'path';
|
|
||||||
import * as trace from '../common/traceEvents';
|
import * as trace from '../common/traceEvents';
|
||||||
import { ResourceSnapshot } from '../../snapshot/snapshotTypes';
|
import { ResourceSnapshot } from '../../snapshot/snapshotTypes';
|
||||||
import { BaseSnapshotStorage } from '../../snapshot/snapshotStorage';
|
import { BaseSnapshotStorage } from '../../snapshot/snapshotStorage';
|
||||||
import { BrowserContextOptions } from '../../types';
|
import { BrowserContextOptions } from '../../types';
|
||||||
import { shouldCaptureSnapshot, VERSION } from '../recorder/tracing';
|
import { shouldCaptureSnapshot, VERSION } from '../recorder/tracing';
|
||||||
|
import { VirtualFileSystem } from '../../../utils/vfs';
|
||||||
export * as trace from '../common/traceEvents';
|
export * as trace from '../common/traceEvents';
|
||||||
|
|
||||||
export class TraceModel {
|
export class TraceModel {
|
||||||
|
|
@ -180,14 +179,13 @@ export type PageEntry = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export class PersistentSnapshotStorage extends BaseSnapshotStorage {
|
export class PersistentSnapshotStorage extends BaseSnapshotStorage {
|
||||||
private _resourcesDir: string;
|
private _loader: VirtualFileSystem;
|
||||||
|
constructor(loader: VirtualFileSystem) {
|
||||||
constructor(resourcesDir: string) {
|
|
||||||
super();
|
super();
|
||||||
this._resourcesDir = resourcesDir;
|
this._loader = loader;
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceContent(sha1: string): Buffer | undefined {
|
async resourceContent(sha1: string): Promise<Buffer | undefined> {
|
||||||
return fs.readFileSync(path.join(this._resourcesDir, sha1));
|
return this._loader.read('resources/' + sha1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,12 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import extract from 'extract-zip';
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import readline from 'readline';
|
import readline from 'readline';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import rimraf from 'rimraf';
|
import rimraf from 'rimraf';
|
||||||
|
import stream from 'stream';
|
||||||
import { createPlaywright } from '../../playwright';
|
import { createPlaywright } from '../../playwright';
|
||||||
import { PersistentSnapshotStorage, TraceModel } from './traceModel';
|
import { PersistentSnapshotStorage, TraceModel } from './traceModel';
|
||||||
import { ServerRouteHandler, HttpServer } from '../../../utils/httpServer';
|
import { ServerRouteHandler, HttpServer } from '../../../utils/httpServer';
|
||||||
|
|
@ -32,15 +32,20 @@ import { BrowserContext } from '../../browserContext';
|
||||||
import { findChromiumChannel } from '../../../utils/registry';
|
import { findChromiumChannel } from '../../../utils/registry';
|
||||||
import { installAppIcon } from '../../chromium/crApp';
|
import { installAppIcon } from '../../chromium/crApp';
|
||||||
import { debugLogger } from '../../../utils/debugLogger';
|
import { debugLogger } from '../../../utils/debugLogger';
|
||||||
|
import { VirtualFileSystem, RealFileSystem, ZipFileSystem } from '../../../utils/vfs';
|
||||||
|
|
||||||
export class TraceViewer {
|
export class TraceViewer {
|
||||||
|
private _vfs: VirtualFileSystem;
|
||||||
private _server: HttpServer;
|
private _server: HttpServer;
|
||||||
private _browserName: string;
|
private _browserName: string;
|
||||||
|
|
||||||
constructor(tracesDir: string, browserName: string) {
|
constructor(vfs: VirtualFileSystem, browserName: string) {
|
||||||
|
this._vfs = vfs;
|
||||||
this._browserName = browserName;
|
this._browserName = browserName;
|
||||||
const resourcesDir = path.join(tracesDir, 'resources');
|
this._server = new HttpServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
// Served by TraceServer
|
// Served by TraceServer
|
||||||
// - "/tracemodel" - json with trace model.
|
// - "/tracemodel" - json with trace model.
|
||||||
//
|
//
|
||||||
|
|
@ -55,14 +60,11 @@ export class TraceViewer {
|
||||||
// - "/snapshot/pageId/..." - actual snapshot html.
|
// - "/snapshot/pageId/..." - actual snapshot html.
|
||||||
// - "/snapshot/service-worker.js" - service worker that intercepts snapshot resources
|
// - "/snapshot/service-worker.js" - service worker that intercepts snapshot resources
|
||||||
// and translates them into network requests.
|
// and translates them into network requests.
|
||||||
const actionTraces = fs.readdirSync(tracesDir).filter(name => name.endsWith('.trace'));
|
const entries = await this._vfs.entries();
|
||||||
const debugNames = actionTraces.map(name => {
|
const debugNames = entries.filter(name => name.endsWith('.trace')).map(name => {
|
||||||
const tracePrefix = path.join(tracesDir, name.substring(0, name.indexOf('.trace')));
|
return name.substring(0, name.indexOf('.trace'));
|
||||||
return path.basename(tracePrefix);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this._server = new HttpServer();
|
|
||||||
|
|
||||||
const traceListHandler: ServerRouteHandler = (request, response) => {
|
const traceListHandler: ServerRouteHandler = (request, response) => {
|
||||||
response.statusCode = 200;
|
response.statusCode = 200;
|
||||||
response.setHeader('Content-Type', 'application/json');
|
response.setHeader('Content-Type', 'application/json');
|
||||||
|
|
@ -70,7 +72,7 @@ export class TraceViewer {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
this._server.routePath('/contexts', traceListHandler);
|
this._server.routePath('/contexts', traceListHandler);
|
||||||
const snapshotStorage = new PersistentSnapshotStorage(resourcesDir);
|
const snapshotStorage = new PersistentSnapshotStorage(this._vfs);
|
||||||
new SnapshotServer(this._server, snapshotStorage);
|
new SnapshotServer(this._server, snapshotStorage);
|
||||||
|
|
||||||
const traceModelHandler: ServerRouteHandler = (request, response) => {
|
const traceModelHandler: ServerRouteHandler = (request, response) => {
|
||||||
|
|
@ -79,12 +81,12 @@ export class TraceViewer {
|
||||||
response.statusCode = 200;
|
response.statusCode = 200;
|
||||||
response.setHeader('Content-Type', 'application/json');
|
response.setHeader('Content-Type', 'application/json');
|
||||||
(async () => {
|
(async () => {
|
||||||
const traceFile = path.join(tracesDir, debugName + '.trace');
|
const traceFile = await this._vfs.readStream(debugName + '.trace');
|
||||||
const match = debugName.match(/^(.*)-\d+$/);
|
const match = debugName.match(/^(.*)-\d+$/);
|
||||||
const networkFile = path.join(tracesDir, (match ? match[1] : debugName) + '.network');
|
const networkFile = await this._vfs.readStream((match ? match[1] : debugName) + '.network').catch(() => undefined);
|
||||||
const model = new TraceModel(snapshotStorage);
|
const model = new TraceModel(snapshotStorage);
|
||||||
await appendTraceEvents(model, traceFile);
|
await appendTraceEvents(model, traceFile);
|
||||||
if (fs.existsSync(networkFile))
|
if (networkFile)
|
||||||
await appendTraceEvents(model, networkFile);
|
await appendTraceEvents(model, networkFile);
|
||||||
model.build();
|
model.build();
|
||||||
response.end(JSON.stringify(model.contextEntry));
|
response.end(JSON.stringify(model.contextEntry));
|
||||||
|
|
@ -117,7 +119,8 @@ export class TraceViewer {
|
||||||
const sha1 = request.url!.substring('/sha1/'.length);
|
const sha1 = request.url!.substring('/sha1/'.length);
|
||||||
if (sha1.includes('/'))
|
if (sha1.includes('/'))
|
||||||
return false;
|
return false;
|
||||||
return this._server.serveFile(response, path.join(resourcesDir!, sha1));
|
this._server.serveVirtualFile(response, this._vfs, 'resources/' + sha1).catch(() => {});
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
this._server.routePrefix('/sha1/', sha1Handler);
|
this._server.routePrefix('/sha1/', sha1Handler);
|
||||||
}
|
}
|
||||||
|
|
@ -163,10 +166,9 @@ export class TraceViewer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function appendTraceEvents(model: TraceModel, file: string) {
|
async function appendTraceEvents(model: TraceModel, input: stream.Readable) {
|
||||||
const fileStream = fs.createReadStream(file, 'utf8');
|
|
||||||
const rl = readline.createInterface({
|
const rl = readline.createInterface({
|
||||||
input: fileStream,
|
input,
|
||||||
crlfDelay: Infinity
|
crlfDelay: Infinity
|
||||||
});
|
});
|
||||||
for await (const line of rl as any)
|
for await (const line of rl as any)
|
||||||
|
|
@ -200,17 +202,12 @@ export async function showTraceViewer(tracePath: string, browserName: string, he
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stat.isDirectory()) {
|
if (stat.isDirectory()) {
|
||||||
const traceViewer = new TraceViewer(tracePath, browserName);
|
const traceViewer = new TraceViewer(new RealFileSystem(tracePath), browserName);
|
||||||
|
await traceViewer.init();
|
||||||
return await traceViewer.show(headless);
|
return await traceViewer.show(headless);
|
||||||
}
|
}
|
||||||
|
|
||||||
const zipFile = tracePath;
|
const traceViewer = new TraceViewer(new ZipFileSystem(tracePath), browserName);
|
||||||
try {
|
await traceViewer.init();
|
||||||
await extract(zipFile, { dir });
|
|
||||||
} catch (e) {
|
|
||||||
console.log(`Invalid trace file: ${zipFile}`); // eslint-disable-line no-console
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const traceViewer = new TraceViewer(dir, browserName);
|
|
||||||
return await traceViewer.show(headless);
|
return await traceViewer.show(headless);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import path from 'path';
|
||||||
import { Server as WebSocketServer } from 'ws';
|
import { Server as WebSocketServer } from 'ws';
|
||||||
import * as mime from 'mime';
|
import * as mime from 'mime';
|
||||||
import { assert } from './utils';
|
import { assert } from './utils';
|
||||||
|
import { VirtualFileSystem } from './vfs';
|
||||||
|
|
||||||
export type ServerRouteHandler = (request: http.IncomingMessage, response: http.ServerResponse) => boolean;
|
export type ServerRouteHandler = (request: http.IncomingMessage, response: http.ServerResponse) => boolean;
|
||||||
|
|
||||||
|
|
@ -95,6 +96,22 @@ export class HttpServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async serveVirtualFile(response: http.ServerResponse, vfs: VirtualFileSystem, entry: string, headers?: { [name: string]: string }) {
|
||||||
|
try {
|
||||||
|
const content = await vfs.read(entry);
|
||||||
|
response.statusCode = 200;
|
||||||
|
const contentType = mime.getType(path.extname(entry)) || 'application/octet-stream';
|
||||||
|
response.setHeader('Content-Type', contentType);
|
||||||
|
response.setHeader('Content-Length', content.byteLength);
|
||||||
|
for (const [name, value] of Object.entries(headers || {}))
|
||||||
|
response.setHeader(name, value);
|
||||||
|
response.end(content);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _onRequest(request: http.IncomingMessage, response: http.ServerResponse) {
|
private _onRequest(request: http.IncomingMessage, response: http.ServerResponse) {
|
||||||
request.on('error', () => response.end());
|
request.on('error', () => response.end());
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
124
src/utils/vfs.ts
Normal file
124
src/utils/vfs.ts
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import path from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
|
import stream from 'stream';
|
||||||
|
import yauzl from 'yauzl';
|
||||||
|
|
||||||
|
export interface VirtualFileSystem {
|
||||||
|
entries(): Promise<string[]>;
|
||||||
|
read(entry: string): Promise<Buffer>;
|
||||||
|
readStream(entryPath: string): Promise<stream.Readable>;
|
||||||
|
close(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class BaseFileSystem {
|
||||||
|
|
||||||
|
abstract readStream(entryPath: string): Promise<stream.Readable>;
|
||||||
|
|
||||||
|
async read(entryPath: string): Promise<Buffer> {
|
||||||
|
const readStream = await this.readStream(entryPath);
|
||||||
|
const buffers: Buffer[] = [];
|
||||||
|
return new Promise(f => {
|
||||||
|
readStream.on('data', d => buffers.push(d));
|
||||||
|
readStream.on('end', () => f(Buffer.concat(buffers)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RealFileSystem extends BaseFileSystem implements VirtualFileSystem {
|
||||||
|
private _folder: string;
|
||||||
|
|
||||||
|
constructor(folder: string) {
|
||||||
|
super();
|
||||||
|
this._folder = folder;
|
||||||
|
}
|
||||||
|
|
||||||
|
async entries(): Promise<string[]> {
|
||||||
|
const result: string[] = [];
|
||||||
|
const visit = (dir: string) => {
|
||||||
|
for (const name of fs.readdirSync(dir)) {
|
||||||
|
const fqn = path.join(dir, name);
|
||||||
|
if (fs.statSync(fqn).isDirectory())
|
||||||
|
visit(fqn);
|
||||||
|
if (fs.statSync(fqn).isFile())
|
||||||
|
result.push(fqn);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
visit(this._folder);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async readStream(entry: string): Promise<stream.Readable> {
|
||||||
|
return fs.createReadStream(path.join(this._folder, ...entry.split('/')));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ZipFileSystem extends BaseFileSystem implements VirtualFileSystem {
|
||||||
|
private _fileName: string;
|
||||||
|
private _zipFile: yauzl.ZipFile | undefined;
|
||||||
|
private _entries = new Map<string, yauzl.Entry>();
|
||||||
|
private _openedPromise: Promise<void>;
|
||||||
|
|
||||||
|
constructor(fileName: string) {
|
||||||
|
super();
|
||||||
|
this._fileName = fileName;
|
||||||
|
this._openedPromise = this.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
async open() {
|
||||||
|
await new Promise<yauzl.ZipFile>((fulfill, reject) => {
|
||||||
|
yauzl.open(this._fileName, { autoClose: false }, (e, z) => {
|
||||||
|
if (e) {
|
||||||
|
reject(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._zipFile = z;
|
||||||
|
this._zipFile!.on('entry', (entry: yauzl.Entry) => {
|
||||||
|
this._entries.set(entry.fileName, entry);
|
||||||
|
});
|
||||||
|
this._zipFile!.on('end', fulfill);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async entries(): Promise<string[]> {
|
||||||
|
await this._openedPromise;
|
||||||
|
return [...this._entries.keys()];
|
||||||
|
}
|
||||||
|
|
||||||
|
async readStream(entryPath: string): Promise<stream.Readable> {
|
||||||
|
await this._openedPromise;
|
||||||
|
const entry = this._entries.get(entryPath)!;
|
||||||
|
return new Promise((f, r) => {
|
||||||
|
this._zipFile!.openReadStream(entry, (error, readStream) => {
|
||||||
|
if (error || !readStream) {
|
||||||
|
r(error || 'Entry not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
f(readStream);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
override close() {
|
||||||
|
this._zipFile?.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -146,7 +146,7 @@ it.describe('snapshots', () => {
|
||||||
await page.evaluate(() => { (document.styleSheets[0].cssRules[0] as any).style.color = 'blue'; });
|
await page.evaluate(() => { (document.styleSheets[0].cssRules[0] as any).style.color = 'blue'; });
|
||||||
const snapshot2 = await snapshotter.captureSnapshot(toImpl(page), 'snapshot1');
|
const snapshot2 = await snapshotter.captureSnapshot(toImpl(page), 'snapshot1');
|
||||||
const resource = snapshot2.resourceByUrl(`http://localhost:${server.PORT}/style.css`);
|
const resource = snapshot2.resourceByUrl(`http://localhost:${server.PORT}/style.css`);
|
||||||
expect(snapshotter.resourceContent(resource.response.content._sha1).toString()).toBe('button { color: blue; }');
|
expect((await snapshotter.resourceContent(resource.response.content._sha1)).toString()).toBe('button { color: blue; }');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should capture iframe', async ({ page, server, toImpl, browserName, snapshotter, showSnapshot }) => {
|
it('should capture iframe', async ({ page, server, toImpl, browserName, snapshotter, showSnapshot }) => {
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { expect, contextTest as test, browserTest } from './config/browserTest';
|
import { expect, contextTest as test, browserTest } from './config/browserTest';
|
||||||
import yauzl from 'yauzl';
|
import { ZipFileSystem } from '../lib/utils/vfs';
|
||||||
import jpeg from 'jpeg-js';
|
import jpeg from 'jpeg-js';
|
||||||
|
|
||||||
test.skip(({ trace }) => !!trace);
|
test.skip(({ trace }) => !!trace);
|
||||||
|
|
@ -284,29 +284,12 @@ test('should not hang for clicks that open dialogs', async ({ context, page }) =
|
||||||
});
|
});
|
||||||
|
|
||||||
async function parseTrace(file: string): Promise<{ events: any[], resources: Map<string, Buffer> }> {
|
async function parseTrace(file: string): Promise<{ events: any[], resources: Map<string, Buffer> }> {
|
||||||
const entries = await new Promise<any[]>(f => {
|
const zipFS = new ZipFileSystem(file);
|
||||||
const entries: Promise<any>[] = [];
|
|
||||||
yauzl.open(file, (err, zipFile) => {
|
|
||||||
zipFile.on('entry', entry => {
|
|
||||||
const entryPromise = new Promise(ff => {
|
|
||||||
zipFile.openReadStream(entry, (err, readStream) => {
|
|
||||||
const buffers = [];
|
|
||||||
if (readStream) {
|
|
||||||
readStream.on('data', d => buffers.push(d));
|
|
||||||
readStream.on('end', () => ff({ name: entry.fileName, buffer: Buffer.concat(buffers) }));
|
|
||||||
} else {
|
|
||||||
ff({ name: entry.fileName });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
entries.push(entryPromise);
|
|
||||||
});
|
|
||||||
zipFile.on('end', () => f(entries));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const resources = new Map<string, Buffer>();
|
const resources = new Map<string, Buffer>();
|
||||||
for (const { name, buffer } of await Promise.all(entries))
|
for (const entry of await zipFS.entries())
|
||||||
resources.set(name, buffer);
|
resources.set(entry, await zipFS.read(entry));
|
||||||
|
zipFS.close();
|
||||||
|
|
||||||
const events = [];
|
const events = [];
|
||||||
for (const line of resources.get('trace.trace').toString().split('\n')) {
|
for (const line of resources.get('trace.trace').toString().split('\n')) {
|
||||||
if (line)
|
if (line)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue