diff --git a/src/debug/debugController.ts b/src/debug/debugController.ts new file mode 100644 index 0000000000000..1a8286a206ab6 --- /dev/null +++ b/src/debug/debugController.ts @@ -0,0 +1,61 @@ +/** + * 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 { BrowserContext } from '../server/browserContext'; +import * as frames from '../server/frames'; +import * as js from '../server/javascript'; +import { Page } from '../server/page'; +import { InstrumentingAgent } from '../server/instrumentation'; +import type DebugScript from './injected/debugScript'; +import { Progress } from '../server/progress'; +import { isDebugMode } from '../utils/utils'; +import * as debugScriptSource from '../generated/debugScriptSource'; + +const debugScriptSymbol = Symbol('debugScript'); + +export class DebugController implements InstrumentingAgent { + private async ensureInstalledInFrame(frame: frames.Frame) { + try { + const mainContext = await frame._mainContext(); + if ((mainContext as any)[debugScriptSymbol]) + return; + (mainContext as any)[debugScriptSymbol] = true; + const objectId = await mainContext._delegate.rawEvaluate(`new (${debugScriptSource.source})()`); + const debugScript = new js.JSHandle(mainContext, 'object', objectId) as js.JSHandle; + const injectedScript = await mainContext.injectedScript(); + await debugScript.evaluate((debugScript, injectedScript) => { + debugScript.initialize(injectedScript, { console: true }); + }, injectedScript); + } catch (e) { + } + } + + async onContextCreated(context: BrowserContext): Promise { + if (!isDebugMode()) + return; + context.on(BrowserContext.Events.Page, (page: Page) => { + for (const frame of page.frames()) + this.ensureInstalledInFrame(frame); + page.on(Page.Events.FrameNavigated, frame => this.ensureInstalledInFrame(frame)); + }); + } + + async onContextDestroyed(context: BrowserContext): Promise { + } + + async onBeforePageAction(page: Page, progress: Progress): Promise { + } +} diff --git a/src/server/debug/injected/consoleApi.ts b/src/debug/injected/consoleApi.ts similarity index 96% rename from src/server/debug/injected/consoleApi.ts rename to src/debug/injected/consoleApi.ts index 6290eb087660e..3e6fb2a1eb73c 100644 --- a/src/server/debug/injected/consoleApi.ts +++ b/src/debug/injected/consoleApi.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { ParsedSelector, parseSelector } from '../../common/selectorParser'; -import type InjectedScript from '../../injected/injectedScript'; +import { ParsedSelector, parseSelector } from '../../server/common/selectorParser'; +import type InjectedScript from '../../server/injected/injectedScript'; import { html } from './html'; export class ConsoleAPI { diff --git a/src/server/debug/injected/debugScript.ts b/src/debug/injected/debugScript.ts similarity index 92% rename from src/server/debug/injected/debugScript.ts rename to src/debug/injected/debugScript.ts index a30f44b2be7b7..d0aaf899b3efd 100644 --- a/src/server/debug/injected/debugScript.ts +++ b/src/debug/injected/debugScript.ts @@ -15,7 +15,7 @@ */ import { ConsoleAPI } from './consoleApi'; -import type InjectedScript from '../../injected/injectedScript'; +import type InjectedScript from '../../server/injected/injectedScript'; export default class DebugScript { consoleAPI: ConsoleAPI | undefined; diff --git a/src/server/debug/injected/debugScript.webpack.config.js b/src/debug/injected/debugScript.webpack.config.js similarity index 81% rename from src/server/debug/injected/debugScript.webpack.config.js rename to src/debug/injected/debugScript.webpack.config.js index a63aa7701f893..79f919b170cbb 100644 --- a/src/server/debug/injected/debugScript.webpack.config.js +++ b/src/debug/injected/debugScript.webpack.config.js @@ -15,7 +15,7 @@ */ const path = require('path'); -const InlineSource = require('../../injected/webpack-inline-source-plugin'); +const InlineSource = require('../../server/injected/webpack-inline-source-plugin'); module.exports = { entry: path.join(__dirname, 'debugScript.ts'), @@ -38,9 +38,9 @@ module.exports = { output: { libraryTarget: 'var', filename: 'debugScriptSource.js', - path: path.resolve(__dirname, '../../../../lib/server/injected/packed') + path: path.resolve(__dirname, '../../../lib/server/injected/packed') }, plugins: [ - new InlineSource(path.join(__dirname, '..', '..', '..', 'generated', 'debugScriptSource.ts')), + new InlineSource(path.join(__dirname, '..', '..', 'generated', 'debugScriptSource.ts')), ] }; diff --git a/src/server/debug/injected/html.ts b/src/debug/injected/html.ts similarity index 100% rename from src/server/debug/injected/html.ts rename to src/debug/injected/html.ts diff --git a/src/inprocess.ts b/src/inprocess.ts index c3fd131e59a79..18d71d8a12b62 100644 --- a/src/inprocess.ts +++ b/src/inprocess.ts @@ -21,8 +21,12 @@ import { PlaywrightDispatcher } from './dispatchers/playwrightDispatcher'; import { Connection } from './client/connection'; import { BrowserServerLauncherImpl } from './browserServerImpl'; import { isDevMode } from './utils/utils'; +import { instrumentingAgents } from './server/instrumentation'; +import { DebugController } from './debug/debugController'; export function setupInProcess(playwright: PlaywrightImpl): PlaywrightAPI { + instrumentingAgents.add(new DebugController()); + const clientConnection = new Connection(); const dispatcherConnection = new DispatcherConnection(); diff --git a/src/server.ts b/src/server.ts index 172ecd5bbb879..422a58e5ad63c 100644 --- a/src/server.ts +++ b/src/server.ts @@ -20,6 +20,10 @@ import { Playwright } from './server/playwright'; import { PlaywrightDispatcher } from './dispatchers/playwrightDispatcher'; import { Electron } from './server/electron/electron'; import { gracefullyCloseAll } from './server/processLauncher'; +import { instrumentingAgents } from './server/instrumentation'; +import { DebugController } from './debug/debugController'; + +instrumentingAgents.add(new DebugController()); const dispatcherConnection = new DispatcherConnection(); const transport = new Transport(process.stdout, process.stdin); diff --git a/src/server/browserContext.ts b/src/server/browserContext.ts index 252d51deceaea..b55a7cb3c0c31 100644 --- a/src/server/browserContext.ts +++ b/src/server/browserContext.ts @@ -27,10 +27,8 @@ import { Download } from './download'; import { Browser } from './browser'; import { EventEmitter } from 'events'; import { Progress } from './progress'; -import { DebugController } from './debug/debugController'; -import { isDebugMode } from '../utils/utils'; -import { Snapshotter, SnapshotterDelegate } from './snapshotter'; import { Selectors, serverSelectors } from './selectors'; +import { instrumentingAgents } from './instrumentation'; export class Screencast { readonly page: Page; @@ -70,7 +68,6 @@ export abstract class BrowserContext extends EventEmitter { readonly _browser: Browser; readonly _browserContextId: string | undefined; private _selectors?: Selectors; - _snapshotter?: Snapshotter; constructor(browser: Browser, options: types.BrowserContextOptions, browserContextId: string | undefined) { super(); @@ -90,14 +87,8 @@ export abstract class BrowserContext extends EventEmitter { } async _initialize() { - if (isDebugMode()) - new DebugController(this); - } - - // Used by test runner. - async _initSnapshotter(delegate: SnapshotterDelegate): Promise { - this._snapshotter = new Snapshotter(this, delegate); - return this._snapshotter; + for (const agent of instrumentingAgents) + await agent.onContextCreated(this); } _browserClosed() { @@ -250,8 +241,8 @@ export abstract class BrowserContext extends EventEmitter { this._closedStatus = 'closing'; await this._doClose(); await Promise.all([...this._downloads].map(d => d.delete())); - if (this._snapshotter) - this._snapshotter._dispose(); + for (const agent of instrumentingAgents) + await agent.onContextDestroyed(this); this._didCloseInternal(); } await this._closePromise; diff --git a/src/server/debug/debugController.ts b/src/server/debug/debugController.ts deleted file mode 100644 index fee9786c64ba9..0000000000000 --- a/src/server/debug/debugController.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * 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 { BrowserContext } from '../browserContext'; -import * as frames from '../frames'; -import * as js from '../javascript'; -import { Page } from '../page'; -import type DebugScript from './injected/debugScript'; - -export class DebugController { - constructor(context: BrowserContext) { - context.on(BrowserContext.Events.Page, (page: Page) => { - for (const frame of page.frames()) - this.ensureInstalledInFrame(frame); - page.on(Page.Events.FrameNavigated, frame => this.ensureInstalledInFrame(frame)); - }); - } - - private async ensureInstalledInFrame(frame: frames.Frame): Promise | undefined> { - try { - const mainContext = await frame._mainContext(); - return await mainContext.createDebugScript({ console: true }); - } catch (e) { - } - } -} diff --git a/src/server/dom.ts b/src/server/dom.ts index 6134028a7a696..766ebdddd46b6 100644 --- a/src/server/dom.ts +++ b/src/server/dom.ts @@ -18,13 +18,11 @@ import * as frames from './frames'; import { assert } from '../utils/utils'; import type { InjectedScript, InjectedScriptPoll } from './injected/injectedScript'; import * as injectedScriptSource from '../generated/injectedScriptSource'; -import * as debugScriptSource from '../generated/debugScriptSource'; import * as js from './javascript'; import { Page } from './page'; import { SelectorInfo } from './selectors'; import * as types from './types'; import { Progress } from './progress'; -import type DebugScript from './debug/injected/debugScript'; import { FatalDOMError, RetargetableDOMError } from './common/domErrors'; export class FrameExecutionContext extends js.ExecutionContext { @@ -92,18 +90,6 @@ export class FrameExecutionContext extends js.ExecutionContext { return this._injectedScriptPromise; } - createDebugScript(options: { console?: boolean }): Promise | undefined> { - if (!this._debugScriptPromise) { - const source = `new (${debugScriptSource.source})()`; - this._debugScriptPromise = this._delegate.rawEvaluate(source).then(objectId => new js.JSHandle(this, 'object', objectId)).then(async debugScript => { - const injectedScript = await this.injectedScript(); - await debugScript.evaluate((debugScript: DebugScript, { injectedScript, options }) => debugScript.initialize(injectedScript, options), { injectedScript, options }); - return debugScript; - }).catch(e => undefined); - } - return this._debugScriptPromise; - } - async doSlowMo() { return this.frame._page._doSlowMo(); } diff --git a/src/server/instrumentation.ts b/src/server/instrumentation.ts new file mode 100644 index 0000000000000..7687e945a17cf --- /dev/null +++ b/src/server/instrumentation.ts @@ -0,0 +1,27 @@ +/** + * 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 { BrowserContext } from './browserContext'; +import { Page } from './page'; +import { Progress } from './progress'; + +export interface InstrumentingAgent { + onContextCreated(context: BrowserContext): Promise; + onContextDestroyed(context: BrowserContext): Promise; + onBeforePageAction(page: Page, progress: Progress): Promise; +} + +export const instrumentingAgents = new Set(); diff --git a/src/server/page.ts b/src/server/page.ts index 6a2f491a026b0..d9f0dae34b672 100644 --- a/src/server/page.ts +++ b/src/server/page.ts @@ -32,6 +32,7 @@ import { Progress, runAbortableTask } from './progress'; import { assert, isError } from '../utils/utils'; import { debugLogger } from '../utils/debugLogger'; import { Selectors } from './selectors'; +import { instrumentingAgents } from './instrumentation'; export interface PageDelegate { readonly rawMouse: input.RawMouse; @@ -199,8 +200,8 @@ export class Page extends EventEmitter { async _runAbortableTask(task: (progress: Progress) => Promise, timeout: number): Promise { return runAbortableTask(async progress => { - if (this._browserContext._snapshotter) - await this._browserContext._snapshotter._doSnapshot(progress, this, 'progress'); + for (const agent of instrumentingAgents) + await agent.onBeforePageAction(this, progress); return task(progress); }, timeout); } diff --git a/src/server/snapshotter.ts b/src/trace/snapshotter.ts similarity index 82% rename from src/server/snapshotter.ts rename to src/trace/snapshotter.ts index 4347b8a2c9c1c..3281bd6a35c89 100644 --- a/src/server/snapshotter.ts +++ b/src/trace/snapshotter.ts @@ -14,15 +14,14 @@ * limitations under the License. */ -import { BrowserContext } from './browserContext'; -import { Page } from './page'; -import * as network from './network'; -import { helper, RegisteredListener } from './helper'; -import { Progress, runAbortableTask } from './progress'; +import { BrowserContext } from '../server/browserContext'; +import { Page } from '../server/page'; +import * as network from '../server/network'; +import { helper, RegisteredListener } from '../server/helper'; +import { Progress } from '../server/progress'; import { debugLogger } from '../utils/debugLogger'; -import { Frame } from './frames'; -import * as js from './javascript'; -import * as types from './types'; +import { Frame } from '../server/frames'; +import * as js from '../server/javascript'; import { SnapshotData, takeSnapshotInFrame } from './snapshotterInjected'; import { assert, calculateSha1, createGuid } from '../utils/utils'; @@ -53,11 +52,9 @@ export type PageSnapshot = { }; export interface SnapshotterDelegate { - onContextCreated(context: BrowserContext): void; - onContextDestroyed(context: BrowserContext): void; - onBlob(context: BrowserContext, blob: SnapshotterBlob): void; - onResource(context: BrowserContext, resource: SanpshotterResource): void; - onSnapshot(context: BrowserContext, snapshot: PageSnapshot): void; + onBlob(blob: SnapshotterBlob): void; + onResource(resource: SanpshotterResource): void; + onSnapshot(snapshot: PageSnapshot): void; } export class Snapshotter { @@ -71,25 +68,17 @@ export class Snapshotter { this._eventListeners = [ helper.addEventListener(this._context, BrowserContext.Events.Page, this._onPage.bind(this)), ]; - this._delegate.onContextCreated(this._context); } - async captureSnapshot(page: Page, options: types.TimeoutOptions & { label?: string } = {}): Promise { - return runAbortableTask(async progress => { - await this._doSnapshot(progress, page, options.label || 'snapshot'); - }, page._timeoutSettings.timeout(options)); - } - - _dispose() { + dispose() { helper.removeEventListeners(this._eventListeners); - this._delegate.onContextDestroyed(this._context); } - async _doSnapshot(progress: Progress, page: Page, label: string): Promise { + async takeSnapshot(progress: Progress, page: Page, label: string): Promise { assert(page.context() === this._context); const snapshot = await this._snapshotPage(progress, page, label); if (snapshot) - this._delegate.onSnapshot(this._context, snapshot); + this._delegate.onSnapshot(snapshot); } private _onPage(page: Page) { @@ -124,9 +113,9 @@ export class Snapshotter { responseHeaders: response.headers(), sha1, }; - this._delegate.onResource(this._context, resource); + this._delegate.onResource(resource); if (body) - this._delegate.onBlob(this._context, { sha1, buffer: body }); + this._delegate.onBlob({ sha1, buffer: body }); } private async _snapshotPage(progress: Progress, page: Page, label: string): Promise { @@ -214,7 +203,7 @@ export class Snapshotter { for (const { url, content } of data.resourceOverrides) { const buffer = Buffer.from(content); const sha1 = calculateSha1(buffer); - this._delegate.onBlob(this._context, { sha1, buffer }); + this._delegate.onBlob({ sha1, buffer }); snapshot.resourceOverrides.push({ url, sha1 }); } diff --git a/src/server/snapshotterInjected.ts b/src/trace/snapshotterInjected.ts similarity index 100% rename from src/server/snapshotterInjected.ts rename to src/trace/snapshotterInjected.ts diff --git a/src/trace/traceTypes.ts b/src/trace/traceTypes.ts index 57ccd2f8c9cf9..d9a0700f93926 100644 --- a/src/trace/traceTypes.ts +++ b/src/trace/traceTypes.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -import type * as snapshotter from '../server/snapshotter'; - export type ContextCreatedTraceEvent = { type: 'context-created', browserName: string, @@ -46,6 +44,3 @@ export type SnapshotTraceEvent = { label: string, sha1: string, }; - -export type FrameSnapshot = snapshotter.FrameSnapshot; -export type PageSnapshot = snapshotter.PageSnapshot; diff --git a/src/trace/traceViewer.ts b/src/trace/traceViewer.ts index 427d13b8ac020..49cf35e4c4905 100644 --- a/src/trace/traceViewer.ts +++ b/src/trace/traceViewer.ts @@ -17,7 +17,8 @@ import * as path from 'path'; import * as util from 'util'; import * as fs from 'fs'; -import { NetworkResourceTraceEvent, SnapshotTraceEvent, ContextCreatedTraceEvent, ContextDestroyedTraceEvent, FrameSnapshot, PageSnapshot } from './traceTypes'; +import type { NetworkResourceTraceEvent, SnapshotTraceEvent, ContextCreatedTraceEvent, ContextDestroyedTraceEvent } from './traceTypes'; +import type { FrameSnapshot, PageSnapshot } from './snapshotter'; import type { Browser, BrowserContext, Frame, Page, Route } from '../client/api'; import type { Playwright } from '../client/playwright'; diff --git a/src/trace/tracer.ts b/src/trace/tracer.ts index fbbcc010d4961..8cb8bc996d064 100644 --- a/src/trace/tracer.ts +++ b/src/trace/tracer.ts @@ -15,59 +15,96 @@ */ import type { BrowserContext } from '../server/browserContext'; -import type { PageSnapshot, SanpshotterResource, SnapshotterBlob, SnapshotterDelegate } from '../server/snapshotter'; +import type { PageSnapshot, SanpshotterResource, SnapshotterBlob, SnapshotterDelegate } from './snapshotter'; import { ContextCreatedTraceEvent, ContextDestroyedTraceEvent, NetworkResourceTraceEvent, SnapshotTraceEvent } from './traceTypes'; import * as path from 'path'; import * as util from 'util'; import * as fs from 'fs'; import { calculateSha1, createGuid, mkdirIfNeeded, monotonicTime } from '../utils/utils'; +import { InstrumentingAgent, instrumentingAgents } from '../server/instrumentation'; +import { Page } from '../server/page'; +import { Progress, runAbortableTask } from '../server/progress'; +import { Snapshotter } from './snapshotter'; +import * as types from '../server/types'; const fsWriteFileAsync = util.promisify(fs.writeFile.bind(fs)); const fsAppendFileAsync = util.promisify(fs.appendFile.bind(fs)); const fsAccessAsync = util.promisify(fs.access.bind(fs)); -export class Tracer implements SnapshotterDelegate { - private _contextIds = new Map(); +export class Tracer implements InstrumentingAgent { + private _contextTracers = new Map(); + + constructor() { + instrumentingAgents.add(this); + } + + dispose() { + instrumentingAgents.delete(this); + } + + traceContext(context: BrowserContext, traceStorageDir: string, traceFile: string) { + const contextTracer = new ContextTracer(context, traceStorageDir, traceFile); + this._contextTracers.set(context, contextTracer); + } + + async captureSnapshot(page: Page, options: types.TimeoutOptions & { label?: string } = {}): Promise { + return runAbortableTask(async progress => { + const contextTracer = this._contextTracers.get(page.context()); + if (contextTracer) + await contextTracer._snapshotter.takeSnapshot(progress, page, options.label || 'snapshot'); + }, page._timeoutSettings.timeout(options)); + } + + async onContextCreated(context: BrowserContext): Promise { + } + + async onContextDestroyed(context: BrowserContext): Promise { + const contextTracer = this._contextTracers.get(context); + if (contextTracer) { + await contextTracer.dispose(); + this._contextTracers.delete(context); + } + } + + async onBeforePageAction(page: Page, progress: Progress): Promise { + const contextTracer = this._contextTracers.get(page.context()); + if (contextTracer) + await contextTracer._snapshotter.takeSnapshot(progress, page, 'progress'); + } +} + +class ContextTracer implements SnapshotterDelegate { + private _contextId: string; private _traceStoragePromise: Promise; private _appendEventChain: Promise; private _writeArtifactChain: Promise; + readonly _snapshotter: Snapshotter; - constructor(traceStorageDir: string, traceFile: string) { + constructor(context: BrowserContext, traceStorageDir: string, traceFile: string) { + this._contextId = 'context@' + createGuid(); this._traceStoragePromise = mkdirIfNeeded(path.join(traceStorageDir, 'sha1')).then(() => traceStorageDir); this._appendEventChain = mkdirIfNeeded(traceFile).then(() => traceFile); this._writeArtifactChain = Promise.resolve(); - } - - onContextCreated(context: BrowserContext): void { - const contextId = 'context@' + createGuid(); - this._contextIds.set(context, contextId); const event: ContextCreatedTraceEvent = { type: 'context-created', browserName: context._browser._options.name, - contextId, + contextId: this._contextId, isMobile: !!context._options.isMobile, deviceScaleFactor: context._options.deviceScaleFactor || 1, viewportSize: context._options.viewport || undefined, }; this._appendTraceEvent(event); + this._snapshotter = new Snapshotter(context, this); } - onContextDestroyed(context: BrowserContext): void { - const event: ContextDestroyedTraceEvent = { - type: 'context-destroyed', - contextId: this._contextIds.get(context)!, - }; - this._appendTraceEvent(event); - } - - onBlob(context: BrowserContext, blob: SnapshotterBlob): void { + onBlob(blob: SnapshotterBlob): void { this._writeArtifact(blob.sha1, blob.buffer); } - onResource(context: BrowserContext, resource: SanpshotterResource): void { + onResource(resource: SanpshotterResource): void { const event: NetworkResourceTraceEvent = { type: 'resource', - contextId: this._contextIds.get(context)!, + contextId: this._contextId, frameId: resource.frameId, url: resource.url, contentType: resource.contentType, @@ -77,12 +114,12 @@ export class Tracer implements SnapshotterDelegate { this._appendTraceEvent(event); } - onSnapshot(context: BrowserContext, snapshot: PageSnapshot): void { + onSnapshot(snapshot: PageSnapshot): void { const buffer = Buffer.from(JSON.stringify(snapshot)); const sha1 = calculateSha1(buffer); const event: SnapshotTraceEvent = { type: 'snapshot', - contextId: this._contextIds.get(context)!, + contextId: this._contextId, label: snapshot.label, sha1, }; @@ -91,6 +128,13 @@ export class Tracer implements SnapshotterDelegate { } async dispose() { + this._snapshotter.dispose(); + const event: ContextDestroyedTraceEvent = { + type: 'context-destroyed', + contextId: this._contextId, + }; + this._appendTraceEvent(event); + // Ensure all writes are finished. await this._appendEventChain; await this._writeArtifactChain; diff --git a/test/playwright.fixtures.ts b/test/playwright.fixtures.ts index 5159bbc1d6bc1..15b9f5690cd1b 100644 --- a/test/playwright.fixtures.ts +++ b/test/playwright.fixtures.ts @@ -137,7 +137,14 @@ registerWorkerFixture('playwright', async ({browserName}, test) => { spawnedProcess.stderr.destroy(); await teardownCoverage(); } else { - await test(require('../index')); + const playwright = require('../index'); + if (options.TRACING) { + const tracerFactory = require('../lib/trace/tracer').Tracer; + playwright.__tracer = new tracerFactory(); + } + await test(playwright); + if (playwright.__tracer) + playwright.__tracer.dispose(); await teardownCoverage(); } @@ -148,7 +155,6 @@ registerWorkerFixture('playwright', async ({browserName}, test) => { await fs.promises.mkdir(path.dirname(coveragePath), { recursive: true }); await fs.promises.writeFile(coveragePath, JSON.stringify(coverageJSON, undefined, 2), 'utf8'); } - }); registerWorkerFixture('toImpl', async ({playwright}, test) => { @@ -182,25 +188,21 @@ registerWorkerFixture('golden', async ({browserName}, test) => { await test(p => path.join(browserName, p)); }); -registerFixture('context', async ({browser, toImpl}, runTest, info) => { +registerFixture('context', async ({browser, playwright, toImpl}, runTest, info) => { const context = await browser.newContext(); const { test, config } = info; - if (options.TRACING) { + if ((playwright as any).__tracer) { const traceStorageDir = path.join(config.outputDir, 'trace-storage'); const relativePath = path.relative(config.testDir, test.file).replace(/\.spec\.[jt]s/, ''); const sanitizedTitle = test.title.replace(/[^\w\d]+/g, '_'); const traceFile = path.join(config.outputDir, relativePath, sanitizedTitle + '.trace'); - const tracerFactory = require('../lib/trace/tracer').Tracer; - (context as any).__tracer = new tracerFactory(traceStorageDir, traceFile); - (context as any).__snapshotter = await toImpl(context)._initSnapshotter((context as any).__tracer); + (playwright as any).__tracer.traceContext(toImpl(context), traceStorageDir, traceFile); } await runTest(context); await context.close(); - if ((context as any).__tracer) - await (context as any).__tracer.dispose(); }); -registerFixture('page', async ({context, toImpl}, runTest, info) => { +registerFixture('page', async ({context, playwright, toImpl}, runTest, info) => { const page = await context.newPage(); await runTest(page); const { test, config, result } = info; @@ -209,8 +211,8 @@ registerFixture('page', async ({context, toImpl}, runTest, info) => { const sanitizedTitle = test.title.replace(/[^\w\d]+/g, '_'); const assetPath = path.join(config.outputDir, relativePath, sanitizedTitle) + '-failed.png'; await page.screenshot({ timeout: 5000, path: assetPath }); - if ((context as any).__snapshotter) - await (context as any).__snapshotter.captureSnapshot(toImpl(page), { timeout: 5000, label: 'Test Failed' }); + if ((playwright as any).__tracer) + await (playwright as any).__tracer.captureSnapshot(toImpl(page), { timeout: 5000, label: 'Test Failed' }); } }); diff --git a/test/snapshot.spec.ts b/test/snapshot.spec.ts index 32be5a3793a73..b5e5af07311aa 100644 --- a/test/snapshot.spec.ts +++ b/test/snapshot.spec.ts @@ -18,7 +18,7 @@ import { it, options } from './playwright.fixtures'; it('should not throw', test => { test.skip(!options.TRACING); -}, async ({page, server, context, toImpl}) => { +}, async ({page, server, playwright, toImpl}) => { await page.goto(server.PREFIX + '/snapshot/snapshot-with-css.html'); - await (context as any).__snapshotter.captureSnapshot(toImpl(page), { timeout: 5000, label: 'snapshot' }); + await (playwright as any).__tracer.captureSnapshot(toImpl(page), { timeout: 5000, label: 'snapshot' }); }); diff --git a/utils/check_deps.js b/utils/check_deps.js index 90190d6654375..5ad7ba5ba39c4 100644 --- a/utils/check_deps.js +++ b/utils/check_deps.js @@ -103,7 +103,6 @@ DEPS['src/server/'] = [ // Can depend on any files in these subdirectories. 'src/server/common/**', 'src/server/injected/**', - 'src/server/debug/**', ]; // No dependencies for code shared between node and page. @@ -117,7 +116,10 @@ DEPS['src/server/electron/'] = [...DEPS['src/server/'], 'src/server/chromium/']; DEPS['src/server/playwright.ts'] = [...DEPS['src/server/'], 'src/server/chromium/', 'src/server/webkit/', 'src/server/firefox/']; DEPS['src/server.ts'] = DEPS['src/inprocess.ts'] = DEPS['src/browserServerImpl.ts'] = ['src/**']; -// Tracing depends on client and server, but nothing should depend on tracing. +// Tracing is a client/server plugin, nothing should depend on it. DEPS['src/trace/'] = ['src/utils/', 'src/client/**', 'src/server/**']; +// Debug is a server plugin, nothing should depend on it. +DEPS['src/debug/'] = ['src/utils/', 'src/generated/', 'src/server/**', 'src/debug/**']; + checkDeps(); diff --git a/utils/runWebpack.js b/utils/runWebpack.js index 56e3b3b2193f7..01f6de5760c20 100644 --- a/utils/runWebpack.js +++ b/utils/runWebpack.js @@ -20,7 +20,7 @@ const path = require('path'); const files = [ path.join('src', 'server', 'injected', 'injectedScript.webpack.config.js'), path.join('src', 'server', 'injected', 'utilityScript.webpack.config.js'), - path.join('src', 'server', 'debug', 'injected', 'debugScript.webpack.config.js'), + path.join('src', 'debug', 'injected', 'debugScript.webpack.config.js'), ]; function runOne(runner, file) {