chore: create trace.zip in driver for local runs (#10868)

This commit is contained in:
Yury Semikhatsky 2021-12-13 14:40:29 -08:00 committed by GitHub
parent a436cc8aa0
commit a2a8967bed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 49 additions and 40 deletions

View File

@ -14,7 +14,6 @@
* limitations under the License.
*/
import fs from 'fs';
import * as api from '../../types/types';
import * as channels from '../protocol/channels';
import { Artifact } from './artifact';
@ -52,23 +51,27 @@ export class Tracing implements api.Tracing {
private async _doStopChunk(channel: channels.BrowserContextChannel, filePath: string | undefined) {
const isLocal = !this._context._connection.isRemote();
const result = await channel.tracingStopChunk({ save: !!filePath, skipCompress: isLocal });
let mode: channels.BrowserContextTracingStopChunkParams['mode'] = 'doNotSave';
if (filePath) {
if (isLocal)
mode = 'compressTraceAndSources';
else
mode = 'compressTrace';
}
const result = await channel.tracingStopChunk({ mode });
if (!filePath) {
// Not interested in artifacts.
return;
}
if (filePath && fs.existsSync(filePath))
await fs.promises.unlink(filePath);
// Save trace to the final local file.
const artifact = Artifact.from(result.artifact!);
await artifact.saveAs(filePath);
await artifact.delete();
if (!isLocal) {
// We run against remote Playwright, compress on remote side.
const artifact = Artifact.from(result.artifact!);
await artifact.saveAs(filePath);
await artifact.delete();
}
if (isLocal || result.sourceEntries.length)
await this._context._localUtils.zip(filePath, result.sourceEntries.concat(result.entries));
// Add local sources to the remote trace if necessary.
if (result.sourceEntries?.length)
await this._context._localUtils.zip(filePath, result.sourceEntries);
}
}

View File

@ -198,8 +198,8 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
}
async tracingStopChunk(params: channels.BrowserContextTracingStopChunkParams): Promise<channels.BrowserContextTracingStopChunkResult> {
const { artifact, entries, sourceEntries } = await this._context.tracing.stopChunk(params.save, params.skipCompress);
return { artifact: artifact ? new ArtifactDispatcher(this._scope, artifact) : undefined, entries, sourceEntries };
const { artifact, sourceEntries } = await this._context.tracing.stopChunk(params);
return { artifact: artifact ? new ArtifactDispatcher(this._scope, artifact) : undefined, sourceEntries };
}
async tracingStop(params: channels.BrowserContextTracingStopParams): Promise<channels.BrowserContextTracingStopResult> {

View File

@ -1266,16 +1266,14 @@ export type BrowserContextTracingStartChunkOptions = {
};
export type BrowserContextTracingStartChunkResult = void;
export type BrowserContextTracingStopChunkParams = {
save: boolean,
skipCompress: boolean,
mode: 'doNotSave' | 'compressTrace' | 'compressTraceAndSources',
};
export type BrowserContextTracingStopChunkOptions = {
};
export type BrowserContextTracingStopChunkResult = {
artifact?: ArtifactChannel,
entries: NameValue[],
sourceEntries: NameValue[],
sourceEntries?: NameValue[],
};
export type BrowserContextTracingStopParams = {};
export type BrowserContextTracingStopOptions = {};

View File

@ -833,15 +833,16 @@ BrowserContext:
tracingStopChunk:
parameters:
save: boolean
skipCompress: boolean
mode:
type: enum
literals:
- doNotSave
- compressTrace
- compressTraceAndSources
returns:
artifact: Artifact?
entries:
type: array
items: NameValue
sourceEntries:
type: array
type: array?
items: NameValue
tracingStop:

View File

@ -509,8 +509,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
title: tOptional(tString),
});
scheme.BrowserContextTracingStopChunkParams = tObject({
save: tBoolean,
skipCompress: tBoolean,
mode: tEnum(['doNotSave', 'compressTrace', 'compressTraceAndSources']),
});
scheme.BrowserContextTracingStopParams = tOptional(tObject({}));
scheme.BrowserContextHarExportParams = tOptional(tObject({}));

