diff --git a/packages/playwright-core/src/server/trace/viewer/traceViewer.ts b/packages/playwright-core/src/server/trace/viewer/traceViewer.ts index 0b9ad35d5a..d42138b0ff 100644 --- a/packages/playwright-core/src/server/trace/viewer/traceViewer.ts +++ b/packages/playwright-core/src/server/trace/viewer/traceViewer.ts @@ -120,12 +120,12 @@ export async function installRootRedirect(server: HttpServer, traceUrls: string[ }); } -export async function runTraceViewerApp(traceUrls: string[], browserName: string, options: TraceViewerServerOptions, exitOnClose?: boolean) { +export async function runTraceViewerApp(traceUrls: string[], browserName: string, options: TraceViewerServerOptions & { headless?: boolean }, exitOnClose?: boolean) { if (!validateTraceUrls(traceUrls)) return; const server = await startTraceViewerServer(options); await installRootRedirect(server, traceUrls, options); - const page = await openTraceViewerApp(server.urlPrefix(), browserName); + const page = await openTraceViewerApp(server.urlPrefix(), browserName, options); if (exitOnClose) page.on('close', () => gracefullyProcessExitDoNotHang(0)); return page; @@ -150,7 +150,7 @@ export async function openTraceViewerApp(url: string, browserName: string, optio persistentContextOptions: { ...options?.persistentContextOptions, useWebSocket: isUnderTest(), - headless: options?.headless, + headless: !!options?.headless, }, }); diff --git a/packages/playwright-core/src/utils/httpServer.ts b/packages/playwright-core/src/utils/httpServer.ts index c83c6601eb..32012e6f96 100644 --- a/packages/playwright-core/src/utils/httpServer.ts +++ b/packages/playwright-core/src/utils/httpServer.ts @@ -84,8 +84,8 @@ export class HttpServer { wss.on('connection', ws => { transport.sendEvent = (method, params) => ws.send(JSON.stringify({ method, params })); transport.close = () => ws.close(); - ws.on('message', async (message: string) => { - const { id, method, params } = JSON.parse(message); + ws.on('message', async message => { + const { id, method, params } = JSON.parse(String(message)); try { const result = await transport.dispatch(method, params); ws.send(JSON.stringify({ id, result })); diff --git a/packages/trace-viewer/src/events.ts b/packages/playwright/src/isomorphic/events.ts similarity index 100% rename from packages/trace-viewer/src/events.ts rename to packages/playwright/src/isomorphic/events.ts diff --git a/packages/playwright/src/runner/testServerInterface.ts b/packages/playwright/src/isomorphic/testServerInterface.ts similarity index 80% rename from packages/playwright/src/runner/testServerInterface.ts rename to packages/playwright/src/isomorphic/testServerInterface.ts index f755e66685..dc8a9e9d62 100644 --- a/packages/playwright/src/runner/testServerInterface.ts +++ b/packages/playwright/src/isomorphic/testServerInterface.ts @@ -15,6 +15,7 @@ */ import type * as reporterTypes from '../../types/testReporter'; +import type { Event } from './events'; export interface TestServerInterface { ping(): Promise; @@ -31,6 +32,10 @@ export interface TestServerInterface { installBrowsers(): Promise; + runGlobalSetup(): Promise; + + runGlobalTeardown(): Promise; + listFiles(): Promise<{ projects: { name: string; @@ -69,10 +74,11 @@ export interface TestServerInterface { closeGracefully(): Promise; } -export interface TestServerEvents { - on(event: 'listReport', listener: (params: any) => void): void; - on(event: 'testReport', listener: (params: any) => void): void; - on(event: 'stdio', listener: (params: { type: 'stdout' | 'stderr', text?: string, buffer?: string }) => void): void; - on(event: 'listChanged', listener: () => void): void; - on(event: 'testFilesChanged', listener: (testFileNames: string[]) => void): void; +export interface TestServerInterfaceEvents { + onClose: Event; + onListReport: Event; + onTestReport: Event; + onStdio: Event<{ type: 'stdout' | 'stderr', text?: string, buffer?: string }>; + onListChanged: Event; + onTestFilesChanged: Event; } diff --git a/packages/playwright/src/isomorphic/testTree.ts b/packages/playwright/src/isomorphic/testTree.ts index 8f11dea0d7..bfe932306c 100644 --- a/packages/playwright/src/isomorphic/testTree.ts +++ b/packages/playwright/src/isomorphic/testTree.ts @@ -308,12 +308,6 @@ export class TestTree { visit(treeItem); return testIds; } - - locationToOpen(treeItem?: TreeItem) { - if (!treeItem) - return; - return treeItem.location.file + ':' + treeItem.location.line; - } } export function sortAndPropagateStatus(treeItem: TreeItem) { diff --git a/packages/playwright/src/runner/testServer.ts b/packages/playwright/src/runner/testServer.ts index 9577c52704..3d08fecc5c 100644 --- a/packages/playwright/src/runner/testServer.ts +++ b/packages/playwright/src/runner/testServer.ts @@ -15,7 +15,7 @@ */ import { registry, startTraceViewerServer } from 'playwright-core/lib/server'; -import { ManualPromise, gracefullyProcessExitDoNotHang } from 'playwright-core/lib/utils'; +import { ManualPromise, gracefullyProcessExitDoNotHang, isUnderTest } from 'playwright-core/lib/utils'; import type { Transport, HttpServer } from 'playwright-core/lib/utils'; import type { FullResult, Location, TestError } from '../../types/testReporter'; import { collectAffectedTestFiles, dependenciesForTestFile } from '../transform/compilationCache'; @@ -28,7 +28,7 @@ import ListReporter from '../reporters/list'; import { Multiplexer } from '../reporters/multiplexer'; import { SigIntWatcher } from './sigIntWatcher'; import { Watcher } from '../fsWatcher'; -import type { TestServerInterface } from './testServerInterface'; +import type { TestServerInterface } from '../isomorphic/testServerInterface'; import { Runner } from './runner'; import { serializeError } from '../util'; import { prepareErrorStack } from '../reporters/base'; @@ -36,7 +36,6 @@ import { prepareErrorStack } from '../reporters/base'; class TestServer { private _config: FullConfigInternal; private _dispatcher: TestServerDispatcher | undefined; - globalCleanup: (() => Promise) | undefined; private _originalStdoutWrite: NodeJS.WriteStream['write']; private _originalStderrWrite: NodeJS.WriteStream['write']; @@ -58,27 +57,15 @@ class TestServer { this._originalStderrWrite = process.stderr.write; } - async runGlobalSetup(): Promise { - const reporter = new InternalReporter(new ListReporter()); - const taskRunner = createTaskRunnerForWatchSetup(this._config, reporter); - reporter.onConfigure(this._config.config); - const testRun = new TestRun(this._config, reporter); - const { status, cleanup: globalCleanup } = await taskRunner.runDeferCleanup(testRun, 0); - await reporter.onEnd({ status }); - await reporter.onExit(); - if (status !== 'passed') { - await globalCleanup(); - return status; - } - this.globalCleanup = globalCleanup; - return status; - } - async start(options: { host?: string, port?: number }): Promise { this._dispatcher = new TestServerDispatcher(this._config); return await startTraceViewerServer({ ...options, transport: this._dispatcher.transport }); } + async stop() { + await this._dispatcher?.runGlobalTeardown(); + } + wireStdIO() { if (!process.env.PWTEST_DEBUG) { process.stdout.write = (chunk: string | Buffer) => { @@ -107,6 +94,7 @@ class TestServerDispatcher implements TestServerInterface { private _testRun: { run: Promise, stop: ManualPromise } | undefined; readonly transport: Transport; private _queue = Promise.resolve(); + private _globalCleanup: (() => Promise) | undefined; constructor(config: FullConfigInternal) { this._config = config; @@ -125,8 +113,10 @@ class TestServerDispatcher implements TestServerInterface { async ping() {} async open(params: { location: Location }) { + if (isUnderTest()) + return; // eslint-disable-next-line no-console - open('vscode://file/' + params.location.file + ':' + params.location.column).catch(e => console.error(e)); + open('vscode://file/' + params.location.file + ':' + params.location.line).catch(e => console.error(e)); } async resizeTerminal(params: { cols: number; rows: number; }) { @@ -144,6 +134,30 @@ class TestServerDispatcher implements TestServerInterface { await installBrowsers(); } + async runGlobalSetup(): Promise { + await this.runGlobalTeardown(); + + const reporter = new InternalReporter(new ListReporter()); + const taskRunner = createTaskRunnerForWatchSetup(this._config, reporter); + reporter.onConfigure(this._config.config); + const testRun = new TestRun(this._config, reporter); + const { status, cleanup: globalCleanup } = await taskRunner.runDeferCleanup(testRun, 0); + await reporter.onEnd({ status }); + await reporter.onExit(); + if (status !== 'passed') { + await globalCleanup(); + return status; + } + this._globalCleanup = globalCleanup; + return status; + } + + async runGlobalTeardown() { + const result = (await this._globalCleanup?.()) || 'passed'; + this._globalCleanup = undefined; + return result; + } + async listFiles() { try { const runner = new Runner(this._config); @@ -246,9 +260,6 @@ class TestServerDispatcher implements TestServerInterface { export async function runTestServer(config: FullConfigInternal, options: { host?: string, port?: number }, openUI: (server: HttpServer, cancelPromise: ManualPromise) => Promise): Promise { const testServer = new TestServer(config); - const globalSetupStatus = await testServer.runGlobalSetup(); - if (globalSetupStatus !== 'passed') - return globalSetupStatus; const cancelPromise = new ManualPromise(); const sigintWatcher = new SigIntWatcher(); void sigintWatcher.promise().then(() => cancelPromise.resolve()); @@ -259,9 +270,10 @@ export async function runTestServer(config: FullConfigInternal, options: { host? await cancelPromise; } finally { testServer.unwireStdIO(); + await testServer.stop(); sigintWatcher.disarm(); } - return await testServer.globalCleanup?.() || (sigintWatcher.hadSignal() ? 'interrupted' : 'passed'); + return sigintWatcher.hadSignal() ? 'interrupted' : 'passed'; } type StdioPayload = { diff --git a/packages/trace-viewer/src/ui/testServerConnection.ts b/packages/trace-viewer/src/ui/testServerConnection.ts new file mode 100644 index 0000000000..45f75a633e --- /dev/null +++ b/packages/trace-viewer/src/ui/testServerConnection.ts @@ -0,0 +1,136 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { TestServerInterface, TestServerInterfaceEvents } from '@testIsomorphic/testServerInterface'; +import type { Location, TestError } from 'playwright/types/testReporter'; +import { connect } from './wsPort'; +import type { Event } from '@testIsomorphic/events'; +import { EventEmitter } from '@testIsomorphic/events'; + +export class TestServerConnection implements TestServerInterface, TestServerInterfaceEvents { + readonly onClose: Event; + readonly onListReport: Event; + readonly onTestReport: Event; + readonly onStdio: Event<{ type: 'stderr' | 'stdout'; text?: string | undefined; buffer?: string | undefined; }>; + readonly onListChanged: Event; + readonly onTestFilesChanged: Event; + + private _onCloseEmitter = new EventEmitter(); + private _onListReportEmitter = new EventEmitter(); + private _onTestReportEmitter = new EventEmitter(); + private _onStdioEmitter = new EventEmitter<{ type: 'stderr' | 'stdout'; text?: string | undefined; buffer?: string | undefined; }>(); + private _onListChangedEmitter = new EventEmitter(); + private _onTestFilesChangedEmitter = new EventEmitter(); + + private _send: Promise<(method: string, params?: any) => Promise>; + + constructor() { + this.onClose = this._onCloseEmitter.event; + this.onListReport = this._onListReportEmitter.event; + this.onTestReport = this._onTestReportEmitter.event; + this.onStdio = this._onStdioEmitter.event; + this.onListChanged = this._onListChangedEmitter.event; + this.onTestFilesChanged = this._onTestFilesChangedEmitter.event; + + this._send = connect({ + onEvent: (method, params) => this._dispatchEvent(method, params), + onClose: () => this._onCloseEmitter.fire(), + }); + } + + private async _sendMessage(method: string, params?: any): Promise { + if ((window as any)._sniffProtocolForTest) + (window as any)._sniffProtocolForTest({ method, params }).catch(() => {}); + + const send = await this._send; + const logForTest = (window as any).__logForTest; + logForTest?.({ method, params }); + return send(method, params).catch((e: Error) => { + // eslint-disable-next-line no-console + console.error(e); + }); + } + + private _dispatchEvent(method: string, params?: any) { + if (method === 'close') + this._onCloseEmitter.fire(undefined); + else if (method === 'listReport') + this._onListReportEmitter.fire(params); + else if (method === 'testReport') + this._onTestReportEmitter.fire(params); + else if (method === 'stdio') + this._onStdioEmitter.fire(params); + else if (method === 'listChanged') + this._onListChangedEmitter.fire(undefined); + else if (method === 'testFilesChanged') + this._onTestFilesChangedEmitter.fire(params.testFileNames); + } + + async ping(): Promise { + await this._sendMessage('ping'); + } + + async watch(params: { fileNames: string[]; }): Promise { + await this._sendMessage('watch', params); + } + + async open(params: { location: Location; }): Promise { + await this._sendMessage('open', params); + } + + async resizeTerminal(params: { cols: number; rows: number; }): Promise { + await this._sendMessage('resizeTerminal', params); + } + + async checkBrowsers(): Promise<{ hasBrowsers: boolean; }> { + return await this._sendMessage('checkBrowsers'); + } + + async installBrowsers(): Promise { + await this._sendMessage('installBrowsers'); + } + + async runGlobalSetup(): Promise<'passed' | 'failed' | 'timedout' | 'interrupted'> { + return await this._sendMessage('runGlobalSetup'); + } + + async runGlobalTeardown(): Promise<'passed' | 'failed' | 'timedout' | 'interrupted'> { + return await this._sendMessage('runGlobalTeardown'); + } + + async listFiles(): Promise<{ projects: { name: string; testDir: string; use: { testIdAttribute?: string | undefined; }; files: string[]; }[]; cliEntryPoint?: string | undefined; error?: TestError | undefined; }> { + return await this._sendMessage('listFiles'); + } + + async listTests(params: { reporter?: string | undefined; fileNames?: string[] | undefined; }): Promise { + await this._sendMessage('listTests', params); + } + async runTests(params: { reporter?: string | undefined; locations?: string[] | undefined; grep?: string | undefined; testIds?: string[] | undefined; headed?: boolean | undefined; oneWorker?: boolean | undefined; trace?: 'off' | 'on' | undefined; projects?: string[] | undefined; reuseContext?: boolean | undefined; connectWsEndpoint?: string | undefined; }): Promise { + await this._sendMessage('runTests', params); + } + + async findRelatedTestFiles(params: { files: string[]; }): Promise<{ testFiles: string[]; errors?: TestError[] | undefined; }> { + return await this._sendMessage('findRelatedTestFiles', params); + } + + async stop(): Promise { + await this._sendMessage('stop'); + } + + async closeGracefully(): Promise { + await this._sendMessage('closeGracefully'); + } +} diff --git a/packages/trace-viewer/src/ui/uiModeView.tsx b/packages/trace-viewer/src/ui/uiModeView.tsx index 50d5551664..a4bed25a4e 100644 --- a/packages/trace-viewer/src/ui/uiModeView.tsx +++ b/packages/trace-viewer/src/ui/uiModeView.tsx @@ -39,25 +39,20 @@ import { toggleTheme } from '@web/theme'; import { artifactsFolderName } from '@testIsomorphic/folders'; import { msToString, settings, useSetting } from '@web/uiUtils'; import type { ActionTraceEvent } from '@trace/trace'; -import { connect } from './wsPort'; import { statusEx, TestTree } from '@testIsomorphic/testTree'; import type { TreeItem } from '@testIsomorphic/testTree'; import { testStatusIcon } from './testUtils'; +import { TestServerConnection } from './testServerConnection'; let updateRootSuite: (config: reporterTypes.FullConfig, rootSuite: reporterTypes.Suite, loadErrors: reporterTypes.TestError[], progress: Progress | undefined) => void = () => {}; let runWatchedTests = (fileNames: string[]) => {}; let xtermSize = { cols: 80, rows: 24 }; -let sendMessage: (method: string, params?: any) => Promise = async () => {}; - const xtermDataSource: XtermDataSource = { pending: [], clear: () => {}, write: data => xtermDataSource.pending.push(data), - resize: (cols: number, rows: number) => { - xtermSize = { cols, rows }; - sendMessageNoReply('resizeTerminal', { cols, rows }); - }, + resize: () => {}, }; type TestModel = { @@ -90,31 +85,32 @@ export const UIModeView: React.FC<{}> = ({ const [collapseAllCount, setCollapseAllCount] = React.useState(0); const [isDisconnected, setIsDisconnected] = React.useState(false); const [hasBrowsers, setHasBrowsers] = React.useState(true); + const [testServerConnection, setTestServerConnection] = React.useState(); const inputRef = React.useRef(null); const reloadTests = React.useCallback(() => { + const connection = new TestServerConnection(); + wireConnectionListeners(connection); + connection.onClose(() => setIsDisconnected(true)); + setTestServerConnection(connection); setIsLoading(true); setWatchedTreeIds({ value: new Set() }); updateRootSuite(baseFullConfig, new TeleSuite('', 'root'), [], undefined); - refreshRootSuite().then(async () => { + (async () => { + const status = await connection.runGlobalSetup(); + if (status === 'passed') + await refreshRootSuite(connection); setIsLoading(false); - const { hasBrowsers } = await sendMessage('checkBrowsers'); + const { hasBrowsers } = await connection.checkBrowsers(); setHasBrowsers(hasBrowsers); - }); + })(); }, []); React.useEffect(() => { inputRef.current?.focus(); setIsLoading(true); - connect({ onEvent: dispatchEvent, onClose: () => setIsDisconnected(true) }).then(send => { - sendMessage = async (method, params) => { - const logForTest = (window as any).__logForTest; - logForTest?.({ method, params }); - await send(method, params); - }; - reloadTests(); - }); + reloadTests(); }, [reloadTests]); updateRootSuite = React.useCallback((config: reporterTypes.FullConfig, rootSuite: reporterTypes.Suite, loadErrors: reporterTypes.TestError[], newProgress: Progress | undefined) => { @@ -139,6 +135,8 @@ export const UIModeView: React.FC<{}> = ({ }, [projectFilters, runningState]); const runTests = React.useCallback((mode: 'queue-if-busy' | 'bounce-if-busy', testIds: Set) => { + if (!testServerConnection) + return; if (mode === 'bounce-if-busy' && runningState) return; @@ -166,7 +164,7 @@ export const UIModeView: React.FC<{}> = ({ setProgress({ total: 0, passed: 0, failed: 0, skipped: 0 }); setRunningState({ testIds }); - await sendMessage('runTests', { testIds: [...testIds], projects: [...projectFilters].filter(([_, v]) => v).map(([p]) => p) }); + await testServerConnection.runTests({ testIds: [...testIds], projects: [...projectFilters].filter(([_, v]) => v).map(([p]) => p) }); // Clear pending tests in case of interrupt. for (const test of testModel.rootSuite?.allTests() || []) { if (test.results[0]?.duration === -1) @@ -175,13 +173,15 @@ export const UIModeView: React.FC<{}> = ({ setTestModel({ ...testModel }); setRunningState(undefined); }); - }, [projectFilters, runningState, testModel]); + }, [projectFilters, runningState, testModel, testServerConnection]); React.useEffect(() => { + if (!testServerConnection) + return; const onShortcutEvent = (e: KeyboardEvent) => { if (e.code === 'F6') { e.preventDefault(); - sendMessageNoReply('stop'); + testServerConnection?.stop().catch(() => {}); } else if (e.code === 'F5') { e.preventDefault(); reloadTests(); @@ -193,7 +193,7 @@ export const UIModeView: React.FC<{}> = ({ return () => { removeEventListener('keydown', onShortcutEvent); }; - }, [runTests, reloadTests]); + }, [runTests, reloadTests, testServerConnection]); const isRunningTest = !!runningState; const dialogRef = React.useRef(null); @@ -210,12 +210,12 @@ export const UIModeView: React.FC<{}> = ({ const installBrowsers = React.useCallback((e: React.MouseEvent) => { closeInstallDialog(e); setIsShowingOutput(true); - sendMessage('installBrowsers').then(async () => { + testServerConnection?.installBrowsers().then(async () => { setIsShowingOutput(false); - const { hasBrowsers } = await sendMessage('checkBrowsers'); + const { hasBrowsers } = await testServerConnection?.checkBrowsers(); setHasBrowsers(hasBrowsers); }); - }, [closeInstallDialog]); + }, [closeInstallDialog, testServerConnection]); return
{!hasBrowsers && @@ -275,7 +275,7 @@ export const UIModeView: React.FC<{}> = ({
Running {progress.passed}/{runningState.testIds.size} passed ({(progress.passed / runningState.testIds.size) * 100 | 0}%)
} runTests('bounce-if-busy', visibleTestIds)} disabled={isRunningTest || isLoading}> - sendMessageNoReply('stop')} disabled={!isRunningTest || isLoading}> + testServerConnection?.stop()} disabled={!isRunningTest || isLoading}> { setWatchedTreeIds({ value: new Set() }); setWatchAll(!watchAll); @@ -297,7 +297,8 @@ export const UIModeView: React.FC<{}> = ({ watchedTreeIds={watchedTreeIds} setWatchedTreeIds={setWatchedTreeIds} isLoading={isLoading} - requestedCollapseAllCount={collapseAllCount} /> + requestedCollapseAllCount={collapseAllCount} + testServerConnection={testServerConnection} /> ; @@ -390,7 +391,8 @@ const TestList: React.FC<{ setVisibleTestIds: (testIds: Set) => void, onItemSelected: (item: { treeItem?: TreeItem, testCase?: reporterTypes.TestCase, testFile?: SourceLocation }) => void, requestedCollapseAllCount: number, -}> = ({ statusFilters, projectFilters, filterText, testModel, runTests, runningState, watchAll, watchedTreeIds, setWatchedTreeIds, isLoading, onItemSelected, setVisibleTestIds, requestedCollapseAllCount }) => { + testServerConnection: TestServerConnection | undefined, +}> = ({ statusFilters, projectFilters, filterText, testModel, runTests, runningState, watchAll, watchedTreeIds, setWatchedTreeIds, isLoading, onItemSelected, setVisibleTestIds, requestedCollapseAllCount, testServerConnection }) => { const [treeState, setTreeState] = React.useState({ expandedItems: new Map() }); const [selectedTreeItemId, setSelectedTreeItemId] = React.useState(); const [collapseAllCount, setCollapseAllCount] = React.useState(requestedCollapseAllCount); @@ -464,10 +466,10 @@ const TestList: React.FC<{ // Update watch all. React.useEffect(() => { - if (isLoading) + if (isLoading || !testServerConnection) return; if (watchAll) { - sendMessageNoReply('watch', { fileNames: testTree.fileNames() }); + testServerConnection.watch({ fileNames: testTree.fileNames() }).catch(() => {}); } else { const fileNames = new Set(); for (const itemId of watchedTreeIds.value) { @@ -476,9 +478,9 @@ const TestList: React.FC<{ if (fileName) fileNames.add(fileName); } - sendMessageNoReply('watch', { fileNames: [...fileNames] }); + testServerConnection.watch({ fileNames: [...fileNames] }).catch(() => {}); } - }, [isLoading, testTree, watchAll, watchedTreeIds]); + }, [isLoading, testTree, watchAll, watchedTreeIds, testServerConnection]); const runTreeItem = (treeItem: TreeItem) => { setSelectedTreeItemId(treeItem.id); @@ -520,7 +522,7 @@ const TestList: React.FC<{ {!!treeItem.duration && treeItem.status !== 'skipped' &&
{msToString(treeItem.duration)}
} runTreeItem(treeItem)} disabled={!!runningState}> - sendMessageNoReply('open', { location: testTree.locationToOpen(treeItem) })} style={(treeItem.kind === 'group' && treeItem.subKind === 'folder') ? { visibility: 'hidden' } : {}}> + testServerConnection?.open({ location: treeItem.location }).catch(() => {})} style={(treeItem.kind === 'group' && treeItem.subKind === 'folder') ? { visibility: 'hidden' } : {}}> {!watchAll && { if (watchedTreeIds.value.has(treeItem.id)) watchedTreeIds.value.delete(treeItem.id); @@ -633,7 +635,7 @@ const throttleUpdateRootSuite = (config: reporterTypes.FullConfig, rootSuite: re throttleTimer = setTimeout(throttledAction, 250); }; -const refreshRootSuite = (): Promise => { +const refreshRootSuite = async (testServerConnection: TestServerConnection): Promise => { teleSuiteUpdater = new TeleSuiteUpdater({ onUpdate: (source, immediate) => { throttleUpdateRootSuite(source.config!, source.rootSuite || new TeleSuite('', 'root'), source.loadErrors, source.progress, immediate); @@ -643,45 +645,39 @@ const refreshRootSuite = (): Promise => { }, pathSeparator, }); - return sendMessage('listTests', {}); + return testServerConnection.listTests({}); }; -const sendMessageNoReply = (method: string, params?: any) => { - if ((window as any)._overrideProtocolForTest) { - (window as any)._overrideProtocolForTest({ method, params }).catch(() => {}); - return; - } - sendMessage(method, params).catch((e: Error) => { - // eslint-disable-next-line no-console - console.error(e); +const wireConnectionListeners = (testServerConnection: TestServerConnection) => { + testServerConnection.onListChanged(() => { + testServerConnection.listTests({}).catch(() => {}); }); -}; -const dispatchEvent = (method: string, params?: any) => { - if (method === 'listChanged') { - sendMessage('listTests', {}).catch(() => {}); - return; - } + testServerConnection.onTestFilesChanged(testFiles => { + runWatchedTests(testFiles); + }); - if (method === 'testFilesChanged') { - runWatchedTests(params.testFileNames); - return; - } - - if (method === 'stdio') { + testServerConnection.onStdio(params => { if (params.buffer) { const data = atob(params.buffer); xtermDataSource.write(data); } else { - xtermDataSource.write(params.text); + xtermDataSource.write(params.text!); } - return; - } + }); - if (method === 'listReport') + testServerConnection.onListReport(params => { teleSuiteUpdater?.dispatch('list', params); - if (method === 'testReport') + }); + + testServerConnection.onTestReport(params => { teleSuiteUpdater?.dispatch('test', params); + }); + + xtermDataSource.resize = (cols, rows) => { + xtermSize = { cols, rows }; + testServerConnection.resizeTerminal({ cols, rows }).catch(() => {}); + }; }; const outputDirForTestCase = (testCase: reporterTypes.TestCase): string | undefined => { diff --git a/tests/playwright-test/ui-mode-test-setup.spec.ts b/tests/playwright-test/ui-mode-test-setup.spec.ts index 702fbf7640..4def43d078 100644 --- a/tests/playwright-test/ui-mode-test-setup.spec.ts +++ b/tests/playwright-test/ui-mode-test-setup.spec.ts @@ -40,9 +40,12 @@ test('should run global setup and teardown', async ({ runUITest }) => { }); await page.getByTitle('Run all').click(); await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); + + await page.getByTitle('Toggle output').click(); + await expect(page.getByTestId('output')).toContainText('from-global-setup'); await page.close(); + await expect.poll(() => testProcess.outputLines()).toEqual([ - 'from-global-setup', 'from-global-teardown', ]); }); @@ -70,9 +73,11 @@ test('should teardown on sigint', async ({ runUITest }) => { }); await page.getByTitle('Run all').click(); await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); + await page.getByTitle('Toggle output').click(); + await expect(page.getByTestId('output')).toContainText('from-global-setup'); + testProcess.process.kill('SIGINT'); await expect.poll(() => testProcess.outputLines()).toEqual([ - 'from-global-setup', 'from-global-teardown', ]); }); diff --git a/tests/playwright-test/ui-mode-test-update.spec.ts b/tests/playwright-test/ui-mode-test-update.spec.ts index 67102b55c0..b55633c12f 100644 --- a/tests/playwright-test/ui-mode-test-update.spec.ts +++ b/tests/playwright-test/ui-mode-test-update.spec.ts @@ -183,7 +183,7 @@ test('should update test locations', async ({ runUITest, writeFiles, deleteFile `); const messages: any = []; - await page.exposeBinding('_overrideProtocolForTest', (_, data) => messages.push(data)); + await page.exposeBinding('_sniffProtocolForTest', (_, data) => messages.push(data)); const passesItemLocator = page.getByRole('listitem').filter({ hasText: 'passes' }); await passesItemLocator.hover(); @@ -192,7 +192,11 @@ test('should update test locations', async ({ runUITest, writeFiles, deleteFile expect(messages).toEqual([{ method: 'open', params: { - location: expect.stringContaining('a.test.ts:3'), + location: { + file: expect.stringContaining('a.test.ts'), + line: 3, + column: 11, + } }, }]); @@ -218,7 +222,11 @@ test('should update test locations', async ({ runUITest, writeFiles, deleteFile expect(messages).toEqual([{ method: 'open', params: { - location: expect.stringContaining('a.test.ts:5'), + location: { + file: expect.stringContaining('a.test.ts'), + line: 5, + column: 11, + } }, }]);