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. * limitations under the License.
*/ */
import fs from 'fs';
import * as api from '../../types/types'; import * as api from '../../types/types';
import * as channels from '../protocol/channels'; import * as channels from '../protocol/channels';
import { Artifact } from './artifact'; import { Artifact } from './artifact';
@ -52,23 +51,27 @@ export class Tracing implements api.Tracing {
private async _doStopChunk(channel: channels.BrowserContextChannel, filePath: string | undefined) { private async _doStopChunk(channel: channels.BrowserContextChannel, filePath: string | undefined) {
const isLocal = !this._context._connection.isRemote(); 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) { if (!filePath) {
// Not interested in artifacts. // Not interested in artifacts.
return; return;
} }
if (filePath && fs.existsSync(filePath)) // Save trace to the final local file.
await fs.promises.unlink(filePath);
if (!isLocal) {
// We run against remote Playwright, compress on remote side.
const artifact = Artifact.from(result.artifact!); const artifact = Artifact.from(result.artifact!);
await artifact.saveAs(filePath); await artifact.saveAs(filePath);
await artifact.delete(); await artifact.delete();
}
if (isLocal || result.sourceEntries.length) // Add local sources to the remote trace if necessary.
await this._context._localUtils.zip(filePath, result.sourceEntries.concat(result.entries)); 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> { async tracingStopChunk(params: channels.BrowserContextTracingStopChunkParams): Promise<channels.BrowserContextTracingStopChunkResult> {
const { artifact, entries, sourceEntries } = await this._context.tracing.stopChunk(params.save, params.skipCompress); const { artifact, sourceEntries } = await this._context.tracing.stopChunk(params);
return { artifact: artifact ? new ArtifactDispatcher(this._scope, artifact) : undefined, entries, sourceEntries }; return { artifact: artifact ? new ArtifactDispatcher(this._scope, artifact) : undefined, sourceEntries };
} }
async tracingStop(params: channels.BrowserContextTracingStopParams): Promise<channels.BrowserContextTracingStopResult> { async tracingStop(params: channels.BrowserContextTracingStopParams): Promise<channels.BrowserContextTracingStopResult> {

View File

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

View File

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

View File

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

View File

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