Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: use uimode's test server api #445

Merged
merged 1 commit into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 45 additions & 39 deletions src/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,59 +20,35 @@ import * as vscodeTypes from './vscodeTypes';
import EventEmitter from 'events';
import { WebSocketTransport } from './transport';

export type BackendServerOptions<T extends BackendClient> = {
export type BackendServerOptions = {
args: string[],
cwd: string,
envProvider: () => NodeJS.ProcessEnv,
clientFactory: () => T,
dumpIO?: boolean,
};

export class BackendServer<T extends BackendClient> {
private _vscode: vscodeTypes.VSCode;
private _options: BackendServerOptions<T>;
private _options: BackendServerOptions;
private _clientFactory: () => T;

constructor(vscode: vscodeTypes.VSCode, options: BackendServerOptions<T>) {
constructor(vscode: vscodeTypes.VSCode, clientFactory: () => T, options: BackendServerOptions) {
this._vscode = vscode;
this._clientFactory = clientFactory;
this._options = options;
}

async start(): Promise<T | null> {
const node = await findNode(this._vscode, this._options.cwd);
const serverProcess = spawn(node, this._options.args, {
cwd: this._options.cwd,
stdio: 'pipe',
env: {
...process.env,
...this._options.envProvider(),
},
});

const client = this._options.clientFactory();

serverProcess.stderr?.on('data', data => {
if (this._options.dumpIO)
console.log('[server err]', data.toString());
});
serverProcess.on('error', error => {
client._onErrorEvent.fire(error);
});
return new Promise(fulfill => {
serverProcess.stdout?.on('data', async data => {
if (this._options.dumpIO)
console.log('[server out]', data.toString());
const match = data.toString().match(/Listening on (.*)/);
if (!match)
return;
const wse = match[1];
await client._connect(wse);
fulfill(client);
});
serverProcess.on('exit', () => {
fulfill(null);
client._onCloseEvent.fire();
});
async startAndConnect(): Promise<T | null> {
const client = this._clientFactory();
const wsEndpoint = await startBackend(this._vscode, {
...this._options,
onError: error => client._onErrorEvent.fire(error),
onClose: () => client._onCloseEvent.fire(),
});
if (!wsEndpoint)
return null;
await client._connect(wsEndpoint);
return client;
}
}

Expand Down Expand Up @@ -143,3 +119,33 @@ export class BackendClient extends EventEmitter {
this._transport.close();
}
}

export async function startBackend(vscode: vscodeTypes.VSCode, options: BackendServerOptions & { onError: (error: Error) => void, onClose: () => void }): Promise<string | null> {
const node = await findNode(vscode, options.cwd);
const serverProcess = spawn(node, options.args, {
cwd: options.cwd,
stdio: 'pipe',
env: {
...process.env,
...options.envProvider(),
},
});
serverProcess.stderr?.on('data', data => {
if (options.dumpIO)
console.log('[server err]', data.toString());
});
serverProcess.on('error', options.onError);
serverProcess.on('close', options.onClose);
return new Promise(fulfill => {
serverProcess.stdout?.on('data', async data => {
if (options.dumpIO)
console.log('[server out]', data.toString());
const match = data.toString().match(/Listening on (.*)/);
if (!match)
return;
const wse = match[1];
fulfill(wse);
});
serverProcess.on('exit', () => fulfill(null));
});
}
2 changes: 1 addition & 1 deletion src/debugHighlight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import { locatorForSourcePosition, pruneAstCaches } from './babelHighlightUtil';
import { debugSessionName } from './debugSessionName';
import { replaceActionWithLocator, locatorMethodRegex } from './methodNames';
import type { Location } from './reporter';
import type { Location } from './upstream/reporter';
import { ReusedBrowser } from './reusedBrowser';
import * as vscodeTypes from './vscodeTypes';

Expand Down
92 changes: 29 additions & 63 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@ import path from 'path';
import StackUtils from 'stack-utils';
import { DebugHighlight } from './debugHighlight';
import { installBrowsers, installPlaywright } from './installer';
import { MultiMap } from './multimap';
import { RunHooks, TestConfig, getPlaywrightInfo } from './playwrightTest';
import * as reporterTypes from './reporter';
import * as reporterTypes from './upstream/reporter';
import { ReusedBrowser } from './reusedBrowser';
import { SettingsModel } from './settingsModel';
import { SettingsView } from './settingsView';
Expand Down Expand Up @@ -85,7 +84,7 @@ export class Extension implements RunHooks {
promise: Promise<void>,
finishedCallback: () => void
} | undefined;
private _diagnostics: Record<'configErrors' | 'testErrors', vscodeTypes.DiagnosticCollection>;
private _diagnostics: vscodeTypes.DiagnosticCollection;
private _treeItemObserver: TreeItemObserver;
private _testServerController: TestServerController;
private _watchQueue = Promise.resolve();
Expand Down Expand Up @@ -128,10 +127,7 @@ export class Extension implements RunHooks {
this._debugHighlight = new DebugHighlight(vscode, this._reusedBrowser);
this._debugHighlight.onErrorInDebugger(e => this._errorInDebugger(e.error, e.location));
this._workspaceObserver = new WorkspaceObserver(this._vscode, changes => this._workspaceChanged(changes));
this._diagnostics = {
testErrors: this._vscode.languages.createDiagnosticCollection('pw.testErrors.diagnostic'),
configErrors: this._vscode.languages.createDiagnosticCollection('pw.configErrors.diagnostic'),
};
this._diagnostics = this._vscode.languages.createDiagnosticCollection('pw.testErrors.diagnostic');
this._treeItemObserver = new TreeItemObserver(this._vscode);
this._watchSupport = new WatchSupport(this._vscode, this._testTree, watchData => this._watchesTriggered(watchData));
}
Expand Down Expand Up @@ -229,7 +225,7 @@ export class Extension implements RunHooks {
this._debugProfile,
this._workspaceObserver,
this._reusedBrowser,
...Object.values(this._diagnostics),
this._diagnostics,
this._treeItemObserver,
this._testServerController,
registerTerminalLinkProvider(this._vscode),
Expand Down Expand Up @@ -315,44 +311,11 @@ export class Extension implements RunHooks {
private _modelsUpdated() {
this._workspaceObserver.reset();
this._updateVisibleEditorItems();
this._reportConfigErrors();
this._updateDiagnostics();
for (const testDir of this._models.testDirs())
this._workspaceObserver.addWatchFolder(testDir);
}

private _reportConfigErrors() {
const configErrors = new MultiMap<string, reporterTypes.TestError>();
for (const model of this._models.enabledModels()) {
const error = model.takeConfigError();
if (error)
configErrors.set(model.config.configFile, error);
}

this._updateDiagnostics('configErrors', configErrors);
if (!configErrors.size)
return;
(async () => {
const showDetails = this._vscode.l10n.t('Show details');
const choice = await this._vscode.window.showErrorMessage(this._vscode.l10n.t('There are errors in Playwright configuration files.'), showDetails);
if (choice === showDetails) {
// Show the document to the user.
const document = await this._vscode.workspace.openTextDocument([...configErrors.keys()][0]);
await this._vscode.window.showTextDocument(document);
const error = [...configErrors.values()][0];
// Reveal the error line.
if (error?.location) {
const range = new this._vscode.Range(
new this._vscode.Position(Math.max(error.location.line - 4, 0), 0),
new this._vscode.Position(Math.max(error.location.line - 1, 0), Math.max(error.location.column - 1, 0)),
);
this._vscode.window.activeTextEditor?.revealRange(range);
}
// Focus problems view.
await this._vscode.commands.executeCommand('workbench.action.problems.focus');
}
})();
}

private _envProvider(): NodeJS.ProcessEnv {
const env = this._vscode.workspace.getConfiguration('playwright').get('env', {});
return Object.fromEntries(Object.entries(env).map(entry => {
Expand Down Expand Up @@ -637,24 +600,13 @@ export class Extension implements RunHooks {

const timer = setTimeout(async () => {
delete this._filesPendingListTests;
const allErrors = new Set<string>();
const errorsByFile = new MultiMap<string, reporterTypes.TestError>();
for (const model of this._models.enabledModels()) {
const filteredFiles = model.narrowDownFilesToEnabledProjects(files);
if (!filteredFiles.size)
continue;
const errors = await model.ensureTests([...filteredFiles]).catch(e => console.log(e)) || [];
for (const error of errors) {
if (!error.location || !error.message)
continue;
const key = error.location.file + ':' + error.location.line + ':' + error.message;
if (allErrors.has(key))
continue;
allErrors.add(key);
errorsByFile.set(error.location?.file, error);
}
await model.ensureTests([...filteredFiles]).catch(e => console.log(e));
}
this._updateDiagnostics('testErrors', errorsByFile);
this._updateDiagnostics();
finishedCallback();
}, 0);

Expand All @@ -672,21 +624,35 @@ export class Extension implements RunHooks {
return this._filesPendingListTests.promise;
}

private _updateDiagnostics(diagnosticsType: 'configErrors' | 'testErrors' , errorsByFile: MultiMap<string, reporterTypes.TestError>) {
const diagnosticsCollection = this._diagnostics[diagnosticsType]!;
diagnosticsCollection.clear();
for (const [file, errors] of errorsByFile) {
const diagnostics: vscodeTypes.Diagnostic[] = [];
for (const error of errors) {
diagnostics.push({
private _updateDiagnostics() {
this._diagnostics.clear();
const diagnosticsByFile = new Map<string, Map<string, vscodeTypes.Diagnostic>>();

const addError = (error: reporterTypes.TestError) => {
if (!error.location)
return;
let diagnostics = diagnosticsByFile.get(error.location.file);
if (!diagnostics) {
diagnostics = new Map();
diagnosticsByFile.set(error.location.file, diagnostics);
}
const key = `${error.location?.line}:${error.location?.column}:${error.message}`;
if (!diagnostics.has(key)) {
diagnostics.set(key, {
severity: this._vscode.DiagnosticSeverity.Error,
source: 'playwright',
range: new this._vscode.Range(Math.max(error.location!.line - 1, 0), Math.max(error.location!.column - 1, 0), error.location!.line, 0),
message: error.message!,
});
}
diagnosticsCollection.set(this._vscode.Uri.file(file), diagnostics);
};

for (const model of this._models.enabledModels()) {
for (const error of model.errors().values())
addError(error);
}
for (const [file, diagnostics] of diagnosticsByFile)
this._diagnostics.set(this._vscode.Uri.file(file), [...diagnostics.values()]);
}

private _errorInDebugger(errorStack: string, location: reporterTypes.Location) {
Expand Down
2 changes: 1 addition & 1 deletion src/listTests.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import type { TestError } from './reporter';
import type { TestError } from './upstream/reporter';

// This matches the structs in packages/playwright-test/src/runner/runner.ts.

Expand Down
2 changes: 1 addition & 1 deletion src/oopReporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import { TeleReporterEmitter } from './upstream/teleEmitter';
import { WebSocketTransport } from './transport';
import { FullResult } from './reporter';
import { FullResult } from './upstream/reporter';

class TeleReporter extends TeleReporterEmitter {
private _hasSender: boolean;
Expand Down
Loading