From a2a8967bedc2b8959c7daee47d8969c6e01d24f9 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Mon, 13 Dec 2021 14:40:29 -0800 Subject: [PATCH] chore: create trace.zip in driver for local runs (#10868) --- .../playwright-core/src/client/tracing.ts | 29 +++++++++------- .../dispatchers/browserContextDispatcher.ts | 4 +-- .../playwright-core/src/protocol/channels.ts | 6 ++-- .../playwright-core/src/protocol/protocol.yml | 13 +++---- .../playwright-core/src/protocol/validator.ts | 3 +- .../src/server/trace/recorder/tracing.ts | 34 ++++++++++++------- 6 files changed, 49 insertions(+), 40 deletions(-) diff --git a/packages/playwright-core/src/client/tracing.ts b/packages/playwright-core/src/client/tracing.ts index d4011513ff..1c14fd991d 100644 --- a/packages/playwright-core/src/client/tracing.ts +++ b/packages/playwright-core/src/client/tracing.ts @@ -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); } } diff --git a/packages/playwright-core/src/dispatchers/browserContextDispatcher.ts b/packages/playwright-core/src/dispatchers/browserContextDispatcher.ts index b5f03bfd3a..26f44fe778 100644 --- a/packages/playwright-core/src/dispatchers/browserContextDispatcher.ts +++ b/packages/playwright-core/src/dispatchers/browserContextDispatcher.ts @@ -198,8 +198,8 @@ export class BrowserContextDispatcher extends Dispatcher { - 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 { diff --git a/packages/playwright-core/src/protocol/channels.ts b/packages/playwright-core/src/protocol/channels.ts index 0d0ad13b29..357d593943 100644 --- a/packages/playwright-core/src/protocol/channels.ts +++ b/packages/playwright-core/src/protocol/channels.ts @@ -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 = {}; diff --git a/packages/playwright-core/src/protocol/protocol.yml b/packages/playwright-core/src/protocol/protocol.yml index dbb91e0ef1..a7deefe6c3 100644 --- a/packages/playwright-core/src/protocol/protocol.yml +++ b/packages/playwright-core/src/protocol/protocol.yml @@ -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: diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index a0ab0400bb..1d5e59bf92 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -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({})); diff --git a/packages/playwright-core/src/server/trace/recorder/tracing.ts b/packages/playwright-core/src/server/trace/recorder/tracing.ts index b5303b8afe..59788cf100 100644 --- a/packages/playwright-core/src/server/trace/recorder/tracing.ts +++ b/packages/playwright-core/src/server/trace/recorder/tracing.ts @@ -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 {