chore: queue run and list commands from ui (#30033)

This commit is contained in:
Pavel Feldman 2024-03-21 14:28:07 -07:00 committed by GitHub
parent e0588446c0
commit a9fc4de37e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 38 additions and 28 deletions

View File

@ -15,7 +15,7 @@
*/ */
import type { TestServerInterface, TestServerInterfaceEvents } from '@testIsomorphic/testServerInterface'; import type { TestServerInterface, TestServerInterfaceEvents } from '@testIsomorphic/testServerInterface';
import type { Location, TestError } from 'playwright/types/testReporter'; import type * as reporterTypes from 'playwright/types/testReporter';
import * as events from './events'; import * as events from './events';
export class TestServerConnection implements TestServerInterface, TestServerInterfaceEvents { export class TestServerConnection implements TestServerInterface, TestServerInterfaceEvents {
@ -110,7 +110,7 @@ export class TestServerConnection implements TestServerInterface, TestServerInte
} }
async pingNoReply() { async pingNoReply() {
await this._sendMessageNoReply('ping'); this._sendMessageNoReply('ping');
} }
async watch(params: { fileNames: string[]; }): Promise<void> { async watch(params: { fileNames: string[]; }): Promise<void> {
@ -121,11 +121,11 @@ export class TestServerConnection implements TestServerInterface, TestServerInte
this._sendMessageNoReply('watch', params); this._sendMessageNoReply('watch', params);
} }
async open(params: { location: Location; }): Promise<void> { async open(params: { location: reporterTypes.Location; }): Promise<void> {
await this._sendMessage('open', params); await this._sendMessage('open', params);
} }
openNoReply(params: { location: Location; }) { openNoReply(params: { location: reporterTypes.Location; }) {
this._sendMessageNoReply('open', params); this._sendMessageNoReply('open', params);
} }
@ -153,7 +153,7 @@ export class TestServerConnection implements TestServerInterface, TestServerInte
return await this._sendMessage('runGlobalTeardown'); 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; }> { async listFiles(): Promise<{ projects: { name: string; testDir: string; use: { testIdAttribute?: string | undefined; }; files: string[]; }[]; cliEntryPoint?: string | undefined; error?: reporterTypes.TestError | undefined; }> {
return await this._sendMessage('listFiles'); return await this._sendMessage('listFiles');
} }
@ -161,11 +161,11 @@ export class TestServerConnection implements TestServerInterface, TestServerInte
return await this._sendMessage('listTests', params); return 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> { 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<{ status: reporterTypes.FullResult['status'] }> {
await this._sendMessage('runTests', params); return await this._sendMessage('runTests', params);
} }
async findRelatedTestFiles(params: { files: string[]; }): Promise<{ testFiles: string[]; errors?: TestError[] | undefined; }> { async findRelatedTestFiles(params: { files: string[]; }): Promise<{ testFiles: string[]; errors?: reporterTypes.TestError[] | undefined; }> {
return await this._sendMessage('findRelatedTestFiles', params); return await this._sendMessage('findRelatedTestFiles', params);
} }
@ -177,7 +177,6 @@ export class TestServerConnection implements TestServerInterface, TestServerInte
this._sendMessageNoReply('stopTests'); this._sendMessageNoReply('stopTests');
} }
async closeGracefully(): Promise<void> { async closeGracefully(): Promise<void> {
await this._sendMessage('closeGracefully'); await this._sendMessage('closeGracefully');
} }

View File

