Skip to content

Commit

Permalink
chore: allow watching files (behind a flag) (#409)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelfeldman authored Feb 22, 2024
1 parent 8f08fe4 commit 64de0fe
Show file tree
Hide file tree
Showing 10 changed files with 498 additions and 23 deletions.
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"url": "https://github.com/microsoft/playwright-vscode/issues"
},
"engines": {
"vscode": "^1.73.0"
"vscode": "^1.78.0"
},
"categories": [
"Testing"
Expand Down Expand Up @@ -87,6 +87,10 @@
"playwright.useTestServer": {
"type": "boolean",
"default": false
},
"playwright.allowWatchingFiles": {
"type": "boolean",
"default": false
}
}
},
Expand Down Expand Up @@ -123,7 +127,7 @@
"@types/glob": "^8.0.0",
"@types/node": "^18.11.9",
"@types/stack-utils": "^2.0.1",
"@types/vscode": "1.73.0",
"@types/vscode": "1.78.0",
"@types/which": "^2.0.1",
"@types/ws": "^8.5.3",
"@typescript-eslint/eslint-plugin": "^5.44.0",
Expand Down
27 changes: 21 additions & 6 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import * as vscodeTypes from './vscodeTypes';
import { WorkspaceChange, WorkspaceObserver } from './workspaceObserver';
import { TraceViewer } from './traceViewer';
import { TestServerController } from './testServerController';
import { type Watch, WatchSupport } from './watchSupport';

