chore: prepare to reuse test server from ui mode (4) (#29995)

This commit is contained in:
Pavel Feldman 2024-03-19 13:00:49 -07:00 committed by GitHub
parent 3ee13cbf2b
commit 54aca430b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 263 additions and 106 deletions

View file

@ -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,
},
});

View file

@ -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 }));

View file

@ -15,6 +15,7 @@
*/
import type * as reporterTypes from '../../types/testReporter';
import type { Event } from './events';
export interface TestServerInterface {
ping(): Promise<void>;
@ -31,6 +32,10 @@ export interface TestServerInterface {
installBrowsers(): Promise<void>;
runGlobalSetup(): Promise<reporterTypes.FullResult['status']>;
runGlobalTeardown(): Promise<reporterTypes.FullResult['status']>;
listFiles(): Promise<{
projects: {
name: string;
@ -69,10 +74,11 @@ export interface TestServerInterface {
closeGracefully(): Promise<void>;
}
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<void>;
onListReport: Event<any>;
onTestReport: Event<any>;
onStdio: Event<{ type: 'stdout' | 'stderr', text?: string, buffer?: string }>;
onListChanged: Event<void>;
onTestFilesChanged: Event<string[]>;
}

View file

@ -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) {

View file

@ -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<FullResult['status']>) | 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<FullResult['status']> {
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<HttpServer> {
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<FullResult['status']>, stop: ManualPromise<void> } | undefined;
readonly transport: Transport;
private _queue = Promise.resolve();
private _globalCleanup: (() => Promise<FullResult['status']>) | 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<FullResult['status']> {
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<void>) => Promise<void>): Promise<FullResult['status']> {
const testServer = new TestServer(config);
const globalSetupStatus = await testServer.runGlobalSetup();
if (globalSetupStatus !== 'passed')
return globalSetupStatus;
const cancelPromise = new ManualPromise<void>();
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 = {

View file

@ -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<void>;
readonly onListReport: Event<any>;
readonly onTestReport: Event<any>;
readonly onStdio: Event<{ type: 'stderr' | 'stdout'; text?: string | undefined; buffer?: string | undefined; }>;
readonly onListChanged: Event<void>;
readonly onTestFilesChanged: Event<string[]>;
private _onCloseEmitter = new EventEmitter<void>();
private _onListReportEmitter = new EventEmitter<any>();
private _onTestReportEmitter = new EventEmitter<any>();
private _onStdioEmitter = new EventEmitter<{ type: 'stderr' | 'stdout'; text?: string | undefined; buffer?: string | undefined; }>();
private _onListChangedEmitter = new EventEmitter<void>();
private _onTestFilesChangedEmitter = new EventEmitter<string[]>();
private _send: Promise<(method: string, params?: any) => Promise<any>>;
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<any> {
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<void> {
await this._sendMessage('ping');
}
async watch(params: { fileNames: string[]; }): Promise<void> {
await this._sendMessage('watch', params);
}
async open(params: { location: Location; }): Promise<void> {
await this._sendMessage('open', params);
}
async resizeTerminal(params: { cols: number; rows: number; }): Promise<void> {
await this._sendMessage('resizeTerminal', params);
}
async checkBrowsers(): Promise<{ hasBrowsers: boolean; }> {
return await this._sendMessage('checkBrowsers');
}
async installBrowsers(): Promise<void> {
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<void> {
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<void> {
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<void> {
await this._sendMessage('stop');
}
async closeGracefully(): Promise<void> {
await this._sendMessage('closeGracefully');
}
}

View file

@ -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<any> = 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<TestServerConnection>();
const inputRef = React.useRef<HTMLInputElement>(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<string>) => {
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<HTMLDialogElement>(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 <div className='vbox ui-mode'>
{!hasBrowsers && <dialog ref={dialogRef}>
@ -275,7 +275,7 @@ export const UIModeView: React.FC<{}> = ({
<div>Running {progress.passed}/{runningState.testIds.size} passed ({(progress.passed / runningState.testIds.size) * 100 | 0}%)</div>
</div>}
<ToolbarButton icon='play' title='Run all' onClick={() => runTests('bounce-if-busy', visibleTestIds)} disabled={isRunningTest || isLoading}></ToolbarButton>
<ToolbarButton icon='debug-stop' title='Stop' onClick={() => sendMessageNoReply('stop')} disabled={!isRunningTest || isLoading}></ToolbarButton>
<ToolbarButton icon='debug-stop' title='Stop' onClick={() => testServerConnection?.stop()} disabled={!isRunningTest || isLoading}></ToolbarButton>
<ToolbarButton icon='eye' title='Watch all' toggled={watchAll} onClick={() => {
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} />
</div>
</SplitView>
</div>;
@ -390,7 +391,8 @@ const TestList: React.FC<{
setVisibleTestIds: (testIds: Set<string>) => 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<TreeState>({ expandedItems: new Map() });
const [selectedTreeItemId, setSelectedTreeItemId] = React.useState<string | undefined>();
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<string>();
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' && <div className='ui-mode-list-item-time'>{msToString(treeItem.duration)}</div>}
<Toolbar noMinHeight={true} noShadow={true}>
<ToolbarButton icon='play' title='Run' onClick={() => runTreeItem(treeItem)} disabled={!!runningState}></ToolbarButton>
<ToolbarButton icon='go-to-file' title='Open in VS Code' onClick={() => sendMessageNoReply('open', { location: testTree.locationToOpen(treeItem) })} style={(treeItem.kind === 'group' && treeItem.subKind === 'folder') ? { visibility: 'hidden' } : {}}></ToolbarButton>
<ToolbarButton icon='go-to-file' title='Open in VS Code' onClick={() => testServerConnection?.open({ location: treeItem.location }).catch(() => {})} style={(treeItem.kind === 'group' && treeItem.subKind === 'folder') ? { visibility: 'hidden' } : {}}></ToolbarButton>
{!watchAll && <ToolbarButton icon='eye' title='Watch' onClick={() => {
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<void> => {
const refreshRootSuite = async (testServerConnection: TestServerConnection): Promise<void> => {
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<void> => {
},
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 => {

View file

@ -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',
]);
});

View file

@ -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,
}
},
}]);