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 { Location, TestError } from 'playwright/types/testReporter';
import type * as reporterTypes from 'playwright/types/testReporter';
import * as events from './events';
export class TestServerConnection implements TestServerInterface, TestServerInterfaceEvents {
@ -110,7 +110,7 @@ export class TestServerConnection implements TestServerInterface, TestServerInte
}
async pingNoReply() {
await this._sendMessageNoReply('ping');
this._sendMessageNoReply('ping');
}
async watch(params: { fileNames: string[]; }): Promise<void> {
@ -121,11 +121,11 @@ export class TestServerConnection implements TestServerInterface, TestServerInte
this._sendMessageNoReply('watch', params);
}
async open(params: { location: Location; }): Promise<void> {
async open(params: { location: reporterTypes.Location; }): Promise<void> {
await this._sendMessage('open', params);
}
openNoReply(params: { location: Location; }) {
openNoReply(params: { location: reporterTypes.Location; }) {
this._sendMessageNoReply('open', params);
}
@ -153,7 +153,7 @@ export class TestServerConnection implements TestServerInterface, TestServerInte
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');
}
@ -161,11 +161,11 @@ export class TestServerConnection implements TestServerInterface, TestServerInte
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> {
await this._sendMessage('runTests', 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<{ status: reporterTypes.FullResult['status'] }> {
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);
}
@ -177,7 +177,6 @@ export class TestServerConnection implements TestServerInterface, TestServerInte
this._sendMessageNoReply('stopTests');
}
async closeGracefully(): Promise<void> {
await this._sendMessage('closeGracefully');
}

View File

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

View File

@ -19,7 +19,7 @@ import path from 'path';
import { registry, startTraceViewerServer } from 'playwright-core/lib/server';
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 type * as reporterTypes from '../../types/testReporter';
import { collectAffectedTestFiles, dependenciesForTestFile } from '../transform/compilationCache';
import type { FullConfigInternal } from '../common/config';
import { InternalReporter } from '../reporters/internalReporter';
@ -93,10 +93,10 @@ class TestServerDispatcher implements TestServerInterface {
private _config: FullConfigInternal;
private _globalWatcher: 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;
private _queue = Promise.resolve();
private _globalCleanup: (() => Promise<FullResult['status']>) | undefined;
private _globalCleanup: (() => Promise<reporterTypes.FullResult['status']>) | undefined;
readonly _dispatchEvent: TestServerInterfaceEventEmitters['dispatchEvent'];
constructor(config: FullConfigInternal) {
@ -116,7 +116,7 @@ class TestServerDispatcher implements TestServerInterface {
async ping() {}
async open(params: { location: Location }) {
async open(params: { location: reporterTypes.Location }) {
if (isUnderTest())
return;
// eslint-disable-next-line no-console
@ -138,7 +138,7 @@ class TestServerDispatcher implements TestServerInterface {
await installBrowsers();
}
async runGlobalSetup(): Promise<FullResult['status']> {
async runGlobalSetup(): Promise<reporterTypes.FullResult['status']> {
await this.runGlobalTeardown();
const reporter = new InternalReporter(new ListReporter());
@ -167,7 +167,7 @@ class TestServerDispatcher implements TestServerInterface {
const runner = new Runner(this._config);
return runner.listTestFiles();
} catch (e) {
const error: TestError = serializeError(e);
const error: reporterTypes.TestError = serializeError(e);
error.location = prepareErrorStack(e.stack).location;
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; }) {
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;
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();
const { testIds, projects, locations, grep } = params;
@ -244,7 +248,7 @@ class TestServerDispatcher implements TestServerInterface {
return status;
});
this._testRun = { run, stop };
await run;
return await run;
}
async watch(params: { fileNames: string[]; }) {
@ -256,7 +260,7 @@ class TestServerDispatcher implements TestServerInterface {
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);
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 cancelPromise = new ManualPromise<void>();
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 [watchAll, setWatchAll] = useSetting<boolean>('watch-all', false);
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 [collapseAllCount, setCollapseAllCount] = React.useState(0);
const [isDisconnected, setIsDisconnected] = React.useState(false);
@ -114,7 +114,6 @@ export const UIModeView: React.FC<{}> = ({
};
}, [testServerConnection]);
// This is the main routine, every time connection updates it starts the
// whole workflow.
React.useEffect(() => {
@ -141,10 +140,18 @@ export const UIModeView: React.FC<{}> = ({
});
const updateList = async () => {
setIsLoading(true);
const result = await testServerConnection.listTests({});
teleSuiteUpdater.processListReport(result.report);
setIsLoading(false);
commandQueue.current = commandQueue.current.then(async () => {
setIsLoading(true);
try {
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);
@ -221,7 +228,7 @@ export const UIModeView: React.FC<{}> = ({
return;
runTestBacklog.current = new Set([...runTestBacklog.current, ...testIds]);
runTestPromiseChain.current = runTestPromiseChain.current.then(async () => {
commandQueue.current = commandQueue.current.then(async () => {
const testIds = runTestBacklog.current;
runTestBacklog.current = new Set();
if (!testIds.size)