From 0678b6575f197e52cecba8855e1ad99dae3dfcf1 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Wed, 8 Feb 2023 08:36:02 -0800 Subject: [PATCH] chore(watch): allow toggling browser (#20738) --- .../src/remote/playwrightServer.ts | 5 +++ .../dispatchers/debugControllerDispatcher.ts | 30 +++++++++------ packages/playwright-core/src/utils/index.ts | 1 + .../playwright-test/src/runner/reporters.ts | 9 +++++ .../playwright-test/src/runner/watchMode.ts | 38 ++++++++++++++++--- tests/page/page-click-scroll.spec.ts | 2 +- 6 files changed, 67 insertions(+), 18 deletions(-) diff --git a/packages/playwright-core/src/remote/playwrightServer.ts b/packages/playwright-core/src/remote/playwrightServer.ts index c6fee9a083..767d5adef7 100644 --- a/packages/playwright-core/src/remote/playwrightServer.ts +++ b/packages/playwright-core/src/remote/playwrightServer.ts @@ -174,6 +174,11 @@ export class PlaywrightServer { await new Promise(f => server.options.server!.close(f)); this._wsServer = undefined; debugLog('closed server'); + + debugLog('closing browsers'); + if (this._preLaunchedPlaywright) + await Promise.all(this._preLaunchedPlaywright.allBrowsers().map(browser => browser.close())); + debugLog('closed browsers'); } } diff --git a/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts b/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts index 801ab35d0f..9714d69093 100644 --- a/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts @@ -15,28 +15,33 @@ */ import type * as channels from '@protocol/channels'; +import { eventsHelper } from 'playwright-core/lib/utils'; +import type { RegisteredListener } from 'playwright-core/lib/utils/eventsHelper'; import { DebugController } from '../debugController'; import type { DispatcherConnection, RootDispatcher } from './dispatcher'; import { Dispatcher } from './dispatcher'; export class DebugControllerDispatcher extends Dispatcher implements channels.DebugControllerChannel { _type_DebugController; + private _listeners: RegisteredListener[]; constructor(connection: DispatcherConnection, debugController: DebugController) { super(connection, debugController, 'DebugController', {}); this._type_DebugController = true; - this._object.on(DebugController.Events.StateChanged, params => { - this._dispatchEvent('stateChanged', params); - }); - this._object.on(DebugController.Events.InspectRequested, ({ selector, locator }) => { - this._dispatchEvent('inspectRequested', { selector, locator }); - }); - this._object.on(DebugController.Events.SourceChanged, ({ text, header, footer, actions }) => { - this._dispatchEvent('sourceChanged', ({ text, header, footer, actions })); - }); - this._object.on(DebugController.Events.Paused, ({ paused }) => { - this._dispatchEvent('paused', ({ paused })); - }); + this._listeners = [ + eventsHelper.addEventListener(this._object, DebugController.Events.StateChanged, params => { + this._dispatchEvent('stateChanged', params); + }), + eventsHelper.addEventListener(this._object, DebugController.Events.InspectRequested, ({ selector, locator }) => { + this._dispatchEvent('inspectRequested', { selector, locator }); + }), + eventsHelper.addEventListener(this._object, DebugController.Events.SourceChanged, ({ text, header, footer, actions }) => { + this._dispatchEvent('sourceChanged', ({ text, header, footer, actions })); + }), + eventsHelper.addEventListener(this._object, DebugController.Events.Paused, ({ paused }) => { + this._dispatchEvent('paused', ({ paused })); + }) + ]; } async initialize(params: channels.DebugControllerInitializeParams) { @@ -80,6 +85,7 @@ export class DebugControllerDispatcher extends Dispatcher boolean; } | undefined; + constructor(options?: { + isShowBrowser?: () => boolean, + }) { + super(); + this._options = options; + } + override generateStartingMessage(): string { const tokens: string[] = []; tokens.push('npx playwright test'); @@ -121,6 +129,7 @@ export class WatchModeReporter extends ListReporter { const sep = separator(); lines.push('\x1Bc' + sep); lines.push(`${tokens.join(' ')}` + super.generateStartingMessage()); + lines.push(`${colors.dim('Show & reuse browser:')} ${colors.bold(this._options?.isShowBrowser?.() ? 'on' : 'off')}${colors.dim(', press')} ${colors.bold('s')} ${colors.dim('to toggle.')}`); return lines.join('\n'); } } diff --git a/packages/playwright-test/src/runner/watchMode.ts b/packages/playwright-test/src/runner/watchMode.ts index d8d4be7429..05647142e7 100644 --- a/packages/playwright-test/src/runner/watchMode.ts +++ b/packages/playwright-test/src/runner/watchMode.ts @@ -15,7 +15,7 @@ */ import readline from 'readline'; -import { ManualPromise } from 'playwright-core/lib/utils'; +import { createGuid, ManualPromise } from 'playwright-core/lib/utils'; import type { FullConfigInternal, FullProjectInternal } from '../common/types'; import { Multiplexer } from '../reporters/multiplexer'; import { createFileMatcherFromArguments } from '../util'; @@ -30,6 +30,7 @@ import { WatchModeReporter } from './reporters'; import { colors } from 'playwright-core/lib/utilsBundle'; import { enquirer } from '../utilsBundle'; import { separator } from '../reporters/base'; +import { PlaywrightServer } from 'playwright-core/lib/remote/playwrightServer'; class FSWatcher { private _dirtyFiles = new Set(); @@ -70,6 +71,8 @@ export async function runWatchModeLoop(config: FullConfigInternal, failedTests: const originalCliArgs = config._internal.cliArgs; const originalCliGrep = config._internal.cliGrep; + const originalWorkers = config.workers; + let lastRun: { type: 'changed' | 'regular' | 'failed', failedTestIds?: Set, dirtyFiles?: Set } = { type: 'regular' }; const fsWatcher = new FSWatcher(projectClosure.map(p => p.testDir)); @@ -110,7 +113,6 @@ Waiting for file changes. Press ${colors.bold('h')} for help or ${colors.bold('q type: 'text', name: 'filePattern', message: 'Input filename pattern (regex)', - initial: config._internal.cliArgs.join(' '), }); if (filePattern.trim()) config._internal.cliArgs = [filePattern]; @@ -126,7 +128,6 @@ Waiting for file changes. Press ${colors.bold('h')} for help or ${colors.bold('q type: 'text', name: 'testPattern', message: 'Input test name pattern (regex)', - initial: config._internal.cliGrep, }); if (testPattern.trim()) config._internal.cliGrep = testPattern; @@ -160,6 +161,11 @@ Waiting for file changes. Press ${colors.bold('h')} for help or ${colors.bold('q continue; } + if (command === 'toggle-show-browser') { + await toggleShowBrowser(config, originalWorkers); + continue; + } + if (command === 'exit') return 'passed'; @@ -203,7 +209,7 @@ async function runChangedTests(config: FullConfigInternal, failedTestIdCollector } async function runTests(config: FullConfigInternal, failedTestIdCollector: Set, projectsToIgnore?: Set, additionalFileMatcher?: Matcher) { - const reporter = new Multiplexer([new WatchModeReporter()]); + const reporter = new Multiplexer([new WatchModeReporter({ isShowBrowser: () => !!showBrowserServer })]); const taskRunner = createTaskRunnerForWatch(config, reporter, projectsToIgnore, additionalFileMatcher); const context: TaskRunnerState = { config, @@ -281,6 +287,7 @@ ${commands.map(i => ' ' + colors.bold(i[0]) + `: ${i[1]}`).join('\n')} case 't': result.resolve('grep'); break; case 'f': result.resolve('failed'); break; case 'r': result.resolve('repeat'); break; + case 's': result.resolve('toggle-show-browser'); break; } }; @@ -294,7 +301,27 @@ ${commands.map(i => ' ' + colors.bold(i[0]) + `: ${i[1]}`).join('\n')} return result; } -type Command = 'all' | 'failed' | 'repeat' | 'changed' | 'file' | 'grep' | 'exit' | 'interrupted'; +let showBrowserServer: PlaywrightServer | undefined; + +async function toggleShowBrowser(config: FullConfigInternal, originalWorkers: number) { + if (!showBrowserServer) { + config.workers = 1; + showBrowserServer = new PlaywrightServer({ path: '/' + createGuid(), maxConnections: 1 }); + const wsEndpoint = await showBrowserServer.listen(); + process.env.PW_TEST_REUSE_CONTEXT = '1'; + process.env.PW_TEST_CONNECT_WS_ENDPOINT = wsEndpoint; + process.stdout.write(`${colors.dim('Show & reuse browser:')} ${colors.bold('on')}\n`); + } else { + config.workers = originalWorkers; + await showBrowserServer?.close(); + showBrowserServer = undefined; + delete process.env.PW_TEST_REUSE_CONTEXT; + delete process.env.PW_TEST_CONNECT_WS_ENDPOINT; + process.stdout.write(`${colors.dim('Show & reuse browser:')} ${colors.bold('off')}\n`); + } +} + +type Command = 'all' | 'failed' | 'repeat' | 'changed' | 'file' | 'grep' | 'exit' | 'interrupted' | 'toggle-show-browser'; const commands = [ ['a', 'rerun all tests'], @@ -302,5 +329,6 @@ const commands = [ ['r', 'repeat last run'], ['p', 'filter by a filename'], ['t', 'filter by a test name regex pattern'], + ['s', 'toggle show & reuse the browser'], ['q', 'quit'], ]; diff --git a/tests/page/page-click-scroll.spec.ts b/tests/page/page-click-scroll.spec.ts index f37ac4739d..98c59fb3c9 100644 --- a/tests/page/page-click-scroll.spec.ts +++ b/tests/page/page-click-scroll.spec.ts @@ -95,4 +95,4 @@ it('should scroll into view span element', async ({ page }) => { `); await page.locator('#small').scrollIntoViewIfNeeded(); expect(await page.evaluate(() => window.scrollY)).toBeGreaterThan(9000); -}); \ No newline at end of file +});