fix(ui): print the web server output in the ui mode (#31824)
Fixes https://github.com/microsoft/playwright/issues/31300
This commit is contained in:
parent
1918ae5c4a
commit
7735affef4
|
|
@ -25,8 +25,6 @@ import { createReporters } from './reporters';
|
||||||
import { TestRun, createTaskRunner, createTaskRunnerForList } from './tasks';
|
import { TestRun, createTaskRunner, createTaskRunnerForList } from './tasks';
|
||||||
import type { FullConfigInternal } from '../common/config';
|
import type { FullConfigInternal } from '../common/config';
|
||||||
import { runWatchModeLoop } from './watchMode';
|
import { runWatchModeLoop } from './watchMode';
|
||||||
import { InternalReporter } from '../reporters/internalReporter';
|
|
||||||
import { Multiplexer } from '../reporters/multiplexer';
|
|
||||||
import type { Suite } from '../common/test';
|
import type { Suite } from '../common/test';
|
||||||
import { wrapReporterAsV2 } from '../reporters/reporterV2';
|
import { wrapReporterAsV2 } from '../reporters/reporterV2';
|
||||||
import { affectedTestFiles } from '../transform/compilationCache';
|
import { affectedTestFiles } from '../transform/compilationCache';
|
||||||
|
|
@ -79,25 +77,28 @@ export class Runner {
|
||||||
// Legacy webServer support.
|
// Legacy webServer support.
|
||||||
webServerPluginsForConfig(config).forEach(p => config.plugins.push({ factory: p }));
|
webServerPluginsForConfig(config).forEach(p => config.plugins.push({ factory: p }));
|
||||||
|
|
||||||
const reporter = new InternalReporter(new Multiplexer(await createReporters(config, listOnly ? 'list' : 'test', false)));
|
const reporters = await createReporters(config, listOnly ? 'list' : 'test', false);
|
||||||
const taskRunner = listOnly ? createTaskRunnerForList(config, reporter, 'in-process', { failOnLoadErrors: true })
|
const taskRunner = listOnly ? createTaskRunnerForList(
|
||||||
: createTaskRunner(config, reporter);
|
config,
|
||||||
|
reporters,
|
||||||
|
'in-process',
|
||||||
|
{ failOnLoadErrors: true }) : createTaskRunner(config, reporters);
|
||||||
|
|
||||||
const testRun = new TestRun(config, reporter);
|
const testRun = new TestRun(config);
|
||||||
reporter.onConfigure(config.config);
|
taskRunner.reporter.onConfigure(config.config);
|
||||||
|
|
||||||
const taskStatus = await taskRunner.run(testRun, deadline);
|
const taskStatus = await taskRunner.run(testRun, deadline);
|
||||||
let status: FullResult['status'] = testRun.failureTracker.result();
|
let status: FullResult['status'] = testRun.failureTracker.result();
|
||||||
if (status === 'passed' && taskStatus !== 'passed')
|
if (status === 'passed' && taskStatus !== 'passed')
|
||||||
status = taskStatus;
|
status = taskStatus;
|
||||||
const modifiedResult = await reporter.onEnd({ status });
|
const modifiedResult = await taskRunner.reporter.onEnd({ status });
|
||||||
if (modifiedResult && modifiedResult.status)
|
if (modifiedResult && modifiedResult.status)
|
||||||
status = modifiedResult.status;
|
status = modifiedResult.status;
|
||||||
|
|
||||||
if (!listOnly)
|
if (!listOnly)
|
||||||
await writeLastRunInfo(testRun, status);
|
await writeLastRunInfo(testRun, status);
|
||||||
|
|
||||||
await reporter.onExit();
|
await taskRunner.reporter.onExit();
|
||||||
|
|
||||||
// Calling process.exit() might truncate large stdout/stderr output.
|
// Calling process.exit() might truncate large stdout/stderr output.
|
||||||
// See https://github.com/nodejs/node/issues/6456.
|
// See https://github.com/nodejs/node/issues/6456.
|
||||||
|
|
@ -110,23 +111,23 @@ export class Runner {
|
||||||
async loadAllTests(mode: 'in-process' | 'out-of-process' = 'in-process'): Promise<{ status: FullResult['status'], suite?: Suite, errors: TestError[] }> {
|
async loadAllTests(mode: 'in-process' | 'out-of-process' = 'in-process'): Promise<{ status: FullResult['status'], suite?: Suite, errors: TestError[] }> {
|
||||||
const config = this._config;
|
const config = this._config;
|
||||||
const errors: TestError[] = [];
|
const errors: TestError[] = [];
|
||||||
const reporter = new InternalReporter(new Multiplexer([wrapReporterAsV2({
|
const reporters = [wrapReporterAsV2({
|
||||||
onError(error: TestError) {
|
onError(error: TestError) {
|
||||||
errors.push(error);
|
errors.push(error);
|
||||||
}
|
}
|
||||||
})]));
|
})];
|
||||||
const taskRunner = createTaskRunnerForList(config, reporter, mode, { failOnLoadErrors: true });
|
const taskRunner = createTaskRunnerForList(config, reporters, mode, { failOnLoadErrors: true });
|
||||||
const testRun = new TestRun(config, reporter);
|
const testRun = new TestRun(config);
|
||||||
reporter.onConfigure(config.config);
|
taskRunner.reporter.onConfigure(config.config);
|
||||||
|
|
||||||
const taskStatus = await taskRunner.run(testRun, 0);
|
const taskStatus = await taskRunner.run(testRun, 0);
|
||||||
let status: FullResult['status'] = testRun.failureTracker.result();
|
let status: FullResult['status'] = testRun.failureTracker.result();
|
||||||
if (status === 'passed' && taskStatus !== 'passed')
|
if (status === 'passed' && taskStatus !== 'passed')
|
||||||
status = taskStatus;
|
status = taskStatus;
|
||||||
const modifiedResult = await reporter.onEnd({ status });
|
const modifiedResult = await taskRunner.reporter.onEnd({ status });
|
||||||
if (modifiedResult && modifiedResult.status)
|
if (modifiedResult && modifiedResult.status)
|
||||||
status = modifiedResult.status;
|
status = modifiedResult.status;
|
||||||
await reporter.onExit();
|
await taskRunner.reporter.onExit();
|
||||||
return { status, suite: testRun.rootSuite, errors };
|
return { status, suite: testRun.rootSuite, errors };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,20 +20,26 @@ import type { FullResult, TestError } from '../../types/testReporter';
|
||||||
import { SigIntWatcher } from './sigIntWatcher';
|
import { SigIntWatcher } from './sigIntWatcher';
|
||||||
import { serializeError } from '../util';
|
import { serializeError } from '../util';
|
||||||
import type { ReporterV2 } from '../reporters/reporterV2';
|
import type { ReporterV2 } from '../reporters/reporterV2';
|
||||||
|
import { InternalReporter } from '../reporters/internalReporter';
|
||||||
|
import { Multiplexer } from '../reporters/multiplexer';
|
||||||
|
|
||||||
type TaskPhase<Context> = (context: Context, errors: TestError[], softErrors: TestError[]) => Promise<void> | void;
|
type TaskPhase<Context> = (reporter: ReporterV2, context: Context, errors: TestError[], softErrors: TestError[]) => Promise<void> | void;
|
||||||
export type Task<Context> = { setup?: TaskPhase<Context>, teardown?: TaskPhase<Context> };
|
export type Task<Context> = { setup?: TaskPhase<Context>, teardown?: TaskPhase<Context> };
|
||||||
|
|
||||||
export class TaskRunner<Context> {
|
export class TaskRunner<Context> {
|
||||||
private _tasks: { name: string, task: Task<Context> }[] = [];
|
private _tasks: { name: string, task: Task<Context> }[] = [];
|
||||||
private _reporter: ReporterV2;
|
readonly reporter: InternalReporter;
|
||||||
private _hasErrors = false;
|
private _hasErrors = false;
|
||||||
private _interrupted = false;
|
private _interrupted = false;
|
||||||
private _isTearDown = false;
|
private _isTearDown = false;
|
||||||
private _globalTimeoutForError: number;
|
private _globalTimeoutForError: number;
|
||||||
|
|
||||||
constructor(reporter: ReporterV2, globalTimeoutForError: number) {
|
static create<Context>(reporters: ReporterV2[], globalTimeoutForError: number = 0) {
|
||||||
this._reporter = reporter;
|
return new TaskRunner<Context>(createInternalReporter(reporters), globalTimeoutForError);
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(reporter: InternalReporter, globalTimeoutForError: number) {
|
||||||
|
this.reporter = reporter;
|
||||||
this._globalTimeoutForError = globalTimeoutForError;
|
this._globalTimeoutForError = globalTimeoutForError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,7 +56,7 @@ export class TaskRunner<Context> {
|
||||||
async runDeferCleanup(context: Context, deadline: number, cancelPromise = new ManualPromise<void>()): Promise<{ status: FullResult['status'], cleanup: () => Promise<FullResult['status']> }> {
|
async runDeferCleanup(context: Context, deadline: number, cancelPromise = new ManualPromise<void>()): Promise<{ status: FullResult['status'], cleanup: () => Promise<FullResult['status']> }> {
|
||||||
const sigintWatcher = new SigIntWatcher();
|
const sigintWatcher = new SigIntWatcher();
|
||||||
const timeoutWatcher = new TimeoutWatcher(deadline);
|
const timeoutWatcher = new TimeoutWatcher(deadline);
|
||||||
const teardownRunner = new TaskRunner<Context>(this._reporter, this._globalTimeoutForError);
|
const teardownRunner = new TaskRunner<Context>(this.reporter, this._globalTimeoutForError);
|
||||||
teardownRunner._isTearDown = true;
|
teardownRunner._isTearDown = true;
|
||||||
|
|
||||||
let currentTaskName: string | undefined;
|
let currentTaskName: string | undefined;
|
||||||
|
|
@ -65,13 +71,13 @@ export class TaskRunner<Context> {
|
||||||
const softErrors: TestError[] = [];
|
const softErrors: TestError[] = [];
|
||||||
try {
|
try {
|
||||||
teardownRunner._tasks.unshift({ name: `teardown for ${name}`, task: { setup: task.teardown } });
|
teardownRunner._tasks.unshift({ name: `teardown for ${name}`, task: { setup: task.teardown } });
|
||||||
await task.setup?.(context, errors, softErrors);
|
await task.setup?.(this.reporter, context, errors, softErrors);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debug('pw:test:task')(`error in "${name}": `, e);
|
debug('pw:test:task')(`error in "${name}": `, e);
|
||||||
errors.push(serializeError(e));
|
errors.push(serializeError(e));
|
||||||
} finally {
|
} finally {
|
||||||
for (const error of [...softErrors, ...errors])
|
for (const error of [...softErrors, ...errors])
|
||||||
this._reporter.onError?.(error);
|
this.reporter.onError?.(error);
|
||||||
if (errors.length) {
|
if (errors.length) {
|
||||||
if (!this._isTearDown)
|
if (!this._isTearDown)
|
||||||
this._interrupted = true;
|
this._interrupted = true;
|
||||||
|
|
@ -99,7 +105,7 @@ export class TaskRunner<Context> {
|
||||||
if (sigintWatcher.hadSignal() || cancelPromise?.isDone()) {
|
if (sigintWatcher.hadSignal() || cancelPromise?.isDone()) {
|
||||||
status = 'interrupted';
|
status = 'interrupted';
|
||||||
} else if (timeoutWatcher.timedOut()) {
|
} else if (timeoutWatcher.timedOut()) {
|
||||||
this._reporter.onError?.({ message: colors.red(`Timed out waiting ${this._globalTimeoutForError / 1000}s for the ${currentTaskName} to run`) });
|
this.reporter.onError?.({ message: colors.red(`Timed out waiting ${this._globalTimeoutForError / 1000}s for the ${currentTaskName} to run`) });
|
||||||
status = 'timedout';
|
status = 'timedout';
|
||||||
} else if (this._hasErrors) {
|
} else if (this._hasErrors) {
|
||||||
status = 'failed';
|
status = 'failed';
|
||||||
|
|
@ -140,3 +146,7 @@ class TimeoutWatcher {
|
||||||
clearTimeout(this._timer);
|
clearTimeout(this._timer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createInternalReporter(reporters: ReporterV2[]): InternalReporter {
|
||||||
|
return new InternalReporter(new Multiplexer(reporters));
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,6 @@ export type Phase = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export class TestRun {
|
export class TestRun {
|
||||||
readonly reporter: ReporterV2;
|
|
||||||
readonly config: FullConfigInternal;
|
readonly config: FullConfigInternal;
|
||||||
readonly failureTracker: FailureTracker;
|
readonly failureTracker: FailureTracker;
|
||||||
rootSuite: Suite | undefined = undefined;
|
rootSuite: Suite | undefined = undefined;
|
||||||
|
|
@ -55,36 +54,35 @@ export class TestRun {
|
||||||
projectFiles: Map<FullProjectInternal, string[]> = new Map();
|
projectFiles: Map<FullProjectInternal, string[]> = new Map();
|
||||||
projectSuites: Map<FullProjectInternal, Suite[]> = new Map();
|
projectSuites: Map<FullProjectInternal, Suite[]> = new Map();
|
||||||
|
|
||||||
constructor(config: FullConfigInternal, reporter: ReporterV2) {
|
constructor(config: FullConfigInternal) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.reporter = reporter;
|
|
||||||
this.failureTracker = new FailureTracker(config);
|
this.failureTracker = new FailureTracker(config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createTaskRunner(config: FullConfigInternal, reporter: ReporterV2): TaskRunner<TestRun> {
|
export function createTaskRunner(config: FullConfigInternal, reporters: ReporterV2[]): TaskRunner<TestRun> {
|
||||||
const taskRunner = new TaskRunner<TestRun>(reporter, config.config.globalTimeout);
|
const taskRunner = TaskRunner.create<TestRun>(reporters, config.config.globalTimeout);
|
||||||
addGlobalSetupTasks(taskRunner, config);
|
addGlobalSetupTasks(taskRunner, config);
|
||||||
taskRunner.addTask('load tests', createLoadTask('in-process', { filterOnly: true, filterOnlyChanged: true, failOnLoadErrors: true }));
|
taskRunner.addTask('load tests', createLoadTask('in-process', { filterOnly: true, filterOnlyChanged: true, failOnLoadErrors: true }));
|
||||||
addRunTasks(taskRunner, config);
|
addRunTasks(taskRunner, config);
|
||||||
return taskRunner;
|
return taskRunner;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createTaskRunnerForWatchSetup(config: FullConfigInternal, reporter: ReporterV2): TaskRunner<TestRun> {
|
export function createTaskRunnerForWatchSetup(config: FullConfigInternal, reporters: ReporterV2[]): TaskRunner<TestRun> {
|
||||||
const taskRunner = new TaskRunner<TestRun>(reporter, 0);
|
const taskRunner = TaskRunner.create<TestRun>(reporters);
|
||||||
addGlobalSetupTasks(taskRunner, config);
|
addGlobalSetupTasks(taskRunner, config);
|
||||||
return taskRunner;
|
return taskRunner;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createTaskRunnerForWatch(config: FullConfigInternal, reporter: ReporterV2, additionalFileMatcher?: Matcher): TaskRunner<TestRun> {
|
export function createTaskRunnerForWatch(config: FullConfigInternal, reporters: ReporterV2[], additionalFileMatcher?: Matcher): TaskRunner<TestRun> {
|
||||||
const taskRunner = new TaskRunner<TestRun>(reporter, 0);
|
const taskRunner = TaskRunner.create<TestRun>(reporters);
|
||||||
taskRunner.addTask('load tests', createLoadTask('out-of-process', { filterOnly: true, filterOnlyChanged: true, failOnLoadErrors: false, doNotRunDepsOutsideProjectFilter: true, additionalFileMatcher }));
|
taskRunner.addTask('load tests', createLoadTask('out-of-process', { filterOnly: true, filterOnlyChanged: true, failOnLoadErrors: false, doNotRunDepsOutsideProjectFilter: true, additionalFileMatcher }));
|
||||||
addRunTasks(taskRunner, config);
|
addRunTasks(taskRunner, config);
|
||||||
return taskRunner;
|
return taskRunner;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createTaskRunnerForTestServer(config: FullConfigInternal, reporter: ReporterV2): TaskRunner<TestRun> {
|
export function createTaskRunnerForTestServer(config: FullConfigInternal, reporters: ReporterV2[]): TaskRunner<TestRun> {
|
||||||
const taskRunner = new TaskRunner<TestRun>(reporter, 0);
|
const taskRunner = TaskRunner.create<TestRun>(reporters);
|
||||||
taskRunner.addTask('load tests', createLoadTask('out-of-process', { filterOnly: true, filterOnlyChanged: true, failOnLoadErrors: false, doNotRunDepsOutsideProjectFilter: true }));
|
taskRunner.addTask('load tests', createLoadTask('out-of-process', { filterOnly: true, filterOnlyChanged: true, failOnLoadErrors: false, doNotRunDepsOutsideProjectFilter: true }));
|
||||||
addRunTasks(taskRunner, config);
|
addRunTasks(taskRunner, config);
|
||||||
return taskRunner;
|
return taskRunner;
|
||||||
|
|
@ -108,15 +106,15 @@ function addRunTasks(taskRunner: TaskRunner<TestRun>, config: FullConfigInternal
|
||||||
return taskRunner;
|
return taskRunner;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createTaskRunnerForList(config: FullConfigInternal, reporter: ReporterV2, mode: 'in-process' | 'out-of-process', options: { failOnLoadErrors: boolean }): TaskRunner<TestRun> {
|
export function createTaskRunnerForList(config: FullConfigInternal, reporters: ReporterV2[], mode: 'in-process' | 'out-of-process', options: { failOnLoadErrors: boolean }): TaskRunner<TestRun> {
|
||||||
const taskRunner = new TaskRunner<TestRun>(reporter, config.config.globalTimeout);
|
const taskRunner = TaskRunner.create<TestRun>(reporters, config.config.globalTimeout);
|
||||||
taskRunner.addTask('load tests', createLoadTask(mode, { ...options, filterOnly: false, filterOnlyChanged: false }));
|
taskRunner.addTask('load tests', createLoadTask(mode, { ...options, filterOnly: false, filterOnlyChanged: false }));
|
||||||
taskRunner.addTask('report begin', createReportBeginTask());
|
taskRunner.addTask('report begin', createReportBeginTask());
|
||||||
return taskRunner;
|
return taskRunner;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createTaskRunnerForListFiles(config: FullConfigInternal, reporter: ReporterV2): TaskRunner<TestRun> {
|
export function createTaskRunnerForListFiles(config: FullConfigInternal, reporters: ReporterV2[]): TaskRunner<TestRun> {
|
||||||
const taskRunner = new TaskRunner<TestRun>(reporter, config.config.globalTimeout);
|
const taskRunner = TaskRunner.create<TestRun>(reporters, config.config.globalTimeout);
|
||||||
taskRunner.addTask('load tests', createListFilesTask());
|
taskRunner.addTask('load tests', createListFilesTask());
|
||||||
taskRunner.addTask('report begin', createReportBeginTask());
|
taskRunner.addTask('report begin', createReportBeginTask());
|
||||||
return taskRunner;
|
return taskRunner;
|
||||||
|
|
@ -124,7 +122,7 @@ export function createTaskRunnerForListFiles(config: FullConfigInternal, reporte
|
||||||
|
|
||||||
function createReportBeginTask(): Task<TestRun> {
|
function createReportBeginTask(): Task<TestRun> {
|
||||||
return {
|
return {
|
||||||
setup: async ({ reporter, rootSuite }) => {
|
setup: async (reporter, { rootSuite }) => {
|
||||||
reporter.onBegin(rootSuite!);
|
reporter.onBegin(rootSuite!);
|
||||||
},
|
},
|
||||||
teardown: async ({}) => {},
|
teardown: async ({}) => {},
|
||||||
|
|
@ -133,7 +131,7 @@ function createReportBeginTask(): Task<TestRun> {
|
||||||
|
|
||||||
function createPluginSetupTask(plugin: TestRunnerPluginRegistration): Task<TestRun> {
|
function createPluginSetupTask(plugin: TestRunnerPluginRegistration): Task<TestRun> {
|
||||||
return {
|
return {
|
||||||
setup: async ({ config, reporter }) => {
|
setup: async (reporter, { config }) => {
|
||||||
if (typeof plugin.factory === 'function')
|
if (typeof plugin.factory === 'function')
|
||||||
plugin.instance = await plugin.factory();
|
plugin.instance = await plugin.factory();
|
||||||
else
|
else
|
||||||
|
|
@ -148,7 +146,7 @@ function createPluginSetupTask(plugin: TestRunnerPluginRegistration): Task<TestR
|
||||||
|
|
||||||
function createPluginBeginTask(plugin: TestRunnerPluginRegistration): Task<TestRun> {
|
function createPluginBeginTask(plugin: TestRunnerPluginRegistration): Task<TestRun> {
|
||||||
return {
|
return {
|
||||||
setup: async ({ rootSuite }) => {
|
setup: async (reporter, { rootSuite }) => {
|
||||||
await plugin.instance?.begin?.(rootSuite!);
|
await plugin.instance?.begin?.(rootSuite!);
|
||||||
},
|
},
|
||||||
teardown: async () => {
|
teardown: async () => {
|
||||||
|
|
@ -162,13 +160,13 @@ function createGlobalSetupTask(): Task<TestRun> {
|
||||||
let globalSetupFinished = false;
|
let globalSetupFinished = false;
|
||||||
let teardownHook: any;
|
let teardownHook: any;
|
||||||
return {
|
return {
|
||||||
setup: async ({ config }) => {
|
setup: async (reporter, { config }) => {
|
||||||
const setupHook = config.config.globalSetup ? await loadGlobalHook(config, config.config.globalSetup) : undefined;
|
const setupHook = config.config.globalSetup ? await loadGlobalHook(config, config.config.globalSetup) : undefined;
|
||||||
teardownHook = config.config.globalTeardown ? await loadGlobalHook(config, config.config.globalTeardown) : undefined;
|
teardownHook = config.config.globalTeardown ? await loadGlobalHook(config, config.config.globalTeardown) : undefined;
|
||||||
globalSetupResult = setupHook ? await setupHook(config.config) : undefined;
|
globalSetupResult = setupHook ? await setupHook(config.config) : undefined;
|
||||||
globalSetupFinished = true;
|
globalSetupFinished = true;
|
||||||
},
|
},
|
||||||
teardown: async ({ config }) => {
|
teardown: async (reporter, { config }) => {
|
||||||
if (typeof globalSetupResult === 'function')
|
if (typeof globalSetupResult === 'function')
|
||||||
await globalSetupResult();
|
await globalSetupResult();
|
||||||
if (globalSetupFinished)
|
if (globalSetupFinished)
|
||||||
|
|
@ -179,7 +177,7 @@ function createGlobalSetupTask(): Task<TestRun> {
|
||||||
|
|
||||||
function createRemoveOutputDirsTask(): Task<TestRun> {
|
function createRemoveOutputDirsTask(): Task<TestRun> {
|
||||||
return {
|
return {
|
||||||
setup: async ({ config }) => {
|
setup: async (reporter, { config }) => {
|
||||||
const outputDirs = new Set<string>();
|
const outputDirs = new Set<string>();
|
||||||
const projects = filterProjects(config.projects, config.cliProjectFilter);
|
const projects = filterProjects(config.projects, config.cliProjectFilter);
|
||||||
projects.forEach(p => outputDirs.add(p.project.outputDir));
|
projects.forEach(p => outputDirs.add(p.project.outputDir));
|
||||||
|
|
@ -203,7 +201,7 @@ function createRemoveOutputDirsTask(): Task<TestRun> {
|
||||||
|
|
||||||
function createListFilesTask(): Task<TestRun> {
|
function createListFilesTask(): Task<TestRun> {
|
||||||
return {
|
return {
|
||||||
setup: async (testRun, errors) => {
|
setup: async (reporter, testRun, errors) => {
|
||||||
testRun.rootSuite = await createRootSuite(testRun, errors, false);
|
testRun.rootSuite = await createRootSuite(testRun, errors, false);
|
||||||
testRun.failureTracker.onRootSuite(testRun.rootSuite);
|
testRun.failureTracker.onRootSuite(testRun.rootSuite);
|
||||||
await collectProjectsAndTestFiles(testRun, false);
|
await collectProjectsAndTestFiles(testRun, false);
|
||||||
|
|
@ -226,7 +224,7 @@ function createListFilesTask(): Task<TestRun> {
|
||||||
|
|
||||||
function createLoadTask(mode: 'out-of-process' | 'in-process', options: { filterOnly: boolean, filterOnlyChanged: boolean, failOnLoadErrors: boolean, doNotRunDepsOutsideProjectFilter?: boolean, additionalFileMatcher?: Matcher }): Task<TestRun> {
|
function createLoadTask(mode: 'out-of-process' | 'in-process', options: { filterOnly: boolean, filterOnlyChanged: boolean, failOnLoadErrors: boolean, doNotRunDepsOutsideProjectFilter?: boolean, additionalFileMatcher?: Matcher }): Task<TestRun> {
|
||||||
return {
|
return {
|
||||||
setup: async (testRun, errors, softErrors) => {
|
setup: async (reporter, testRun, errors, softErrors) => {
|
||||||
await collectProjectsAndTestFiles(testRun, !!options.doNotRunDepsOutsideProjectFilter, options.additionalFileMatcher);
|
await collectProjectsAndTestFiles(testRun, !!options.doNotRunDepsOutsideProjectFilter, options.additionalFileMatcher);
|
||||||
await loadFileSuites(testRun, mode, options.failOnLoadErrors ? errors : softErrors);
|
await loadFileSuites(testRun, mode, options.failOnLoadErrors ? errors : softErrors);
|
||||||
|
|
||||||
|
|
@ -257,7 +255,7 @@ function createLoadTask(mode: 'out-of-process' | 'in-process', options: { filter
|
||||||
|
|
||||||
function createPhasesTask(): Task<TestRun> {
|
function createPhasesTask(): Task<TestRun> {
|
||||||
return {
|
return {
|
||||||
setup: async testRun => {
|
setup: async (reporter, testRun) => {
|
||||||
let maxConcurrentTestGroups = 0;
|
let maxConcurrentTestGroups = 0;
|
||||||
|
|
||||||
const processed = new Set<FullProjectInternal>();
|
const processed = new Set<FullProjectInternal>();
|
||||||
|
|
@ -288,7 +286,7 @@ function createPhasesTask(): Task<TestRun> {
|
||||||
processed.add(project);
|
processed.add(project);
|
||||||
if (phaseProjects.length) {
|
if (phaseProjects.length) {
|
||||||
let testGroupsInPhase = 0;
|
let testGroupsInPhase = 0;
|
||||||
const phase: Phase = { dispatcher: new Dispatcher(testRun.config, testRun.reporter, testRun.failureTracker), projects: [] };
|
const phase: Phase = { dispatcher: new Dispatcher(testRun.config, reporter, testRun.failureTracker), projects: [] };
|
||||||
testRun.phases.push(phase);
|
testRun.phases.push(phase);
|
||||||
for (const project of phaseProjects) {
|
for (const project of phaseProjects) {
|
||||||
const projectSuite = projectToSuite.get(project)!;
|
const projectSuite = projectToSuite.get(project)!;
|
||||||
|
|
@ -308,7 +306,7 @@ function createPhasesTask(): Task<TestRun> {
|
||||||
|
|
||||||
function createRunTestsTask(): Task<TestRun> {
|
function createRunTestsTask(): Task<TestRun> {
|
||||||
return {
|
return {
|
||||||
setup: async ({ phases, failureTracker }) => {
|
setup: async (reporter, { phases, failureTracker }) => {
|
||||||
const successfulProjects = new Set<FullProjectInternal>();
|
const successfulProjects = new Set<FullProjectInternal>();
|
||||||
const extraEnvByProjectId: EnvByProjectId = new Map();
|
const extraEnvByProjectId: EnvByProjectId = new Map();
|
||||||
const teardownToSetups = buildTeardownToSetupsMap(phases.map(phase => phase.projects.map(p => p.project)).flat());
|
const teardownToSetups = buildTeardownToSetupsMap(phases.map(phase => phase.projects.map(p => p.project)).flat());
|
||||||
|
|
@ -352,7 +350,7 @@ function createRunTestsTask(): Task<TestRun> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
teardown: async ({ phases }) => {
|
teardown: async (reporter, { phases }) => {
|
||||||
for (const { dispatcher } of phases.reverse())
|
for (const { dispatcher } of phases.reverse())
|
||||||
await dispatcher.stop();
|
await dispatcher.stop();
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,10 @@ import type { Transport, HttpServer } from 'playwright-core/lib/utils';
|
||||||
import type * as reporterTypes from '../../types/testReporter';
|
import type * as reporterTypes from '../../types/testReporter';
|
||||||
import { collectAffectedTestFiles, dependenciesForTestFile } from '../transform/compilationCache';
|
import { collectAffectedTestFiles, dependenciesForTestFile } from '../transform/compilationCache';
|
||||||
import type { ConfigLocation, FullConfigInternal } from '../common/config';
|
import type { ConfigLocation, FullConfigInternal } from '../common/config';
|
||||||
import { InternalReporter } from '../reporters/internalReporter';
|
|
||||||
import { createReporterForTestServer, createReporters } from './reporters';
|
import { createReporterForTestServer, createReporters } from './reporters';
|
||||||
import { TestRun, createTaskRunnerForList, createTaskRunnerForTestServer, createTaskRunnerForWatchSetup, createTaskRunnerForListFiles } from './tasks';
|
import { TestRun, createTaskRunnerForList, createTaskRunnerForTestServer, createTaskRunnerForWatchSetup, createTaskRunnerForListFiles } from './tasks';
|
||||||
import { open } from 'playwright-core/lib/utilsBundle';
|
import { open } from 'playwright-core/lib/utilsBundle';
|
||||||
import ListReporter from '../reporters/list';
|
import ListReporter from '../reporters/list';
|
||||||
import { Multiplexer } from '../reporters/multiplexer';
|
|
||||||
import { SigIntWatcher } from './sigIntWatcher';
|
import { SigIntWatcher } from './sigIntWatcher';
|
||||||
import { Watcher } from '../fsWatcher';
|
import { Watcher } from '../fsWatcher';
|
||||||
import type { ReportEntry, TestServerInterface, TestServerInterfaceEventEmitters } from '../isomorphic/testServerInterface';
|
import type { ReportEntry, TestServerInterface, TestServerInterfaceEventEmitters } from '../isomorphic/testServerInterface';
|
||||||
|
|
@ -40,6 +38,7 @@ import type { TestRunnerPluginRegistration } from '../plugins';
|
||||||
import { serializeError } from '../util';
|
import { serializeError } from '../util';
|
||||||
import { cacheDir } from '../transform/compilationCache';
|
import { cacheDir } from '../transform/compilationCache';
|
||||||
import { baseFullConfig } from '../isomorphic/teleReceiver';
|
import { baseFullConfig } from '../isomorphic/teleReceiver';
|
||||||
|
import { InternalReporter } from '../reporters/internalReporter';
|
||||||
|
|
||||||
const originalStdoutWrite = process.stdout.write;
|
const originalStdoutWrite = process.stdout.write;
|
||||||
const originalStderrWrite = process.stderr.write;
|
const originalStderrWrite = process.stderr.write;
|
||||||
|
|
@ -102,9 +101,13 @@ class TestServerDispatcher implements TestServerInterface {
|
||||||
|
|
||||||
private async _collectingReporter() {
|
private async _collectingReporter() {
|
||||||
const report: ReportEntry[] = [];
|
const report: ReportEntry[] = [];
|
||||||
const wireReporter = await createReporterForTestServer(this._serializer, e => report.push(e));
|
const collectingReporter = await createReporterForTestServer(this._serializer, e => report.push(e));
|
||||||
const reporter = new InternalReporter(wireReporter);
|
return { collectingReporter, report };
|
||||||
return { reporter, report };
|
}
|
||||||
|
|
||||||
|
private async _collectingInternalReporter() {
|
||||||
|
const { collectingReporter, report } = await this._collectingReporter();
|
||||||
|
return { reporter: new InternalReporter(collectingReporter), report };
|
||||||
}
|
}
|
||||||
|
|
||||||
async initialize(params: Parameters<TestServerInterface['initialize']>[0]): ReturnType<TestServerInterface['initialize']> {
|
async initialize(params: Parameters<TestServerInterface['initialize']>[0]): ReturnType<TestServerInterface['initialize']> {
|
||||||
|
|
@ -145,9 +148,9 @@ class TestServerDispatcher implements TestServerInterface {
|
||||||
async runGlobalSetup(params: Parameters<TestServerInterface['runGlobalSetup']>[0]): ReturnType<TestServerInterface['runGlobalSetup']> {
|
async runGlobalSetup(params: Parameters<TestServerInterface['runGlobalSetup']>[0]): ReturnType<TestServerInterface['runGlobalSetup']> {
|
||||||
await this.runGlobalTeardown();
|
await this.runGlobalTeardown();
|
||||||
|
|
||||||
const { reporter, report } = await this._collectingReporter();
|
|
||||||
const { config, error } = await this._loadConfig();
|
const { config, error } = await this._loadConfig();
|
||||||
if (!config) {
|
if (!config) {
|
||||||
|
const { reporter, report } = await this._collectingInternalReporter();
|
||||||
// Produce dummy config when it has an error.
|
// Produce dummy config when it has an error.
|
||||||
reporter.onConfigure(baseFullConfig);
|
reporter.onConfigure(baseFullConfig);
|
||||||
reporter.onError(error!);
|
reporter.onError(error!);
|
||||||
|
|
@ -156,13 +159,14 @@ class TestServerDispatcher implements TestServerInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
webServerPluginsForConfig(config).forEach(p => config.plugins.push({ factory: p }));
|
webServerPluginsForConfig(config).forEach(p => config.plugins.push({ factory: p }));
|
||||||
const listReporter = new InternalReporter(new ListReporter());
|
const { collectingReporter, report } = await this._collectingReporter();
|
||||||
const taskRunner = createTaskRunnerForWatchSetup(config, new Multiplexer([reporter, listReporter]));
|
const listReporter = new ListReporter();
|
||||||
reporter.onConfigure(config.config);
|
const taskRunner = createTaskRunnerForWatchSetup(config, [collectingReporter, listReporter]);
|
||||||
const testRun = new TestRun(config, reporter);
|
taskRunner.reporter.onConfigure(config.config);
|
||||||
|
const testRun = new TestRun(config);
|
||||||
const { status, cleanup: globalCleanup } = await taskRunner.runDeferCleanup(testRun, 0);
|
const { status, cleanup: globalCleanup } = await taskRunner.runDeferCleanup(testRun, 0);
|
||||||
await reporter.onEnd({ status });
|
await taskRunner.reporter.onEnd({ status });
|
||||||
await reporter.onExit();
|
await taskRunner.reporter.onExit();
|
||||||
if (status !== 'passed') {
|
if (status !== 'passed') {
|
||||||
await globalCleanup();
|
await globalCleanup();
|
||||||
return { report, status };
|
return { report, status };
|
||||||
|
|
@ -181,7 +185,7 @@ class TestServerDispatcher implements TestServerInterface {
|
||||||
async startDevServer(params: Parameters<TestServerInterface['startDevServer']>[0]): ReturnType<TestServerInterface['startDevServer']> {
|
async startDevServer(params: Parameters<TestServerInterface['startDevServer']>[0]): ReturnType<TestServerInterface['startDevServer']> {
|
||||||
if (this._devServerHandle)
|
if (this._devServerHandle)
|
||||||
return { status: 'failed', report: [] };
|
return { status: 'failed', report: [] };
|
||||||
const { reporter, report } = await this._collectingReporter();
|
const { reporter, report } = await this._collectingInternalReporter();
|
||||||
const { config, error } = await this._loadConfig();
|
const { config, error } = await this._loadConfig();
|
||||||
if (!config) {
|
if (!config) {
|
||||||
reporter.onError(error!);
|
reporter.onError(error!);
|
||||||
|
|
@ -209,7 +213,7 @@ class TestServerDispatcher implements TestServerInterface {
|
||||||
this._devServerHandle = undefined;
|
this._devServerHandle = undefined;
|
||||||
return { status: 'passed', report: [] };
|
return { status: 'passed', report: [] };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const { reporter, report } = await this._collectingReporter();
|
const { reporter, report } = await this._collectingInternalReporter();
|
||||||
reporter.onError(serializeError(e));
|
reporter.onError(serializeError(e));
|
||||||
return { status: 'failed', report };
|
return { status: 'failed', report };
|
||||||
}
|
}
|
||||||
|
|
@ -222,20 +226,21 @@ class TestServerDispatcher implements TestServerInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
async listFiles(params: Parameters<TestServerInterface['listFiles']>[0]): ReturnType<TestServerInterface['listFiles']> {
|
async listFiles(params: Parameters<TestServerInterface['listFiles']>[0]): ReturnType<TestServerInterface['listFiles']> {
|
||||||
const { reporter, report } = await this._collectingReporter();
|
|
||||||
const { config, error } = await this._loadConfig();
|
const { config, error } = await this._loadConfig();
|
||||||
if (!config) {
|
if (!config) {
|
||||||
|
const { reporter, report } = await this._collectingInternalReporter();
|
||||||
reporter.onError(error!);
|
reporter.onError(error!);
|
||||||
return { status: 'failed', report };
|
return { status: 'failed', report };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { collectingReporter, report } = await this._collectingReporter();
|
||||||
config.cliProjectFilter = params.projects?.length ? params.projects : undefined;
|
config.cliProjectFilter = params.projects?.length ? params.projects : undefined;
|
||||||
const taskRunner = createTaskRunnerForListFiles(config, reporter);
|
const taskRunner = createTaskRunnerForListFiles(config, [collectingReporter]);
|
||||||
reporter.onConfigure(config.config);
|
taskRunner.reporter.onConfigure(config.config);
|
||||||
const testRun = new TestRun(config, reporter);
|
const testRun = new TestRun(config);
|
||||||
const status = await taskRunner.run(testRun, 0);
|
const status = await taskRunner.run(testRun, 0);
|
||||||
await reporter.onEnd({ status });
|
await taskRunner.reporter.onEnd({ status });
|
||||||
await reporter.onExit();
|
await taskRunner.reporter.onExit();
|
||||||
return { report, status };
|
return { report, status };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -253,11 +258,11 @@ class TestServerDispatcher implements TestServerInterface {
|
||||||
repeatEach: 1,
|
repeatEach: 1,
|
||||||
retries: 0,
|
retries: 0,
|
||||||
};
|
};
|
||||||
const { reporter, report } = await this._collectingReporter();
|
|
||||||
const { config, error } = await this._loadConfig(overrides);
|
const { config, error } = await this._loadConfig(overrides);
|
||||||
if (!config) {
|
if (!config) {
|
||||||
|
const { reporter, report } = await this._collectingInternalReporter();
|
||||||
reporter.onError(error!);
|
reporter.onError(error!);
|
||||||
return { report: [], status: 'failed' };
|
return { report, status: 'failed' };
|
||||||
}
|
}
|
||||||
|
|
||||||
config.cliArgs = params.locations || [];
|
config.cliArgs = params.locations || [];
|
||||||
|
|
@ -266,12 +271,13 @@ class TestServerDispatcher implements TestServerInterface {
|
||||||
config.cliProjectFilter = params.projects?.length ? params.projects : undefined;
|
config.cliProjectFilter = params.projects?.length ? params.projects : undefined;
|
||||||
config.cliListOnly = true;
|
config.cliListOnly = true;
|
||||||
|
|
||||||
const taskRunner = createTaskRunnerForList(config, reporter, 'out-of-process', { failOnLoadErrors: false });
|
const { collectingReporter, report } = await this._collectingReporter();
|
||||||
const testRun = new TestRun(config, reporter);
|
const taskRunner = createTaskRunnerForList(config, [collectingReporter], 'out-of-process', { failOnLoadErrors: false });
|
||||||
reporter.onConfigure(config.config);
|
const testRun = new TestRun(config);
|
||||||
|
taskRunner.reporter.onConfigure(config.config);
|
||||||
const status = await taskRunner.run(testRun, 0);
|
const status = await taskRunner.run(testRun, 0);
|
||||||
await reporter.onEnd({ status });
|
await taskRunner.reporter.onEnd({ status });
|
||||||
await reporter.onExit();
|
await taskRunner.reporter.onExit();
|
||||||
|
|
||||||
const projectDirs = new Set<string>();
|
const projectDirs = new Set<string>();
|
||||||
const projectOutputs = new Set<string>();
|
const projectOutputs = new Set<string>();
|
||||||
|
|
@ -343,14 +349,13 @@ class TestServerDispatcher implements TestServerInterface {
|
||||||
const reporters = await createReporters(config, 'test', true);
|
const reporters = await createReporters(config, 'test', true);
|
||||||
const wireReporter = await this._wireReporter(e => this._dispatchEvent('report', e));
|
const wireReporter = await this._wireReporter(e => this._dispatchEvent('report', e));
|
||||||
reporters.push(wireReporter);
|
reporters.push(wireReporter);
|
||||||
const reporter = new InternalReporter(new Multiplexer(reporters));
|
const taskRunner = createTaskRunnerForTestServer(config, reporters);
|
||||||
const taskRunner = createTaskRunnerForTestServer(config, reporter);
|
const testRun = new TestRun(config);
|
||||||
const testRun = new TestRun(config, reporter);
|
taskRunner.reporter.onConfigure(config.config);
|
||||||
reporter.onConfigure(config.config);
|
|
||||||
const stop = new ManualPromise();
|
const stop = new ManualPromise();
|
||||||
const run = taskRunner.run(testRun, 0, stop).then(async status => {
|
const run = taskRunner.run(testRun, 0, stop).then(async status => {
|
||||||
await reporter.onEnd({ status });
|
await taskRunner.reporter.onEnd({ status });
|
||||||
await reporter.onExit();
|
await taskRunner.reporter.onExit();
|
||||||
this._testRun = undefined;
|
this._testRun = undefined;
|
||||||
return status;
|
return status;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
import readline from 'readline';
|
import readline from 'readline';
|
||||||
import { createGuid, getPackageManagerExecCommand, ManualPromise } from 'playwright-core/lib/utils';
|
import { createGuid, getPackageManagerExecCommand, ManualPromise } from 'playwright-core/lib/utils';
|
||||||
import type { FullConfigInternal, FullProjectInternal } from '../common/config';
|
import type { FullConfigInternal, FullProjectInternal } from '../common/config';
|
||||||
import { InternalReporter } from '../reporters/internalReporter';
|
|
||||||
import { createFileMatcher, createFileMatcherFromArguments } from '../util';
|
import { createFileMatcher, createFileMatcherFromArguments } from '../util';
|
||||||
import type { Matcher } from '../util';
|
import type { Matcher } from '../util';
|
||||||
import { TestRun, createTaskRunnerForWatch, createTaskRunnerForWatchSetup } from './tasks';
|
import { TestRun, createTaskRunnerForWatch, createTaskRunnerForWatchSetup } from './tasks';
|
||||||
|
|
@ -112,15 +111,14 @@ export async function runWatchModeLoop(config: FullConfigInternal): Promise<Full
|
||||||
p.project.retries = 0;
|
p.project.retries = 0;
|
||||||
|
|
||||||
// Perform global setup.
|
// Perform global setup.
|
||||||
const reporter = new InternalReporter(new ListReporter());
|
const testRun = new TestRun(config);
|
||||||
const testRun = new TestRun(config, reporter);
|
const taskRunner = createTaskRunnerForWatchSetup(config, [new ListReporter()]);
|
||||||
const taskRunner = createTaskRunnerForWatchSetup(config, reporter);
|
taskRunner.reporter.onConfigure(config.config);
|
||||||
reporter.onConfigure(config.config);
|
|
||||||
const { status, cleanup: globalCleanup } = await taskRunner.runDeferCleanup(testRun, 0);
|
const { status, cleanup: globalCleanup } = await taskRunner.runDeferCleanup(testRun, 0);
|
||||||
if (status !== 'passed')
|
if (status !== 'passed')
|
||||||
await globalCleanup();
|
await globalCleanup();
|
||||||
await reporter.onEnd({ status });
|
await taskRunner.reporter.onEnd({ status });
|
||||||
await reporter.onExit();
|
await taskRunner.reporter.onExit();
|
||||||
if (status !== 'passed')
|
if (status !== 'passed')
|
||||||
return status;
|
return status;
|
||||||
|
|
||||||
|
|
@ -280,10 +278,9 @@ async function runTests(config: FullConfigInternal, failedTestIdCollector: Set<s
|
||||||
title?: string,
|
title?: string,
|
||||||
}) {
|
}) {
|
||||||
printConfiguration(config, options?.title);
|
printConfiguration(config, options?.title);
|
||||||
const reporter = new InternalReporter(new ListReporter());
|
const taskRunner = createTaskRunnerForWatch(config, [new ListReporter()], options?.additionalFileMatcher);
|
||||||
const taskRunner = createTaskRunnerForWatch(config, reporter, options?.additionalFileMatcher);
|
const testRun = new TestRun(config);
|
||||||
const testRun = new TestRun(config, reporter);
|
taskRunner.reporter.onConfigure(config.config);
|
||||||
reporter.onConfigure(config.config);
|
|
||||||
const taskStatus = await taskRunner.run(testRun, 0);
|
const taskStatus = await taskRunner.run(testRun, 0);
|
||||||
let status: FullResult['status'] = 'passed';
|
let status: FullResult['status'] = 'passed';
|
||||||
|
|
||||||
|
|
@ -301,8 +298,8 @@ async function runTests(config: FullConfigInternal, failedTestIdCollector: Set<s
|
||||||
status = 'failed';
|
status = 'failed';
|
||||||
if (status === 'passed' && taskStatus !== 'passed')
|
if (status === 'passed' && taskStatus !== 'passed')
|
||||||
status = taskStatus;
|
status = taskStatus;
|
||||||
await reporter.onEnd({ status });
|
await taskRunner.reporter.onEnd({ status });
|
||||||
await reporter.onExit();
|
await taskRunner.reporter.onExit();
|
||||||
}
|
}
|
||||||
|
|
||||||
function affectedProjectsClosure(projectClosure: FullProjectInternal[], affected: FullProjectInternal[]): Set<FullProjectInternal> {
|
function affectedProjectsClosure(projectClosure: FullProjectInternal[], affected: FullProjectInternal[]): Set<FullProjectInternal> {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
|
|
||||||
|
console.log('output from server');
|
||||||
console.error('error from server');
|
console.error('error from server');
|
||||||
|
|
||||||
const port = process.argv[2] || 3000;
|
const port = process.argv[2] || 3000;
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { test, expect, retries } from './ui-mode-fixtures';
|
import { test, expect, retries } from './ui-mode-fixtures';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
test.describe.configure({ mode: 'parallel', retries });
|
test.describe.configure({ mode: 'parallel', retries });
|
||||||
|
|
||||||
|
|
@ -202,3 +203,29 @@ test('should print beforeAll console messages once', async ({ runUITest }, testI
|
||||||
'test log',
|
'test log',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should print web server output', async ({ runUITest }, { workerIndex }) => {
|
||||||
|
const port = workerIndex * 2 + 10500;
|
||||||
|
const serverPath = path.join(__dirname, 'assets', 'simple-server.js');
|
||||||
|
const { page } = await runUITest({
|
||||||
|
'test.spec.ts': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('connect to the server', async ({baseURL, page}) => {
|
||||||
|
expect(baseURL).toBe('http://localhost:${port}');
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
'playwright.config.ts': `
|
||||||
|
module.exports = {
|
||||||
|
webServer: {
|
||||||
|
command: 'node ${JSON.stringify(serverPath)} ${port}',
|
||||||
|
port: ${port},
|
||||||
|
stdout: 'pipe',
|
||||||
|
stderr: 'pipe',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
await page.getByTitle('Toggle output').click();
|
||||||
|
await expect(page.getByTestId('output')).toContainText('output from server');
|
||||||
|
await expect(page.getByTestId('output')).toContainText('error from server');
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue