Skip to content

Commit f3d922f

Browse files
authored
refactor: improve performance and coverage of @vscode-wdio/test (#75)
1 parent 3723927 commit f3d922f

20 files changed

Lines changed: 614 additions & 483 deletions

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"test:e2e": "pnpm --filter @vscode-wdio/e2e run test:e2e",
3232
"test:smoke": "pnpm --filter @vscode-wdio/e2e run test:smoke",
3333
"coverage": "vitest --run --coverage",
34+
"coverage:ui": "pnpx sirv-cli ./coverage --single",
3435
"graph": "pnpm run build --graph assets/build.png",
3536
"version": "pnpm --filter @vscode-wdio/release run changelog && git add CHANGELOG.md",
3637
"postversion": "git show",

packages/vscode-wdio-test/src/converter.ts

Lines changed: 2 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,9 @@
11
import path from 'node:path'
22

33
import * as vscode from 'vscode'
4-
import type { ReadSpecsResult } from '@vscode-wdio/types/server'
5-
import type { TestData, SourceRange, VscodeTestData } from '@vscode-wdio/types/test'
6-
/**
7-
* Convert the parser's TestData to VSCode compatible TestData
8-
*
9-
* @param testCases Array of TestData from the parser
10-
* @param document VSCode document for position conversion
11-
* @returns Array of VscodeTestData
12-
*/
13-
export async function convertTestData(testData: ReadSpecsResult): Promise<VscodeTestData[]> {
14-
try {
15-
const uri = convertPathToUri(testData.spec)
16-
17-
return testData.tests.map((testCase) => _convertTestData(testCase, uri))
18-
} catch (error) {
19-
throw new Error(`Failed to parse or adapt test cases: ${(error as Error).message}`)
20-
}
21-
}
22-
23-
/**
24-
* Convert a single TestData to use VSCode Range
25-
*
26-
* @param testCase TestData from the parser
27-
* @param uri VSCode Uri for Spec file
28-
* @returns VscodeTestData
29-
*/
30-
function _convertTestData(testCase: TestData, uri: vscode.Uri): VscodeTestData {
31-
// Convert SourceRange to VSCode Range
32-
const vsCodeRange = convertSourceRangeToVSCodeRange(testCase.range)
33-
34-
// Convert children recursively
35-
const vsCodeChildren = testCase.children.map((child) => _convertTestData(child, uri))
36-
37-
return {
38-
type: testCase.type,
39-
name: testCase.name,
40-
uri,
41-
range: vsCodeRange,
42-
children: vsCodeChildren,
43-
}
44-
}
4+
import type { SourceRange } from '@vscode-wdio/types/test'
455

46-
/**
47-
* Convert a SourceRange to a VSCode Range
48-
*
49-
* @param sourceRange SourceRange with offsets
50-
* @param document VSCode document for position conversion
51-
* @returns VSCode Range
52-
*/
53-
function convertSourceRangeToVSCodeRange(sourceRange: SourceRange): vscode.Range {
6+
export function convertSourceRangeToVSCodeRange(sourceRange: SourceRange): vscode.Range {
547
const start = new vscode.Position(sourceRange.start.line, sourceRange.start.column)
558
const end = new vscode.Position(sourceRange.end.line, sourceRange.end.column)
569
return new vscode.Range(start, end)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
/* c8 ignore start */
12
export { RepositoryManager } from './manager.js'
23
export { TestReporter } from './reporter.js'
34
export { TestfileWatcher } from './watcher.js'
5+
/* c8 ignore stop */

packages/vscode-wdio-test/src/manager.ts

Lines changed: 51 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,20 @@ import type { IRepositoryManager, ITestRepository } from '@vscode-wdio/types/tes
2222

2323
const LOADING_TEST_ITEM_ID = '_resolving'
2424

25-
export class RepositoryManager extends MetadataRepository implements IRepositoryManager {
25+
export class RepositoryManager implements IRepositoryManager {
2626
private readonly _repos = new Set<ITestRepository>()
2727
private _loadingTestItem: vscode.TestItem
2828
private _workspaceTestItems: vscode.TestItem[] = []
2929
private _wdioConfigTestItems: vscode.TestItem[] = []
3030
private _isInitialized = false
31-
private isCreatedDefaultProfile = false
31+
private _isCreatedDefaultProfile = false
3232

3333
constructor(
3434
public readonly controller: vscode.TestController,
3535
public readonly configManager: ExtensionConfigManager,
36-
private readonly workerManager: IWorkerManager
36+
private readonly _workerManager: IWorkerManager,
37+
private readonly _metadata = new MetadataRepository()
3738
) {
38-
super()
3939
this._loadingTestItem = this.controller.createTestItem(LOADING_TEST_ITEM_ID, 'Resolving WebdriverIO Tests...')
4040
this._loadingTestItem.sortText = '.0' // show at first line
4141
this._loadingTestItem.busy = true
@@ -56,17 +56,24 @@ export class RepositoryManager extends MetadataRepository implements IRepository
5656
.then(async () => await this.initialize())
5757
.then(async () => await Promise.all(this.repos.map(async (repo) => await repo.discoverAllTests())))
5858
.then(() => this.registerToTestController())
59-
.then(() => this.workerManager.reorganize(configManager.getWdioConfigPaths()))
59+
.then(() => this._workerManager.reorganize(configManager.getWdioConfigPaths()))
6060
})
6161
}
6262

6363
public get repos() {
6464
return Array.from(this._repos)
6565
}
6666

67+
public getMetadata(testItem: vscode.TestItem) {
68+
return this._metadata.getMetadata(testItem)
69+
}
70+
71+
public getRepository(testItem: vscode.TestItem): ITestRepository {
72+
return this._metadata.getRepository(testItem)
73+
}
74+
6775
public async initialize() {
6876
const workspaces = this.configManager.workspaces
69-
7077
if (workspaces.length < 1) {
7178
log.info('No workspaces is detected.')
7279
return
@@ -79,11 +86,11 @@ export class RepositoryManager extends MetadataRepository implements IRepository
7986

8087
this._workspaceTestItems = await Promise.all(
8188
workspaces.map(async (workspace) => {
82-
const workspaceTestItem = this.createWorkspaceTestItem(workspace.workspaceFolder)
89+
const workspaceTestItem = this._createWorkspaceTestItem(workspace.workspaceFolder)
8390
for (const wdioConfigFile of workspace.wdioConfigFiles) {
84-
await this.createWdioConfigTestItem(workspace.workspaceFolder, workspaceTestItem, wdioConfigFile)
91+
await this._createWdioConfigTestItem(workspace.workspaceFolder, workspaceTestItem, wdioConfigFile)
8592

86-
this.isCreatedDefaultProfile = true
93+
this._isCreatedDefaultProfile = true
8794
}
8895
return workspaceTestItem
8996
})
@@ -97,13 +104,13 @@ export class RepositoryManager extends MetadataRepository implements IRepository
97104
return item.uri?.fsPath === workspaceFolder.uri.fsPath
98105
})
99106
for (const workspaceTestItem of affectedWorkspaceItems) {
100-
const configTestItem = await this.createWdioConfigTestItem(
107+
const configTestItem = await this._createWdioConfigTestItem(
101108
workspaceFolder,
102109
workspaceTestItem,
103110
wdioConfigPath
104111
)
105112

106-
const repo = this.getRepository(configTestItem)
113+
const repo = this._metadata.getRepository(configTestItem)
107114
await repo.discoverAllTests()
108115
if (!this.configManager.isMultiWorkspace) {
109116
this.controller.items.add(configTestItem)
@@ -118,12 +125,12 @@ export class RepositoryManager extends MetadataRepository implements IRepository
118125
log.debug(`Remove the config file from ${affectedWorkspaceItems.length} workspace(s)`)
119126
const configUri = convertPathToUri(wdioConfigPath)
120127
for (const workspaceItem of affectedWorkspaceItems) {
121-
const config = workspaceItem.children.get(this.generateConfigTestItemId(workspaceItem, configUri))
128+
const config = workspaceItem.children.get(this._generateConfigTestItemId(workspaceItem, configUri))
122129
if (!config) {
123130
continue
124131
}
125132
log.debug(`Remove the TestItem: ${config.id}`)
126-
const targetRepo = this.getRepository(config)
133+
const targetRepo = this._metadata.getRepository(config)
127134
targetRepo.dispose()
128135
this._repos.delete(targetRepo)
129136

@@ -134,7 +141,7 @@ export class RepositoryManager extends MetadataRepository implements IRepository
134141
this.controller.items.delete(workspaceItem.id)
135142
}
136143
} else {
137-
const targetId = this.generateConfigTestItemId(workspaceItem, configUri)
144+
const targetId = this._generateConfigTestItemId(workspaceItem, configUri)
138145
log.debug(`Remove Configuration from the controller: ${targetId}`)
139146
this.controller.items.delete(targetId)
140147
}
@@ -155,103 +162,80 @@ export class RepositoryManager extends MetadataRepository implements IRepository
155162
log.debug('Successfully registered.')
156163
}
157164

158-
private createWorkspaceTestItem(workspaceFolder: vscode.WorkspaceFolder) {
165+
private _createWorkspaceTestItem(workspaceFolder: vscode.WorkspaceFolder) {
159166
const workspaceItem = this.controller.createTestItem(
160167
`workspace:${workspaceFolder.uri.fsPath}`,
161168
workspaceFolder.name,
162169
workspaceFolder.uri
163170
)
164-
this.setMetadata(workspaceItem, {
165-
uri: workspaceFolder.uri,
166-
isWorkspace: true,
167-
isConfigFile: false,
168-
isSpecFile: false,
169-
isTestcase: false,
170-
})
171+
this._metadata.createWorkspaceMetadata(workspaceItem, { uri: workspaceFolder.uri })
171172
return workspaceItem
172173
}
173174

174-
private async createWdioConfigTestItem(
175+
private async _createWdioConfigTestItem(
175176
workspaceFolder: vscode.WorkspaceFolder,
176177
workspaceTestItem: vscode.TestItem,
177178
wdioConfigPath: string
178179
) {
179180
const uri = convertPathToUri(wdioConfigPath)
180181
const configItem = this.controller.createTestItem(
181-
this.generateConfigTestItemId(workspaceTestItem, uri),
182+
this._generateConfigTestItemId(workspaceTestItem, uri),
182183
basename(wdioConfigPath),
183184
uri
184185
)
185186

186187
workspaceTestItem.children.add(configItem)
187188
this._wdioConfigTestItems.push(configItem)
188189

189-
const repo = new TestRepository(
190+
const repository = new TestRepository(
190191
this.configManager,
191192
this.controller,
192193
wdioConfigPath,
193194
configItem,
194-
this.workerManager,
195+
this._workerManager,
195196
workspaceFolder
196197
)
197-
this._repos.add(repo)
198+
this._repos.add(repository)
198199

199200
configItem.description = relative(workspaceTestItem.uri!.fsPath, dirname(wdioConfigPath))
200201

201-
this.setMetadata(configItem, {
202-
uri,
203-
isWorkspace: false,
204-
isConfigFile: true,
205-
isSpecFile: false,
206-
isTestcase: false,
207-
repository: repo,
208-
runProfiles: createRunProfile.call(this, configItem, !this.isCreatedDefaultProfile),
209-
})
202+
const runProfiles = createRunProfile.call(this, configItem, !this._isCreatedDefaultProfile)
203+
204+
this._metadata.createWdioConfigFileMetadata(configItem, { uri, repository, runProfiles })
210205
return configItem
211206
}
212207

213-
private generateConfigTestItemId(workspaceTestItem: vscode.TestItem, configUri: vscode.Uri) {
208+
private _generateConfigTestItemId(workspaceTestItem: vscode.TestItem, configUri: vscode.Uri) {
214209
return [workspaceTestItem.id, `config:${configUri.fsPath}`].join(TEST_ID_SEPARATOR)
215210
}
216211

217212
/**
218213
* Refresh WebdriverIO tests
219214
*/
220215
public async refreshTests(): Promise<void> {
221-
return vscode.window.withProgress(
222-
{
223-
location: vscode.ProgressLocation.Notification,
224-
title: 'Reloading WebdriverIO tests...',
225-
cancellable: false,
226-
},
227-
async () => {
228-
try {
229-
if (!this._isInitialized) {
230-
await this.initialize()
231-
}
232-
for (const repo of this._repos) {
233-
// Clear existing tests
234-
repo.clearTests()
235-
// Discover tests again
236-
await repo.discoverAllTests()
237-
}
238-
239-
vscode.window.showInformationMessage('WebdriverIO tests reloaded successfully')
240-
} catch (error) {
241-
const errorMessage = error instanceof Error ? error.message : String(error)
242-
log.error(`Failed to reload tests: ${errorMessage}`)
243-
vscode.window.showErrorMessage(`Failed to reload WebdriverIO tests: ${errorMessage}`)
244-
}
216+
this.controller.items.replace([this._loadingTestItem])
217+
try {
218+
if (!this._isInitialized) {
219+
await this.initialize()
245220
}
246-
)
221+
await Promise.all(
222+
this.repos.map(async (repo) => {
223+
return await repo.discoverAllTests()
224+
})
225+
)
226+
227+
this.registerToTestController()
228+
await this._workerManager.reorganize(this.configManager.getWdioConfigPaths())
229+
} catch (error) {
230+
this.controller.items.replace([])
231+
const errorMessage = error instanceof Error ? error.message : String(error)
232+
log.error(`Failed to reload tests: ${errorMessage}`)
233+
vscode.window.showErrorMessage(`Failed to reload WebdriverIO tests: ${errorMessage}`)
234+
}
247235
}
248236

249237
public async dispose() {
250-
await Promise.all(
251-
Array.from(this._repos).map(async (repo) => {
252-
await repo.dispose()
253-
})
254-
)
238+
await Promise.all(this.repos.map(async (repo) => repo.dispose()))
255239
this._repos.clear()
256240
this._workspaceTestItems = []
257241
this._wdioConfigTestItems = []

packages/vscode-wdio-test/src/metadata.ts

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import type { IMetadataRepository, TestItemMetadata } from '@vscode-wdio/types/test'
1+
import type { ITestRepository, TestItemMetadata, TestType } from '@vscode-wdio/types/test'
22
import type * as vscode from 'vscode'
33

4-
export class MetadataRepository implements IMetadataRepository {
5-
private static testMetadataRepository = new WeakMap<vscode.TestItem, TestItemMetadata>()
4+
export class MetadataRepository {
5+
private static _testMetadataRepository = new WeakMap<vscode.TestItem, TestItemMetadata>()
6+
67
public getMetadata(testItem: vscode.TestItem) {
7-
const metadata = MetadataRepository.testMetadataRepository.get(testItem)
8+
const metadata = MetadataRepository._testMetadataRepository.get(testItem)
89
if (!metadata) {
910
throw new Error("The metadata for TestItem is not set. This is extension's bug.")
1011
}
@@ -19,7 +20,60 @@ export class MetadataRepository implements IMetadataRepository {
1920
return metadata.repository
2021
}
2122

22-
public setMetadata(testItem: vscode.TestItem, metadata: TestItemMetadata) {
23-
MetadataRepository.testMetadataRepository.set(testItem, metadata)
23+
protected setMetadata(testItem: vscode.TestItem, metadata: TestItemMetadata) {
24+
MetadataRepository._testMetadataRepository.set(testItem, metadata)
25+
}
26+
27+
public createTestMetadata(
28+
testItem: vscode.TestItem,
29+
options: { uri: vscode.Uri; repository: ITestRepository; testType: TestType }
30+
) {
31+
this.setMetadata(testItem, {
32+
uri: options.uri,
33+
isWorkspace: false,
34+
isConfigFile: false,
35+
isSpecFile: false,
36+
isTestcase: true,
37+
repository: options.repository,
38+
type: options.testType,
39+
})
40+
}
41+
42+
public createSpecFileMetadata(
43+
testItem: vscode.TestItem,
44+
options: { repository: ITestRepository; uri: vscode.Uri }
45+
) {
46+
this.setMetadata(testItem, {
47+
uri: options.uri,
48+
isWorkspace: false,
49+
isConfigFile: false,
50+
isSpecFile: true,
51+
isTestcase: false,
52+
repository: options.repository,
53+
})
54+
}
55+
public createWdioConfigFileMetadata(
56+
testItem: vscode.TestItem,
57+
options: { repository: ITestRepository; uri: vscode.Uri; runProfiles?: vscode.TestRunProfile[] }
58+
) {
59+
this.setMetadata(testItem, {
60+
uri: options.uri,
61+
isWorkspace: false,
62+
isConfigFile: true,
63+
isSpecFile: false,
64+
isTestcase: false,
65+
repository: options.repository,
66+
runProfiles: options.runProfiles,
67+
})
68+
}
69+
70+
public createWorkspaceMetadata(testItem: vscode.TestItem, options: { uri: vscode.Uri }) {
71+
this.setMetadata(testItem, {
72+
uri: options.uri,
73+
isWorkspace: true,
74+
isConfigFile: false,
75+
isSpecFile: false,
76+
isTestcase: false,
77+
})
2478
}
2579
}

0 commit comments

Comments
 (0)