chore(watch): allow toggling browser (#20738)

This commit is contained in:
Pavel Feldman 2023-02-08 08:36:02 -08:00 committed by GitHub
parent d962f3b70a
commit 0678b6575f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 67 additions and 18 deletions

View file

@ -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');
}
}

View file

@ -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<DebugController, channels.DebugControllerChannel, RootDispatcher> 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<DebugController, chann
}
override _onDispose() {
eventsHelper.removeEventListeners(this._listeners);
this._object.dispose();
}
}

View file

@ -19,6 +19,7 @@ export * from './comparators';
export * from './crypto';
export * from './debug';
export * from './env';
export * from './eventsHelper';
export * from './fileUtils';
export * from './glob';
export * from './headers';

View file

@ -108,6 +108,14 @@ export class ListModeReporter implements Reporter {
let seq = 0;
export class WatchModeReporter extends ListReporter {
private _options: { isShowBrowser?: () => 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');
}
}

View file

@ -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<string>();
@ -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<string>, dirtyFiles?: Set<string> } = { 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<string>, projectsToIgnore?: Set<FullProjectInternal>, 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'],
];

View file

@ -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);
});
});