@ -66,7 +66,7 @@ export interface TestServerInterface {
projects?: string[]; projects?: string[];
reuseContext?: boolean; reuseContext?: boolean;
connectWsEndpoint?: string; connectWsEndpoint?: string;
}): Promise<void>; }): Promise<{ status: reporterTypes.FullResult['status'] }>;
findRelatedTestFiles(params: { findRelatedTestFiles(params: {
files: string[]; files: string[];

View File

@ -19,7 +19,7 @@ import path from 'path';
import { registry, startTraceViewerServer } from 'playwright-core/lib/server'; import { registry, startTraceViewerServer } from 'playwright-core/lib/server';
import { ManualPromise, gracefullyProcessExitDoNotHang, isUnderTest } 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 { Transport, HttpServer } from 'playwright-core/lib/utils';
import type { FullResult, Location, TestError } from '../../types/testReporter'; import type * as reporterTypes from '../../types/testReporter';
import { collectAffectedTestFiles, dependenciesForTestFile } from '../transform/compilationCache'; import { collectAffectedTestFiles, dependenciesForTestFile } from '../transform/compilationCache';
import type { FullConfigInternal } from '../common/config'; import type { FullConfigInternal } from '../common/config';
import { InternalReporter } from '../reporters/internalReporter'; import { InternalReporter } from '../reporters/internalReporter';
@ -93,10 +93,10 @@ class TestServerDispatcher implements TestServerInterface {
private _config: FullConfigInternal; private _config: FullConfigInternal;
private _globalWatcher: Watcher; private _globalWatcher: Watcher;
private _testWatcher: Watcher; private _testWatcher: Watcher;
private _testRun: { run: Promise<FullResult['status']>, stop: ManualPromise<void> } | undefined; private _testRun: { run: Promise<reporterTypes.FullResult['status']>, stop: ManualPromise<void> } | undefined;
readonly transport: Transport; readonly transport: Transport;
private _queue = Promise.resolve(); private _queue = Promise.resolve();
private _globalCleanup: (() => Promise<FullResult['status']>) | undefined; private _globalCleanup: (() => Promise<reporterTypes.FullResult['status']>) | undefined;
readonly _dispatchEvent: TestServerInterfaceEventEmitters['dispatchEvent']; readonly _dispatchEvent: TestServerInterfaceEventEmitters['dispatchEvent'];
constructor(config: FullConfigInternal) { constructor(config: FullConfigInternal) {
@ -116,7 +116,7 @@ class TestServerDispatcher implements TestServerInterface {
async ping() {} async ping() {}
async open(params: { location: Location }) { async open(params: { location: reporterTypes.Location }) {
if (isUnderTest()) if (isUnderTest())
return; return;
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -138,7 +138,7 @@ class TestServerDispatcher implements TestServerInterface {
await installBrowsers(); await installBrowsers();
} }
async runGlobalSetup(): Promise<FullResult['status']> { async runGlobalSetup(): Promise<reporterTypes.FullResult['status']> {
await this.runGlobalTeardown(); await this.runGlobalTeardown();
const reporter = new InternalReporter(new ListReporter()); const reporter = new InternalReporter(new ListReporter());
@ -167,7 +167,7 @@ class TestServerDispatcher implements TestServerInterface {
const runner = new Runner(this._config); const runner = new Runner(this._config);
return runner.listTestFiles(); return runner.listTestFiles();
} catch (e) { } catch (e) {
const error: TestError = serializeError(e); const error: reporterTypes.TestError = serializeError(e);
error.location = prepareErrorStack(e.stack).location; error.location = prepareErrorStack(e.stack).location;
return { projects: [], error }; return { projects: [], error };
} }
@ -214,11 +214,15 @@ class TestServerDispatcher implements TestServerInterface {
} }
async runTests(params: { reporter?: string; 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; }) { async runTests(params: { reporter?: string; 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; }) {
this._queue = this._queue.then(() => this._innerRunTests(params)).catch(printInternalError); let status: reporterTypes.FullResult['status'];
this._queue = this._queue.then(async () => {
status = await this._innerRunTests(params).catch(printInternalError) || 'failed';
});
await this._queue; await this._queue;
return { status: status! };
} }
private async _innerRunTests(params: { reporter?: string; 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; }) { private async _innerRunTests(params: { reporter?: string; 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<reporterTypes.FullResult['status']> {
await this.stopTests(); await this.stopTests();
const { testIds, projects, locations, grep } = params; const { testIds, projects, locations, grep } = params;
@ -244,7 +248,7 @@ class TestServerDispatcher implements TestServerInterface {
return status; return status;
}); });
this._testRun = { run, stop }; this._testRun = { run, stop };
await run; return await run;
} }
async watch(params: { fileNames: string[]; }) { async watch(params: { fileNames: string[]; }) {
@ -256,7 +260,7 @@ class TestServerDispatcher implements TestServerInterface {
this._testWatcher.update([...files], [], true); this._testWatcher.update([...files], [], true);
} }
findRelatedTestFiles(params: { files: string[]; }): Promise<{ testFiles: string[]; errors?: TestError[] | undefined; }> { findRelatedTestFiles(params: { files: string[]; }): Promise<{ testFiles: string[]; errors?: reporterTypes.TestError[] | undefined; }> {
const runner = new Runner(this._config); const runner = new Runner(this._config);
return runner.findRelatedTestFiles('out-of-process', params.files); return runner.findRelatedTestFiles('out-of-process', params.files);
} }
@ -271,7 +275,7 @@ 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']> { export async function runTestServer(config: FullConfigInternal, options: { host?: string, port?: number }, openUI: (server: HttpServer, cancelPromise: ManualPromise<void>) => Promise<void>): Promise<reporterTypes.FullResult['status']> {
const testServer = new TestServer(config); const testServer = new TestServer(config);
const cancelPromise = new ManualPromise<void>(); const cancelPromise = new ManualPromise<void>();
const sigintWatcher = new SigIntWatcher(); const sigintWatcher = new SigIntWatcher();

View File

@ -66,7 +66,7 @@ export const UIModeView: React.FC<{}> = ({
const [runningState, setRunningState] = React.useState<{ testIds: Set<string>, itemSelectedByUser?: boolean } | undefined>(); const [runningState, setRunningState] = React.useState<{ testIds: Set<string>, itemSelectedByUser?: boolean } | undefined>();
const [watchAll, setWatchAll] = useSetting<boolean>('watch-all', false); const [watchAll, setWatchAll] = useSetting<boolean>('watch-all', false);
const [watchedTreeIds, setWatchedTreeIds] = React.useState<{ value: Set<string> }>({ value: new Set() }); const [watchedTreeIds, setWatchedTreeIds] = React.useState<{ value: Set<string> }>({ value: new Set() });
const runTestPromiseChain = React.useRef(Promise.resolve()); const commandQueue = React.useRef(Promise.resolve());
const runTestBacklog = React.useRef<Set<string>>(new Set()); const runTestBacklog = React.useRef<Set<string>>(new Set());
const [collapseAllCount, setCollapseAllCount] = React.useState(0); const [collapseAllCount, setCollapseAllCount] = React.useState(0);
const [isDisconnected, setIsDisconnected] = React.useState(false); const [isDisconnected, setIsDisconnected] = React.useState(false);
@ -114,7 +114,6 @@ export const UIModeView: React.FC<{}> = ({
}; };
}, [testServerConnection]); }, [testServerConnection]);
// This is the main routine, every time connection updates it starts the // This is the main routine, every time connection updates it starts the
// whole workflow. // whole workflow.
React.useEffect(() => { React.useEffect(() => {
@ -141,10 +140,18 @@ export const UIModeView: React.FC<{}> = ({
}); });
const updateList = async () => { const updateList = async () => {
setIsLoading(true); commandQueue.current = commandQueue.current.then(async () => {
const result = await testServerConnection.listTests({}); setIsLoading(true);
teleSuiteUpdater.processListReport(result.report); try {
setIsLoading(false); const result = await testServerConnection.listTests({});
teleSuiteUpdater.processListReport(result.report);
} catch (e) {
// eslint-disable-next-line no-console
console.log(e);
} finally {
setIsLoading(false);
}
});
}; };
setTestModel(undefined); setTestModel(undefined);
@ -221,7 +228,7 @@ export const UIModeView: React.FC<{}> = ({
return; return;
runTestBacklog.current = new Set([...runTestBacklog.current, ...testIds]); runTestBacklog.current = new Set([...runTestBacklog.current, ...testIds]);
runTestPromiseChain.current = runTestPromiseChain.current.then(async () => { commandQueue.current = commandQueue.current.then(async () => {
const testIds = runTestBacklog.current; const testIds = runTestBacklog.current;
runTestBacklog.current = new Set(); runTestBacklog.current = new Set();
if (!testIds.size) if (!testIds.size)