chore: prepare to reuse test server from ui mode (4) (#29995)
This commit is contained in:
parent
3ee13cbf2b
commit
54aca430b0
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }));
|
||||
|
|
|
|||
|
|
@ -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[]>;
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
136
packages/trace-viewer/src/ui/testServerConnection.ts
Normal file
136
packages/trace-viewer/src/ui/testServerConnection.ts
Normal 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');
|
||||
}
|
||||
}
|
||||
|
|
@ -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 => {
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
]);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
},
|
||||
}]);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue