diff --git a/packages/playwright/src/isomorphic/teleReceiver.ts b/packages/playwright/src/isomorphic/teleReceiver.ts index 3a9a34e391..15feb60370 100644 --- a/packages/playwright/src/isomorphic/teleReceiver.ts +++ b/packages/playwright/src/isomorphic/teleReceiver.ts @@ -124,9 +124,9 @@ export type JsonEvent = { }; type TeleReporterReceiverOptions = { - mergeProjects: boolean; - mergeTestCases: boolean; - resolvePath: (rootDir: string, relativePath: string) => string; + mergeProjects?: boolean; + mergeTestCases?: boolean; + resolvePath?: (rootDir: string, relativePath: string) => string; configOverrides?: Pick; clearPreviousResultsWhenTestBegins?: boolean; }; @@ -140,7 +140,7 @@ export class TeleReporterReceiver { private _rootDir!: string; private _config!: reporterTypes.FullConfig; - constructor(reporter: Partial, options: TeleReporterReceiverOptions) { + constructor(reporter: Partial, options: TeleReporterReceiverOptions = {}) { this._rootSuite = new TeleSuite('', 'root'); this._options = options; this._reporter = reporter; @@ -388,7 +388,7 @@ export class TeleReporterReceiver { private _absolutePath(relativePath?: string): string | undefined { if (relativePath === undefined) return; - return this._options.resolvePath(this._rootDir, relativePath); + return this._options.resolvePath ? this._options.resolvePath(this._rootDir, relativePath) : this._rootDir + '/' + relativePath; } } diff --git a/packages/playwright/src/runner/DEPS.list b/packages/playwright/src/runner/DEPS.list index 828f2307e9..cdf6044844 100644 --- a/packages/playwright/src/runner/DEPS.list +++ b/packages/playwright/src/runner/DEPS.list @@ -8,4 +8,5 @@ ../util.ts ../utilsBundle.ts ../isomorphic/folders.ts +../isomorphic/teleReceiver.ts ../fsWatcher.ts diff --git a/packages/playwright/src/runner/testServer.ts b/packages/playwright/src/runner/testServer.ts index 19a43beed0..0809bd43d7 100644 --- a/packages/playwright/src/runner/testServer.ts +++ b/packages/playwright/src/runner/testServer.ts @@ -39,6 +39,7 @@ import type { TraceViewerRedirectOptions, TraceViewerServerOptions } from 'playw import type { TestRunnerPluginRegistration } from '../plugins'; import { serializeError } from '../util'; import { cacheDir } from '../transform/compilationCache'; +import { baseFullConfig } from '../isomorphic/teleReceiver'; const originalStdoutWrite = process.stdout.write; const originalStderrWrite = process.stderr.write; @@ -147,7 +148,10 @@ class TestServerDispatcher implements TestServerInterface { const { reporter, report } = await this._collectingReporter(); const { config, error } = await this._loadConfig(); if (!config) { + // Produce dummy config when it has an error. + reporter.onConfigure(baseFullConfig); reporter.onError(error!); + await reporter.onExit(); return { status: 'failed', report }; } diff --git a/packages/trace-viewer/src/ui/teleSuiteUpdater.ts b/packages/trace-viewer/src/ui/teleSuiteUpdater.ts index a766627af3..a2fbb4e39a 100644 --- a/packages/trace-viewer/src/ui/teleSuiteUpdater.ts +++ b/packages/trace-viewer/src/ui/teleSuiteUpdater.ts @@ -115,11 +115,7 @@ export class TeleSuiteUpdater { this._options.onUpdate(); }, - onError: (error: reporterTypes.TestError) => { - this.loadErrors.push(error); - this._options.onError?.(error); - this._options.onUpdate(); - }, + onError: (error: reporterTypes.TestError) => this._handleOnError(error), printsToStdio: () => { return false; @@ -133,6 +129,17 @@ export class TeleSuiteUpdater { }; } + processGlobalReport(report: any[]) { + const receiver = new TeleReporterReceiver({ + onConfigure: (c: reporterTypes.FullConfig) => { + this.config = c; + }, + onError: (error: reporterTypes.TestError) => this._handleOnError(error) + }); + for (const message of report) + receiver.dispatch(message); + } + processListReport(report: any[]) { // Save test results and reset all projects, the results will be restored after // new project structure is built. @@ -150,6 +157,12 @@ export class TeleSuiteUpdater { this._receiver.dispatch(message)?.catch(() => {}); } + private _handleOnError(error: reporterTypes.TestError) { + this.loadErrors.push(error); + this._options.onError?.(error); + this._options.onUpdate(); + } + asModel(): TestModel { return { rootSuite: this.rootSuite || new TeleSuite('', 'root'), diff --git a/packages/trace-viewer/src/ui/uiModeView.tsx b/packages/trace-viewer/src/ui/uiModeView.tsx index 03abb468ab..93ae4d9577 100644 --- a/packages/trace-viewer/src/ui/uiModeView.tsx +++ b/packages/trace-viewer/src/ui/uiModeView.tsx @@ -172,24 +172,29 @@ export const UIModeView: React.FC<{}> = ({ setIsLoading(true); setWatchedTreeIds({ value: new Set() }); (async () => { - await testServerConnection.initialize({ - interceptStdio: true, - watchTestDirs: true - }); - const { status } = await testServerConnection.runGlobalSetup({}); - if (status !== 'passed') - return; - const result = await testServerConnection.listTests({ projects: queryParams.projects, locations: queryParams.args }); - teleSuiteUpdater.processListReport(result.report); + try { + await testServerConnection.initialize({ + interceptStdio: true, + watchTestDirs: true + }); + const { status, report } = await testServerConnection.runGlobalSetup({}); + teleSuiteUpdater.processGlobalReport(report); + if (status !== 'passed') + return; - testServerConnection.onListChanged(updateList); - testServerConnection.onReport(params => { - teleSuiteUpdater.processTestReportEvent(params); - }); - setIsLoading(false); + const result = await testServerConnection.listTests({ projects: queryParams.projects, locations: queryParams.args }); + teleSuiteUpdater.processListReport(result.report); - const { hasBrowsers } = await testServerConnection.checkBrowsers({}); - setHasBrowsers(hasBrowsers); + testServerConnection.onListChanged(updateList); + testServerConnection.onReport(params => { + teleSuiteUpdater.processTestReportEvent(params); + }); + + const { hasBrowsers } = await testServerConnection.checkBrowsers({}); + setHasBrowsers(hasBrowsers); + } finally { + setIsLoading(false); + } })(); return () => { clearTimeout(throttleTimer); diff --git a/tests/playwright-test/ui-mode-test-setup.spec.ts b/tests/playwright-test/ui-mode-test-setup.spec.ts index 7a5ce3f084..08857d5508 100644 --- a/tests/playwright-test/ui-mode-test-setup.spec.ts +++ b/tests/playwright-test/ui-mode-test-setup.spec.ts @@ -84,6 +84,17 @@ test('should teardown on sigint', async ({ runUITest, nodeVersion }) => { ]); }); +test('should show errors in config', async ({ runUITest }) => { + const { page } = await runUITest({ + 'playwright.config.ts': ` + import { defineConfig, devices } from '@playwright/test'; + throw new Error("URL is empty") + `, + }); + await page.getByText('playwright.config.ts').click(); + await expect(page.getByText('Error: URL is empty')).toBeInViewport(); +}); + const testsWithSetup = { 'playwright.config.ts': ` import { defineConfig } from '@playwright/test';