View File

@ -19,7 +19,7 @@ import fs from 'fs';
import path from 'path';
import yazl from 'yazl';
import { NameValue } from '../../../common/types';
import { commandsWithTracingSnapshots } from '../../../protocol/channels';
import { commandsWithTracingSnapshots, BrowserContextTracingStopChunkParams } from '../../../protocol/channels';
import { ManualPromise } from '../../../utils/async';
import { eventsHelper, RegisteredListener } from '../../../utils/eventsHelper';
import { calculateSha1, createGuid, mkdirIfNeeded, monotonicTime } from '../../../utils/utils';
@ -113,7 +113,7 @@ export class Tracing implements InstrumentationListener, SnapshotterDelegate, Ha
async startChunk(options: { title?: string } = {}) {
if (this._state && this._state.recording)
await this.stopChunk(false, false);
await this.stopChunk({ mode: 'doNotSave' });
if (!this._state)
throw new Error('Must start tracing before starting a new chunk');
@ -169,16 +169,16 @@ export class Tracing implements InstrumentationListener, SnapshotterDelegate, Ha
await this._writeChain;
}
async stopChunk(save: boolean, skipCompress: boolean): Promise<{ artifact: Artifact | null, entries: NameValue[], sourceEntries: NameValue[] }> {
async stopChunk(params: BrowserContextTracingStopChunkParams): Promise<{ artifact: Artifact | null, sourceEntries: NameValue[] | undefined }> {
if (this._isStopping)
throw new Error(`Tracing is already stopping`);
this._isStopping = true;
if (!this._state || !this._state.recording) {
this._isStopping = false;
if (save)
if (params.mode !== 'doNotSave')
throw new Error(`Must start tracing before stopping`);
return { artifact: null, entries: [], sourceEntries: [] };
return { artifact: null, sourceEntries: [] };
}
const state = this._state!;
@ -205,8 +205,8 @@ export class Tracing implements InstrumentationListener, SnapshotterDelegate, Ha
// Chain the export operation against write operations,
// so that neither trace files nor sha1s change during the export.
return await this._appendTraceOperation(async () => {
if (!save)
return { artifact: null, entries: [], sourceEntries: [] };
if (params.mode === 'doNotSave')
return { artifact: null, sourceEntries: undefined };
// Har files a live, make a snapshot before returning the resulting entries.
const networkFile = path.join(state.networkFile, '..', createGuid());
@ -218,19 +218,27 @@ export class Tracing implements InstrumentationListener, SnapshotterDelegate, Ha
for (const sha1 of new Set([...state.traceSha1s, ...state.networkSha1s]))
entries.push({ name: path.join('resources', sha1), value: path.join(this._resourcesDir, sha1) });
const sourceEntries: NameValue[] = [];
for (const value of state.sources)
sourceEntries.push({ name: 'resources/src@' + calculateSha1(value) + '.txt', value });
let sourceEntries: NameValue[] | undefined;
if (state.sources.size) {
sourceEntries = [];
for (const value of state.sources) {
const entry = { name: 'resources/src@' + calculateSha1(value) + '.txt', value };
if (params.mode === 'compressTraceAndSources')
entries.push(entry);
else
sourceEntries.push(entry);
}
}
const artifact = skipCompress ? null : await this._exportZip(entries, state).catch(() => null);
return { artifact, entries, sourceEntries };
const artifact = await this._exportZip(entries, state).catch(() => null);
return { artifact, sourceEntries };
}).finally(() => {
// Only reset trace sha1s, network resources are preserved between chunks.
state.traceSha1s = new Set();
state.sources = new Set();
this._isStopping = false;
state.recording = false;
}) || { artifact: null, entries: [], sourceEntries: [] };
}) || { artifact: null, sourceEntries: undefined };
}
private async _exportZip(entries: NameValue[], state: RecordingState): Promise<Artifact | null> {