const stackUtils = new StackUtils({
cwd: '/ensure_absolute_paths'
Expand Down Expand Up @@ -80,6 +81,7 @@ export class Extension implements RunHooks {
private _traceViewer: TraceViewer;
private _settingsModel: SettingsModel;
private _settingsView!: SettingsView;
private _watchSupport: WatchSupport;
private _filesPendingListTests: {
files: Set<string>,
timer: NodeJS.Timeout,
Expand All @@ -89,6 +91,7 @@ export class Extension implements RunHooks {
private _diagnostics: Record<'configErrors' | 'testErrors', vscodeTypes.DiagnosticCollection>;
private _treeItemObserver: TreeItemObserver;
private _testServerController: TestServerController;
private _watchQueue = Promise.resolve();

constructor(vscode: vscodeTypes.VSCode) {
this._vscode = vscode;
Expand Down Expand Up @@ -127,6 +130,7 @@ export class Extension implements RunHooks {
configErrors: this._vscode.languages.createDiagnosticCollection('pw.configErrors.diagnostic'),
};
this._treeItemObserver = new TreeItemObserver(this._vscode);
this._watchSupport = new WatchSupport(this._vscode, this._playwrightTest, this._testTree, watchData => this._watchesTriggered(watchData));
}

async onWillRunTests(config: TestConfig, debug: boolean) {
Expand Down Expand Up @@ -350,20 +354,21 @@ export class Extension implements RunHooks {
let runProfile = this._runProfiles.get(keyPrefix + ':run');
const projectTag = this._testTree.projectTag(project);
const isDefault = false;
const supportsContinuousRun = this._settingsModel.allowWatchingFiles.get();
if (!runProfile) {
runProfile = this._testController.createRunProfile(`${projectPrefix}${folderName}${path.sep}${configName}`, this._vscode.TestRunProfileKind.Run, this._scheduleTestRunRequest.bind(this, configFile, project.name, false), isDefault, projectTag);
runProfile = this._testController.createRunProfile(`${projectPrefix}${folderName}${path.sep}${configName}`, this._vscode.TestRunProfileKind.Run, this._scheduleTestRunRequest.bind(this, configFile, project.name, false), isDefault, projectTag, supportsContinuousRun);
this._runProfiles.set(keyPrefix + ':run', runProfile);
}
let debugProfile = this._runProfiles.get(keyPrefix + ':debug');
if (!debugProfile) {
debugProfile = this._testController.createRunProfile(`${projectPrefix}${folderName}${path.sep}${configName}`, this._vscode.TestRunProfileKind.Debug, this._scheduleTestRunRequest.bind(this, configFile, project.name, true), isDefault, projectTag);
debugProfile = this._testController.createRunProfile(`${projectPrefix}${folderName}${path.sep}${configName}`, this._vscode.TestRunProfileKind.Debug, this._scheduleTestRunRequest.bind(this, configFile, project.name, true), isDefault, projectTag, supportsContinuousRun);
this._runProfiles.set(keyPrefix + ':debug', debugProfile);
}
}

private _scheduleTestRunRequest(configFile: string, projectName: string, isDebug: boolean, request: vscodeTypes.TestRunRequest) {
private _scheduleTestRunRequest(configFile: string, projectName: string, isDebug: boolean, request: vscodeTypes.TestRunRequest, cancellationToken?: vscodeTypes.CancellationToken) {
// Never run tests concurrently.
if (this._testRun)
if (this._testRun && !request.continuous)
return;

// We can't dispose projects (and bind them to TestProject instances) because otherwise VS Code would forget its selection state.
Expand All @@ -375,6 +380,11 @@ export class Extension implements RunHooks {
if (!project)
return;

if (request.continuous) {
this._watchSupport.addToWatch(project, request.include, cancellationToken!);
return;
}

// VSCode will issue several test run requests (one per enabled run profile). Sometimes
// these profiles belong to the same config and we only want to run tests once per config.
// So we collect all requests and sort them out in the microtask.
Expand All @@ -397,7 +407,7 @@ export class Extension implements RunHooks {
}
}

private async _runMatchingTests(testRunInfos: TestRunInfo[], mode: 'run' | 'debug') {
private async _runMatchingTests(testRunInfos: TestRunInfo[], mode: 'run' | 'debug' | 'watch') {
this._completedSteps.clear();
this._executionLinesChanged();

Expand All @@ -409,7 +419,7 @@ export class Extension implements RunHooks {
// selected test items.
const rootItems: vscodeTypes.TestItem[] = [];
this._testController.items.forEach(item => rootItems.push(item));
const requestWithDeps = new this._vscode.TestRunRequest(rootItems, [], undefined);
const requestWithDeps = new this._vscode.TestRunRequest(rootItems, [], undefined, mode === 'watch');

// Global errors are attributed to the first test item in the request.
// If the request is global, find the first root test item (folder, file) that has
Expand Down Expand Up @@ -525,6 +535,11 @@ located next to Run / Debug Tests toolbar buttons.`);
// Workspace change can be deferred, make sure editors are
// decorated.
await this._updateVisibleEditorItems();
await this._watchSupport.workspaceChanged(change);
}

private _watchesTriggered(watches: Watch[]) {
this._watchQueue = this._watchQueue.then(() => this._runMatchingTests(watches, 'watch'));
}

private async _runTest(
Expand Down
49 changes: 46 additions & 3 deletions src/playwrightTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import { spawn } from 'child_process';
import path from 'path';
import { debugSessionName } from './debugSessionName';
import { ConfigListFilesReport } from './listTests';
import { ConfigFindRelatedTestFilesReport, ConfigListFilesReport } from './listTests';
import type { TestError, Entry, StepBeginParams, StepEndParams, TestBeginParams, TestEndParams } from './oopReporter';
import { ReporterServer } from './reporterServer';
import { findNode, spawnAsync } from './utils';
Expand Down Expand Up @@ -170,8 +170,12 @@ export class PlaywrightTest {
return { entries, errors };
}

private _useTestServer(config: TestConfig) {
return config.version >= 1.43 || this._settingsModel.useTestServer.get();
}

private async _test(config: TestConfig, locations: string[], mode: 'list' | 'run', options: PlaywrightTestOptions, listener: TestListener, token: vscodeTypes.CancellationToken): Promise<void> {
if (config.version >= 1.43 || this._settingsModel.useTestServer.get())
if (this._useTestServer(config))
await this._testWithServer(config, locations, mode, options, listener, token);
else
await this._testWithCLI(config, locations, mode, options, listener, token);
Expand Down Expand Up @@ -245,8 +249,10 @@ export class PlaywrightTest {
private async _testWithServer(config: TestConfig, locations: string[], mode: 'list' | 'run', options: PlaywrightTestOptions, listener: TestListener, token: vscodeTypes.CancellationToken): Promise<void> {
const reporterServer = new ReporterServer(this._vscode);
const testServer = await this._testServerController.testServerFor(config);
if (!testServer)
if (token?.isCancellationRequested)
return;
if (!testServer)
return this._testWithCLI(config, locations, mode, options, listener, token);
if (token?.isCancellationRequested)
return;
const env = await reporterServer.env({ selfDestruct: false });
Expand All @@ -269,6 +275,43 @@ export class PlaywrightTest {
await reporterServer.wireTestListener(listener, token);
}

async findRelatedTestFiles(config: TestConfig, files: string[]): Promise<ConfigFindRelatedTestFilesReport> {
if (this._useTestServer(config))
return await this._findRelatedTestFilesServer(config, files);
else
return await this._findRelatedTestFilesCLI(config, files);
}

async _findRelatedTestFilesCLI(config: TestConfig, files: string[]): Promise<ConfigFindRelatedTestFilesReport> {
const configFolder = path.dirname(config.configFile);
const configFile = path.basename(config.configFile);
const allArgs = [config.cli, 'find-related-test-files', '-c', configFile, ...files];
{
// For tests.
this._log(`${escapeRegex(path.relative(config.workspaceFolder, configFolder))}> playwright find-related-test-files -c ${configFile}`);
}
try {
const output = await this._runNode(allArgs, configFolder);
const result = JSON.parse(output) as ConfigFindRelatedTestFilesReport;
return result;
} catch (error: any) {
return {
errors: [{
location: { file: configFile, line: 0, column: 0 },
message: error.message,
}],
testFiles: files,
};
}
}

async _findRelatedTestFilesServer(config: TestConfig, files: string[]): Promise<ConfigFindRelatedTestFilesReport> {
const testServer = await this._testServerController.testServerFor(config);
if (!testServer)
return await this._findRelatedTestFilesCLI(config, files);
return await testServer.findRelatedTestFiles({ files });
}

async debugTests(vscode: vscodeTypes.VSCode, config: TestConfig, projectNames: string[], testDirs: string[], settingsEnv: NodeJS.ProcessEnv, locations: string[] | null, listener: TestListener, parametrizedTestTitle: string | undefined, token: vscodeTypes.CancellationToken) {
const configFolder = path.dirname(config.configFile);
const configFile = path.basename(config.configFile);
Expand Down
2 changes: 2 additions & 0 deletions src/settingsModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export class SettingsModel implements vscodeTypes.Disposable {
showBrowser: Setting<boolean>;
showTrace: Setting<boolean>;
useTestServer: Setting<boolean>;
allowWatchingFiles: Setting<boolean>;

constructor(vscode: vscodeTypes.VSCode) {
this._vscode = vscode;
Expand All @@ -34,6 +35,7 @@ export class SettingsModel implements vscodeTypes.Disposable {
this.showBrowser = this._createSetting('reuseBrowser');
this.showTrace = this._createSetting('showTrace');
this.useTestServer = this._createSetting('useTestServer');
this.allowWatchingFiles = this._createSetting('allowWatchingFiles');

this.showBrowser.onChange(enabled => {
if (enabled && this.showTrace.get())
Expand Down
5 changes: 5 additions & 0 deletions src/testServerController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import { BackendClient, BackendServer } from './backend';
import { ConfigFindRelatedTestFilesReport } from './listTests';
import { TestConfig } from './playwrightTest';
import * as vscodeTypes from './vscodeTypes';

Expand Down Expand Up @@ -71,6 +72,10 @@ class TestServer extends BackendClient {
await this.send('list', params);
}

findRelatedTestFiles(params: { files: string[]; }): Promise<ConfigFindRelatedTestFilesReport> {
return this.send('findRelatedTestFiles', params);
}

async test(params: any) {
await this.send('test', params);
}
Expand Down
Loading

0 comments on commit 64de0fe

Please sign in to comment.