Skip to content
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"test:e2e": "pnpm --filter @vscode-wdio/e2e run test:e2e",
"test:smoke": "pnpm --filter @vscode-wdio/e2e run test:smoke",
"coverage": "vitest --run --coverage",
"coverage:ui": "pnpx sirv-cli ./coverage --single",
"graph": "pnpm run build --graph assets/build.png",
"version": "pnpm --filter @vscode-wdio/release run changelog && git add CHANGELOG.md",
"postversion": "git show",
Expand Down
51 changes: 2 additions & 49 deletions packages/vscode-wdio-test/src/converter.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,9 @@
import path from 'node:path'

import * as vscode from 'vscode'
import type { ReadSpecsResult } from '@vscode-wdio/types/server'
import type { TestData, SourceRange, VscodeTestData } from '@vscode-wdio/types/test'
/**
* Convert the parser's TestData to VSCode compatible TestData
*
* @param testCases Array of TestData from the parser
* @param document VSCode document for position conversion
* @returns Array of VscodeTestData
*/
export async function convertTestData(testData: ReadSpecsResult): Promise<VscodeTestData[]> {
try {
const uri = convertPathToUri(testData.spec)

return testData.tests.map((testCase) => _convertTestData(testCase, uri))
} catch (error) {
throw new Error(`Failed to parse or adapt test cases: ${(error as Error).message}`)
}
}

/**
* Convert a single TestData to use VSCode Range
*
* @param testCase TestData from the parser
* @param uri VSCode Uri for Spec file
* @returns VscodeTestData
*/
function _convertTestData(testCase: TestData, uri: vscode.Uri): VscodeTestData {
// Convert SourceRange to VSCode Range
const vsCodeRange = convertSourceRangeToVSCodeRange(testCase.range)

// Convert children recursively
const vsCodeChildren = testCase.children.map((child) => _convertTestData(child, uri))

return {
type: testCase.type,
name: testCase.name,
uri,
range: vsCodeRange,
children: vsCodeChildren,
}
}
import type { SourceRange } from '@vscode-wdio/types/test'

/**
* Convert a SourceRange to a VSCode Range
*
* @param sourceRange SourceRange with offsets
* @param document VSCode document for position conversion
* @returns VSCode Range
*/
function convertSourceRangeToVSCodeRange(sourceRange: SourceRange): vscode.Range {
export function convertSourceRangeToVSCodeRange(sourceRange: SourceRange): vscode.Range {
const start = new vscode.Position(sourceRange.start.line, sourceRange.start.column)
const end = new vscode.Position(sourceRange.end.line, sourceRange.end.column)
return new vscode.Range(start, end)
Expand Down
2 changes: 2 additions & 0 deletions packages/vscode-wdio-test/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* c8 ignore start */
export { RepositoryManager } from './manager.js'
export { TestReporter } from './reporter.js'
export { TestfileWatcher } from './watcher.js'
/* c8 ignore stop */
118 changes: 51 additions & 67 deletions packages/vscode-wdio-test/src/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,20 @@ import type { IRepositoryManager, ITestRepository } from '@vscode-wdio/types/tes

const LOADING_TEST_ITEM_ID = '_resolving'

export class RepositoryManager extends MetadataRepository implements IRepositoryManager {
export class RepositoryManager implements IRepositoryManager {
private readonly _repos = new Set<ITestRepository>()
private _loadingTestItem: vscode.TestItem
private _workspaceTestItems: vscode.TestItem[] = []
private _wdioConfigTestItems: vscode.TestItem[] = []
private _isInitialized = false
private isCreatedDefaultProfile = false
private _isCreatedDefaultProfile = false

constructor(
public readonly controller: vscode.TestController,
public readonly configManager: ExtensionConfigManager,
private readonly workerManager: IWorkerManager
private readonly _workerManager: IWorkerManager,
private readonly _metadata = new MetadataRepository()
) {
super()
this._loadingTestItem = this.controller.createTestItem(LOADING_TEST_ITEM_ID, 'Resolving WebdriverIO Tests...')
this._loadingTestItem.sortText = '.0' // show at first line
this._loadingTestItem.busy = true
Expand All @@ -56,17 +56,24 @@ export class RepositoryManager extends MetadataRepository implements IRepository
.then(async () => await this.initialize())
.then(async () => await Promise.all(this.repos.map(async (repo) => await repo.discoverAllTests())))
.then(() => this.registerToTestController())
.then(() => this.workerManager.reorganize(configManager.getWdioConfigPaths()))
.then(() => this._workerManager.reorganize(configManager.getWdioConfigPaths()))
})
}

public get repos() {
return Array.from(this._repos)
}

public getMetadata(testItem: vscode.TestItem) {
return this._metadata.getMetadata(testItem)
}

public getRepository(testItem: vscode.TestItem): ITestRepository {
return this._metadata.getRepository(testItem)
}

public async initialize() {
const workspaces = this.configManager.workspaces

if (workspaces.length < 1) {
log.info('No workspaces is detected.')
return
Expand All @@ -79,11 +86,11 @@ export class RepositoryManager extends MetadataRepository implements IRepository

this._workspaceTestItems = await Promise.all(
workspaces.map(async (workspace) => {
const workspaceTestItem = this.createWorkspaceTestItem(workspace.workspaceFolder)
const workspaceTestItem = this._createWorkspaceTestItem(workspace.workspaceFolder)
for (const wdioConfigFile of workspace.wdioConfigFiles) {
await this.createWdioConfigTestItem(workspace.workspaceFolder, workspaceTestItem, wdioConfigFile)
await this._createWdioConfigTestItem(workspace.workspaceFolder, workspaceTestItem, wdioConfigFile)

this.isCreatedDefaultProfile = true
this._isCreatedDefaultProfile = true
}
return workspaceTestItem
})
Expand All @@ -97,13 +104,13 @@ export class RepositoryManager extends MetadataRepository implements IRepository
return item.uri?.fsPath === workspaceFolder.uri.fsPath
})
for (const workspaceTestItem of affectedWorkspaceItems) {
const configTestItem = await this.createWdioConfigTestItem(
const configTestItem = await this._createWdioConfigTestItem(
workspaceFolder,
workspaceTestItem,
wdioConfigPath
)

const repo = this.getRepository(configTestItem)
const repo = this._metadata.getRepository(configTestItem)
await repo.discoverAllTests()
if (!this.configManager.isMultiWorkspace) {
this.controller.items.add(configTestItem)
Expand All @@ -118,12 +125,12 @@ export class RepositoryManager extends MetadataRepository implements IRepository
log.debug(`Remove the config file from ${affectedWorkspaceItems.length} workspace(s)`)
const configUri = convertPathToUri(wdioConfigPath)
for (const workspaceItem of affectedWorkspaceItems) {
const config = workspaceItem.children.get(this.generateConfigTestItemId(workspaceItem, configUri))
const config = workspaceItem.children.get(this._generateConfigTestItemId(workspaceItem, configUri))
if (!config) {
continue
}
log.debug(`Remove the TestItem: ${config.id}`)
const targetRepo = this.getRepository(config)
const targetRepo = this._metadata.getRepository(config)
targetRepo.dispose()
this._repos.delete(targetRepo)

Expand All @@ -134,7 +141,7 @@ export class RepositoryManager extends MetadataRepository implements IRepository
this.controller.items.delete(workspaceItem.id)
}
} else {
const targetId = this.generateConfigTestItemId(workspaceItem, configUri)
const targetId = this._generateConfigTestItemId(workspaceItem, configUri)
log.debug(`Remove Configuration from the controller: ${targetId}`)
this.controller.items.delete(targetId)
}
Expand All @@ -155,103 +162,80 @@ export class RepositoryManager extends MetadataRepository implements IRepository
log.debug('Successfully registered.')
}

private createWorkspaceTestItem(workspaceFolder: vscode.WorkspaceFolder) {
private _createWorkspaceTestItem(workspaceFolder: vscode.WorkspaceFolder) {
const workspaceItem = this.controller.createTestItem(
`workspace:${workspaceFolder.uri.fsPath}`,
workspaceFolder.name,
workspaceFolder.uri
)
this.setMetadata(workspaceItem, {
uri: workspaceFolder.uri,
isWorkspace: true,
isConfigFile: false,
isSpecFile: false,
isTestcase: false,
})
this._metadata.createWorkspaceMetadata(workspaceItem, { uri: workspaceFolder.uri })
return workspaceItem
}

private async createWdioConfigTestItem(
private async _createWdioConfigTestItem(
workspaceFolder: vscode.WorkspaceFolder,
workspaceTestItem: vscode.TestItem,
wdioConfigPath: string
) {
const uri = convertPathToUri(wdioConfigPath)
const configItem = this.controller.createTestItem(
this.generateConfigTestItemId(workspaceTestItem, uri),
this._generateConfigTestItemId(workspaceTestItem, uri),
basename(wdioConfigPath),
uri
)

workspaceTestItem.children.add(configItem)
this._wdioConfigTestItems.push(configItem)

const repo = new TestRepository(
const repository = new TestRepository(
this.configManager,
this.controller,
wdioConfigPath,
configItem,
this.workerManager,
this._workerManager,
workspaceFolder
)
this._repos.add(repo)
this._repos.add(repository)

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

this.setMetadata(configItem, {
uri,
isWorkspace: false,
isConfigFile: true,
isSpecFile: false,
isTestcase: false,
repository: repo,
runProfiles: createRunProfile.call(this, configItem, !this.isCreatedDefaultProfile),
})
const runProfiles = createRunProfile.call(this, configItem, !this._isCreatedDefaultProfile)

this._metadata.createWdioConfigFileMetadata(configItem, { uri, repository, runProfiles })
return configItem
}

private generateConfigTestItemId(workspaceTestItem: vscode.TestItem, configUri: vscode.Uri) {
private _generateConfigTestItemId(workspaceTestItem: vscode.TestItem, configUri: vscode.Uri) {
return [workspaceTestItem.id, `config:${configUri.fsPath}`].join(TEST_ID_SEPARATOR)
}

/**
* Refresh WebdriverIO tests
*/
public async refreshTests(): Promise<void> {
return vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: 'Reloading WebdriverIO tests...',
cancellable: false,
},
async () => {
try {
if (!this._isInitialized) {
await this.initialize()
}
for (const repo of this._repos) {
// Clear existing tests
repo.clearTests()
// Discover tests again
await repo.discoverAllTests()
}

vscode.window.showInformationMessage('WebdriverIO tests reloaded successfully')
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
log.error(`Failed to reload tests: ${errorMessage}`)
vscode.window.showErrorMessage(`Failed to reload WebdriverIO tests: ${errorMessage}`)
}
this.controller.items.replace([this._loadingTestItem])
try {
if (!this._isInitialized) {
await this.initialize()
}
)
await Promise.all(
this.repos.map(async (repo) => {
return await repo.discoverAllTests()
})
)

this.registerToTestController()
await this._workerManager.reorganize(this.configManager.getWdioConfigPaths())
} catch (error) {
this.controller.items.replace([])
const errorMessage = error instanceof Error ? error.message : String(error)
log.error(`Failed to reload tests: ${errorMessage}`)
vscode.window.showErrorMessage(`Failed to reload WebdriverIO tests: ${errorMessage}`)
}
}

public async dispose() {
await Promise.all(
Array.from(this._repos).map(async (repo) => {
await repo.dispose()
})
)
await Promise.all(this.repos.map(async (repo) => repo.dispose()))
this._repos.clear()
this._workspaceTestItems = []
this._wdioConfigTestItems = []
Expand Down
66 changes: 60 additions & 6 deletions packages/vscode-wdio-test/src/metadata.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { IMetadataRepository, TestItemMetadata } from '@vscode-wdio/types/test'
import type { ITestRepository, TestItemMetadata, TestType } from '@vscode-wdio/types/test'
import type * as vscode from 'vscode'

export class MetadataRepository implements IMetadataRepository {
private static testMetadataRepository = new WeakMap<vscode.TestItem, TestItemMetadata>()
export class MetadataRepository {
private static _testMetadataRepository = new WeakMap<vscode.TestItem, TestItemMetadata>()

public getMetadata(testItem: vscode.TestItem) {
const metadata = MetadataRepository.testMetadataRepository.get(testItem)
const metadata = MetadataRepository._testMetadataRepository.get(testItem)
if (!metadata) {
throw new Error("The metadata for TestItem is not set. This is extension's bug.")
}
Expand All @@ -19,7 +20,60 @@ export class MetadataRepository implements IMetadataRepository {
return metadata.repository
}

public setMetadata(testItem: vscode.TestItem, metadata: TestItemMetadata) {
MetadataRepository.testMetadataRepository.set(testItem, metadata)
protected setMetadata(testItem: vscode.TestItem, metadata: TestItemMetadata) {
MetadataRepository._testMetadataRepository.set(testItem, metadata)
}

public createTestMetadata(
testItem: vscode.TestItem,
options: { uri: vscode.Uri; repository: ITestRepository; testType: TestType }
) {
this.setMetadata(testItem, {
uri: options.uri,
isWorkspace: false,
isConfigFile: false,
isSpecFile: false,
isTestcase: true,
repository: options.repository,
type: options.testType,
})
}

public createSpecFileMetadata(
testItem: vscode.TestItem,
options: { repository: ITestRepository; uri: vscode.Uri }
) {
this.setMetadata(testItem, {
uri: options.uri,
isWorkspace: false,
isConfigFile: false,
isSpecFile: true,
isTestcase: false,
repository: options.repository,
})
}
public createWdioConfigFileMetadata(
testItem: vscode.TestItem,
options: { repository: ITestRepository; uri: vscode.Uri; runProfiles?: vscode.TestRunProfile[] }
) {
this.setMetadata(testItem, {
uri: options.uri,
isWorkspace: false,
isConfigFile: true,
isSpecFile: false,
isTestcase: false,
repository: options.repository,
runProfiles: options.runProfiles,
})
}

public createWorkspaceMetadata(testItem: vscode.TestItem, options: { uri: vscode.Uri }) {
this.setMetadata(testItem, {
uri: options.uri,
isWorkspace: true,
isConfigFile: false,
isSpecFile: false,
isTestcase: false,
})
}
}
Loading