chore(watch): allow toggling browser (#20738)
This commit is contained in:
parent
d962f3b70a
commit
0678b6575f
|
|
@ -174,6 +174,11 @@ export class PlaywrightServer {
|
||||||
await new Promise(f => server.options.server!.close(f));
|
await new Promise(f => server.options.server!.close(f));
|
||||||
this._wsServer = undefined;
|
this._wsServer = undefined;
|
||||||
debugLog('closed server');
|
debugLog('closed server');
|
||||||
|
|
||||||
|
debugLog('closing browsers');
|
||||||
|
if (this._preLaunchedPlaywright)
|
||||||
|
await Promise.all(this._preLaunchedPlaywright.allBrowsers().map(browser => browser.close()));
|
||||||
|
debugLog('closed browsers');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,28 +15,33 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type * as channels from '@protocol/channels';
|
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 { DebugController } from '../debugController';
|
||||||
import type { DispatcherConnection, RootDispatcher } from './dispatcher';
|
import type { DispatcherConnection, RootDispatcher } from './dispatcher';
|
||||||
import { Dispatcher } from './dispatcher';
|
import { Dispatcher } from './dispatcher';
|
||||||
|
|
||||||
export class DebugControllerDispatcher extends Dispatcher<DebugController, channels.DebugControllerChannel, RootDispatcher> implements channels.DebugControllerChannel {
|
export class DebugControllerDispatcher extends Dispatcher<DebugController, channels.DebugControllerChannel, RootDispatcher> implements channels.DebugControllerChannel {
|
||||||
_type_DebugController;
|
_type_DebugController;
|
||||||
|
private _listeners: RegisteredListener[];
|
||||||
|
|
||||||
constructor(connection: DispatcherConnection, debugController: DebugController) {
|
constructor(connection: DispatcherConnection, debugController: DebugController) {
|
||||||
super(connection, debugController, 'DebugController', {});
|
super(connection, debugController, 'DebugController', {});
|
||||||
this._type_DebugController = true;
|
this._type_DebugController = true;
|
||||||
this._object.on(DebugController.Events.StateChanged, params => {
|
this._listeners = [
|
||||||
this._dispatchEvent('stateChanged', params);
|
eventsHelper.addEventListener(this._object, DebugController.Events.StateChanged, params => {
|
||||||
});
|
this._dispatchEvent('stateChanged', params);
|
||||||
this._object.on(DebugController.Events.InspectRequested, ({ selector, locator }) => {
|
}),
|
||||||
this._dispatchEvent('inspectRequested', { selector, locator });
|
eventsHelper.addEventListener(this._object, 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 }));
|
eventsHelper.addEventListener(this._object, 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 }));
|
eventsHelper.addEventListener(this._object, DebugController.Events.Paused, ({ paused }) => {
|
||||||
});
|
this._dispatchEvent('paused', ({ paused }));
|
||||||
|
})
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
async initialize(params: channels.DebugControllerInitializeParams) {
|
async initialize(params: channels.DebugControllerInitializeParams) {
|
||||||
|
|
@ -80,6 +85,7 @@ export class DebugControllerDispatcher extends Dispatcher<DebugController, chann
|
||||||
}
|
}
|
||||||
|
|
||||||
override _onDispose() {
|
override _onDispose() {
|
||||||
|
eventsHelper.removeEventListeners(this._listeners);
|
||||||
this._object.dispose();
|
this._object.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ export * from './comparators';
|
||||||
export * from './crypto';
|
export * from './crypto';
|
||||||
export * from './debug';
|
export * from './debug';
|
||||||
export * from './env';
|
export * from './env';
|
||||||
|
export * from './eventsHelper';
|
||||||
export * from './fileUtils';
|
export * from './fileUtils';
|
||||||
export * from './glob';
|
export * from './glob';
|
||||||
export * from './headers';
|
export * from './headers';
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,14 @@ export class ListModeReporter implements Reporter {
|
||||||
let seq = 0;
|
let seq = 0;
|
||||||
|
|
||||||
export class WatchModeReporter extends ListReporter {
|
export class WatchModeReporter extends ListReporter {
|
||||||
|
private _options: { isShowBrowser?: () => boolean; } | undefined;
|
||||||
|
constructor(options?: {
|
||||||
|
isShowBrowser?: () => boolean,
|
||||||
|
}) {
|
||||||
|
super();
|
||||||
|
this._options = options;
|
||||||
|
}
|
||||||
|
|
||||||
override generateStartingMessage(): string {
|
override generateStartingMessage(): string {
|
||||||
const tokens: string[] = [];
|
const tokens: string[] = [];
|
||||||
tokens.push('npx playwright test');
|
tokens.push('npx playwright test');
|
||||||
|
|
@ -121,6 +129,7 @@ export class WatchModeReporter extends ListReporter {
|
||||||
const sep = separator();
|
const sep = separator();
|
||||||
lines.push('\x1Bc' + sep);
|
lines.push('\x1Bc' + sep);
|
||||||
lines.push(`${tokens.join(' ')}` + super.generateStartingMessage());
|
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');
|
return lines.join('\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import readline from 'readline';
|
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 type { FullConfigInternal, FullProjectInternal } from '../common/types';
|
||||||
import { Multiplexer } from '../reporters/multiplexer';
|
import { Multiplexer } from '../reporters/multiplexer';
|
||||||
import { createFileMatcherFromArguments } from '../util';
|
import { createFileMatcherFromArguments } from '../util';
|
||||||
|
|
@ -30,6 +30,7 @@ import { WatchModeReporter } from './reporters';
|
||||||
import { colors } from 'playwright-core/lib/utilsBundle';
|
import { colors } from 'playwright-core/lib/utilsBundle';
|
||||||
import { enquirer } from '../utilsBundle';
|
import { enquirer } from '../utilsBundle';
|
||||||
import { separator } from '../reporters/base';
|
import { separator } from '../reporters/base';
|
||||||
|
import { PlaywrightServer } from 'playwright-core/lib/remote/playwrightServer';
|
||||||
|
|
||||||
class FSWatcher {
|
class FSWatcher {
|
||||||
private _dirtyFiles = new Set<string>();
|
private _dirtyFiles = new Set<string>();
|
||||||
|
|
@ -70,6 +71,8 @@ export async function runWatchModeLoop(config: FullConfigInternal, failedTests:
|
||||||
|
|
||||||
const originalCliArgs = config._internal.cliArgs;
|
const originalCliArgs = config._internal.cliArgs;
|
||||||
const originalCliGrep = config._internal.cliGrep;
|
const originalCliGrep = config._internal.cliGrep;
|
||||||
|
const originalWorkers = config.workers;
|
||||||
|
|
||||||
let lastRun: { type: 'changed' | 'regular' | 'failed', failedTestIds?: Set<string>, dirtyFiles?: Set<string> } = { type: 'regular' };
|
let lastRun: { type: 'changed' | 'regular' | 'failed', failedTestIds?: Set<string>, dirtyFiles?: Set<string> } = { type: 'regular' };
|
||||||
|
|
||||||
const fsWatcher = new FSWatcher(projectClosure.map(p => p.testDir));
|
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',
|
type: 'text',
|
||||||
name: 'filePattern',
|
name: 'filePattern',
|
||||||
message: 'Input filename pattern (regex)',
|
message: 'Input filename pattern (regex)',
|
||||||
initial: config._internal.cliArgs.join(' '),
|
|
||||||
});
|
});
|
||||||
if (filePattern.trim())
|
if (filePattern.trim())
|
||||||
config._internal.cliArgs = [filePattern];
|
config._internal.cliArgs = [filePattern];
|
||||||
|
|
@ -126,7 +128,6 @@ Waiting for file changes. Press ${colors.bold('h')} for help or ${colors.bold('q
|
||||||
type: 'text',
|
type: 'text',
|
||||||
name: 'testPattern',
|
name: 'testPattern',
|
||||||
message: 'Input test name pattern (regex)',
|
message: 'Input test name pattern (regex)',
|
||||||
initial: config._internal.cliGrep,
|
|
||||||
});
|
});
|
||||||
if (testPattern.trim())
|
if (testPattern.trim())
|
||||||
config._internal.cliGrep = testPattern;
|
config._internal.cliGrep = testPattern;
|
||||||
|
|
@ -160,6 +161,11 @@ Waiting for file changes. Press ${colors.bold('h')} for help or ${colors.bold('q
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (command === 'toggle-show-browser') {
|
||||||
|
await toggleShowBrowser(config, originalWorkers);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (command === 'exit')
|
if (command === 'exit')
|
||||||
return 'passed';
|
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) {
|
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 taskRunner = createTaskRunnerForWatch(config, reporter, projectsToIgnore, additionalFileMatcher);
|
||||||
const context: TaskRunnerState = {
|
const context: TaskRunnerState = {
|
||||||
config,
|
config,
|
||||||
|
|
@ -281,6 +287,7 @@ ${commands.map(i => ' ' + colors.bold(i[0]) + `: ${i[1]}`).join('\n')}
|
||||||
case 't': result.resolve('grep'); break;
|
case 't': result.resolve('grep'); break;
|
||||||
case 'f': result.resolve('failed'); break;
|
case 'f': result.resolve('failed'); break;
|
||||||
case 'r': result.resolve('repeat'); 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;
|
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 = [
|
const commands = [
|
||||||
['a', 'rerun all tests'],
|
['a', 'rerun all tests'],
|
||||||
|
|
@ -302,5 +329,6 @@ const commands = [
|
||||||
['r', 'repeat last run'],
|
['r', 'repeat last run'],
|
||||||
['p', 'filter by a filename'],
|
['p', 'filter by a filename'],
|
||||||
['t', 'filter by a test name regex pattern'],
|
['t', 'filter by a test name regex pattern'],
|
||||||
|
['s', 'toggle show & reuse the browser'],
|
||||||
['q', 'quit'],
|
['q', 'quit'],
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -95,4 +95,4 @@ it('should scroll into view span element', async ({ page }) => {
|
||||||
`);
|
`);
|
||||||
await page.locator('#small').scrollIntoViewIfNeeded();
|
await page.locator('#small').scrollIntoViewIfNeeded();
|
||||||
expect(await page.evaluate(() => window.scrollY)).toBeGreaterThan(9000);
|
expect(await page.evaluate(() => window.scrollY)).toBeGreaterThan(9000);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue