chore: set filters and run tests separately (#20759)
This commit is contained in:
parent
6e5964cccd
commit
027d6b5239
|
|
@ -30,4 +30,4 @@ import stoppableLibrary from 'stoppable';
|
||||||
export const stoppable = stoppableLibrary;
|
export const stoppable = stoppableLibrary;
|
||||||
|
|
||||||
import enquirerLibrary from 'enquirer';
|
import enquirerLibrary from 'enquirer';
|
||||||
export const enquirer = enquirerLibrary;
|
export const enquirer = enquirerLibrary;
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import type { Reporter, TestError } from '../../types/testReporter';
|
import type { Reporter, TestError } from '../../types/testReporter';
|
||||||
import { separator, formatError } from '../reporters/base';
|
import { formatError } from '../reporters/base';
|
||||||
import DotReporter from '../reporters/dot';
|
import DotReporter from '../reporters/dot';
|
||||||
import EmptyReporter from '../reporters/empty';
|
import EmptyReporter from '../reporters/empty';
|
||||||
import GitHubReporter from '../reporters/github';
|
import GitHubReporter from '../reporters/github';
|
||||||
|
|
@ -30,7 +30,6 @@ import type { Suite } from '../common/test';
|
||||||
import type { FullConfigInternal } from '../common/types';
|
import type { FullConfigInternal } from '../common/types';
|
||||||
import { loadReporter } from './loadUtils';
|
import { loadReporter } from './loadUtils';
|
||||||
import type { BuiltInReporter } from '../common/configLoader';
|
import type { BuiltInReporter } from '../common/configLoader';
|
||||||
import { colors } from 'playwright-core/lib/utilsBundle';
|
|
||||||
|
|
||||||
export async function createReporter(config: FullConfigInternal, mode: 'list' | 'watch' | 'run') {
|
export async function createReporter(config: FullConfigInternal, mode: 'list' | 'watch' | 'run') {
|
||||||
const defaultReporters: {[key in BuiltInReporter]: new(arg: any) => Reporter} = {
|
const defaultReporters: {[key in BuiltInReporter]: new(arg: any) => Reporter} = {
|
||||||
|
|
@ -45,7 +44,7 @@ export async function createReporter(config: FullConfigInternal, mode: 'list' |
|
||||||
};
|
};
|
||||||
const reporters: Reporter[] = [];
|
const reporters: Reporter[] = [];
|
||||||
if (mode === 'watch') {
|
if (mode === 'watch') {
|
||||||
reporters.push(new WatchModeReporter());
|
reporters.push(new ListReporter());
|
||||||
} else {
|
} else {
|
||||||
for (const r of config.reporter) {
|
for (const r of config.reporter) {
|
||||||
const [name, arg] = r;
|
const [name, arg] = r;
|
||||||
|
|
@ -104,32 +103,3 @@ export class ListModeReporter implements Reporter {
|
||||||
console.error('\n' + formatError(this.config, error, false).message);
|
console.error('\n' + formatError(this.config, error, false).message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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');
|
|
||||||
tokens.push(...(this.config._internal.cliProjectFilter || [])?.map(p => colors.blue(`--project ${p}`)));
|
|
||||||
if (this.config._internal.cliGrep)
|
|
||||||
tokens.push(colors.red(`--grep ${this.config._internal.cliGrep}`));
|
|
||||||
if (this.config._internal.cliArgs)
|
|
||||||
tokens.push(...this.config._internal.cliArgs.map(a => colors.bold(a)));
|
|
||||||
tokens.push(colors.dim(`#${++seq}`));
|
|
||||||
const lines: string[] = [];
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,12 @@ import { buildProjectsClosure, filterProjects } from './projectUtils';
|
||||||
import { clearCompilationCache, collectAffectedTestFiles } from '../common/compilationCache';
|
import { clearCompilationCache, collectAffectedTestFiles } from '../common/compilationCache';
|
||||||
import type { FullResult } from 'packages/playwright-test/reporter';
|
import type { FullResult } from 'packages/playwright-test/reporter';
|
||||||
import chokidar from 'chokidar';
|
import chokidar from 'chokidar';
|
||||||
import { createReporter, WatchModeReporter } from './reporters';
|
import { createReporter } 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';
|
import { PlaywrightServer } from 'playwright-core/lib/remote/playwrightServer';
|
||||||
|
import ListReporter from '../reporters/list';
|
||||||
|
|
||||||
class FSWatcher {
|
class FSWatcher {
|
||||||
private _dirtyFiles = new Set<string>();
|
private _dirtyFiles = new Set<string>();
|
||||||
|
|
@ -64,6 +65,11 @@ class FSWatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runWatchModeLoop(config: FullConfigInternal): Promise<FullResult['status']> {
|
export async function runWatchModeLoop(config: FullConfigInternal): Promise<FullResult['status']> {
|
||||||
|
// Reset the settings that don't apply to watch.
|
||||||
|
config._internal.passWithNoTests = true;
|
||||||
|
for (const p of config.projects)
|
||||||
|
p.retries = 0;
|
||||||
|
|
||||||
// Perform global setup.
|
// Perform global setup.
|
||||||
const reporter = await createReporter(config, 'watch');
|
const reporter = await createReporter(config, 'watch');
|
||||||
const context: TaskRunnerState = {
|
const context: TaskRunnerState = {
|
||||||
|
|
@ -77,26 +83,20 @@ export async function runWatchModeLoop(config: FullConfigInternal): Promise<Full
|
||||||
if (status !== 'passed')
|
if (status !== 'passed')
|
||||||
return await globalCleanup();
|
return await globalCleanup();
|
||||||
|
|
||||||
|
// Prepare projects that will be watched, set up watcher.
|
||||||
const projects = filterProjects(config.projects, config._internal.cliProjectFilter);
|
const projects = filterProjects(config.projects, config._internal.cliProjectFilter);
|
||||||
const projectClosure = buildProjectsClosure(projects);
|
const projectClosure = buildProjectsClosure(projects);
|
||||||
config._internal.passWithNoTests = true;
|
|
||||||
const failedTestIdCollector = new Set<string>();
|
const failedTestIdCollector = new Set<string>();
|
||||||
|
|
||||||
const originalCliArgs = config._internal.cliArgs;
|
|
||||||
const originalCliGrep = config._internal.cliGrep;
|
|
||||||
const originalWorkers = config.workers;
|
const originalWorkers = config.workers;
|
||||||
|
const fsWatcher = new FSWatcher(projectClosure.map(p => p.testDir));
|
||||||
|
|
||||||
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' };
|
||||||
|
|
||||||
let result: FullResult['status'] = 'passed';
|
let result: FullResult['status'] = 'passed';
|
||||||
|
|
||||||
const fsWatcher = new FSWatcher(projectClosure.map(p => p.testDir));
|
// Enter the watch loop.
|
||||||
|
printConfiguration(config);
|
||||||
while (true) {
|
while (true) {
|
||||||
const sep = separator();
|
printPrompt();
|
||||||
process.stdout.write(`
|
|
||||||
${sep}
|
|
||||||
Waiting for file changes. Press ${colors.bold('a')} to run all, ${colors.bold('q')} to quit or ${colors.bold('h')} for more options.
|
|
||||||
`);
|
|
||||||
const readCommandPromise = readCommand();
|
const readCommandPromise = readCommand();
|
||||||
await Promise.race([
|
await Promise.race([
|
||||||
fsWatcher.onDirtyFiles(),
|
fsWatcher.onDirtyFiles(),
|
||||||
|
|
@ -109,32 +109,47 @@ Waiting for file changes. Press ${colors.bold('a')} to run all, ${colors.bold('q
|
||||||
|
|
||||||
if (command === 'changed') {
|
if (command === 'changed') {
|
||||||
const dirtyFiles = fsWatcher.takeDirtyFiles();
|
const dirtyFiles = fsWatcher.takeDirtyFiles();
|
||||||
|
printConfiguration(config, 'files changed');
|
||||||
await runChangedTests(config, failedTestIdCollector, projectClosure, dirtyFiles);
|
await runChangedTests(config, failedTestIdCollector, projectClosure, dirtyFiles);
|
||||||
lastRun = { type: 'changed', dirtyFiles };
|
lastRun = { type: 'changed', dirtyFiles };
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command === 'all') {
|
if (command === 'run') {
|
||||||
// All means reset filters.
|
// All means reset filters.
|
||||||
config._internal.cliArgs = originalCliArgs;
|
printConfiguration(config);
|
||||||
config._internal.cliGrep = originalCliGrep;
|
|
||||||
await runTests(config, failedTestIdCollector);
|
await runTests(config, failedTestIdCollector);
|
||||||
lastRun = { type: 'regular' };
|
lastRun = { type: 'regular' };
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (command === 'project') {
|
||||||
|
const { projectNames } = await enquirer.prompt<{ projectNames: string[] }>({
|
||||||
|
type: 'multiselect',
|
||||||
|
name: 'projectNames',
|
||||||
|
message: 'Select projects',
|
||||||
|
choices: config.projects.map(p => ({ name: p.name })),
|
||||||
|
}).catch(() => ({ projectNames: null }));
|
||||||
|
if (!projectNames)
|
||||||
|
continue;
|
||||||
|
config._internal.cliProjectFilter = projectNames;
|
||||||
|
printConfiguration(config);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (command === 'file') {
|
if (command === 'file') {
|
||||||
const { filePattern } = await enquirer.prompt<{ filePattern: string }>({
|
const { filePattern } = await enquirer.prompt<{ filePattern: string }>({
|
||||||
type: 'text',
|
type: 'text',
|
||||||
name: 'filePattern',
|
name: 'filePattern',
|
||||||
message: 'Input filename pattern (regex)',
|
message: 'Input filename pattern (regex)',
|
||||||
});
|
}).catch(() => ({ filePattern: null }));
|
||||||
|
if (filePattern === null)
|
||||||
|
continue;
|
||||||
if (filePattern.trim())
|
if (filePattern.trim())
|
||||||
config._internal.cliArgs = [filePattern];
|
config._internal.cliArgs = filePattern.split(' ');
|
||||||
else
|
else
|
||||||
config._internal.cliArgs = [];
|
config._internal.cliArgs = [];
|
||||||
await runTests(config, failedTestIdCollector);
|
printConfiguration(config);
|
||||||
lastRun = { type: 'regular' };
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,19 +158,21 @@ Waiting for file changes. Press ${colors.bold('a')} to run all, ${colors.bold('q
|
||||||
type: 'text',
|
type: 'text',
|
||||||
name: 'testPattern',
|
name: 'testPattern',
|
||||||
message: 'Input test name pattern (regex)',
|
message: 'Input test name pattern (regex)',
|
||||||
});
|
}).catch(() => ({ testPattern: null }));
|
||||||
|
if (testPattern === null)
|
||||||
|
continue;
|
||||||
if (testPattern.trim())
|
if (testPattern.trim())
|
||||||
config._internal.cliGrep = testPattern;
|
config._internal.cliGrep = testPattern;
|
||||||
else
|
else
|
||||||
config._internal.cliGrep = undefined;
|
config._internal.cliGrep = undefined;
|
||||||
await runTests(config, failedTestIdCollector);
|
printConfiguration(config);
|
||||||
lastRun = { type: 'regular' };
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command === 'failed') {
|
if (command === 'failed') {
|
||||||
config._internal.testIdMatcher = id => failedTestIdCollector.has(id);
|
config._internal.testIdMatcher = id => failedTestIdCollector.has(id);
|
||||||
const failedTestIds = new Set(failedTestIdCollector);
|
const failedTestIds = new Set(failedTestIdCollector);
|
||||||
|
printConfiguration(config, 'running failed tests');
|
||||||
await runTests(config, failedTestIdCollector);
|
await runTests(config, failedTestIdCollector);
|
||||||
config._internal.testIdMatcher = undefined;
|
config._internal.testIdMatcher = undefined;
|
||||||
lastRun = { type: 'failed', failedTestIds };
|
lastRun = { type: 'failed', failedTestIds };
|
||||||
|
|
@ -163,6 +180,7 @@ Waiting for file changes. Press ${colors.bold('a')} to run all, ${colors.bold('q
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command === 'repeat') {
|
if (command === 'repeat') {
|
||||||
|
printConfiguration(config, 're-running tests');
|
||||||
if (lastRun.type === 'regular') {
|
if (lastRun.type === 'regular') {
|
||||||
await runTests(config, failedTestIdCollector);
|
await runTests(config, failedTestIdCollector);
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -227,8 +245,11 @@ async function runChangedTests(config: FullConfigInternal, failedTestIdCollector
|
||||||
return await runTests(config, failedTestIdCollector, projectsToIgnore, additionalFileMatcher);
|
return await runTests(config, failedTestIdCollector, projectsToIgnore, additionalFileMatcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let seq = 0;
|
||||||
|
|
||||||
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({ isShowBrowser: () => !!showBrowserServer })]);
|
++seq;
|
||||||
|
const reporter = new Multiplexer([new ListReporter()]);
|
||||||
const taskRunner = createTaskRunnerForWatch(config, reporter, projectsToIgnore, additionalFileMatcher);
|
const taskRunner = createTaskRunnerForWatch(config, reporter, projectsToIgnore, additionalFileMatcher);
|
||||||
const context: TaskRunnerState = {
|
const context: TaskRunnerState = {
|
||||||
config,
|
config,
|
||||||
|
|
@ -293,19 +314,28 @@ function readCommand(): ManualPromise<Command> {
|
||||||
}
|
}
|
||||||
if (name === 'h') {
|
if (name === 'h') {
|
||||||
process.stdout.write(`${separator()}
|
process.stdout.write(`${separator()}
|
||||||
Watch Usage
|
Run tests
|
||||||
${commands.map(i => ' ' + colors.bold(i[0]) + `: ${i[1]}`).join('\n')}
|
${colors.bold('enter')} ${colors.dim('run tests')}
|
||||||
|
${colors.bold('f')} ${colors.dim('run failed tests')}
|
||||||
|
${colors.bold('r')} ${colors.dim('repeat last run')}
|
||||||
|
${colors.bold('q')} ${colors.dim('quit')}
|
||||||
|
|
||||||
|
Change settings
|
||||||
|
${colors.bold('c')} ${colors.dim('set project')}
|
||||||
|
${colors.bold('p')} ${colors.dim('set file filter')}
|
||||||
|
${colors.bold('t')} ${colors.dim('set title filter')}
|
||||||
|
${colors.bold('s')} ${colors.dim('toggle show & reuse the browser')}
|
||||||
`);
|
`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case 'a': result.resolve('all'); break;
|
case 'return': result.resolve('run'); break;
|
||||||
|
case 'r': result.resolve('repeat'); break;
|
||||||
|
case 'c': result.resolve('project'); break;
|
||||||
case 'p': result.resolve('file'); break;
|
case 'p': result.resolve('file'); break;
|
||||||
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 's': result.resolve('toggle-show-browser'); break;
|
case 's': result.resolve('toggle-show-browser'); break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -322,6 +352,34 @@ ${commands.map(i => ' ' + colors.bold(i[0]) + `: ${i[1]}`).join('\n')}
|
||||||
|
|
||||||
let showBrowserServer: PlaywrightServer | undefined;
|
let showBrowserServer: PlaywrightServer | undefined;
|
||||||
|
|
||||||
|
function printConfiguration(config: FullConfigInternal, title?: string) {
|
||||||
|
const tokens: string[] = [];
|
||||||
|
tokens.push('npx playwright test');
|
||||||
|
tokens.push(...(config._internal.cliProjectFilter || [])?.map(p => colors.blue(`--project ${p}`)));
|
||||||
|
if (config._internal.cliGrep)
|
||||||
|
tokens.push(colors.red(`--grep ${config._internal.cliGrep}`));
|
||||||
|
if (config._internal.cliArgs)
|
||||||
|
tokens.push(...config._internal.cliArgs.map(a => colors.bold(a)));
|
||||||
|
if (title)
|
||||||
|
tokens.push(colors.dim(`(${title})`));
|
||||||
|
if (seq)
|
||||||
|
tokens.push(colors.dim(`#${seq}`));
|
||||||
|
const lines: string[] = [];
|
||||||
|
const sep = separator();
|
||||||
|
lines.push('\x1Bc' + sep);
|
||||||
|
lines.push(`${tokens.join(' ')}`);
|
||||||
|
lines.push(`${colors.dim('Show & reuse browser:')} ${colors.bold(showBrowserServer ? 'on' : 'off')}`);
|
||||||
|
process.stdout.write(lines.join('\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function printPrompt() {
|
||||||
|
const sep = separator();
|
||||||
|
process.stdout.write(`
|
||||||
|
${sep}
|
||||||
|
${colors.dim('Waiting for file changes. Press')} ${colors.bold('enter')} ${colors.dim('to run tests')}, ${colors.bold('q')} ${colors.dim('to quit or')} ${colors.bold('h')} ${colors.dim('for more options.')}
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
async function toggleShowBrowser(config: FullConfigInternal, originalWorkers: number) {
|
async function toggleShowBrowser(config: FullConfigInternal, originalWorkers: number) {
|
||||||
if (!showBrowserServer) {
|
if (!showBrowserServer) {
|
||||||
config.workers = 1;
|
config.workers = 1;
|
||||||
|
|
@ -340,14 +398,4 @@ async function toggleShowBrowser(config: FullConfigInternal, originalWorkers: nu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Command = 'all' | 'failed' | 'repeat' | 'changed' | 'file' | 'grep' | 'exit' | 'interrupted' | 'toggle-show-browser';
|
type Command = 'run' | 'failed' | 'repeat' | 'changed' | 'project' | 'file' | 'grep' | 'exit' | 'interrupted' | 'toggle-show-browser';
|
||||||
|
|
||||||
const commands = [
|
|
||||||
['a', 'rerun all tests'],
|
|
||||||
['f', 'rerun only failed tests'],
|
|
||||||
['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'],
|
|
||||||
];
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue