From 61fc908298257e4fa0c5768c7ba6c2e2565c8a34 Mon Sep 17 00:00:00 2001 From: mato533 Date: Fri, 20 Jun 2025 14:53:25 +0900 Subject: [PATCH 1/7] chore: update startup process --- packages/vscode-wdio-server/src/manager.ts | 73 ++---- packages/vscode-wdio-server/src/run.ts | 4 +- packages/vscode-wdio-server/src/worker.ts | 17 +- .../vscode-wdio-server/tests/debug.test.ts | 1 + .../vscode-wdio-server/tests/manager.test.ts | 212 ++++++++++-------- packages/vscode-wdio-server/tests/run.test.ts | 1 + packages/vscode-wdio-test/src/manager.ts | 11 +- packages/vscode-wdio-test/src/repository.ts | 10 +- .../vscode-wdio-test/tests/manager.test.ts | 2 - .../vscode-wdio-test/tests/repository.test.ts | 6 +- packages/vscode-wdio-types/src/server.ts | 14 +- packages/vscode-webdriverio/src/extension.ts | 11 - .../tests/extension.test.ts | 39 +--- 13 files changed, 171 insertions(+), 230 deletions(-) diff --git a/packages/vscode-wdio-server/src/manager.ts b/packages/vscode-wdio-server/src/manager.ts index 2af26f8..379035a 100644 --- a/packages/vscode-wdio-server/src/manager.ts +++ b/packages/vscode-wdio-server/src/manager.ts @@ -1,11 +1,10 @@ import { dirname, normalize } from 'node:path' import { log } from '@vscode-wdio/logger' -import * as vscode from 'vscode' -import { WdioExtensionWorker } from './worker.js' +import { WdioExtensionWorkerFactory } from './worker.js' import type { IExtensionConfigManager } from '@vscode-wdio/types/config' -import type { IWorkerManager, IWdioExtensionWorker } from '@vscode-wdio/types/server' +import type { IWorkerManager, IWdioExtensionWorker, IWdioExtensionWorkerFactory } from '@vscode-wdio/types/server' export class WdioWorkerManager implements IWorkerManager { private _workerPool = new Map() @@ -15,9 +14,12 @@ export class WdioWorkerManager implements IWorkerManager { private _operationLock = false private _operationQueue: (() => Promise)[] = [] - constructor(private readonly configManager: IExtensionConfigManager) { + constructor( + configManager: IExtensionConfigManager, + private workerFactory: IWdioExtensionWorkerFactory = new WdioExtensionWorkerFactory(configManager) + ) { configManager.on('update:nodeExecutable', async (nodeExecutable: string | undefined) => { - log.debug(`Restart worker using webdriverio.nodeExecutable: ${nodeExecutable}`) + log.debug(`Stop all worker using webdriverio.nodeExecutable: ${nodeExecutable}`) const cwds = Array.from(this._workerPool.keys()) await Promise.all( cwds.map(async (cwd) => { @@ -27,13 +29,6 @@ export class WdioWorkerManager implements IWorkerManager { } }) ) - try { - await this.start(this.configManager.getWdioConfigPaths()) - } catch (error) { - const errorMessage = `Failed to restart WebdriverIO worker process: ${error instanceof Error ? error.message : String(error)}` - log.error(errorMessage) - vscode.window.showErrorMessage(errorMessage) - } }) // Listen for worker idle timeout configuration changes @@ -43,32 +38,6 @@ export class WdioWorkerManager implements IWorkerManager { }) } - /** - * Start worker process directory by directory which is located the wdio config file. - * @param configPaths path to the configuration file for wdio (e.g. /path/to/wdio.config.js) - */ - public async start(configPaths: string[]) { - // Add to queue and then execute the process - return this.queueOperation(async () => { - const duplicatedWorkerCwds = new Set() - configPaths.forEach((configPath) => { - const normalizedConfigPath = normalize(configPath) - const wdioDirName = dirname(normalizedConfigPath) - duplicatedWorkerCwds.add(wdioDirName) - }) - - const workerCwds = Array.from(duplicatedWorkerCwds) - const ids = Array.from({ length: workerCwds.length }, (_, i) => this.latestId + i) - this.latestId = ids[ids.length - 1] - - await Promise.all( - workerCwds.map(async (workerCwd, index) => { - await this.startWorker(ids[index], workerCwd) - }) - ) - }) - } - /** * * @param configPaths path to the configuration file for wdio (e.g. /path/to/wdio.config.js) @@ -160,12 +129,11 @@ export class WdioWorkerManager implements IWorkerManager { */ private async updateWorkerIdleTimeout(worker: IWdioExtensionWorker, idleTimeout: number): Promise { try { - worker.idleMonitor.updateTimeout(idleTimeout) + worker.updateIdleTimeout(idleTimeout) log.debug(`[server manager] successfully updated idle timeout for worker ${worker.cid}`) } catch (error) { const errorMessage = `Failed to update idle timeout for worker ${worker.cid}: ${error instanceof Error ? error.message : String(error)}` log.error(errorMessage) - // Don't throw error to prevent stopping other workers from being updated } } @@ -175,14 +143,15 @@ export class WdioWorkerManager implements IWorkerManager { * @param workerCwd Worker's current working directory */ public async handleWorkerIdleTimeout(workerCwd: string): Promise { - log.debug(`[server manager] received idle timeout notification for worker: ${workerCwd}`) + const normalizedConfigPath = normalize(workerCwd) + log.debug(`[server manager] received idle timeout notification for worker: ${normalizedConfigPath}`) - const worker = this._workerPool.get(workerCwd) + const worker = this._workerPool.get(normalizedConfigPath) if (worker) { - await this.stopWorker(workerCwd, worker) - log.debug(`[server manager] worker stopped due to idle timeout: ${workerCwd}`) + await this.stopWorker(normalizedConfigPath, worker) + log.debug(`[server manager] worker stopped due to idle timeout: ${normalizedConfigPath}`) } else { - log.warn(`[server manager] received idle timeout for unknown worker: ${workerCwd}`) + log.warn(`[server manager] received idle timeout for unknown worker: ${normalizedConfigPath}`) } } @@ -255,9 +224,9 @@ export class WdioWorkerManager implements IWorkerManager { } } - private async createWorker(id: number, configPaths: string): Promise { + private async createWorker(id: number, configPaths: string): Promise { const strId = `#${String(id)}` - const worker = new WdioExtensionWorker(this.configManager, strId, configPaths) + const worker = this.workerFactory.generate(strId, configPaths) // Set up idle timeout notification handler worker.on('idleTimeout', () => { @@ -267,16 +236,6 @@ export class WdioWorkerManager implements IWorkerManager { await worker.start() await worker.waitForStart() - // Send initial idle timeout configuration - const idleTimeout = this.configManager.globalConfig.workerIdleTimeout - if ( - idleTimeout !== undefined && - 'updateIdleTimeout' in worker && - typeof worker.updateIdleTimeout === 'function' - ) { - worker.updateIdleTimeout(idleTimeout) - } - log.debug(`[server manager] server was registered: ${configPaths}`) this._workerPool.set(configPaths, worker) return worker diff --git a/packages/vscode-wdio-server/src/run.ts b/packages/vscode-wdio-server/src/run.ts index 75c5994..aa093d4 100644 --- a/packages/vscode-wdio-server/src/run.ts +++ b/packages/vscode-wdio-server/src/run.ts @@ -23,7 +23,7 @@ export class TestRunner implements vscode.Disposable { protected workspaceFolder: vscode.WorkspaceFolder, protected worker: IWdioExtensionWorker ) { - worker.idleMonitor.pauseTimer() + worker.pauseIdleTimer() } public get stdout() { @@ -165,6 +165,6 @@ export class TestRunner implements vscode.Disposable { } async dispose() { - this.worker.idleMonitor.resumeTimer() + this.worker.resumeIdleTimer() } } diff --git a/packages/vscode-wdio-server/src/worker.ts b/packages/vscode-wdio-server/src/worker.ts index 107efa4..79a31f6 100644 --- a/packages/vscode-wdio-server/src/worker.ts +++ b/packages/vscode-wdio-server/src/worker.ts @@ -18,17 +18,26 @@ import type { IWdioExtensionWorker, WorkerApi, IWorkerIdleMonitor, + IWdioExtensionWorkerFactory, } from '@vscode-wdio/types/server' import type * as vscode from 'vscode' import type { WebSocketServer } from 'ws' const WORKER_PATH = resolve(__dirname, 'worker.cjs') +export class WdioExtensionWorkerFactory implements IWdioExtensionWorkerFactory { + constructor(private configManager: IExtensionConfigManager) {} + + generate(id: string, cwd: string): IWdioExtensionWorker { + return new WdioExtensionWorker(this.configManager, id, cwd) + } +} + export class WdioExtensionWorker extends TypedEventEmitter implements IWdioExtensionWorker { protected configManager: IExtensionConfigManager public cid: string protected cwd: string - public idleMonitor: IWorkerIdleMonitor + protected idleMonitor: IWorkerIdleMonitor protected disposables: vscode.Disposable[] = [] private _workerProcess: ChildProcess | null = null private _workerRpc: WorkerApi | null = null @@ -345,6 +354,12 @@ export class WdioExtensionWorker extends TypedEventEmitter { diff --git a/packages/vscode-wdio-server/tests/debug.test.ts b/packages/vscode-wdio-server/tests/debug.test.ts index 61f4435..8df6e3e 100644 --- a/packages/vscode-wdio-server/tests/debug.test.ts +++ b/packages/vscode-wdio-server/tests/debug.test.ts @@ -68,6 +68,7 @@ describe('DebugRunner', () => { pauseTimer: vi.fn(), resumeTimer: vi.fn(), }, + pauseIdleTimer: vi.fn(), } as unknown as WdioExtensionDebugWorker mockWorkerResult = { diff --git a/packages/vscode-wdio-server/tests/manager.test.ts b/packages/vscode-wdio-server/tests/manager.test.ts index e083f3e..7f748d2 100644 --- a/packages/vscode-wdio-server/tests/manager.test.ts +++ b/packages/vscode-wdio-server/tests/manager.test.ts @@ -3,26 +3,10 @@ import { dirname, join, normalize } from 'node:path' import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { WdioWorkerManager } from '../src/manager.js' -import { WdioExtensionWorker } from '../src/worker.js' -import type { IExtensionConfigManager } from '@vscode-wdio/types/config' +import type { IExtensionConfigManager, IWdioExtensionWorker, IWdioExtensionWorkerFactory } from '@vscode-wdio/types' vi.mock('vscode', () => import('../../../tests/__mocks__/vscode.cjs')) -// Mock the worker.js module -vi.mock('../src/worker.js', () => { - const WdioExtensionWorker = vi.fn(function (_configManager, cid, configPath) { - // @ts-ignore - this.cid = cid - // @ts-ignore - this.configPath = configPath - }) - WdioExtensionWorker.prototype.start = vi.fn().mockResolvedValue(undefined) - WdioExtensionWorker.prototype.waitForStart = vi.fn().mockResolvedValue(undefined) - WdioExtensionWorker.prototype.stop = vi.fn().mockResolvedValue(undefined) - WdioExtensionWorker.prototype.on = vi.fn() - return { WdioExtensionWorker } -}) - // Mock the logger module vi.mock('@vscode-wdio/logger', () => import('../../../tests/__mocks__/logger.js')) @@ -35,51 +19,56 @@ vi.mock('../src/utils.js', async (importActual) => { } }) -const mockConfigManager = { - globalConfig: { - workerIdleTimeout: 600, - }, - on: vi.fn(), -} as unknown as IExtensionConfigManager +// Mock the worker.js module +class MockWorkerFactory implements IWdioExtensionWorkerFactory { + WdioExtensionWorker + generatedWorkers: IWdioExtensionWorker[] = [] + + constructor(private configManager: IExtensionConfigManager) { + this.WdioExtensionWorker = vi.fn(function (_configManager, cid, configPath) { + // @ts-ignore + this.cid = cid + // @ts-ignore + this.configPath = configPath + }) + this.WdioExtensionWorker.prototype.start = vi.fn().mockResolvedValue(undefined) + this.WdioExtensionWorker.prototype.waitForStart = vi.fn().mockResolvedValue(undefined) + this.WdioExtensionWorker.prototype.stop = vi.fn().mockResolvedValue(undefined) + this.WdioExtensionWorker.prototype.on = vi.fn() + this.WdioExtensionWorker.prototype.isConnected = vi.fn().mockReturnValue(true) + this.WdioExtensionWorker.prototype.updateIdleTimeout = vi.fn() + } + generate(id: string, cwd: string): IWdioExtensionWorker { + const worker = new this.WdioExtensionWorker(this.configManager, id, cwd) as unknown as IWdioExtensionWorker + this.generatedWorkers.push(worker) + return worker + } +} describe('ServerManager', () => { let workerManager: WdioWorkerManager + let workerFactory: MockWorkerFactory + let mockConfigManager: IExtensionConfigManager // Create a fresh instance of ServerManager before each test beforeEach(() => { vi.resetAllMocks() - workerManager = new WdioWorkerManager(mockConfigManager) + + mockConfigManager = { + globalConfig: { + workerIdleTimeout: 600, + }, + on: vi.fn(), + } as unknown as IExtensionConfigManager + + workerFactory = new MockWorkerFactory(mockConfigManager) + workerManager = new WdioWorkerManager(mockConfigManager, workerFactory) }) afterEach(() => { vi.resetAllMocks() }) - describe('start', () => { - it('should start workers for each unique directory', async () => { - // Setup - const configPaths = ['/path/to/wdio.config.js', '/path/to/wdio.config.ts', '/another/path/wdio.config.js'] - - // Execute - await workerManager.start(configPaths) - - // Assert - expect(WdioExtensionWorker).toHaveBeenCalledTimes(2) - expect(WdioExtensionWorker).toHaveBeenCalledWith(mockConfigManager, '#0', normalize('/path/to')) - expect(WdioExtensionWorker).toHaveBeenCalledWith(mockConfigManager, '#1', normalize('/another/path')) - - // Check that start was called on each worker - expect(vi.mocked(WdioExtensionWorker).mock.instances.length).toBe(2) - expect(WdioExtensionWorker.prototype.start).toHaveBeenCalledTimes(2) - }) - - it('should handle empty config paths array', async () => { - await workerManager.start([]) - - expect(WdioExtensionWorker).not.toHaveBeenCalled() - }) - }) - describe('getConnection', () => { it('should return existing worker if it exists', async () => { // Setup @@ -87,15 +76,13 @@ describe('ServerManager', () => { // First call to create server const result = await workerManager.getConnection(configPath) - - // Reset mocks before second call - vi.clearAllMocks() + const spyFactory = vi.spyOn(workerFactory, 'generate') // Execute - second call should use existing server const cachedResult = await workerManager.getConnection(configPath) // Assert - WdioExtensionWorker constructor should not be called again - expect(WdioExtensionWorker).not.toHaveBeenCalled() + expect(spyFactory).not.toHaveBeenCalled() expect(cachedResult).toBeDefined() expect(result.cid).toBe(cachedResult.cid) }) @@ -104,72 +91,70 @@ describe('ServerManager', () => { // Setup const configPath = '/path/to/wdio.config.js' const wdioDirName = dirname(configPath) + const spyFactory = vi.spyOn(workerFactory, 'generate') // Execute const result = await workerManager.getConnection(configPath) // Assert - expect(WdioExtensionWorker).toHaveBeenCalledTimes(1) - expect(WdioExtensionWorker).toHaveBeenCalledWith(mockConfigManager, '#1', wdioDirName) + expect(spyFactory).toHaveBeenCalledTimes(1) + expect(spyFactory).toHaveBeenCalledWith('#1', wdioDirName) expect(result).toBeDefined() expect(result.cid).toBe('#1') }) it('should increment the id for each new worker', async () => { // Setup - create first worker + const spyFactory = vi.spyOn(workerFactory, 'generate') await workerManager.getConnection('/path/to/wdio.config.js') // Execute - create second worker with different path await workerManager.getConnection('/another/path/wdio.config.js') // Assert - expect(WdioExtensionWorker).toHaveBeenCalledTimes(2) - expect(WdioExtensionWorker).toHaveBeenCalledWith(mockConfigManager, '#1', '/path/to') - expect(WdioExtensionWorker).toHaveBeenCalledWith(mockConfigManager, '#2', '/another/path') + expect(spyFactory).toHaveBeenCalledTimes(2) + expect(spyFactory).toHaveBeenCalledWith('#1', '/path/to') + expect(spyFactory).toHaveBeenCalledWith('#2', '/another/path') }) }) describe('dispose', () => { it('should stop all workers', async () => { - // Setup - create multiple workers - const configPaths = ['/path/to/wdio.config.js', '/another/path/wdio.config.js'] - - await workerManager.start(configPaths) - + // Setup - create worker + const worker1 = await workerManager.getConnection('/path/to/wdio.config.js') + const worker2 = await workerManager.getConnection('/another/path/wdio.config.js') + const spyWorkerStop1 = vi.spyOn(worker1, 'stop') + const spyWorkerStop2 = vi.spyOn(worker2, 'stop') // Execute await workerManager.dispose() - // Assert - expect(vi.mocked(WdioExtensionWorker).mock.instances.length).toBe(2) - expect(WdioExtensionWorker.prototype.stop).toHaveBeenCalledTimes(2) - }) - - it('should handle empty server pool', async () => { - // Execute - await workerManager.dispose() - - // Assert - should not throw an error - expect(WdioExtensionWorker).not.toHaveBeenCalled() + // Assert - All worker was called `stop` + expect(spyWorkerStop1).toHaveBeenCalledTimes(1) + expect(spyWorkerStop2).toHaveBeenCalledTimes(1) }) }) describe('reorganize', () => { it('should stop unnecessary workers and start new ones', async () => { - // Setup - create initial workers - await workerManager.start(['/path/to/wdio.config.js', '/another/path/wdio.config.js']) + const oldWorker1 = await workerManager.getConnection('/path/to/wdio.config.js') + const oldWorker2 = await workerManager.getConnection('/another/path/wdio.config.js') - vi.clearAllMocks() + const spyWorkerStop1 = vi.spyOn(oldWorker1, 'stop') + const spyWorkerStop2 = vi.spyOn(oldWorker2, 'stop') + const spyFactory = vi.spyOn(workerFactory, 'generate') // Execute - reorganize with different paths await workerManager.reorganize(['/path/to/wdio.config.js', '/new/path/wdio.config.js']) // Assert // Should stop one worker - expect(WdioExtensionWorker.prototype.stop).toHaveBeenCalledTimes(1) + expect(workerFactory.generatedWorkers.length).toBe(3) + expect(spyWorkerStop1).not.toHaveBeenCalled() + expect(spyWorkerStop2).toHaveBeenCalledTimes(1) // Should create one new worker - expect(WdioExtensionWorker).toHaveBeenCalledTimes(1) - expect(WdioExtensionWorker).toHaveBeenCalledWith(mockConfigManager, '#2', normalize('/new/path')) + expect(spyFactory).toHaveBeenCalledTimes(1) + expect(spyFactory).toHaveBeenCalledWith('#3', normalize('/new/path')) }) }) @@ -246,6 +231,7 @@ describe('ServerManager', () => { // Setup const id = 42 const configPath = '/path/to/wdio.config.js' + const spyFactory = vi.spyOn(workerFactory, 'generate') // Access private method using any cast const startWorker = (workerManager as any).startWorker.bind(workerManager) @@ -254,8 +240,8 @@ describe('ServerManager', () => { const result = await startWorker(id, configPath) // Assert - expect(WdioExtensionWorker).toHaveBeenCalledTimes(1) - expect(WdioExtensionWorker).toHaveBeenCalledWith(mockConfigManager, '#42', configPath) + expect(spyFactory).toHaveBeenCalledTimes(1) + expect(spyFactory).toHaveBeenCalledWith('#42', configPath) expect(result).toBeDefined() expect(result.start).toHaveBeenCalledTimes(1) expect(result.waitForStart).toHaveBeenCalledTimes(1) @@ -270,15 +256,6 @@ describe('ServerManager', () => { // Access private method using any cast const startWorker = (workerManager as any).startWorker.bind(workerManager) - // Add tracking to see if createWorker is called multiple times - let createWorkerCalls = 0 - vi.spyOn(workerManager as any, 'createWorker').mockImplementation((async (id: number, path: string) => { - createWorkerCalls++ - // Mock delay to ensure operations overlap - await new Promise((resolve) => setTimeout(resolve, 50)) - return new WdioExtensionWorker(mockConfigManager, `#${id}`, path) - }) as any) - // Execute two concurrent calls with same path const promise1 = startWorker(id1, configPath) const promise2 = startWorker(id2, configPath) @@ -287,7 +264,7 @@ describe('ServerManager', () => { const [result1, result2] = await Promise.all([promise1, promise2]) // Assert - expect(createWorkerCalls).toBe(1) // Only one actual worker creation + // expect(createWorkerCalls).toBe(1) // Only one actual worker creation expect(result1).toBe(result2) // Should be the same worker instance }) }) @@ -340,4 +317,55 @@ describe('ServerManager', () => { expect(executeStopWorkerCalls).toBe(1) // Only one actual stop execution }) }) + + describe('Worker idle timeout', () => { + it('should return the same promise for concurrent stop calls', async () => { + // Setup - create worker + const worker1 = await workerManager.getConnection('/path/to/wdio.config.js') + const worker2 = await workerManager.getConnection('/another/path/wdio.config.js') + + const spyWorkerTimeout1 = vi.spyOn(worker1, 'updateIdleTimeout') + const spyWorkerTimeout2 = vi.spyOn(worker2, 'updateIdleTimeout') + + const updateHandler = vi.mocked(mockConfigManager.on).mock.calls[1][1] + + // Execute + updateHandler(987) + // Assert - All worker was called `stop` + expect(spyWorkerTimeout1).toHaveBeenCalledTimes(1) + expect(spyWorkerTimeout1).toHaveBeenCalledWith(987) + expect(spyWorkerTimeout2).toHaveBeenCalledTimes(1) + expect(spyWorkerTimeout2).toHaveBeenCalledWith(987) + }) + + it('should stop worker when timeout occurred', async () => { + // Setup - create worker + const worker1 = await workerManager.getConnection('/path/to/wdio.config.js') + const spyWorkerStop1 = vi.spyOn(worker1, 'stop') + + await workerManager.handleWorkerIdleTimeout('/path/to') + + expect(spyWorkerStop1).toHaveBeenCalledTimes(1) + }) + }) + + describe('Node path change', () => { + it('should stop all worker when node path update', async () => { + // Setup - create worker + const worker1 = await workerManager.getConnection('/path/to/wdio.config.js') + const worker2 = await workerManager.getConnection('/another/path/wdio.config.js') + + const spyWorkerStop1 = vi.spyOn(worker1, 'stop') + const spyWorkerStop2 = vi.spyOn(worker2, 'stop') + + const updateHandler = vi.mocked(mockConfigManager.on).mock.calls[0][1] + + // Execute + updateHandler('/path/to/node') + + // Assert - All worker was called `stop` + expect(spyWorkerStop1).toHaveBeenCalledTimes(1) + expect(spyWorkerStop2).toHaveBeenCalledTimes(1) + }) + }) }) diff --git a/packages/vscode-wdio-server/tests/run.test.ts b/packages/vscode-wdio-server/tests/run.test.ts index 2a476f9..5a82e3e 100644 --- a/packages/vscode-wdio-server/tests/run.test.ts +++ b/packages/vscode-wdio-server/tests/run.test.ts @@ -56,6 +56,7 @@ describe('TestRunner', () => { pauseTimer: vi.fn(), resumeTimer: vi.fn(), }, + pauseIdleTimer: vi.fn(), } as unknown as IWdioExtensionWorker vi.mocked(getEnvOptions).mockResolvedValue({ diff --git a/packages/vscode-wdio-test/src/manager.ts b/packages/vscode-wdio-test/src/manager.ts index 0ff52f0..707451f 100644 --- a/packages/vscode-wdio-test/src/manager.ts +++ b/packages/vscode-wdio-test/src/manager.ts @@ -54,14 +54,7 @@ export class RepositoryManager extends MetadataRepository implements IRepository await configManager .initialize() .then(async () => await this.initialize()) - .then( - async () => - await Promise.all( - this.repos.map(async (repo) => { - return await repo.discoverAllTests() - }) - ) - ) + .then(async () => await Promise.all(this.repos.map(async (repo) => await repo.discoverAllTests()))) .then(() => this.registerToTestController()) .then(() => this.workerManager.reorganize(configManager.getWdioConfigPaths())) }) @@ -193,11 +186,9 @@ export class RepositoryManager extends MetadataRepository implements IRepository workspaceTestItem.children.add(configItem) this._wdioConfigTestItems.push(configItem) - const worker = await this.workerManager.getConnection(wdioConfigPath) const repo = new TestRepository( this.configManager, this.controller, - worker, wdioConfigPath, configItem, this.workerManager, diff --git a/packages/vscode-wdio-test/src/repository.ts b/packages/vscode-wdio-test/src/repository.ts index b8e792b..e570c86 100644 --- a/packages/vscode-wdio-test/src/repository.ts +++ b/packages/vscode-wdio-test/src/repository.ts @@ -17,13 +17,10 @@ import type * as vscode from 'vscode' class WorkerProxy extends MetadataRepository { private _worker: IWdioExtensionWorker | undefined constructor( - private readonly _wdioConfigPath: string, - worker: IWdioExtensionWorker, - private workerManager: IWorkerManager + private workerManager: IWorkerManager, + private readonly _wdioConfigPath: string ) { super() - this._worker = worker - this.setListener() } async getWorker() { @@ -55,13 +52,12 @@ export class TestRepository extends WorkerProxy implements ITestRepository { constructor( public readonly configManager: IExtensionConfigManager, public readonly controller: vscode.TestController, - _worker: IWdioExtensionWorker, public readonly wdioConfigPath: string, private _wdioConfigTestItem: vscode.TestItem, workerManager: IWorkerManager, private _workspaceFolder: vscode.WorkspaceFolder ) { - super(wdioConfigPath, _worker, workerManager) + super(workerManager, wdioConfigPath) } public get specPatterns() { diff --git a/packages/vscode-wdio-test/tests/manager.test.ts b/packages/vscode-wdio-test/tests/manager.test.ts index 4378c33..1d15a1b 100644 --- a/packages/vscode-wdio-test/tests/manager.test.ts +++ b/packages/vscode-wdio-test/tests/manager.test.ts @@ -114,8 +114,6 @@ describe('RepositoryManager', () => { expect((repositoryManager as any)._workspaceTestItems.length).toBe(1) expect((repositoryManager as any)._wdioConfigTestItems.length).toBe(1) - - expect(workerManager.getConnection).toHaveBeenCalledWith(fakeWorkspaces[0].wdioConfigFiles[0]) }) }) diff --git a/packages/vscode-wdio-test/tests/repository.test.ts b/packages/vscode-wdio-test/tests/repository.test.ts index 44b1e18..b6fe347 100644 --- a/packages/vscode-wdio-test/tests/repository.test.ts +++ b/packages/vscode-wdio-test/tests/repository.test.ts @@ -88,13 +88,14 @@ describe('TestRepository', () => { } } - workerManager = vi.fn() as unknown as IWorkerManager + workerManager = { + getConnection: vi.fn(() => mockWorker), + } as unknown as IWorkerManager // Create repository with mocked dependencies testRepository = new MockTestRepository( mockConfigManager, testController, - mockWorker, mockWdioConfigPath, wdioConfigTestItem, workerManager, @@ -157,7 +158,6 @@ describe('TestRepository', () => { const repo = new TestRepository( mockConfigManager, testController, - mockWorker, mockWdioConfigPath, wdioConfigTestItem, workerManager, diff --git a/packages/vscode-wdio-types/src/server.ts b/packages/vscode-wdio-types/src/server.ts index 191e204..f868f87 100644 --- a/packages/vscode-wdio-types/src/server.ts +++ b/packages/vscode-wdio-types/src/server.ts @@ -95,11 +95,13 @@ export interface TestResultData { export interface IWdioExtensionWorker extends ITypedEventEmitter { cid: string rpc: WorkerApi - idleMonitor: IWorkerIdleMonitor start(): Promise waitForStart(): Promise stop(): Promise isConnected(): boolean + updateIdleTimeout(timeout: number): void + pauseIdleTimer(): void + resumeIdleTimer(): void ensureConnected(): Promise } @@ -110,8 +112,11 @@ export interface WdioExtensionWorkerEvents { shutdown: undefined } +export interface IWdioExtensionWorkerFactory { + generate(id: string, cwd: string): IWdioExtensionWorker +} + export interface IWorkerManager extends vscode.Disposable { - start(configPaths: string[]): Promise getConnection(configPaths: string): Promise reorganize(configPaths: string[]): Promise } @@ -148,11 +153,6 @@ export interface IWorkerIdleMonitor { */ resumeTimer(): void - /** - * Check if monitoring is currently active - */ - isActive(): boolean - /** * Add event listener for idle timeout events * @param event Event name ('idleTimeout') diff --git a/packages/vscode-webdriverio/src/extension.ts b/packages/vscode-webdriverio/src/extension.ts index fc515ca..fe0e112 100644 --- a/packages/vscode-webdriverio/src/extension.ts +++ b/packages/vscode-webdriverio/src/extension.ts @@ -32,10 +32,6 @@ class WdioExtension implements vscode.Disposable { log.info('WebdriverIO Runner extension is now active') await this.configManager.initialize() - const configPaths = this.configManager.getWdioConfigPaths() - - // Start worker process asynchronously - const starting = this.workerManager.start(configPaths) // Create Manages and watchers const repositoryManager = new RepositoryManager(this.controller, this.configManager, this.workerManager) @@ -59,13 +55,6 @@ class WdioExtension implements vscode.Disposable { // Initialize try { - try { - await starting - } catch (error) { - const errorMessage = `Failed to start WebdriverIO worker process: ${error instanceof Error ? error.message : String(error)}` - log.error(errorMessage) - vscode.window.showErrorMessage(errorMessage) - } await repositoryManager.initialize() return Promise.all( repositoryManager.repos.map(async (repo) => { diff --git a/packages/vscode-webdriverio/tests/extension.test.ts b/packages/vscode-webdriverio/tests/extension.test.ts index 450550e..b256228 100644 --- a/packages/vscode-webdriverio/tests/extension.test.ts +++ b/packages/vscode-webdriverio/tests/extension.test.ts @@ -1,9 +1,7 @@ import { ExtensionConfigManager } from '@vscode-wdio/config' import { log } from '@vscode-wdio/logger' -import { WdioWorkerManager } from '@vscode-wdio/server' import { RepositoryManager } from '@vscode-wdio/test' import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' -import * as vscode from 'vscode' import { activate, deactivate } from '../src/extension.js' @@ -25,19 +23,13 @@ vi.mock('vscode', async () => { }) vi.mock('@vscode-wdio/logger', () => import('../../../tests/__mocks__/logger.js')) -vi.mock('@vscode-wdio/server', () => { - const WdioWorkerManager = vi.fn() - WdioWorkerManager.prototype.start = vi.fn(() => Promise.resolve()) - WdioWorkerManager.prototype.dispose = vi.fn(() => Promise.resolve()) - - return { WdioWorkerManager } -}) vi.mock('@vscode-wdio/config', () => { const ExtensionConfigManager = vi.fn() ExtensionConfigManager.prototype.initialize = vi.fn(() => Promise.resolve()) ExtensionConfigManager.prototype.dispose = vi.fn(() => Promise.resolve()) ExtensionConfigManager.prototype.getWdioConfigPaths = vi.fn(() => ['/test/wdio.conf.ts']) ExtensionConfigManager.prototype.listener = vi.fn() + ExtensionConfigManager.prototype.on = vi.fn() const ConfigFileWatcher = vi.fn() ConfigFileWatcher.prototype.enable = vi.fn() @@ -95,41 +87,12 @@ describe('extension', () => { // vi.mocked(TestRunner).mock.instances[0].run // expect(vi.mocked(ExtensionConfigManager).mock.instances[0].initialize).toHaveBeenCalled() - expect(vi.mocked(WdioWorkerManager).mock.instances[0].start).toHaveBeenCalled() expect(vi.mocked(RepositoryManager).mock.instances[0].initialize).toHaveBeenCalled() expect(vi.mocked(RepositoryManager).mock.instances[0].registerToTestController).toHaveBeenCalled() // Verify context subscriptions expect(fakeContext.subscriptions.length).toBeGreaterThan(0) }) - - it('should handle server start error gracefully', async () => { - // Arrange - const errorMessage = 'Failed to start server' - vi.mocked(WdioWorkerManager.prototype.start).mockRejectedValueOnce(new Error(errorMessage)) - - // Act - await activate(fakeContext) - - // Assert - expect(log.error).toHaveBeenCalledWith(`Failed to start WebdriverIO worker process: ${errorMessage}`) - expect(vscode.window.showErrorMessage).toHaveBeenCalledWith( - `Failed to start WebdriverIO worker process: ${errorMessage}` - ) - }) - - it('should continue activation even when workerManager.start rejects', async () => { - // Arrange - vi.mocked(WdioWorkerManager.prototype.start).mockRejectedValueOnce(new Error('Server failed')) - - // Act - await activate(fakeContext) - - // Assert - expect(vi.mocked(RepositoryManager).mock.instances[0].initialize).toHaveBeenCalled() - expect(vi.mocked(RepositoryManager).mock.instances[0].registerToTestController).toHaveBeenCalled() - expect(log.error).toHaveBeenCalledWith(expect.stringContaining('Server failed')) - }) }) describe('deactivate', () => { From a0ae5e4be565df0fbdacabee2e7a451b439ccd12 Mon Sep 17 00:00:00 2001 From: mato533 Date: Sun, 22 Jun 2025 23:34:47 +0900 Subject: [PATCH 2/7] chore: update tsconfig for e2e --- e2e/assertions/index.ts | 2 +- e2e/helpers/constants.ts | 10 ++++++++-- e2e/helpers/cucumber.ts | 2 +- e2e/helpers/index.ts | 7 +------ e2e/tests/basic.spec.ts | 4 ++-- e2e/tests/basicCucumber.spec.ts | 4 ++-- e2e/tests/basicWorkspace.spec.ts | 4 ++-- e2e/tests/envEnable.spec.ts | 2 +- e2e/tests/updateConfig.spec.ts | 2 +- e2e/tests/updateErrorConfig.spec.ts | 2 +- e2e/tests/updateErrorSpec.spec.ts | 2 +- e2e/tests/updateSpec.spec.ts | 2 +- e2e/tests/workerIdleTimeout.spec.ts | 4 ++-- e2e/tsconfig.json | 2 +- e2e/wdioSmoke.conf.ts | 2 +- 15 files changed, 26 insertions(+), 25 deletions(-) diff --git a/e2e/assertions/index.ts b/e2e/assertions/index.ts index 8d9d398..27fcb34 100644 --- a/e2e/assertions/index.ts +++ b/e2e/assertions/index.ts @@ -1,6 +1,6 @@ import type { MatcherContext } from 'expect' import type { BottomBarPanel, TreeItem, Workbench } from 'wdio-vscode-service' -import type { STATUS } from '../helpers/index.ts' +import type { STATUS } from '../helpers/index.js' export interface ExpectedTreeItem { text: string diff --git a/e2e/helpers/constants.ts b/e2e/helpers/constants.ts index 11c279a..7501858 100644 --- a/e2e/helpers/constants.ts +++ b/e2e/helpers/constants.ts @@ -1,5 +1,11 @@ -import * as cucumber from './cucumber.ts' -import { STATUS } from './index.ts' +import * as cucumber from './cucumber.js' + +export const STATUS = { + NOT_YET_RUN: 'Not yet run', + PASSED: 'Passed', + FAILED: 'Failed', + SKIPPED: 'Skipped', +} as const function createExpectedNotRun(targetFramework: 'mocha' | 'jasmine') { return { diff --git a/e2e/helpers/cucumber.ts b/e2e/helpers/cucumber.ts index 754d300..4221844 100644 --- a/e2e/helpers/cucumber.ts +++ b/e2e/helpers/cucumber.ts @@ -1,4 +1,4 @@ -import { STATUS } from './index.ts' +import { STATUS } from './constants.js' export function createExpectedNotRun() { return { diff --git a/e2e/helpers/index.ts b/e2e/helpers/index.ts index 1fbb62e..8743b29 100644 --- a/e2e/helpers/index.ts +++ b/e2e/helpers/index.ts @@ -2,12 +2,7 @@ import { DefaultTreeSection } from 'wdio-vscode-service' import type { StatusStrings } from 'assertions/index.ts' import type { TreeItem, Workbench, ViewControl, ViewContent, ViewItemAction, ViewTitlePart } from 'wdio-vscode-service' -export const STATUS = { - NOT_YET_RUN: 'Not yet run', - PASSED: 'Passed', - FAILED: 'Failed', - SKIPPED: 'Skipped', -} as const +export { STATUS } from './constants.js' export async function openTestingView(workbench: Workbench) { const activityBar = workbench.getActivityBar() diff --git a/e2e/tests/basic.spec.ts b/e2e/tests/basic.spec.ts index ab958fc..657749f 100644 --- a/e2e/tests/basic.spec.ts +++ b/e2e/tests/basic.spec.ts @@ -1,5 +1,5 @@ import { browser, expect } from '@wdio/globals' -import { createExpected } from 'helpers/constants.ts' +import { createExpected } from 'helpers/constants.js' import { STATUS, @@ -11,7 +11,7 @@ import { openTestingView, waitForResolved, waitForTestStatus, -} from '../helpers/index.ts' +} from '../helpers/index.js' import type { SideBarView, ViewControl, Workbench } from 'wdio-vscode-service' diff --git a/e2e/tests/basicCucumber.spec.ts b/e2e/tests/basicCucumber.spec.ts index 60e5227..fba5837 100644 --- a/e2e/tests/basicCucumber.spec.ts +++ b/e2e/tests/basicCucumber.spec.ts @@ -1,6 +1,6 @@ import { browser, expect } from '@wdio/globals' -import { createCucumberExpected } from '../helpers/cucumber.ts' +import { createCucumberExpected } from '../helpers/cucumber.js' import { STATUS, clearAllTestResults, @@ -11,7 +11,7 @@ import { openTestingView, waitForResolved, waitForTestStatus, -} from '../helpers/index.ts' +} from '../helpers/index.js' import type { SideBarView, ViewControl, Workbench } from 'wdio-vscode-service' diff --git a/e2e/tests/basicWorkspace.spec.ts b/e2e/tests/basicWorkspace.spec.ts index d92db92..768ccc4 100644 --- a/e2e/tests/basicWorkspace.spec.ts +++ b/e2e/tests/basicWorkspace.spec.ts @@ -1,5 +1,5 @@ import { browser, expect } from '@wdio/globals' -import { createWorkspaceExpected } from 'helpers/constants.ts' +import { createWorkspaceExpected } from 'helpers/constants.js' import { STATUS, @@ -11,7 +11,7 @@ import { openTestingView, waitForResolved, waitForTestStatus, -} from '../helpers/index.ts' +} from '../helpers/index.js' import type { SideBarView, ViewControl, Workbench } from 'wdio-vscode-service' const expected = createWorkspaceExpected() diff --git a/e2e/tests/envEnable.spec.ts b/e2e/tests/envEnable.spec.ts index ca6bc68..f51f8e2 100644 --- a/e2e/tests/envEnable.spec.ts +++ b/e2e/tests/envEnable.spec.ts @@ -14,7 +14,7 @@ import { openTestingView, waitForResolved, waitForTestStatus, -} from '../helpers/index.ts' +} from '../helpers/index.js' import type { SideBarView, Workbench } from 'wdio-vscode-service' diff --git a/e2e/tests/updateConfig.spec.ts b/e2e/tests/updateConfig.spec.ts index 027372a..ac6ec69 100644 --- a/e2e/tests/updateConfig.spec.ts +++ b/e2e/tests/updateConfig.spec.ts @@ -13,7 +13,7 @@ import { openTestingView, waitForResolved, waitForTestStatus, -} from '../helpers/index.ts' +} from '../helpers/index.js' import type { SideBarView, Workbench } from 'wdio-vscode-service' diff --git a/e2e/tests/updateErrorConfig.spec.ts b/e2e/tests/updateErrorConfig.spec.ts index 891f24c..943b307 100644 --- a/e2e/tests/updateErrorConfig.spec.ts +++ b/e2e/tests/updateErrorConfig.spec.ts @@ -13,7 +13,7 @@ import { openTestingView, waitForResolved, waitForTestStatus, -} from '../helpers/index.ts' +} from '../helpers/index.js' import type { SideBarView, Workbench } from 'wdio-vscode-service' diff --git a/e2e/tests/updateErrorSpec.spec.ts b/e2e/tests/updateErrorSpec.spec.ts index 0308c0c..fb5bab2 100644 --- a/e2e/tests/updateErrorSpec.spec.ts +++ b/e2e/tests/updateErrorSpec.spec.ts @@ -13,7 +13,7 @@ import { openTestingView, waitForResolved, waitForTestStatus, -} from '../helpers/index.ts' +} from '../helpers/index.js' import type { SideBarView, Workbench } from 'wdio-vscode-service' diff --git a/e2e/tests/updateSpec.spec.ts b/e2e/tests/updateSpec.spec.ts index 6e2be7e..3c4defe 100644 --- a/e2e/tests/updateSpec.spec.ts +++ b/e2e/tests/updateSpec.spec.ts @@ -13,7 +13,7 @@ import { openTestingView, waitForResolved, waitForTestStatus, -} from '../helpers/index.ts' +} from '../helpers/index.js' import type { SideBarView, Workbench } from 'wdio-vscode-service' diff --git a/e2e/tests/workerIdleTimeout.spec.ts b/e2e/tests/workerIdleTimeout.spec.ts index 267a10f..fca8c99 100644 --- a/e2e/tests/workerIdleTimeout.spec.ts +++ b/e2e/tests/workerIdleTimeout.spec.ts @@ -1,6 +1,6 @@ import { browser, expect } from '@wdio/globals' -import { createCucumberExpected } from '../helpers/cucumber.ts' +import { createCucumberExpected } from '../helpers/cucumber.js' import { STATUS, clearAllTestResults, @@ -10,7 +10,7 @@ import { openTestingView, waitForResolved, waitForTestStatus, -} from '../helpers/index.ts' +} from '../helpers/index.js' import type { SideBarView, ViewControl, Workbench } from 'wdio-vscode-service' diff --git a/e2e/tsconfig.json b/e2e/tsconfig.json index 026249a..764885e 100644 --- a/e2e/tsconfig.json +++ b/e2e/tsconfig.json @@ -6,12 +6,12 @@ "types": ["node", "@wdio/globals/types", "@wdio/mocha-framework", "wdio-vscode-service"], "skipLibCheck": true, "noEmit": true, - "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, + "experimentalDecorators": true, "noFallthroughCasesInSwitch": true } } diff --git a/e2e/wdioSmoke.conf.ts b/e2e/wdioSmoke.conf.ts index a1633ec..0f39169 100644 --- a/e2e/wdioSmoke.conf.ts +++ b/e2e/wdioSmoke.conf.ts @@ -1,4 +1,4 @@ -import { createBaseConfig } from './wdio.conf.ts' +import { createBaseConfig } from './wdio.conf.js' type TestTargets = 'config' | 'timeout' | 'env' From ff0660c628254dad1b8aa35cb7a63363b34a62fd Mon Sep 17 00:00:00 2001 From: mato533 Date: Sun, 22 Jun 2025 23:35:42 +0900 Subject: [PATCH 3/7] test: add e2e to update settings for glob pattern of configuration file --- e2e/locators/index.ts | 7 + e2e/pageobjects/SettingsEditor.ts | 86 +++++++++++ e2e/tests/updateSettings.spec.ts | 138 ++++++++++++++++++ e2e/wdioSmoke.conf.ts | 1 + eslint.config.mjs | 9 ++ .../smoke/update-config/webdriverio.conf.ts | 7 + 6 files changed, 248 insertions(+) create mode 100644 e2e/locators/index.ts create mode 100644 e2e/pageobjects/SettingsEditor.ts create mode 100644 e2e/tests/updateSettings.spec.ts create mode 100644 samples/smoke/update-config/webdriverio.conf.ts diff --git a/e2e/locators/index.ts b/e2e/locators/index.ts new file mode 100644 index 0000000..b921cf3 --- /dev/null +++ b/e2e/locators/index.ts @@ -0,0 +1,7 @@ +export const ListSetting = { + listSetting: '.setting-item-list', + listRow: '.setting-list-row', + ListRowEdit: '.setting-list-edit-row', + ListRowTextSetting: 'input', + ListRowOkButton: '.setting-list-ok-button', +} diff --git a/e2e/pageobjects/SettingsEditor.ts b/e2e/pageobjects/SettingsEditor.ts new file mode 100644 index 0000000..762bd6d --- /dev/null +++ b/e2e/pageobjects/SettingsEditor.ts @@ -0,0 +1,86 @@ +import { PageDecorator, SettingsEditor, BasePage, sleep } from 'wdio-vscode-service' + +import { ListSetting as ListSettingLocators } from '../locators/index.js' +import * as locatorMap from '../locators/index.js' +import type { Workbench, IPageDecorator } from 'wdio-vscode-service' + +class ExtendSettingsEditor extends SettingsEditor { + extendedLocator = ListSettingLocators + constructor(workbench: Workbench) { + super(workbench.locatorMap) + } + + async findListSetting(title: string, ...categories: string[]): Promise { + const category = categories.join(' › ') + const searchBox = await this.elem.$(this.locatorMap.Editor.inputArea) + await searchBox.setValue(`${category}: ${title}`) + const count = await this.itemCount$ + let textCount = await count.getText() + await browser.waitUntil(async () => { + await sleep(1500) + const text = await count.getText() + if (text !== textCount) { + textCount = text + return false + } + return true + }) + const items = await this.itemRow$$ + for (const item of items) { + try { + return await (await this.createListSetting(item, title, category)).wait() + } catch { + // ignore + } + } + throw new Error('The setting is not found.') + } + + private async createListSetting(element: WebdriverIO.Element, title: string, category: string) { + if (!(await element.$(this.locators.settingConstructor(title, category)).isExisting())) { + throw new Error('Setting not found') + } + // try a list setting + if (await element.$(this.extendedLocator.listSetting).isExisting()) { + return new ListSetting(locatorMap, title, category, this) + } + throw new Error('Setting type not supported') + } +} + +export interface ListSetting extends IPageDecorator {} + +@PageDecorator(ListSettingLocators) +export class ListSetting extends BasePage { + constructor( + locators: typeof locatorMap, + title: string, + category: string, + public settings: SettingsEditor + ) { + super(locators, settings.locators.settingConstructor(title, category)) + } + + /** + * @private + */ + public locatorKey = 'ListSetting' as const + + async editValue(index: number, value: string) { + const rows = this.listRow$$ + if (rows.length < index + 1) { + throw new Error('invalid index') + } + await (rows[index] as WebdriverIO.Element).doubleClick() + + const editRow = await this.elem.$(this.locators.ListRowEdit) + + const input = await editRow.$(this.locators.ListRowTextSetting) + const okBtn = editRow.$(this.locators.ListRowOkButton) + + await input.setValue(value) + await okBtn.click() + } +} + +export { ExtendSettingsEditor as SettingsEditor } diff --git a/e2e/tests/updateSettings.spec.ts b/e2e/tests/updateSettings.spec.ts new file mode 100644 index 0000000..ea346bb --- /dev/null +++ b/e2e/tests/updateSettings.spec.ts @@ -0,0 +1,138 @@ +import { browser, expect } from '@wdio/globals' + +import { + STATUS, + clearAllTestResults, + clickTreeItemButton, + collapseAllTests, + getTestingSection, + openTestingView, + waitForResolved, + waitForTestStatus, +} from '../helpers/index.js' +import { SettingsEditor as ExtendSettingsEditor } from '../pageobjects/SettingsEditor.js' + +import type { SideBarView, ViewControl, Workbench } from 'wdio-vscode-service' + +describe('VS Code Extension Testing (Update config)', function () { + let workbench: Workbench + let sideBarView: SideBarView + let testingVew: ViewControl + + beforeEach(async function () { + workbench = await browser.getWorkbench() + testingVew = await openTestingView(workbench) + sideBarView = workbench.getSideBar() + + const testingSection = await getTestingSection(sideBarView.getContent()) + await collapseAllTests(testingSection) + + await browser.waitUntil(async () => (await testingSection.getVisibleItems()).length === 1) + }) + + afterEach(async function () { + await clearAllTestResults(workbench) + }) + + it('should be resolved the defined tests after configuration changed', async function () { + const testingSection = await getTestingSection(sideBarView.getContent()) + const items = await testingSection.getVisibleItems() + + await waitForResolved(browser, items[0]) + + await expect(items).toMatchTreeStructure([ + { + text: 'wdio.conf.ts', + status: STATUS.NOT_YET_RUN, + children: [ + { + text: 'before.spec.ts', + status: STATUS.NOT_YET_RUN, + children: [ + { + text: 'Before Tests', + status: STATUS.NOT_YET_RUN, + children: [{ text: 'TEST BEFORE 1', status: STATUS.NOT_YET_RUN }], + }, + ], + }, + { + text: 'sample.spec.ts', + status: STATUS.NOT_YET_RUN, + children: [ + { + text: 'Sample 1', + status: STATUS.NOT_YET_RUN, + children: [{ text: 'TEST SAMPLE 1', status: STATUS.NOT_YET_RUN }], + }, + ], + }, + ], + }, + ]) + + // Emulate the changing configuration + await workbench.openSettings() + + await testingVew.closeView() + const setting2 = new ExtendSettingsEditor(workbench) + const listSetting = await setting2.findListSetting('Config File Pattern', 'Webdriverio') + await listSetting.editValue(0, '**/webdriverio.conf.ts') + + await workbench.getEditorView().closeAllEditors() + await testingVew.openView() + + await waitForResolved(browser, items[0]) + + await expect(items).toMatchTreeStructure([ + { + text: 'webdriverio.conf.ts', + status: STATUS.NOT_YET_RUN, + children: [ + { + text: 'after.test.ts', + status: STATUS.NOT_YET_RUN, + children: [ + { + text: 'After Tests', + status: STATUS.NOT_YET_RUN, + children: [{ text: 'TEST AFTER 1', status: STATUS.NOT_YET_RUN }], + }, + ], + }, + ], + }, + ]) + }) + + it('should run tests successfully after changing the settings', async function () { + const testingSection = await getTestingSection(sideBarView.getContent()) + const items = await testingSection.getVisibleItems() + + await waitForResolved(browser, items[0]) + + await clickTreeItemButton(browser, items[0], 'Run Test') + + await waitForTestStatus(browser, items[0], STATUS.PASSED) + + await expect(items).toMatchTreeStructure([ + { + text: 'webdriverio.conf.ts', + status: STATUS.PASSED, + children: [ + { + text: 'after.test.ts', + status: STATUS.PASSED, + children: [ + { + text: 'After Tests', + status: STATUS.PASSED, + children: [{ text: 'TEST AFTER 1', status: STATUS.PASSED }], + }, + ], + }, + ], + }, + ]) + }) +}) diff --git a/e2e/wdioSmoke.conf.ts b/e2e/wdioSmoke.conf.ts index 0f39169..6b77819 100644 --- a/e2e/wdioSmoke.conf.ts +++ b/e2e/wdioSmoke.conf.ts @@ -13,6 +13,7 @@ function defineSmokePrams(target: TestTargets) { './tests/updateSpec.spec.ts', './tests/updateErrorSpec.spec.ts', './tests/updateErrorConfig.spec.ts', + './tests/updateSettings.spec.ts', ], workspace: '../samples/smoke/update-config', settings: {}, diff --git a/eslint.config.mjs b/eslint.config.mjs index 75aa2de..b70177e 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -91,4 +91,13 @@ export default wdioEslint.config([ files: ['e2e/**/*.spec.ts'], ...mochaPlugin.configs.recommended, }, + { + /** + * for extension of wdio-vscode-service + */ + files: ['e2e/pageobjects/**/*.ts'], + rules: { + '@typescript-eslint/no-unsafe-declaration-merging': 'off', + }, + }, ]) diff --git a/samples/smoke/update-config/webdriverio.conf.ts b/samples/smoke/update-config/webdriverio.conf.ts new file mode 100644 index 0000000..d826b87 --- /dev/null +++ b/samples/smoke/update-config/webdriverio.conf.ts @@ -0,0 +1,7 @@ +import '@wdio/types' +import { config as baseConfig } from './wdio.conf.ts' + +export const config = { + ...baseConfig, + specs: ['tests/*.test.ts'], +} From 21e67d3e952ec7177c6226cafeb6721e045ae89f Mon Sep 17 00:00:00 2001 From: mato533 Date: Mon, 23 Jun 2025 06:38:21 +0900 Subject: [PATCH 4/7] test: fix unit test on windows --- packages/vscode-wdio-server/src/manager.ts | 2 +- packages/vscode-wdio-server/tests/manager.test.ts | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/vscode-wdio-server/src/manager.ts b/packages/vscode-wdio-server/src/manager.ts index 379035a..768faa2 100644 --- a/packages/vscode-wdio-server/src/manager.ts +++ b/packages/vscode-wdio-server/src/manager.ts @@ -53,7 +53,7 @@ export class WdioWorkerManager implements IWorkerManager { return server } this.latestId++ - return this.startWorker(this.latestId, dirname(configPaths)) + return this.startWorker(this.latestId, dirname(normalizedConfigPath)) }) } diff --git a/packages/vscode-wdio-server/tests/manager.test.ts b/packages/vscode-wdio-server/tests/manager.test.ts index 7f748d2..69f1a30 100644 --- a/packages/vscode-wdio-server/tests/manager.test.ts +++ b/packages/vscode-wdio-server/tests/manager.test.ts @@ -90,7 +90,7 @@ describe('ServerManager', () => { it('should create a new worker if it does not exist', async () => { // Setup const configPath = '/path/to/wdio.config.js' - const wdioDirName = dirname(configPath) + const wdioDirName = dirname(normalize(configPath)) const spyFactory = vi.spyOn(workerFactory, 'generate') // Execute @@ -113,8 +113,8 @@ describe('ServerManager', () => { // Assert expect(spyFactory).toHaveBeenCalledTimes(2) - expect(spyFactory).toHaveBeenCalledWith('#1', '/path/to') - expect(spyFactory).toHaveBeenCalledWith('#2', '/another/path') + expect(spyFactory).toHaveBeenCalledWith('#1', normalize('/path/to')) + expect(spyFactory).toHaveBeenCalledWith('#2', normalize('/another/path')) }) }) @@ -187,9 +187,9 @@ describe('ServerManager', () => { // Assert operations were queued and processed in order expect(operations.length).toBe(3) - expect(operations[0]).toBe('start:/path/1') - expect(operations[1]).toBe('start:/path/2') - expect(operations[2]).toBe('start:/path/3') + expect(operations[0]).toBe(`start:${normalize('/path/1')}`) + expect(operations[1]).toBe(`start:${normalize('/path/2')}`) + expect(operations[2]).toBe(`start:${normalize('/path/3')}`) }) it('should avoid duplicate operations for the same path', async () => { @@ -278,7 +278,7 @@ describe('ServerManager', () => { const stopWorker = (workerManager as any).stopWorker.bind(workerManager) // Get the worker from the server pool - const worker = (workerManager as any)._workerPool.get('/path/to') + const worker = (workerManager as any)._workerPool.get(normalize('/path/to')) // Execute await stopWorker('/path/to', worker) From e688d076fce728e2bbf0436ff54c54b9d1fb9536 Mon Sep 17 00:00:00 2001 From: mato533 Date: Mon, 23 Jun 2025 07:08:46 +0900 Subject: [PATCH 5/7] test: fix smoke for timeout --- e2e/tests/workerIdleTimeout.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e/tests/workerIdleTimeout.spec.ts b/e2e/tests/workerIdleTimeout.spec.ts index fca8c99..db1dd86 100644 --- a/e2e/tests/workerIdleTimeout.spec.ts +++ b/e2e/tests/workerIdleTimeout.spec.ts @@ -56,7 +56,7 @@ describe(`VS Code Extension Testing with ${targetFramework}`, function () { it('should shutdown the work process after idle timeout was reached', async function () { await new Promise((resolve) => setTimeout(resolve, 2000)) - await expect(workbench).hasExpectedLog(/Worker#0 process shutdown gracefully/) + await expect(workbench).hasExpectedLog(/Worker#1 process shutdown gracefully/) const bottomBar = workbench.getBottomBar() const outputView = await bottomBar.openOutputView() @@ -75,7 +75,7 @@ describe(`VS Code Extension Testing with ${targetFramework}`, function () { await waitForTestStatus(browser, items[0], STATUS.PASSED) // assert that start work process - await expect(workbench).hasExpectedLog(/\[#1\] Worker process started successfully/) + await expect(workbench).hasExpectedLog(/\[#2\] Worker process started successfully/) // assert that run test successfully await expect(items).toMatchTreeStructure(expected.runAll) From 1d0329623d6e3bff65341145b3118974927dffc5 Mon Sep 17 00:00:00 2001 From: mato533 Date: Mon, 23 Jun 2025 08:33:25 +0900 Subject: [PATCH 6/7] test: fix smoke for update settings --- e2e/tests/updateSettings.spec.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/e2e/tests/updateSettings.spec.ts b/e2e/tests/updateSettings.spec.ts index ea346bb..8e40e5a 100644 --- a/e2e/tests/updateSettings.spec.ts +++ b/e2e/tests/updateSettings.spec.ts @@ -1,4 +1,5 @@ import { browser, expect } from '@wdio/globals' +import { sleep } from 'wdio-vscode-service' import { STATUS, @@ -34,7 +35,7 @@ describe('VS Code Extension Testing (Update config)', function () { await clearAllTestResults(workbench) }) - it('should be resolved the defined tests after configuration changed', async function () { + it('should be resolved the defined tests after settings changed', async function () { const testingSection = await getTestingSection(sideBarView.getContent()) const items = await testingSection.getVisibleItems() @@ -75,8 +76,10 @@ describe('VS Code Extension Testing (Update config)', function () { await workbench.openSettings() await testingVew.closeView() - const setting2 = new ExtendSettingsEditor(workbench) - const listSetting = await setting2.findListSetting('Config File Pattern', 'Webdriverio') + await sleep(1500) + + const settingEditor = new ExtendSettingsEditor(workbench) + const listSetting = await settingEditor.findListSetting('Config File Pattern', 'Webdriverio') await listSetting.editValue(0, '**/webdriverio.conf.ts') await workbench.getEditorView().closeAllEditors() From e0b0c177d24d458d62b62fa7581dc4f78c108ca4 Mon Sep 17 00:00:00 2001 From: mato533 Date: Mon, 23 Jun 2025 11:10:12 +0900 Subject: [PATCH 7/7] fix: update config --- e2e/locators/index.ts | 7 --- e2e/pageobjects/SettingsEditor.ts | 86 ------------------------------- e2e/tests/updateSettings.spec.ts | 45 +++++++++++----- 3 files changed, 33 insertions(+), 105 deletions(-) delete mode 100644 e2e/locators/index.ts delete mode 100644 e2e/pageobjects/SettingsEditor.ts diff --git a/e2e/locators/index.ts b/e2e/locators/index.ts deleted file mode 100644 index b921cf3..0000000 --- a/e2e/locators/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const ListSetting = { - listSetting: '.setting-item-list', - listRow: '.setting-list-row', - ListRowEdit: '.setting-list-edit-row', - ListRowTextSetting: 'input', - ListRowOkButton: '.setting-list-ok-button', -} diff --git a/e2e/pageobjects/SettingsEditor.ts b/e2e/pageobjects/SettingsEditor.ts deleted file mode 100644 index 762bd6d..0000000 --- a/e2e/pageobjects/SettingsEditor.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { PageDecorator, SettingsEditor, BasePage, sleep } from 'wdio-vscode-service' - -import { ListSetting as ListSettingLocators } from '../locators/index.js' -import * as locatorMap from '../locators/index.js' -import type { Workbench, IPageDecorator } from 'wdio-vscode-service' - -class ExtendSettingsEditor extends SettingsEditor { - extendedLocator = ListSettingLocators - constructor(workbench: Workbench) { - super(workbench.locatorMap) - } - - async findListSetting(title: string, ...categories: string[]): Promise { - const category = categories.join(' › ') - const searchBox = await this.elem.$(this.locatorMap.Editor.inputArea) - await searchBox.setValue(`${category}: ${title}`) - const count = await this.itemCount$ - let textCount = await count.getText() - await browser.waitUntil(async () => { - await sleep(1500) - const text = await count.getText() - if (text !== textCount) { - textCount = text - return false - } - return true - }) - const items = await this.itemRow$$ - for (const item of items) { - try { - return await (await this.createListSetting(item, title, category)).wait() - } catch { - // ignore - } - } - throw new Error('The setting is not found.') - } - - private async createListSetting(element: WebdriverIO.Element, title: string, category: string) { - if (!(await element.$(this.locators.settingConstructor(title, category)).isExisting())) { - throw new Error('Setting not found') - } - // try a list setting - if (await element.$(this.extendedLocator.listSetting).isExisting()) { - return new ListSetting(locatorMap, title, category, this) - } - throw new Error('Setting type not supported') - } -} - -export interface ListSetting extends IPageDecorator {} - -@PageDecorator(ListSettingLocators) -export class ListSetting extends BasePage { - constructor( - locators: typeof locatorMap, - title: string, - category: string, - public settings: SettingsEditor - ) { - super(locators, settings.locators.settingConstructor(title, category)) - } - - /** - * @private - */ - public locatorKey = 'ListSetting' as const - - async editValue(index: number, value: string) { - const rows = this.listRow$$ - if (rows.length < index + 1) { - throw new Error('invalid index') - } - await (rows[index] as WebdriverIO.Element).doubleClick() - - const editRow = await this.elem.$(this.locators.ListRowEdit) - - const input = await editRow.$(this.locators.ListRowTextSetting) - const okBtn = editRow.$(this.locators.ListRowOkButton) - - await input.setValue(value) - await okBtn.click() - } -} - -export { ExtendSettingsEditor as SettingsEditor } diff --git a/e2e/tests/updateSettings.spec.ts b/e2e/tests/updateSettings.spec.ts index 8e40e5a..a663a94 100644 --- a/e2e/tests/updateSettings.spec.ts +++ b/e2e/tests/updateSettings.spec.ts @@ -11,18 +11,24 @@ import { waitForResolved, waitForTestStatus, } from '../helpers/index.js' -import { SettingsEditor as ExtendSettingsEditor } from '../pageobjects/SettingsEditor.js' -import type { SideBarView, ViewControl, Workbench } from 'wdio-vscode-service' +import type { SideBarView, TextEditor, Workbench } from 'wdio-vscode-service' describe('VS Code Extension Testing (Update config)', function () { let workbench: Workbench let sideBarView: SideBarView - let testingVew: ViewControl + let orgSettings: string + + before(async function () { + workbench = await browser.getWorkbench() + const tab = await getSettingTextEditor(workbench) + orgSettings = await tab.getText() + await workbench.getEditorView().closeAllEditors() + }) beforeEach(async function () { workbench = await browser.getWorkbench() - testingVew = await openTestingView(workbench) + await openTestingView(workbench) sideBarView = workbench.getSideBar() const testingSection = await getTestingSection(sideBarView.getContent()) @@ -35,6 +41,15 @@ describe('VS Code Extension Testing (Update config)', function () { await clearAllTestResults(workbench) }) + after(async function () { + const tab = await getSettingTextEditor(workbench) + await tab.clearText() + await tab.setText(JSON.stringify(JSON.parse(orgSettings), null, 2)) + await tab.save() + + await workbench.getEditorView().closeAllEditors() + }) + it('should be resolved the defined tests after settings changed', async function () { const testingSection = await getTestingSection(sideBarView.getContent()) const items = await testingSection.getVisibleItems() @@ -73,17 +88,16 @@ describe('VS Code Extension Testing (Update config)', function () { ]) // Emulate the changing configuration - await workbench.openSettings() - - await testingVew.closeView() - await sleep(1500) + const settings = JSON.parse(orgSettings) + settings['webdriverio.configFilePattern'] = ['**/webdriverio.conf.ts'] - const settingEditor = new ExtendSettingsEditor(workbench) - const listSetting = await settingEditor.findListSetting('Config File Pattern', 'Webdriverio') - await listSetting.editValue(0, '**/webdriverio.conf.ts') + const tab = await getSettingTextEditor(workbench) + await tab.clearText() + await tab.setText(JSON.stringify(settings, null, 2)) + await tab.save() await workbench.getEditorView().closeAllEditors() - await testingVew.openView() + await sleep(1500) await waitForResolved(browser, items[0]) @@ -139,3 +153,10 @@ describe('VS Code Extension Testing (Update config)', function () { ]) }) }) + +async function getSettingTextEditor(workbench: Workbench) { + await workbench.executeCommand('Preferences: Open User Settings (JSON)') + await sleep(1500) + const editorView = workbench.getEditorView() + return (await editorView.openEditor('settings.json')) as TextEditor +}