diff --git a/.github/workflows/actions/set-screen-resolution/action.yml b/.github/workflows/actions/set-screen-resolution/action.yml
new file mode 100644
index 0000000..facb73f
--- /dev/null
+++ b/.github/workflows/actions/set-screen-resolution/action.yml
@@ -0,0 +1,41 @@
+name: 'vscode-webdriverio Set screen resolution'
+description: 'Set screen resolution'
+inputs:
+ width:
+ description: 'screen width'
+ default: '1920'
+ height:
+ description: 'screen height'
+ default: '1080'
+runs:
+ using: 'composite'
+ steps:
+ # https://github.com/actions/runner-images/issues/2935
+ - name: Set display resolution on Windows
+ if: runner.os == 'Windows'
+ shell: pwsh
+ run: |
+ Set-DisplayResolution -Width ${{ inputs.width }} -Height ${{ inputs.height }} -Force
+
+ # I don't know the details, but it appears that it needs to be maximized at the Webdriver level
+ # because it does not launch in full screen on Linux.
+ # However, as the following Issue states, Electron(base of vscode) can not be muximize by webdriver protocol.
+ # https://github.com/electron/electron/issues/33942
+ # Therefore, GUI-based methods are used to maximize the screen.
+ # The process of maximizing screen size using xdotool is done in the before hook of wdio.conf.ts.
+ - name: Set display resolution on Linux
+ if: runner.os == 'Linux'
+ shell: bash
+ run: |
+ pnpm --filter @vscode-wdio/xvfb-patch run patch -w ${{ inputs.width }} -h ${{ inputs.height }}
+ echo "::group::apt install -y xdotool"
+ sudo apt install -y xdotool
+ echo "::endgroup::"
+
+ # already use FHD resolution
+ - name: Set display resolution on MacOS
+ if: runner.os == 'macOS'
+ shell: bash
+ run: |
+ brew install displayplacer
+ displayplacer list
diff --git a/.github/workflows/ci-e2e.yml b/.github/workflows/ci-e2e.yml
index 2b7e14a..4ee481e 100644
--- a/.github/workflows/ci-e2e.yml
+++ b/.github/workflows/ci-e2e.yml
@@ -15,13 +15,14 @@ env:
VSCODE_WDIO_E2E_COMPATIBILITY_MODE: ${{ inputs.compatibility-mode }}
jobs:
- unit:
- name: E2E Tests (${{ matrix.os }}.${{ matrix.node-version }})
+ e2e:
+ name: E2E Tests - ${{ matrix.scenario }} (${{ matrix.os }}.${{ matrix.node-version }})
strategy:
fail-fast: false
matrix:
node-version: ['20']
os: ['ubuntu-latest', 'windows-latest', 'macos-latest']
+ scenario: ['basic', 'workspace']
runs-on: ${{ matrix.os }}
steps:
- name: ๐ท Checkout
@@ -46,16 +47,21 @@ jobs:
with:
path: e2e/.wdio-vscode-service
+ - name: ๐ฅ๏ธ Set screen resolution
+ uses: ./.github/workflows/actions/set-screen-resolution
+
- name: ๐งช Run the e2e test
- run: pnpm run test:e2e
+ env:
+ E2E_SCENARIO: ${{ matrix.scenario }}
+ run: pnpm --filter @vscode-wdio/e2e run test:e2e:${E2E_SCENARIO}
shell: bash
- name: ๐ฆ Upload Test Logs on Failure
uses: ./.github/workflows/actions/upload-archive
if: failure()
with:
- name: ${{ inputs.compatibility-mode == 'yes' && 'compatibility' || 'e2e' }}-logs-${{ matrix.os }}
- output: ${{ inputs.compatibility-mode == 'yes' && 'compatibility' || 'e2e' }}-logs-${{ matrix.os }}.zip
+ name: ${{ inputs.compatibility-mode == 'yes' && 'compatibility' || 'e2e' }}-${{ matrix.scenario }}-logs-${{ matrix.os }}
+ output: ${{ inputs.compatibility-mode == 'yes' && 'compatibility' || 'e2e' }}-${{ matrix.scenario }}-logs-${{ matrix.os }}.zip
paths: e2e/logs
- name: ๐ Debug Build
diff --git a/.github/workflows/ci-lint.yml b/.github/workflows/ci-lint.yml
index 6624ade..97bf024 100644
--- a/.github/workflows/ci-lint.yml
+++ b/.github/workflows/ci-lint.yml
@@ -1,4 +1,4 @@
-name: Lint
+name: Static code analysis
on:
workflow_call:
@@ -11,12 +11,7 @@ env:
jobs:
lint:
name: Lint
- strategy:
- fail-fast: false
- matrix:
- node-version: ['20']
- os: ['ubuntu-latest']
- runs-on: ${{ matrix.os }}
+ runs-on: 'ubuntu-latest'
steps:
- name: ๐ท Checkout
uses: actions/checkout@v4.2.2
@@ -26,14 +21,7 @@ jobs:
- name: ๐ ๏ธ Setup workspace
uses: ./.github/workflows/actions/setup-workspace
with:
- node-version: ${{ matrix.node-version }}
-
- - name: โฌ๏ธ Download Build Archive
- uses: ./.github/workflows/actions/download-archive
- with:
- name: vscode-webdriverio
- path: .
- filename: vscode-webdriverio-build.zip
+ node-version: '20'
- name: ๐ Run the lint
run: pnpm run style:fix
diff --git a/.github/workflows/ci-smoke.yml b/.github/workflows/ci-smoke.yml
index c9bec43..1acc5df 100644
--- a/.github/workflows/ci-smoke.yml
+++ b/.github/workflows/ci-smoke.yml
@@ -4,12 +4,16 @@ on:
workflow_call:
# Make this a reusable workflow, no value needed
# https://docs.github.com/en/actions/using-workflows/reusing-workflows
+ inputs:
+ scenario:
+ description: 'Smoke scenario'
+ type: string
env:
TURBO_TELEMETRY_DISABLED: 1
jobs:
- unit:
+ smoke:
name: Smoke Tests (${{ matrix.os }}.${{ matrix.node-version }})
strategy:
fail-fast: false
@@ -40,16 +44,21 @@ jobs:
with:
path: e2e/.wdio-vscode-service
+ - name: ๐ฅ๏ธ Set screen resolution
+ uses: ./.github/workflows/actions/set-screen-resolution
+
- name: ๐ Run the smoke test
- run: pnpm run test:smoke
+ env:
+ E2E_SCENARIO: ${{ inputs.scenario }}
+ run: pnpm --filter @vscode-wdio/e2e run test:smoke:${E2E_SCENARIO}
shell: bash
- name: ๐ฆ Upload Test Logs on Failure
uses: ./.github/workflows/actions/upload-archive
if: failure()
with:
- name: smoke-logs-${{ matrix.os }}
- output: smoke-logs-${{ matrix.os }}.zip
+ name: smoke-${{ inputs.scenario }}--logs-${{ matrix.os }}
+ output: smoke-${{ inputs.scenario }}-logs-${{ matrix.os }}.zip
paths: e2e/logs
- name: ๐ Debug Build
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2326474..6fca503 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -21,33 +21,41 @@ jobs:
os: 'ubuntu-latest'
lint:
- name: Lint
- needs: [build]
+ name: Static code analysis
uses: ./.github/workflows/ci-lint.yml
typecheck:
name: Typecheck
- needs: [build]
+ needs: [lint, build]
uses: ./.github/workflows/ci-typecheck.yml
unit:
name: Unit
- needs: [build]
+ needs: [lint, build]
uses: ./.github/workflows/ci-unit.yml
e2e:
name: E2E
- needs: [build]
+ needs: [lint, build]
uses: ./.github/workflows/ci-e2e.yml
compatibility:
name: Compatibility
- needs: [build]
+ needs: [lint, build]
uses: ./.github/workflows/ci-e2e.yml
with:
compatibility-mode: 'yes'
- smoke:
- name: Smoke
- needs: [build, e2e]
+ smoke-config:
+ name: Smoke - Update Config
+ needs: [lint, build, e2e]
uses: ./.github/workflows/ci-smoke.yml
+ with:
+ scenario: 'config'
+
+ smoke-timeout:
+ name: Smoke - Worker idle timeout
+ needs: [lint, build, e2e]
+ uses: ./.github/workflows/ci-smoke.yml
+ with:
+ scenario: 'timeout'
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 0a9c92c..31f3712 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -132,7 +132,7 @@ This Extension consists of several packages. Eventually, these packages will be
**Service Layer**
@vscode-wdio/config (constants, logger, utils)
-โโโ @vscode-wdio/api (constants, logger, utils)
+โโโ @vscode-wdio/server (constants, logger, utils)
**Integration Layer**
โโโ @vscode-wdio/worker (constants, utils)
diff --git a/README.md b/README.md
index 1fd9206..82e3598 100644
--- a/README.md
+++ b/README.md
@@ -80,6 +80,7 @@ This extension contributes the following settings:
- `webdriverio.nodeExecutable`: The path to the Node.js executable. If not assigned, WebdriverIO try to resolve the node path from environment valuables of `PATH`.
- `webdriverio.configFilePattern`: Glob pattern for WebdriverIO configuration file
+- `webdriverio.workerIdleTimeout`: If no processing is performed in the Worker for the set amount of time(defined by seconds), the Worker is terminated. If processing is requested again, it will be started automatically.
- `webdriverio.logLevel`: Set the logLevel
- `webdriverio.showOutput`: Show WebdriverIO output in the test result when set `true` this option
diff --git a/assets/build.png b/assets/build.png
index dba9630..e287fdb 100644
Binary files a/assets/build.png and b/assets/build.png differ
diff --git a/e2e/assertions/index.ts b/e2e/assertions/index.ts
index 3cee567..8d9d398 100644
--- a/e2e/assertions/index.ts
+++ b/e2e/assertions/index.ts
@@ -1,5 +1,5 @@
import type { MatcherContext } from 'expect'
-import type { TreeItem } from 'wdio-vscode-service'
+import type { BottomBarPanel, TreeItem, Workbench } from 'wdio-vscode-service'
import type { STATUS } from '../helpers/index.ts'
export interface ExpectedTreeItem {
@@ -92,8 +92,38 @@ try {
async toMatchTreeStructure(tree: TreeItem[], expectedStructure: ExpectedTreeItem[]) {
return await expectTreeToMatchStructure.call(this as unknown as MatcherContext, tree, expectedStructure)
},
+ async hasExpectedLog(workbench: Workbench, expectedLog: RegExp | string) {
+ const bottomBar = workbench.getBottomBar()
+
+ const outputView = await bottomBar.openOutputView()
+ await outputView.selectChannel('WebdriverIO')
+ await clickGlobalAction(bottomBar, bottomBar.locators.maximize)
+ const logs = await outputView.getText()
+
+ const regexp = typeof expectedLog === 'string' ? new RegExp(expectedLog) : expectedLog
+
+ const pass = logs.some((log) => regexp.test(log))
+
+ await clickGlobalAction(bottomBar, bottomBar.locators.restore)
+ const message = pass ? 'The log outputs include expected text.' : 'The expected text is not included'
+ return { pass, message: () => message }
+ },
})
}
} catch (error) {
console.warn('Failed to extend expect:', error)
}
+
+async function clickGlobalAction(bottomBar: BottomBarPanel, label: string) {
+ let action
+ try {
+ action = (await bottomBar.elem
+ .$(bottomBar.locators.globalActions)
+ .$(`.//a[contains(@aria-label, '${label}') and @role='checkbox']`)) as WebdriverIO.Element
+ } catch {
+ // the panel is already maximized
+ }
+ if (action) {
+ await action.click({})
+ }
+}
diff --git a/e2e/assertions/wdio.shim.d.ts b/e2e/assertions/wdio.shim.d.ts
index 917ba54..09e1558 100644
--- a/e2e/assertions/wdio.shim.d.ts
+++ b/e2e/assertions/wdio.shim.d.ts
@@ -4,6 +4,7 @@ declare global {
namespace ExpectWebdriverIO {
interface Matchers {
toMatchTreeStructure(expectedStructure: ExpectedTreeItem[]): R
+ hasExpectedLog(expectedLog: RegExp | string): R
}
}
}
diff --git a/e2e/package.json b/e2e/package.json
index 80eb67c..9d74cb3 100644
--- a/e2e/package.json
+++ b/e2e/package.json
@@ -10,13 +10,15 @@
}
},
"scripts": {
- "test:e2e": "run-s test:e2e:*",
- "test:e2e:mocha": "cross-env VSCODE_WDIO_E2E_FRAMEWORK=mocha xvfb-maybe pnpm run wdio",
- "test:e2e:jasmine": "cross-env VSCODE_WDIO_E2E_FRAMEWORK=jasmine xvfb-maybe pnpm run wdio",
- "test:e2e:cucumber": "cross-env VSCODE_WDIO_E2E_FRAMEWORK=cucumber xvfb-maybe pnpm run wdio",
- "test:e2e:workspace": "cross-env VSCODE_WDIO_E2E_FRAMEWORK=workspace xvfb-maybe pnpm run wdio",
+ "test:e2e": "run-s test:e2e:basic test:e2e:workspace",
+ "test:e2e:basic": "run-s test:e2e:basic:*",
+ "test:e2e:basic:mocha": "cross-env VSCODE_WDIO_E2E_SCENARIO=mocha xvfb-maybe pnpm run wdio",
+ "test:e2e:basic:jasmine": "cross-env VSCODE_WDIO_E2E_SCENARIO=jasmine xvfb-maybe pnpm run wdio",
+ "test:e2e:basic:cucumber": "cross-env VSCODE_WDIO_E2E_SCENARIO=cucumber xvfb-maybe pnpm run wdio",
+ "test:e2e:workspace": "cross-env VSCODE_WDIO_E2E_SCENARIO=workspace xvfb-maybe pnpm run wdio",
"test:smoke": "run-s test:smoke:*",
- "test:smoke:update-config": "xvfb-maybe wdio run ./wdioSmoke.conf.ts",
+ "test:smoke:config": "cross-env VSCODE_WDIO_E2E_SCENARIO=config xvfb-maybe wdio run ./wdioSmoke.conf.ts",
+ "test:smoke:timeout": "cross-env VSCODE_WDIO_E2E_SCENARIO=timeout xvfb-maybe wdio run ./wdioSmoke.conf.ts",
"wdio": "wdio run ./wdio.conf.ts"
},
"devDependencies": {
diff --git a/e2e/tests/basic.spec.ts b/e2e/tests/basic.spec.ts
index e579cab..ab958fc 100644
--- a/e2e/tests/basic.spec.ts
+++ b/e2e/tests/basic.spec.ts
@@ -15,7 +15,7 @@ import {
import type { SideBarView, ViewControl, Workbench } from 'wdio-vscode-service'
-const targetFramework = (process.env.VSCODE_WDIO_E2E_FRAMEWORK || 'mocha') as 'mocha' | 'jasmine'
+const targetFramework = (process.env.VSCODE_WDIO_E2E_SCENARIO || 'mocha') as 'mocha' | 'jasmine'
const expected = createExpected(targetFramework)
diff --git a/e2e/tests/basicCucumber.spec.ts b/e2e/tests/basicCucumber.spec.ts
index 4b95c8d..60e5227 100644
--- a/e2e/tests/basicCucumber.spec.ts
+++ b/e2e/tests/basicCucumber.spec.ts
@@ -15,7 +15,7 @@ import {
import type { SideBarView, ViewControl, Workbench } from 'wdio-vscode-service'
-const targetFramework = process.env.VSCODE_WDIO_E2E_FRAMEWORK || 'mocha'
+const targetFramework = process.env.VSCODE_WDIO_E2E_SCENARIO || 'mocha'
const expected = createCucumberExpected()
diff --git a/e2e/tests/workerIdleTimeout.spec.ts b/e2e/tests/workerIdleTimeout.spec.ts
new file mode 100644
index 0000000..267a10f
--- /dev/null
+++ b/e2e/tests/workerIdleTimeout.spec.ts
@@ -0,0 +1,83 @@
+import { browser, expect } from '@wdio/globals'
+
+import { createCucumberExpected } from '../helpers/cucumber.ts'
+import {
+ STATUS,
+ clearAllTestResults,
+ clickTitleActionButton,
+ collapseAllTests,
+ getTestingSection,
+ openTestingView,
+ waitForResolved,
+ waitForTestStatus,
+} from '../helpers/index.ts'
+
+import type { SideBarView, ViewControl, Workbench } from 'wdio-vscode-service'
+
+const targetFramework = process.env.VSCODE_WDIO_E2E_SCENARIO || 'mocha'
+
+const expected = createCucumberExpected()
+
+describe(`VS Code Extension Testing with ${targetFramework}`, function () {
+ this.retries(3)
+ let workbench: Workbench
+ let testingViewControl: ViewControl
+ let sideBarView: SideBarView
+
+ beforeEach(async function () {
+ workbench = await browser.getWorkbench()
+ testingViewControl = 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 displayed the testing screen at the sideBar', async function () {
+ expect(await testingViewControl.getTitle()).toBe('Testing')
+ expect(await sideBarView.getTitlePart().getTitle()).toBe('TESTING')
+ })
+
+ it('should resolve defined tests correctly', async function () {
+ const testingSection = await getTestingSection(sideBarView.getContent())
+ const items = await testingSection.getVisibleItems()
+
+ await waitForResolved(browser, items[0])
+
+ await expect(items).toMatchTreeStructure(expected.notRun)
+ })
+
+ 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/)
+
+ const bottomBar = workbench.getBottomBar()
+ const outputView = await bottomBar.openOutputView()
+ await outputView.selectChannel('WebdriverIO')
+ await outputView.clearText()
+ })
+
+ it('should start work process and run test successfully', async function () {
+ const testingSection = await getTestingSection(sideBarView.getContent())
+ const items = await testingSection.getVisibleItems()
+
+ await waitForResolved(browser, items[0])
+
+ await clickTitleActionButton(sideBarView.getTitlePart(), 'Run Tests')
+
+ await waitForTestStatus(browser, items[0], STATUS.PASSED)
+
+ // assert that start work process
+ await expect(workbench).hasExpectedLog(/\[#1\] Worker process started successfully/)
+
+ // assert that run test successfully
+ await expect(items).toMatchTreeStructure(expected.runAll)
+ })
+})
diff --git a/e2e/wdio.conf.ts b/e2e/wdio.conf.ts
index f088bd1..95771c0 100644
--- a/e2e/wdio.conf.ts
+++ b/e2e/wdio.conf.ts
@@ -2,6 +2,7 @@ import * as path from 'node:path'
import * as url from 'node:url'
import { minVersion } from 'semver'
+import shell from 'shelljs'
import pkg from '../packages/vscode-webdriverio/package.json' with { type: 'json' }
import type { Frameworks } from '@wdio/types'
@@ -9,7 +10,7 @@ import type { Frameworks } from '@wdio/types'
type TestTargets = 'workspace' | 'mocha' | 'jasmine' | 'cucumber'
const __dirname = path.dirname(url.fileURLToPath(import.meta.url))
-const target = (process.env.VSCODE_WDIO_E2E_FRAMEWORK || 'mocha') as TestTargets
+const target = (process.env.VSCODE_WDIO_E2E_SCENARIO || 'mocha') as TestTargets
const minimumVersion = minVersion(pkg.engines.vscode)?.version || 'stable'
@@ -33,7 +34,15 @@ function defineSpecs(target: TestTargets) {
const specs = defineSpecs(target)
let screenshotCount = 0
-export function createBaseConfig(workspacePath: string): WebdriverIO.Config {
+export function createBaseConfig(workspacePath: string, userSettings = {}): WebdriverIO.Config {
+ const resolvedUserSettings = Object.assign(
+ {},
+ {
+ 'webdriverio.logLevel': 'trace',
+ },
+ userSettings
+ )
+
return {
runner: 'local',
tsConfigPath: './tsconfig.json',
@@ -47,9 +56,7 @@ export function createBaseConfig(workspacePath: string): WebdriverIO.Config {
// points to directory where extension package.json is located
extensionPath: path.resolve('../packages/vscode-webdriverio'),
// optional VS Code settings
- userSettings: {
- 'webdriverio.logLevel': 'trace',
- },
+ userSettings: resolvedUserSettings,
workspacePath: path.resolve(workspacePath),
},
'wdio:enforceWebDriverClassic': true,
@@ -70,6 +77,14 @@ export function createBaseConfig(workspacePath: string): WebdriverIO.Config {
timeout: 6000000,
require: ['assertions/index.ts'],
},
+ before: async function (_capabilities, _specs, _browser) {
+ if (process.platform === 'linux') {
+ const result = shell.exec('xdotool search --onlyvisible --name code')
+ const windowId = result.stdout.trim()
+ shell.exec(`xdotool windowmove ${windowId} 0 0`)
+ shell.exec(`xdotool windowsize ${windowId} 100% 100%`)
+ }
+ },
afterTest: async function (_test: unknown, _context: unknown, result: Frameworks.TestResult) {
if (!result.passed) {
await browser.saveScreenshot(path.join(outputDir, `screenshot-${screenshotCount++}.png`))
diff --git a/e2e/wdioSmoke.conf.ts b/e2e/wdioSmoke.conf.ts
index c8d0cbf..802ff7a 100644
--- a/e2e/wdioSmoke.conf.ts
+++ b/e2e/wdioSmoke.conf.ts
@@ -1,14 +1,31 @@
import { createBaseConfig } from './wdio.conf.ts'
-const specs = [
- './tests/updateConfig.spec.ts',
- './tests/updateSpec.spec.ts',
- './tests/updateErrorSpec.spec.ts',
- './tests/updateErrorConfig.spec.ts',
-]
+type TestTargets = 'config' | 'timeout'
+
+const target = (process.env.VSCODE_WDIO_E2E_SCENARIO || 'config') as TestTargets
+
+const workspace = target === 'config' ? '../samples/smoke/update-config' : '../samples/e2e/cucumber'
+
+function defineSpecs(target: TestTargets) {
+ switch (target) {
+ case 'config':
+ return [
+ './tests/updateConfig.spec.ts',
+ './tests/updateSpec.spec.ts',
+ './tests/updateErrorSpec.spec.ts',
+ './tests/updateErrorConfig.spec.ts',
+ ]
+ default:
+ return ['./tests/workerIdleTimeout.spec.ts']
+ }
+}
+
+const specs = defineSpecs(target)
+
+const settings = target === 'timeout' ? { 'webdriverio.logLevel': 'debug', 'webdriverio.workerIdleTimeout': 2 } : {}
export const config: WebdriverIO.Config = {
- ...createBaseConfig('../samples/smoke/update-config'),
+ ...createBaseConfig(workspace, settings),
specs,
maxInstances: 1,
}
diff --git a/infra/xvfb-patch/package.json b/infra/xvfb-patch/package.json
new file mode 100644
index 0000000..541e5d7
--- /dev/null
+++ b/infra/xvfb-patch/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "@vscode-wdio/xvfb-patch",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "patch": "tsx ./src/index.ts"
+ }
+}
diff --git a/infra/xvfb-patch/src/index.ts b/infra/xvfb-patch/src/index.ts
new file mode 100644
index 0000000..d9cb5ba
--- /dev/null
+++ b/infra/xvfb-patch/src/index.ts
@@ -0,0 +1,48 @@
+import * as fs from 'node:fs'
+import * as path from 'node:path'
+import { fileURLToPath } from 'node:url'
+import { parseArgs } from 'node:util'
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url))
+const rootDir = path.resolve(__dirname, '../../..')
+const filePath = path.join(rootDir, 'node_modules', 'xvfb-maybe', 'src', 'xvfb-maybe.js')
+
+const args = process.argv.slice(2)
+const optionsDef = {
+ width: {
+ type: 'string',
+ short: 'w',
+ },
+ height: {
+ type: 'string',
+ short: 'h',
+ },
+} as const
+
+const { values: options } = parseArgs({ args, options: optionsDef })
+
+console.log('Adjust screen resolution')
+console.log(` Width : ${options.width}`)
+console.log(` Height: ${options.height}`)
+
+const insertBefore = "const dblDashPos = args.indexOf('--'),"
+const codeToInsert = ` args.unshift('--server-args=-screen 0 ${options.width}x${options.height}x24', '--');`
+
+const sourceCode = fs.readFileSync(filePath, 'utf-8')
+
+if (sourceCode.includes(codeToInsert)) {
+ console.log('๐ง xvfb-maybe is already patched')
+ process.exit(0)
+}
+
+const lines = sourceCode.split('\n')
+const index = lines.findIndex((line) => line.includes(insertBefore))
+
+if (index !== -1) {
+ lines.splice(index, 0, codeToInsert)
+ const newCode = lines.join('\n')
+ fs.writeFileSync(filePath, newCode, 'utf-8')
+ console.log('\nโ
xvfb-maybe is patched successfully\n\n')
+} else {
+ console.log('\n๐ฅ could not find the target line.\n\n')
+}
diff --git a/infra/xvfb-patch/tsconfig.json b/infra/xvfb-patch/tsconfig.json
new file mode 100644
index 0000000..ea50023
--- /dev/null
+++ b/infra/xvfb-patch/tsconfig.json
@@ -0,0 +1,6 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "resolveJsonModule": true
+ }
+}
diff --git a/packages/vscode-wdio-config/src/index.ts b/packages/vscode-wdio-config/src/index.ts
index 5246eef..2381c9c 100644
--- a/packages/vscode-wdio-config/src/index.ts
+++ b/packages/vscode-wdio-config/src/index.ts
@@ -6,14 +6,9 @@ import { convertUriToPath, normalizePath } from '@vscode-wdio/utils'
import * as vscode from 'vscode'
import { findWdioConfig } from './find.js'
-import type {
- WebdriverIOConfig,
- ConfigPropertyNames,
- WorkspaceData,
- ExtensionConfigManagerInterface,
-} from '@vscode-wdio/types'
-
-export class ExtensionConfigManager extends EventEmitter implements ExtensionConfigManagerInterface {
+import type { WebdriverIOConfig, ConfigPropertyNames, WorkspaceData, IExtensionConfigManager } from '@vscode-wdio/types'
+
+export class ExtensionConfigManager extends EventEmitter implements IExtensionConfigManager {
private _isInitialized = false
private _isMultiWorkspace = false
private _globalConfig: WebdriverIOConfig
@@ -33,6 +28,7 @@ export class ExtensionConfigManager extends EventEmitter implements ExtensionCon
configFilePattern && configFilePattern.length > 0
? configFilePattern
: [...DEFAULT_CONFIG_VALUES.configFilePattern],
+ workerIdleTimeout: config.get('workerIdleTimeout', DEFAULT_CONFIG_VALUES.workerIdleTimeout),
showOutput: this.resolveBooleanConfig(config, 'showOutput', DEFAULT_CONFIG_VALUES.showOutput),
logLevel: config.get('logLevel', DEFAULT_CONFIG_VALUES.logLevel),
}
diff --git a/packages/vscode-wdio-config/src/watcher.ts b/packages/vscode-wdio-config/src/watcher.ts
index 5bcac02..44f9b6f 100644
--- a/packages/vscode-wdio-config/src/watcher.ts
+++ b/packages/vscode-wdio-config/src/watcher.ts
@@ -1,14 +1,14 @@
import { convertUriToPath, normalizePath, FileWatcherManager, type WatchPattern } from '@vscode-wdio/utils'
-import type { ServerManagerInterface } from '@vscode-wdio/types/api'
-import type { ExtensionConfigManagerInterface } from '@vscode-wdio/types/config'
-import type { RepositoryManagerInterface } from '@vscode-wdio/types/test'
+import type { IExtensionConfigManager } from '@vscode-wdio/types/config'
+import type { IWorkerManager } from '@vscode-wdio/types/server'
+import type { IRepositoryManager } from '@vscode-wdio/types/test'
import type * as vscode from 'vscode'
export class ConfigFileWatcher extends FileWatcherManager {
constructor(
- public readonly configManager: ExtensionConfigManagerInterface,
- private readonly serverManager: ServerManagerInterface,
- private readonly repositoryManager: RepositoryManagerInterface,
+ public readonly configManager: IExtensionConfigManager,
+ private readonly workerManager: IWorkerManager,
+ private readonly repositoryManager: IRepositoryManager,
private readonly testfileWatcher: FileWatcherManager
) {
super()
@@ -53,7 +53,7 @@ export class ConfigFileWatcher extends FileWatcherManager {
const workspaceUris = this.configManager.removeWdioConfig(wdioConfigPath)
for (const workspaceUri of workspaceUris) {
this.repositoryManager.removeWdioConfig(workspaceUri, wdioConfigPath)
- await this.serverManager.reorganize(this.configManager.getWdioConfigPaths())
+ await this.workerManager.reorganize(this.configManager.getWdioConfigPaths())
}
this.testfileWatcher.refreshWatchers()
}
diff --git a/packages/vscode-wdio-config/tests/index.test.ts b/packages/vscode-wdio-config/tests/index.test.ts
index b16f6e3..a2effef 100644
--- a/packages/vscode-wdio-config/tests/index.test.ts
+++ b/packages/vscode-wdio-config/tests/index.test.ts
@@ -87,8 +87,10 @@ describe('ExtensionConfigManager', () => {
// Verify
expect(instance.globalConfig).toEqual({
configFilePattern: customConfigFilePattern,
+ nodeExecutable: undefined,
showOutput: customShowOutput,
logLevel: customLogLevel,
+ workerIdleTimeout: 600,
})
})
diff --git a/packages/vscode-wdio-config/tests/watcher.test.ts b/packages/vscode-wdio-config/tests/watcher.test.ts
index c0da93d..d890914 100644
--- a/packages/vscode-wdio-config/tests/watcher.test.ts
+++ b/packages/vscode-wdio-config/tests/watcher.test.ts
@@ -5,8 +5,8 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import * as vscode from 'vscode'
import { ConfigFileWatcher } from '../src/watcher.js'
-import type { ServerManagerInterface } from '@vscode-wdio/types/api'
-import type { RepositoryManagerInterface } from '@vscode-wdio/types/test'
+import type { IWorkerManager } from '@vscode-wdio/types/server'
+import type { IRepositoryManager } from '@vscode-wdio/types/test'
import type { ExtensionConfigManager } from '../src/index.js'
// Mock dependencies
@@ -53,8 +53,8 @@ class MockTestFileWatcher extends FileWatcherManager {
describe('ConfigFileWatcher', () => {
let watcher: ConfigFileWatcher
let mockConfigManager: ExtensionConfigManager
- let mockServerManager: ServerManagerInterface
- let mockRepositoryManager: RepositoryManagerInterface
+ let mockServerManager: IWorkerManager
+ let mockRepositoryManager: IRepositoryManager
let mockRepo1: any
let mockRepo2: any
let mockUri: vscode.Uri
@@ -81,12 +81,12 @@ describe('ConfigFileWatcher', () => {
repos: [mockRepo1, mockRepo2],
addWdioConfig: vi.fn(),
removeWdioConfig: vi.fn(),
- } as unknown as RepositoryManagerInterface
+ } as unknown as IRepositoryManager
// Create mock server manager
mockServerManager = {
reorganize: vi.fn().mockResolvedValue(undefined),
- } as unknown as ServerManagerInterface
+ } as unknown as IWorkerManager
// Create mock config manager
mockConfigManager = {
diff --git a/packages/vscode-wdio-constants/src/index.ts b/packages/vscode-wdio-constants/src/index.ts
index 662eadc..70bff5d 100644
--- a/packages/vscode-wdio-constants/src/index.ts
+++ b/packages/vscode-wdio-constants/src/index.ts
@@ -1,7 +1,9 @@
/* c8 ignore start */
+
export interface WebdriverIOConfig {
nodeExecutable: string | undefined
configFilePattern: string[]
+ workerIdleTimeout: number
showOutput: boolean
logLevel: string
}
@@ -11,6 +13,7 @@ export const EXTENSION_ID = 'webdriverio'
export const DEFAULT_CONFIG_VALUES: WebdriverIOConfig = {
nodeExecutable: undefined,
configFilePattern: ['**/*wdio*.conf*.{ts,js,mjs,cjs,cts,mts}'],
+ workerIdleTimeout: 600,
showOutput: true,
logLevel: 'info',
} as const
diff --git a/packages/vscode-wdio-logger/src/logger.ts b/packages/vscode-wdio-logger/src/logger.ts
index a6f86b8..68852ff 100644
--- a/packages/vscode-wdio-logger/src/logger.ts
+++ b/packages/vscode-wdio-logger/src/logger.ts
@@ -2,7 +2,7 @@ import { EXTENSION_ID, LOG_LEVEL } from '@vscode-wdio/constants'
import * as vscode from 'vscode'
import { FileLogger } from './fileLogger.js'
-import type { LoggerInterface, WdioLogLevel } from '@vscode-wdio/types'
+import type { ILogger, WdioLogLevel } from '@vscode-wdio/types'
export const LOG_LEVEL_NAMES: Record = {
[LOG_LEVEL.TRACE]: 'TRACE',
@@ -13,7 +13,7 @@ export const LOG_LEVEL_NAMES: Record = {
[LOG_LEVEL.SILENT]: 'SILENT ',
} as const
-export class VscodeWdioLogger implements LoggerInterface, vscode.Disposable {
+export class VscodeWdioLogger implements ILogger, vscode.Disposable {
private _timezoneString: string | undefined
private _disposables: vscode.Disposable[] = []
private _logLevel: LOG_LEVEL
diff --git a/packages/vscode-wdio-api/package.json b/packages/vscode-wdio-server/package.json
similarity index 95%
rename from packages/vscode-wdio-api/package.json
rename to packages/vscode-wdio-server/package.json
index f952d9a..849c28d 100644
--- a/packages/vscode-wdio-api/package.json
+++ b/packages/vscode-wdio-server/package.json
@@ -1,5 +1,5 @@
{
- "name": "@vscode-wdio/api",
+ "name": "@vscode-wdio/server",
"version": "0.3.2",
"private": true,
"type": "module",
diff --git a/packages/vscode-wdio-api/src/debug.ts b/packages/vscode-wdio-server/src/debug.ts
similarity index 97%
rename from packages/vscode-wdio-api/src/debug.ts
rename to packages/vscode-wdio-server/src/debug.ts
index 0c50b7c..8dbc2ca 100644
--- a/packages/vscode-wdio-api/src/debug.ts
+++ b/packages/vscode-wdio-server/src/debug.ts
@@ -5,7 +5,7 @@ import * as vscode from 'vscode'
import { TestRunner } from './run.js'
import { WdioExtensionWorker } from './worker.js'
-import type { ExtensionConfigManagerInterface } from '@vscode-wdio/types/config'
+import type { IExtensionConfigManager } from '@vscode-wdio/types/config'
import type { TestItemMetadata } from '@vscode-wdio/types/test'
let debuggerId = 0
@@ -23,7 +23,7 @@ export class DebugRunner extends TestRunner {
private _runController: AbortController | null = null
constructor(
- configManager: ExtensionConfigManagerInterface,
+ configManager: IExtensionConfigManager,
workspaceFolder: vscode.WorkspaceFolder | undefined,
token: vscode.CancellationToken,
workerCwd: string,
@@ -80,7 +80,7 @@ export class WdioExtensionDebugWorker extends WdioExtensionWorker {
private _debugTerminationCallback: (() => void) | null = null
constructor(
- configManager: ExtensionConfigManagerInterface,
+ configManager: IExtensionConfigManager,
cid: string = '#0',
cwd: string,
private _workspaceFolder: vscode.WorkspaceFolder | undefined,
diff --git a/packages/vscode-wdio-server/src/idleMonitor.ts b/packages/vscode-wdio-server/src/idleMonitor.ts
new file mode 100644
index 0000000..a11519c
--- /dev/null
+++ b/packages/vscode-wdio-server/src/idleMonitor.ts
@@ -0,0 +1,171 @@
+import EventEmitter from 'node:events'
+
+import { log } from '@vscode-wdio/logger'
+
+import type { WorkerIdleMonitorOptions, IWorkerIdleMonitor } from '@vscode-wdio/types/server'
+
+/**
+ * Monitor worker idle state and emit timeout events
+ */
+export class WorkerIdleMonitor extends EventEmitter implements IWorkerIdleMonitor {
+ private _timer: NodeJS.Timeout | null = null
+ private _isActive = false
+ private _pauseCounter = 0
+ private _idleTimeout: number
+ private _isTimeoutDisabled = false
+ private readonly _workerId: string
+
+ constructor(workerId: string, options: WorkerIdleMonitorOptions) {
+ super()
+ this._workerId = workerId
+ const timeoutSeconds = options.idleTimeout
+ this._isTimeoutDisabled = timeoutSeconds <= 0
+ this._idleTimeout = this._isTimeoutDisabled ? 0 : timeoutSeconds * 1000 // Convert seconds to milliseconds
+
+ if (this._isTimeoutDisabled) {
+ log.debug(`[${this._workerId}] IdleMonitor created with timeout disabled`)
+ } else {
+ log.debug(`[${this._workerId}] IdleMonitor created with timeout: ${this._idleTimeout}ms`)
+ }
+ }
+
+ /**
+ * Start monitoring for idle timeout
+ */
+ public start(): void {
+ if (this._isActive) {
+ log.debug(`[${this._workerId}] IdleMonitor already active`)
+ return
+ }
+
+ this._isActive = true
+ this.resetTimer()
+ log.debug(`[${this._workerId}] IdleMonitor started`)
+ }
+
+ /**
+ * Stop monitoring and clear any pending timeout
+ */
+ public stop(): void {
+ if (!this._isActive) {
+ return
+ }
+
+ this._isActive = false
+ this._pauseCounter = 0
+ this.clearTimer()
+ log.debug(`[${this._workerId}] IdleMonitor stopped`)
+ }
+
+ /**
+ * Reset the idle timer (called when worker is accessed)
+ */
+ public resetTimer(): void {
+ if (!this._isActive || this._pauseCounter > 0 || this._isTimeoutDisabled) {
+ return
+ }
+
+ this.clearTimer()
+ this.startTimer()
+ log.trace(`[${this._workerId}] IdleMonitor timer reset`)
+ }
+
+ /**
+ * Pause the idle timer (called when RPC operation starts)
+ */
+ public pauseTimer(): void {
+ if (!this._isActive || this._isTimeoutDisabled) {
+ return
+ }
+
+ this._pauseCounter++
+ this.clearTimer()
+ log.trace(`[${this._workerId}] IdleMonitor timer paused`)
+ }
+
+ /**
+ * Resume the idle timer (called when RPC operation completes)
+ */
+ public resumeTimer(): void {
+ if (!this._isActive || this._isTimeoutDisabled) {
+ return
+ }
+
+ this._pauseCounter = this._pauseCounter - 1 < 0 ? 0 : this._pauseCounter - 1
+ if (this._pauseCounter < 1 && !this._timer) {
+ this.startTimer()
+ log.trace(`[${this._workerId}] IdleMonitor timer resumed`)
+ }
+ }
+
+ /**
+ * Update the idle timeout configuration
+ * @param timeout New timeout value in milliseconds
+ */
+ public updateTimeout(timeout: number): void {
+ const newTimeout = timeout * 1000
+ if (newTimeout === this._idleTimeout) {
+ return
+ }
+
+ const oldTimeout = this._idleTimeout
+ const timeoutSeconds = timeout
+ this._isTimeoutDisabled = timeoutSeconds <= 0
+ this._idleTimeout = this._isTimeoutDisabled ? 0 : timeoutSeconds * 1000
+
+ if (this._isTimeoutDisabled) {
+ log.debug(`[${this._workerId}] IdleMonitor updated with timeout disabled`)
+ } else {
+ log.debug(`[${this._workerId}] IdleMonitor timeout updated: ${oldTimeout}ms -> ${timeout}ms`)
+ }
+
+ // Restart timer with new timeout if currently active
+ if (this._isActive) {
+ this.resetTimer()
+ }
+ }
+
+ /**
+ * Check if monitoring is currently active
+ */
+ public isActive(): boolean {
+ return this._isActive
+ }
+
+ /**
+ * Clear the current timer
+ */
+ private clearTimer(): void {
+ if (this._timer) {
+ clearTimeout(this._timer)
+ this._timer = null
+ }
+ }
+
+ /**
+ * Start a new timer with current timeout value
+ */
+ private startTimer(): void {
+ if (this._pauseCounter > 0 || this._isTimeoutDisabled) {
+ return
+ }
+
+ this._timer = setTimeout(() => {
+ log.debug(`[${this._workerId}] Worker idle timeout reached after ${this._idleTimeout}ms`)
+ this.handleIdleTimeout()
+ }, this._idleTimeout)
+ }
+
+ /**
+ * Handle idle timeout event
+ */
+ private handleIdleTimeout(): void {
+ // Stop monitoring first to prevent multiple timeout events
+ this.stop()
+
+ log.info(`[${this._workerId}] Worker idle timeout triggered`)
+
+ // Emit idle timeout event
+ this.emit('idleTimeout')
+ }
+}
diff --git a/packages/vscode-wdio-api/src/index.ts b/packages/vscode-wdio-server/src/index.ts
similarity index 61%
rename from packages/vscode-wdio-api/src/index.ts
rename to packages/vscode-wdio-server/src/index.ts
index ef1ce9d..6f82d5b 100644
--- a/packages/vscode-wdio-api/src/index.ts
+++ b/packages/vscode-wdio-server/src/index.ts
@@ -1,3 +1,3 @@
-export { ServerManager } from './manager.js'
+export { WdioWorkerManager } from './manager.js'
export { TestRunner } from './run.js'
export { DebugRunner } from './debug.js'
diff --git a/packages/vscode-wdio-api/src/manager.ts b/packages/vscode-wdio-server/src/manager.ts
similarity index 64%
rename from packages/vscode-wdio-api/src/manager.ts
rename to packages/vscode-wdio-server/src/manager.ts
index 68eb903..2af26f8 100644
--- a/packages/vscode-wdio-api/src/manager.ts
+++ b/packages/vscode-wdio-server/src/manager.ts
@@ -4,24 +4,24 @@ import { log } from '@vscode-wdio/logger'
import * as vscode from 'vscode'
import { WdioExtensionWorker } from './worker.js'
-import type { ServerManagerInterface, WdioExtensionWorkerInterface } from '@vscode-wdio/types/api'
-import type { ExtensionConfigManagerInterface } from '@vscode-wdio/types/config'
+import type { IExtensionConfigManager } from '@vscode-wdio/types/config'
+import type { IWorkerManager, IWdioExtensionWorker } from '@vscode-wdio/types/server'
-export class ServerManager implements ServerManagerInterface {
- private _serverPool = new Map()
- private _pendingOperations = new Map>()
+export class WdioWorkerManager implements IWorkerManager {
+ private _workerPool = new Map()
+ private _pendingOperations = new Map>()
private latestId = 0
// Semaphore to track the overall operation (for complete sequential execution)
private _operationLock = false
private _operationQueue: (() => Promise)[] = []
- constructor(private readonly configManager: ExtensionConfigManagerInterface) {
- configManager.on('update:nodeExecutable', async (nodeExecutable: string) => {
+ constructor(private readonly configManager: IExtensionConfigManager) {
+ configManager.on('update:nodeExecutable', async (nodeExecutable: string | undefined) => {
log.debug(`Restart worker using webdriverio.nodeExecutable: ${nodeExecutable}`)
- const cwds = Array.from(this._serverPool.keys())
+ const cwds = Array.from(this._workerPool.keys())
await Promise.all(
cwds.map(async (cwd) => {
- const worker = this._serverPool.get(cwd)
+ const worker = this._workerPool.get(cwd)
if (worker) {
await this.stopWorker(cwd, worker)
}
@@ -35,6 +35,12 @@ export class ServerManager implements ServerManagerInterface {
vscode.window.showErrorMessage(errorMessage)
}
})
+
+ // Listen for worker idle timeout configuration changes
+ configManager.on('update:workerIdleTimeout', async (workerIdleTimeout: number) => {
+ log.debug(`Update worker idle timeout: ${workerIdleTimeout}s`)
+ await this.updateWorkersIdleTimeout(workerIdleTimeout)
+ })
}
/**
@@ -73,7 +79,7 @@ export class ServerManager implements ServerManagerInterface {
const normalizedConfigPath = normalize(configPaths)
const wdioDirName = dirname(normalizedConfigPath)
log.debug(`[server manager] detecting server: ${wdioDirName}`)
- const server = this._serverPool.get(wdioDirName)
+ const server = this._workerPool.get(wdioDirName)
if (server) {
return server
}
@@ -98,7 +104,7 @@ export class ServerManager implements ServerManagerInterface {
// Find workers that need to be stopped
const stoppingPromises: Promise[] = []
- for (const [cwd, worker] of this._serverPool.entries()) {
+ for (const [cwd, worker] of this._workerPool.entries()) {
if (!newConfigDirs.has(cwd)) {
log.debug(`[server manager] stopping unnecessary worker: ${cwd}`)
stoppingPromises.push(this.stopWorker(cwd, worker))
@@ -111,10 +117,10 @@ export class ServerManager implements ServerManagerInterface {
}
// Start new workers
- const startingPromises: Promise[] = []
+ const startingPromises: Promise[] = []
const workerCwds = Array.from(newConfigDirs)
for (const cwd of workerCwds) {
- if (!this._serverPool.has(cwd)) {
+ if (!this._workerPool.has(cwd)) {
this.latestId++
startingPromises.push(this.startWorker(this.latestId, cwd))
}
@@ -127,6 +133,59 @@ export class ServerManager implements ServerManagerInterface {
})
}
+ /**
+ * Update idle timeout configuration for all active workers
+ * @param idleTimeout Idle timeout in milliseconds
+ */
+ private async updateWorkersIdleTimeout(idleTimeout: number): Promise {
+ const updatePromises: Promise[] = []
+
+ for (const [cwd, worker] of this._workerPool.entries()) {
+ if (worker.isConnected()) {
+ log.debug(`[server manager] updating idle timeout for worker: ${cwd}`)
+ const updatePromise = this.updateWorkerIdleTimeout(worker, idleTimeout)
+ updatePromises.push(updatePromise)
+ }
+ }
+
+ if (updatePromises.length > 0) {
+ await Promise.all(updatePromises)
+ }
+ }
+
+ /**
+ * Update idle timeout configuration for a specific worker
+ * @param worker Worker to update
+ * @param idleTimeout Idle timeout in milliseconds
+ */
+ private async updateWorkerIdleTimeout(worker: IWdioExtensionWorker, idleTimeout: number): Promise {
+ try {
+ worker.idleMonitor.updateTimeout(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
+ }
+ }
+
+ /**
+ * Handle worker idle timeout notification
+ * This method is called when a worker sends an idle timeout notification
+ * @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 worker = this._workerPool.get(workerCwd)
+ if (worker) {
+ await this.stopWorker(workerCwd, worker)
+ log.debug(`[server manager] worker stopped due to idle timeout: ${workerCwd}`)
+ } else {
+ log.warn(`[server manager] received idle timeout for unknown worker: ${workerCwd}`)
+ }
+ }
+
private async queueOperation(operation: () => Promise): Promise {
// Execute immediately if no operation is in progress
if (!this._operationLock) {
@@ -170,9 +229,9 @@ export class ServerManager implements ServerManagerInterface {
}
}
- private async startWorker(id: number, workerCwd: string): Promise {
+ private async startWorker(id: number, workerCwd: string): Promise {
// Return existing server if already created
- const existingServer = this._serverPool.get(workerCwd)
+ const existingServer = this._workerPool.get(workerCwd)
if (existingServer) {
return existingServer
}
@@ -180,7 +239,7 @@ export class ServerManager implements ServerManagerInterface {
// Return pending operation if one is in progress
const pendingOperation = this._pendingOperations.get(`start:${workerCwd}`)
if (pendingOperation) {
- return pendingOperation as Promise
+ return pendingOperation as Promise
}
// Start a new process and track it
@@ -198,15 +257,32 @@ export class ServerManager implements ServerManagerInterface {
private async createWorker(id: number, configPaths: string): Promise {
const strId = `#${String(id)}`
- const server = new WdioExtensionWorker(this.configManager, strId, configPaths)
- await server.start()
- await server.waitForStart()
+ const worker = new WdioExtensionWorker(this.configManager, strId, configPaths)
+
+ // Set up idle timeout notification handler
+ worker.on('idleTimeout', () => {
+ this.handleWorkerIdleTimeout(configPaths)
+ })
+
+ 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._serverPool.set(configPaths, server)
- return server
+ this._workerPool.set(configPaths, worker)
+ return worker
}
- private async stopWorker(configPath: string, worker: WdioExtensionWorkerInterface): Promise {
+ private async stopWorker(configPath: string, worker: IWdioExtensionWorker): Promise {
// Return pending stop operation if one is in progress
const pendingOperation = this._pendingOperations.get(`stop:${configPath}`)
if (pendingOperation) {
@@ -225,16 +301,16 @@ export class ServerManager implements ServerManagerInterface {
}
}
- private async executeStopWorker(configPath: string, worker: WdioExtensionWorkerInterface): Promise {
+ private async executeStopWorker(configPath: string, worker: IWdioExtensionWorker): Promise {
log.trace(`shutdown the worker ${worker.cid} for ${configPath}`)
await worker.stop()
- this._serverPool.delete(configPath)
+ this._workerPool.delete(configPath)
}
public async dispose() {
return this.queueOperation(async () => {
const stopPromises: Promise[] = []
- for (const [cwd, worker] of this._serverPool.entries()) {
+ for (const [cwd, worker] of this._workerPool.entries()) {
log.trace(`shutdown the worker ${worker.cid} for ${cwd}`)
stopPromises.push(this.stopWorker(cwd, worker))
}
diff --git a/packages/vscode-wdio-api/src/run.ts b/packages/vscode-wdio-server/src/run.ts
similarity index 95%
rename from packages/vscode-wdio-api/src/run.ts
rename to packages/vscode-wdio-server/src/run.ts
index 88f968f..4468d2c 100644
--- a/packages/vscode-wdio-api/src/run.ts
+++ b/packages/vscode-wdio-server/src/run.ts
@@ -2,7 +2,7 @@ import { log } from '@vscode-wdio/logger'
import { getGrep, getRange, getCucumberSpec, getSpec } from './utils.js'
-import type { RunTestOptions, WdioExtensionWorkerInterface } from '@vscode-wdio/types/api'
+import type { RunTestOptions, IWdioExtensionWorker } from '@vscode-wdio/types/server'
import type { TestItemMetadata, TestItemMetadataWithRepository } from '@vscode-wdio/types/test'
import type * as vscode from 'vscode'
@@ -16,7 +16,9 @@ export class TestRunner implements vscode.Disposable {
private _stderr = ''
private _listeners: Listeners | undefined
- constructor(protected worker: WdioExtensionWorkerInterface) {}
+ constructor(protected worker: IWdioExtensionWorker) {
+ worker.idleMonitor.pauseTimer()
+ }
public get stdout() {
return this._stdout
@@ -153,6 +155,6 @@ export class TestRunner implements vscode.Disposable {
}
async dispose() {
- // noting to do
+ this.worker.idleMonitor.resumeTimer()
}
}
diff --git a/packages/vscode-wdio-api/src/utils.ts b/packages/vscode-wdio-server/src/utils.ts
similarity index 96%
rename from packages/vscode-wdio-api/src/utils.ts
rename to packages/vscode-wdio-server/src/utils.ts
index bb157b6..fc256a7 100644
--- a/packages/vscode-wdio-api/src/utils.ts
+++ b/packages/vscode-wdio-server/src/utils.ts
@@ -6,7 +6,7 @@ import which from 'which'
import { WebSocketServer } from 'ws'
import type { Server } from 'node:http'
-import type { ExtensionConfigManagerInterface } from '@vscode-wdio/types/config'
+import type { IExtensionConfigManager } from '@vscode-wdio/types/config'
import type { TestItemMetadataWithRepository } from '@vscode-wdio/types/test'
import type { NumericLogLevel } from '@vscode-wdio/types/utils'
import type * as vscode from 'vscode'
@@ -95,7 +95,7 @@ export function getCucumberSpec(testItem: vscode.TestItem, metadata: TestItemMet
return baseSpec
}
-export async function resolveNodePath(configManager: ExtensionConfigManagerInterface) {
+export async function resolveNodePath(configManager: IExtensionConfigManager) {
log.debug('Resolving the Node executable path')
const configuredPath = configManager.globalConfig.nodeExecutable
if (configuredPath && (await checkExistence(configuredPath))) {
diff --git a/packages/vscode-wdio-api/src/worker.ts b/packages/vscode-wdio-server/src/worker.ts
similarity index 77%
rename from packages/vscode-wdio-api/src/worker.ts
rename to packages/vscode-wdio-server/src/worker.ts
index 8be7f96..107efa4 100644
--- a/packages/vscode-wdio-api/src/worker.ts
+++ b/packages/vscode-wdio-server/src/worker.ts
@@ -1,25 +1,34 @@
import { spawn, type ChildProcess } from 'node:child_process'
-import EventEmitter from 'node:events'
import { createServer as createHttpServer, type Server } from 'node:http'
import { resolve } from 'node:path'
import * as v8 from 'node:v8'
import { log } from '@vscode-wdio/logger'
+import { TypedEventEmitter } from '@vscode-wdio/utils'
import { createBirpc } from 'birpc'
import getPort from 'get-port'
+import { WorkerIdleMonitor } from './idleMonitor.js'
import { createWss, loggingFn, resolveNodePath } from './utils.js'
-import type { ExtensionApi, WdioExtensionWorkerInterface, WorkerApi } from '@vscode-wdio/types/api'
-import type { ExtensionConfigManagerInterface } from '@vscode-wdio/types/config'
+import type { IExtensionConfigManager } from '@vscode-wdio/types/config'
+import type {
+ ExtensionApi,
+ WdioExtensionWorkerEvents,
+ IWdioExtensionWorker,
+ WorkerApi,
+ IWorkerIdleMonitor,
+} 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 WdioExtensionWorker extends EventEmitter implements WdioExtensionWorkerInterface {
- protected configManager: ExtensionConfigManagerInterface
+
+export class WdioExtensionWorker extends TypedEventEmitter implements IWdioExtensionWorker {
+ protected configManager: IExtensionConfigManager
public cid: string
protected cwd: string
+ public idleMonitor: IWorkerIdleMonitor
protected disposables: vscode.Disposable[] = []
private _workerProcess: ChildProcess | null = null
private _workerRpc: WorkerApi | null = null
@@ -28,12 +37,21 @@ export class WdioExtensionWorker extends EventEmitter implements WdioExtensionWo
private _server: Server | null = null
private _wss: WebSocketServer | null = null
- constructor(configManager: ExtensionConfigManagerInterface, cid: string = '#0', cwd: string) {
+ constructor(configManager: IExtensionConfigManager, cid: string = '#0', cwd: string) {
super()
this.cid = cid
this.cwd = cwd
this.configManager = configManager
+ // Initialize idle monitor
+ const idleTimeout = this.configManager.globalConfig.workerIdleTimeout
+ this.idleMonitor = new WorkerIdleMonitor(this.cid, { idleTimeout })
+
+ // Forward idle timeout events
+ this.idleMonitor.on('idleTimeout', () => {
+ this.emit('idleTimeout', undefined)
+ })
+
const psListener = () => {
if (this._workerProcess && !this._workerProcess.killed) {
log.debug('Extension host exiting - ensuring worker process is terminated')
@@ -112,10 +130,13 @@ export class WdioExtensionWorker extends EventEmitter implements WdioExtensionWo
// Handle process exit
wp.on('exit', (code) => {
- log.debug(`Worker process exited with code ${code}`)
+ log.debug(`Worker${this.cid} process exited with code ${code}`)
this._workerProcess = null
this._workerRpc = null
this._workerConnected = false
+
+ // Stop idle monitoring when worker process exits
+ this.idleMonitor.stop()
})
}
@@ -129,7 +150,6 @@ export class WdioExtensionWorker extends EventEmitter implements WdioExtensionWo
/**
* Connect to worker via WebSocket
*/
-
public async waitForStart(): Promise {
if (!this._workerPort) {
throw new Error('Worker port not set')
@@ -168,6 +188,9 @@ export class WdioExtensionWorker extends EventEmitter implements WdioExtensionWo
log.debug('Worker connection closed')
this._workerConnected = false
this._workerRpc = null
+
+ // Stop idle monitoring when connection is closed
+ this.idleMonitor.stop()
}
})
@@ -180,14 +203,20 @@ export class WdioExtensionWorker extends EventEmitter implements WdioExtensionWo
resolve()
})
}).then(() => {
+ // Start idle monitoring after successful connection
+ this.idleMonitor.start()
this.startHealthCheck()
- log.debug('Worker process started successfully')
+ log.debug(`[${this.cid}] Worker process started successfully`)
})
}
+
/**
* Stop the worker process
*/
public async stop(): Promise {
+ // Stop idle monitoring first
+ this.idleMonitor.stop()
+
let shutdownSucceeded = false
// Set a timeout for graceful shutdown
@@ -203,9 +232,11 @@ export class WdioExtensionWorker extends EventEmitter implements WdioExtensionWo
await Promise.race([this._workerRpc.shutdown(), timeoutPromise])
shutdownSucceeded = true
- log.debug('Worker process shutdown gracefully')
+ log.debug(`Worker${this.cid} process shutdown gracefully`)
} catch (error) {
- log.debug(`Error during worker shutdown: ${error instanceof Error ? error.message : String(error)}`)
+ log.debug(
+ `Error during worker${this.cid} shutdown: ${error instanceof Error ? error.message : String(error)}`
+ )
}
}
@@ -256,17 +287,38 @@ export class WdioExtensionWorker extends EventEmitter implements WdioExtensionWo
})
this._server = null
}
- log.debug('Extension worker stopped completely')
+ this.emit('shutdown', undefined)
+ log.debug(`Extension worker${this.cid} stopped completely`)
}
/**
* Get worker RPC interface
+ * This getter resets the idle timer when accessed
*/
public get rpc(): WorkerApi {
if (!this._workerRpc || !this._workerConnected) {
throw new Error('Worker not connected')
}
- return this._workerRpc
+
+ // Reset idle timer when RPC is accessed
+ this.idleMonitor.resetTimer()
+
+ return new Proxy(this._workerRpc, {
+ get: (target: WorkerApi, prop: K): any => {
+ const originalMethod = target[prop]
+ if (typeof originalMethod === 'function') {
+ return (async (...args: any[]) => {
+ this.idleMonitor.pauseTimer()
+ try {
+ return await (originalMethod as Function).apply(target, args)
+ } finally {
+ this.idleMonitor.resumeTimer()
+ }
+ }) as WorkerApi[K]
+ }
+ return originalMethod
+ },
+ })
}
/**
@@ -286,6 +338,14 @@ export class WdioExtensionWorker extends EventEmitter implements WdioExtensionWo
}
}
+ /**
+ * Update idle timeout configuration
+ * @param timeout Idle timeout in milliseconds
+ */
+ public updateIdleTimeout(timeout: number): void {
+ this.idleMonitor.updateTimeout(timeout)
+ }
+
private startHealthCheck() {
const interval = setInterval(async () => {
if (!this.isConnected()) {
diff --git a/packages/vscode-wdio-api/tests/debug.test.ts b/packages/vscode-wdio-server/tests/debug.test.ts
similarity index 97%
rename from packages/vscode-wdio-api/tests/debug.test.ts
rename to packages/vscode-wdio-server/tests/debug.test.ts
index d47d9a5..61f4435 100644
--- a/packages/vscode-wdio-api/tests/debug.test.ts
+++ b/packages/vscode-wdio-server/tests/debug.test.ts
@@ -7,7 +7,7 @@ import { DebugRunner, DebugSessionTerminatedError, WdioExtensionDebugWorker } fr
import * as runModule from '../src/run.js'
import * as workerModule from '../src/worker.js'
-import type { ExtensionConfigManagerInterface } from '@vscode-wdio/types/config'
+import type { IExtensionConfigManager } from '@vscode-wdio/types/config'
// Mock VSCode
vi.mock('vscode', async () => {
@@ -26,7 +26,11 @@ vi.mock('vscode', async () => {
// Mock logger
vi.mock('@vscode-wdio/logger', () => import('../../../tests/__mocks__/logger.js'))
-const mockConfigManager = {} as unknown as ExtensionConfigManagerInterface
+const mockConfigManager = {
+ globalConfig: {
+ workerIdleTimeout: 600,
+ },
+} as unknown as IExtensionConfigManager
describe('DebugRunner', () => {
let workspaceFolder: vscode.WorkspaceFolder
@@ -60,6 +64,10 @@ describe('DebugRunner', () => {
setDebugTerminationCallback: vi.fn().mockImplementation((callback: () => void) => {
terminationCallback = callback
}),
+ idleMonitor: {
+ pauseTimer: vi.fn(),
+ resumeTimer: vi.fn(),
+ },
} as unknown as WdioExtensionDebugWorker
mockWorkerResult = {
diff --git a/packages/vscode-wdio-server/tests/idleMonitor.test.ts b/packages/vscode-wdio-server/tests/idleMonitor.test.ts
new file mode 100644
index 0000000..8794b8f
--- /dev/null
+++ b/packages/vscode-wdio-server/tests/idleMonitor.test.ts
@@ -0,0 +1,554 @@
+import { log } from '@vscode-wdio/logger'
+import { beforeEach, afterEach, describe, it, expect, vi } from 'vitest'
+
+import { WorkerIdleMonitor } from '../src/idleMonitor.js'
+
+// Mock the logger module
+vi.mock('@vscode-wdio/logger', () => ({
+ log: {
+ debug: vi.fn(),
+ trace: vi.fn(),
+ info: vi.fn(),
+ warn: vi.fn(),
+ error: vi.fn(),
+ },
+}))
+
+describe('WorkerIdleMonitor', () => {
+ let monitor: WorkerIdleMonitor
+ let mockLoggerDebug: ReturnType
+ let mockLoggerTrace: ReturnType
+ let mockLoggerInfo: ReturnType
+
+ beforeEach(() => {
+ // Reset all mocks before each test
+ vi.clearAllMocks()
+ vi.useFakeTimers()
+
+ // Get references to mocked logger functions
+ mockLoggerDebug = vi.mocked(log.debug)
+ mockLoggerTrace = vi.mocked(log.trace)
+ mockLoggerInfo = vi.mocked(log.info)
+ })
+
+ afterEach(() => {
+ // Clean up after each test
+ if (monitor) {
+ monitor.stop()
+ }
+ vi.useRealTimers()
+ vi.restoreAllMocks()
+ })
+
+ describe('Constructor', () => {
+ it('should create monitor with valid timeout', () => {
+ // Arrange & Act
+ monitor = new WorkerIdleMonitor('test-worker', { idleTimeout: 300 })
+
+ // Assert
+ expect(monitor.isActive()).toBe(false)
+ expect(mockLoggerDebug).toHaveBeenCalledWith('[test-worker] IdleMonitor created with timeout: 300000ms')
+ })
+
+ it('should create monitor with disabled timeout when timeout is 0', () => {
+ // Arrange & Act
+ monitor = new WorkerIdleMonitor('test-worker', { idleTimeout: 0 })
+
+ // Assert
+ expect(monitor.isActive()).toBe(false)
+ expect(mockLoggerDebug).toHaveBeenCalledWith('[test-worker] IdleMonitor created with timeout disabled')
+ })
+
+ it('should create monitor with disabled timeout when timeout is negative', () => {
+ // Arrange & Act
+ monitor = new WorkerIdleMonitor('test-worker', { idleTimeout: -10 })
+
+ // Assert
+ expect(monitor.isActive()).toBe(false)
+ expect(mockLoggerDebug).toHaveBeenCalledWith('[test-worker] IdleMonitor created with timeout disabled')
+ })
+ })
+
+ describe('start()', () => {
+ beforeEach(() => {
+ monitor = new WorkerIdleMonitor('test-worker', { idleTimeout: 5 })
+ })
+
+ it('should start monitoring and set active state', () => {
+ // Arrange & Act
+ monitor.start()
+
+ // Assert
+ expect(monitor.isActive()).toBe(true)
+ expect(mockLoggerDebug).toHaveBeenCalledWith('[test-worker] IdleMonitor started')
+ })
+
+ it('should not start monitoring if already active', () => {
+ // Arrange
+ monitor.start()
+ vi.clearAllMocks()
+
+ // Act
+ monitor.start()
+
+ // Assert
+ expect(mockLoggerDebug).toHaveBeenCalledWith('[test-worker] IdleMonitor already active')
+ })
+
+ it('should start timer when timeout is enabled', () => {
+ // Arrange
+ const timeoutSpy = vi.spyOn(global, 'setTimeout')
+
+ // Act
+ monitor.start()
+
+ // Assert
+ expect(timeoutSpy).toHaveBeenCalledWith(expect.any(Function), 5000)
+ })
+
+ it('should not start timer when timeout is disabled', () => {
+ // Arrange
+ monitor = new WorkerIdleMonitor('test-worker', { idleTimeout: 0 })
+ const timeoutSpy = vi.spyOn(global, 'setTimeout')
+
+ // Act
+ monitor.start()
+
+ // Assert
+ expect(timeoutSpy).not.toHaveBeenCalled()
+ })
+ })
+
+ describe('stop()', () => {
+ beforeEach(() => {
+ monitor = new WorkerIdleMonitor('test-worker', { idleTimeout: 5 })
+ })
+
+ it('should stop monitoring and clear timer', () => {
+ // Arrange
+ monitor.start()
+ const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout')
+
+ // Act
+ monitor.stop()
+
+ // Assert
+ expect(monitor.isActive()).toBe(false)
+ expect(clearTimeoutSpy).toHaveBeenCalled()
+ expect(mockLoggerDebug).toHaveBeenCalledWith('[test-worker] IdleMonitor stopped')
+ })
+
+ it('should not do anything if already stopped', () => {
+ // Arrange
+ vi.clearAllMocks()
+
+ // Act
+ monitor.stop()
+
+ // Assert
+ expect(mockLoggerDebug).not.toHaveBeenCalled()
+ })
+
+ it('should reset pause counter when stopped', () => {
+ // Arrange
+ monitor.start()
+ monitor.pauseTimer()
+ monitor.pauseTimer() // Multiple pauses
+
+ // Act
+ monitor.stop()
+
+ // Assert - Should be able to resume properly after restart
+ monitor.start()
+ monitor.pauseTimer()
+ monitor.resumeTimer()
+ expect(mockLoggerTrace).toHaveBeenCalledWith('[test-worker] IdleMonitor timer resumed')
+ })
+ })
+
+ describe('resetTimer()', () => {
+ beforeEach(() => {
+ monitor = new WorkerIdleMonitor('test-worker', { idleTimeout: 5 })
+ })
+
+ it('should reset timer when active and not paused', () => {
+ // Arrange
+ monitor.start()
+ const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout')
+ const setTimeoutSpy = vi.spyOn(global, 'setTimeout')
+ vi.clearAllMocks()
+
+ // Act
+ monitor.resetTimer()
+
+ // Assert
+ expect(clearTimeoutSpy).toHaveBeenCalled()
+ expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 5000)
+ expect(mockLoggerTrace).toHaveBeenCalledWith('[test-worker] IdleMonitor timer reset')
+ })
+
+ it('should not reset timer when not active', () => {
+ // Arrange
+ const setTimeoutSpy = vi.spyOn(global, 'setTimeout')
+
+ // Act
+ monitor.resetTimer()
+
+ // Assert
+ expect(setTimeoutSpy).not.toHaveBeenCalled()
+ expect(mockLoggerTrace).not.toHaveBeenCalled()
+ })
+
+ it('should not reset timer when paused', () => {
+ // Arrange
+ monitor.start()
+ monitor.pauseTimer()
+ const setTimeoutSpy = vi.spyOn(global, 'setTimeout')
+ vi.clearAllMocks()
+
+ // Act
+ monitor.resetTimer()
+
+ // Assert
+ expect(setTimeoutSpy).not.toHaveBeenCalled()
+ expect(mockLoggerTrace).not.toHaveBeenCalled()
+ })
+
+ it('should not reset timer when timeout is disabled', () => {
+ // Arrange
+ monitor = new WorkerIdleMonitor('test-worker', { idleTimeout: 0 })
+ monitor.start()
+ const setTimeoutSpy = vi.spyOn(global, 'setTimeout')
+
+ // Act
+ monitor.resetTimer()
+
+ // Assert
+ expect(setTimeoutSpy).not.toHaveBeenCalled()
+ expect(mockLoggerTrace).not.toHaveBeenCalled()
+ })
+ })
+
+ describe('pauseTimer()', () => {
+ beforeEach(() => {
+ monitor = new WorkerIdleMonitor('test-worker', { idleTimeout: 5 })
+ })
+
+ it('should pause timer and increment pause counter', () => {
+ // Arrange
+ monitor.start()
+ const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout')
+ vi.clearAllMocks()
+
+ // Act
+ monitor.pauseTimer()
+
+ // Assert
+ expect(clearTimeoutSpy).toHaveBeenCalled()
+ expect(mockLoggerTrace).toHaveBeenCalledWith('[test-worker] IdleMonitor timer paused')
+ })
+
+ it('should handle multiple pauses correctly', () => {
+ // Arrange
+ monitor.start()
+ vi.clearAllMocks()
+
+ // Act
+ monitor.pauseTimer()
+ monitor.pauseTimer()
+ monitor.pauseTimer()
+
+ // Assert
+ expect(mockLoggerTrace).toHaveBeenCalledTimes(3)
+ })
+
+ it('should not pause when not active', () => {
+ // Arrange
+ const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout')
+
+ // Act
+ monitor.pauseTimer()
+
+ // Assert
+ expect(clearTimeoutSpy).not.toHaveBeenCalled()
+ expect(mockLoggerTrace).not.toHaveBeenCalled()
+ })
+
+ it('should not pause when timeout is disabled', () => {
+ // Arrange
+ monitor = new WorkerIdleMonitor('test-worker', { idleTimeout: 0 })
+ monitor.start()
+ const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout')
+
+ // Act
+ monitor.pauseTimer()
+
+ // Assert
+ expect(clearTimeoutSpy).not.toHaveBeenCalled()
+ expect(mockLoggerTrace).not.toHaveBeenCalled()
+ })
+ })
+
+ describe('resumeTimer()', () => {
+ beforeEach(() => {
+ monitor = new WorkerIdleMonitor('test-worker', { idleTimeout: 5 })
+ })
+
+ it('should resume timer after single pause', () => {
+ // Arrange
+ monitor.start()
+ monitor.pauseTimer()
+ const setTimeoutSpy = vi.spyOn(global, 'setTimeout')
+ vi.clearAllMocks()
+
+ // Act
+ monitor.resumeTimer()
+
+ // Assert
+ expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 5000)
+ expect(mockLoggerTrace).toHaveBeenCalledWith('[test-worker] IdleMonitor timer resumed')
+ })
+
+ it('should handle multiple pause/resume correctly', () => {
+ // Arrange
+ monitor.start()
+ monitor.pauseTimer()
+ monitor.pauseTimer()
+ monitor.pauseTimer()
+ const setTimeoutSpy = vi.spyOn(global, 'setTimeout')
+ vi.clearAllMocks()
+
+ // Act - First two resumes should not start timer
+ monitor.resumeTimer()
+ monitor.resumeTimer()
+ expect(setTimeoutSpy).not.toHaveBeenCalled()
+ expect(mockLoggerTrace).not.toHaveBeenCalled()
+
+ // Third resume should start timer
+ monitor.resumeTimer()
+
+ // Assert
+ expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 5000)
+ expect(mockLoggerTrace).toHaveBeenCalledWith('[test-worker] IdleMonitor timer resumed')
+ })
+
+ it('should not resume when not active', () => {
+ // Arrange
+ const setTimeoutSpy = vi.spyOn(global, 'setTimeout')
+
+ // Act
+ monitor.resumeTimer()
+
+ // Assert
+ expect(setTimeoutSpy).not.toHaveBeenCalled()
+ expect(mockLoggerTrace).not.toHaveBeenCalled()
+ })
+
+ it('should not resume when timeout is disabled', () => {
+ // Arrange
+ monitor = new WorkerIdleMonitor('test-worker', { idleTimeout: 0 })
+ monitor.start()
+ const setTimeoutSpy = vi.spyOn(global, 'setTimeout')
+
+ // Act
+ monitor.resumeTimer()
+
+ // Assert
+ expect(setTimeoutSpy).not.toHaveBeenCalled()
+ expect(mockLoggerTrace).not.toHaveBeenCalled()
+ })
+ })
+
+ describe('updateTimeout()', () => {
+ beforeEach(() => {
+ monitor = new WorkerIdleMonitor('test-worker', { idleTimeout: 5 })
+ })
+
+ it('should update timeout to new value', () => {
+ // Arrange
+ monitor.start()
+ vi.clearAllMocks()
+
+ // Act
+ monitor.updateTimeout(10)
+
+ // Assert
+ expect(mockLoggerDebug).toHaveBeenCalledWith('[test-worker] IdleMonitor timeout updated: 5000ms -> 10ms')
+ })
+
+ it('should disable timeout when set to 0', () => {
+ // Arrange
+ monitor.start()
+ vi.clearAllMocks()
+
+ // Act
+ monitor.updateTimeout(0)
+
+ // Assert
+ expect(mockLoggerDebug).toHaveBeenCalledWith('[test-worker] IdleMonitor updated with timeout disabled')
+ })
+
+ it('should disable timeout when set to negative value', () => {
+ // Arrange
+ monitor.start()
+ vi.clearAllMocks()
+
+ // Act
+ monitor.updateTimeout(-5)
+
+ // Assert
+ expect(mockLoggerDebug).toHaveBeenCalledWith('[test-worker] IdleMonitor updated with timeout disabled')
+ })
+
+ it('should not update when same timeout value', () => {
+ // Arrange
+ monitor.start()
+ vi.clearAllMocks()
+
+ // Act
+ monitor.updateTimeout(5)
+
+ // Assert
+ expect(mockLoggerDebug).not.toHaveBeenCalled()
+ })
+
+ it('should reset timer when active and timeout changes', () => {
+ // Arrange
+ monitor.start()
+ const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout')
+ const setTimeoutSpy = vi.spyOn(global, 'setTimeout')
+ vi.clearAllMocks()
+
+ // Act
+ monitor.updateTimeout(10)
+
+ // Assert
+ expect(clearTimeoutSpy).toHaveBeenCalled()
+ expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 10000)
+ })
+ })
+
+ describe('Timeout behavior', () => {
+ beforeEach(() => {
+ monitor = new WorkerIdleMonitor('test-worker', { idleTimeout: 5 })
+ })
+
+ it('should emit idleTimeout event when timer expires', () => {
+ // Arrange
+ monitor.start()
+ const idleTimeoutHandler = vi.fn()
+ monitor.on('idleTimeout', idleTimeoutHandler)
+
+ // Act
+ vi.advanceTimersByTime(5000)
+
+ // Assert
+ expect(idleTimeoutHandler).toHaveBeenCalledTimes(1)
+ expect(mockLoggerInfo).toHaveBeenCalledWith('[test-worker] Worker idle timeout triggered')
+ })
+
+ it('should stop monitoring after timeout event', () => {
+ // Arrange
+ monitor.start()
+ const idleTimeoutHandler = vi.fn()
+ monitor.on('idleTimeout', idleTimeoutHandler)
+
+ // Act
+ vi.advanceTimersByTime(5000)
+
+ // Assert
+ expect(monitor.isActive()).toBe(false)
+ })
+
+ it('should not timeout when paused', () => {
+ // Arrange
+ monitor.start()
+ monitor.pauseTimer()
+ const idleTimeoutHandler = vi.fn()
+ monitor.on('idleTimeout', idleTimeoutHandler)
+
+ // Act
+ vi.advanceTimersByTime(10000)
+
+ // Assert
+ expect(idleTimeoutHandler).not.toHaveBeenCalled()
+ })
+
+ it('should timeout after resuming from pause', () => {
+ // Arrange
+ monitor.start()
+ monitor.pauseTimer()
+ const idleTimeoutHandler = vi.fn()
+ monitor.on('idleTimeout', idleTimeoutHandler)
+
+ // Act
+ vi.advanceTimersByTime(3000) // Should not timeout while paused
+ monitor.resumeTimer()
+ vi.advanceTimersByTime(5000) // Should timeout after resume
+
+ // Assert
+ expect(idleTimeoutHandler).toHaveBeenCalledTimes(1)
+ })
+
+ it('should not timeout when timeout is disabled', () => {
+ // Arrange
+ monitor = new WorkerIdleMonitor('test-worker', { idleTimeout: 0 })
+ monitor.start()
+ const idleTimeoutHandler = vi.fn()
+ monitor.on('idleTimeout', idleTimeoutHandler)
+
+ // Act
+ vi.advanceTimersByTime(100000)
+
+ // Assert
+ expect(idleTimeoutHandler).not.toHaveBeenCalled()
+ })
+ })
+
+ describe('Edge cases', () => {
+ it('should handle rapid start/stop cycles', () => {
+ // Arrange
+ monitor = new WorkerIdleMonitor('test-worker', { idleTimeout: 5 })
+
+ // Act & Assert - Should not throw errors
+ expect(() => {
+ monitor.start()
+ monitor.stop()
+ monitor.start()
+ monitor.stop()
+ monitor.start()
+ }).not.toThrow()
+ })
+
+ it('should handle rapid pause/resume cycles', () => {
+ // Arrange
+ monitor = new WorkerIdleMonitor('test-worker', { idleTimeout: 5 })
+ monitor.start()
+
+ // Act & Assert - Should not throw errors
+ expect(() => {
+ monitor.pauseTimer()
+ monitor.resumeTimer()
+ monitor.pauseTimer()
+ monitor.pauseTimer()
+ monitor.resumeTimer()
+ monitor.resumeTimer()
+ }).not.toThrow()
+ })
+
+ it('should handle excessive resume calls gracefully', () => {
+ // Arrange
+ const setTimeoutSpy = vi.spyOn(global, 'setTimeout')
+ monitor = new WorkerIdleMonitor('test-worker', { idleTimeout: 5 })
+ monitor.start()
+
+ // Act
+ monitor.resumeTimer() // Resume without pause
+ monitor.resumeTimer() // Resume again
+ monitor.resumeTimer() // Resume again
+
+ // Assert - Should only start timer once
+ expect(setTimeoutSpy).toHaveBeenCalledTimes(1)
+ })
+ })
+})
diff --git a/packages/vscode-wdio-api/tests/manager.test.ts b/packages/vscode-wdio-server/tests/manager.test.ts
similarity index 82%
rename from packages/vscode-wdio-api/tests/manager.test.ts
rename to packages/vscode-wdio-server/tests/manager.test.ts
index 07615d6..e083f3e 100644
--- a/packages/vscode-wdio-api/tests/manager.test.ts
+++ b/packages/vscode-wdio-server/tests/manager.test.ts
@@ -2,9 +2,9 @@ import { dirname, join, normalize } from 'node:path'
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
-import { ServerManager } from '../src/manager.js'
+import { WdioWorkerManager } from '../src/manager.js'
import { WdioExtensionWorker } from '../src/worker.js'
-import type { ExtensionConfigManagerInterface } from '@vscode-wdio/types/config'
+import type { IExtensionConfigManager } from '@vscode-wdio/types/config'
vi.mock('vscode', () => import('../../../tests/__mocks__/vscode.cjs'))
@@ -19,6 +19,7 @@ vi.mock('../src/worker.js', () => {
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 }
})
@@ -35,16 +36,19 @@ vi.mock('../src/utils.js', async (importActual) => {
})
const mockConfigManager = {
+ globalConfig: {
+ workerIdleTimeout: 600,
+ },
on: vi.fn(),
-} as unknown as ExtensionConfigManagerInterface
+} as unknown as IExtensionConfigManager
describe('ServerManager', () => {
- let serverManager: ServerManager
+ let workerManager: WdioWorkerManager
// Create a fresh instance of ServerManager before each test
beforeEach(() => {
vi.resetAllMocks()
- serverManager = new ServerManager(mockConfigManager)
+ workerManager = new WdioWorkerManager(mockConfigManager)
})
afterEach(() => {
@@ -57,7 +61,7 @@ describe('ServerManager', () => {
const configPaths = ['/path/to/wdio.config.js', '/path/to/wdio.config.ts', '/another/path/wdio.config.js']
// Execute
- await serverManager.start(configPaths)
+ await workerManager.start(configPaths)
// Assert
expect(WdioExtensionWorker).toHaveBeenCalledTimes(2)
@@ -70,7 +74,7 @@ describe('ServerManager', () => {
})
it('should handle empty config paths array', async () => {
- await serverManager.start([])
+ await workerManager.start([])
expect(WdioExtensionWorker).not.toHaveBeenCalled()
})
@@ -82,13 +86,13 @@ describe('ServerManager', () => {
const configPath = join(process.cwd(), 'path', 'to', '/wdio.config.js')
// First call to create server
- const result = await serverManager.getConnection(configPath)
+ const result = await workerManager.getConnection(configPath)
// Reset mocks before second call
vi.clearAllMocks()
// Execute - second call should use existing server
- const cachedResult = await serverManager.getConnection(configPath)
+ const cachedResult = await workerManager.getConnection(configPath)
// Assert - WdioExtensionWorker constructor should not be called again
expect(WdioExtensionWorker).not.toHaveBeenCalled()
@@ -102,7 +106,7 @@ describe('ServerManager', () => {
const wdioDirName = dirname(configPath)
// Execute
- const result = await serverManager.getConnection(configPath)
+ const result = await workerManager.getConnection(configPath)
// Assert
expect(WdioExtensionWorker).toHaveBeenCalledTimes(1)
@@ -113,10 +117,10 @@ describe('ServerManager', () => {
it('should increment the id for each new worker', async () => {
// Setup - create first worker
- await serverManager.getConnection('/path/to/wdio.config.js')
+ await workerManager.getConnection('/path/to/wdio.config.js')
// Execute - create second worker with different path
- await serverManager.getConnection('/another/path/wdio.config.js')
+ await workerManager.getConnection('/another/path/wdio.config.js')
// Assert
expect(WdioExtensionWorker).toHaveBeenCalledTimes(2)
@@ -130,10 +134,10 @@ describe('ServerManager', () => {
// Setup - create multiple workers
const configPaths = ['/path/to/wdio.config.js', '/another/path/wdio.config.js']
- await serverManager.start(configPaths)
+ await workerManager.start(configPaths)
// Execute
- await serverManager.dispose()
+ await workerManager.dispose()
// Assert
expect(vi.mocked(WdioExtensionWorker).mock.instances.length).toBe(2)
@@ -142,7 +146,7 @@ describe('ServerManager', () => {
it('should handle empty server pool', async () => {
// Execute
- await serverManager.dispose()
+ await workerManager.dispose()
// Assert - should not throw an error
expect(WdioExtensionWorker).not.toHaveBeenCalled()
@@ -152,12 +156,12 @@ describe('ServerManager', () => {
describe('reorganize', () => {
it('should stop unnecessary workers and start new ones', async () => {
// Setup - create initial workers
- await serverManager.start(['/path/to/wdio.config.js', '/another/path/wdio.config.js'])
+ await workerManager.start(['/path/to/wdio.config.js', '/another/path/wdio.config.js'])
vi.clearAllMocks()
// Execute - reorganize with different paths
- await serverManager.reorganize(['/path/to/wdio.config.js', '/new/path/wdio.config.js'])
+ await workerManager.reorganize(['/path/to/wdio.config.js', '/new/path/wdio.config.js'])
// Assert
// Should stop one worker
@@ -178,8 +182,8 @@ describe('ServerManager', () => {
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
// Mock createWorker to add delay and tracking
- const originalCreateWorker = (serverManager as any).createWorker.bind(serverManager)
- vi.spyOn(serverManager as any, 'createWorker').mockImplementation((async (
+ const originalCreateWorker = (workerManager as any).createWorker.bind(workerManager)
+ vi.spyOn(workerManager as any, 'createWorker').mockImplementation((async (
id: number,
configPath: string
) => {
@@ -189,9 +193,9 @@ describe('ServerManager', () => {
}) as any)
// Execute multiple operations concurrently
- const promise1 = serverManager.getConnection('/path/1/wdio.config.js')
- const promise2 = serverManager.getConnection('/path/2/wdio.config.js')
- const promise3 = serverManager.getConnection('/path/3/wdio.config.js')
+ const promise1 = workerManager.getConnection('/path/1/wdio.config.js')
+ const promise2 = workerManager.getConnection('/path/2/wdio.config.js')
+ const promise3 = workerManager.getConnection('/path/3/wdio.config.js')
// Wait for all operations to complete
await Promise.all([promise1, promise2, promise3])
@@ -208,8 +212,8 @@ describe('ServerManager', () => {
const startCount = { count: 0 }
// Mock createWorker to track calls
- const originalCreateWorker = (serverManager as any).createWorker.bind(serverManager)
- vi.spyOn(serverManager as any, 'createWorker').mockImplementation((async (
+ const originalCreateWorker = (workerManager as any).createWorker.bind(workerManager)
+ vi.spyOn(workerManager as any, 'createWorker').mockImplementation((async (
id: number,
configPath: string
) => {
@@ -221,7 +225,7 @@ describe('ServerManager', () => {
// Execute multiple operations for the same path concurrently
const promises = Array(5)
.fill(null)
- .map(() => serverManager.getConnection('/same/path/wdio.config.js'))
+ .map(() => workerManager.getConnection('/same/path/wdio.config.js'))
// Wait for all operations to complete
const results = await Promise.all(promises)
@@ -244,7 +248,7 @@ describe('ServerManager', () => {
const configPath = '/path/to/wdio.config.js'
// Access private method using any cast
- const startWorker = (serverManager as any).startWorker.bind(serverManager)
+ const startWorker = (workerManager as any).startWorker.bind(workerManager)
// Execute
const result = await startWorker(id, configPath)
@@ -264,11 +268,11 @@ describe('ServerManager', () => {
const configPath = '/path/to/wdio.config.js'
// Access private method using any cast
- const startWorker = (serverManager as any).startWorker.bind(serverManager)
+ const startWorker = (workerManager as any).startWorker.bind(workerManager)
// Add tracking to see if createWorker is called multiple times
let createWorkerCalls = 0
- vi.spyOn(serverManager as any, 'createWorker').mockImplementation((async (id: number, path: string) => {
+ 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))
@@ -291,35 +295,35 @@ describe('ServerManager', () => {
describe('stopWorker', () => {
it('should stop a worker and remove it from the server pool', async () => {
// Setup - create a worker first
- await serverManager.getConnection('/path/to/wdio.config.js')
+ await workerManager.getConnection('/path/to/wdio.config.js')
// Access private methods using any cast
- const stopWorker = (serverManager as any).stopWorker.bind(serverManager)
+ const stopWorker = (workerManager as any).stopWorker.bind(workerManager)
// Get the worker from the server pool
- const worker = (serverManager as any)._serverPool.get('/path/to')
+ const worker = (workerManager as any)._workerPool.get('/path/to')
// Execute
await stopWorker('/path/to', worker)
// Assert
expect(worker.stop).toHaveBeenCalledTimes(1)
- expect((serverManager as any)._serverPool.has('/path/to')).toBe(false)
+ expect((workerManager as any)._workerPool.has('/path/to')).toBe(false)
})
it('should return the same promise for concurrent stop calls', async () => {
// Setup - create a worker first
- await serverManager.getConnection('/path/to/wdio.config.js')
+ await workerManager.getConnection('/path/to/wdio.config.js')
// Access private methods using any cast
- const stopWorker = (serverManager as any).stopWorker.bind(serverManager)
+ const stopWorker = (workerManager as any).stopWorker.bind(workerManager)
// Get the worker from the server pool
- const worker = (serverManager as any)._serverPool.get('/path/to')
+ const worker = (workerManager as any)._workerPool.get('/path/to')
// Add tracking
let executeStopWorkerCalls = 0
- vi.spyOn(serverManager as any, 'executeStopWorker').mockImplementation(async () => {
+ vi.spyOn(workerManager as any, 'executeStopWorker').mockImplementation(async () => {
executeStopWorkerCalls++
// Mock delay to ensure operations overlap
await new Promise((resolve) => setTimeout(resolve, 50))
diff --git a/packages/vscode-wdio-api/tests/run.test.ts b/packages/vscode-wdio-server/tests/run.test.ts
similarity index 97%
rename from packages/vscode-wdio-api/tests/run.test.ts
rename to packages/vscode-wdio-server/tests/run.test.ts
index 165bb7b..c7fb259 100644
--- a/packages/vscode-wdio-api/tests/run.test.ts
+++ b/packages/vscode-wdio-server/tests/run.test.ts
@@ -3,7 +3,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { createTestItem } from '../../../tests/utils.js'
import { TestRunner } from '../src/run.js'
-import type { WdioExtensionWorkerInterface } from '@vscode-wdio/types'
+import type { IWdioExtensionWorker } from '@vscode-wdio/types'
import type { ResultSet } from '@vscode-wdio/types/reporter'
// Mock dependencies
@@ -14,7 +14,7 @@ vi.mock('../src/debug.js', () => ({}))
describe('TestRunner', () => {
let testRunner: TestRunner
- let mockWorker: WdioExtensionWorkerInterface
+ let mockWorker: IWdioExtensionWorker
let mockRpc: any
beforeEach(() => {
@@ -35,7 +35,11 @@ describe('TestRunner', () => {
removeListener: vi.fn(),
ensureConnected: vi.fn().mockResolvedValue(undefined),
rpc: mockRpc,
- } as unknown as WdioExtensionWorkerInterface
+ idleMonitor: {
+ pauseTimer: vi.fn(),
+ resumeTimer: vi.fn(),
+ },
+ } as unknown as IWdioExtensionWorker
// Create test runner instance
testRunner = new TestRunner(mockWorker)
diff --git a/packages/vscode-wdio-api/tests/utils.test.ts b/packages/vscode-wdio-server/tests/utils.test.ts
similarity index 98%
rename from packages/vscode-wdio-api/tests/utils.test.ts
rename to packages/vscode-wdio-server/tests/utils.test.ts
index d093a83..0b69927 100644
--- a/packages/vscode-wdio-api/tests/utils.test.ts
+++ b/packages/vscode-wdio-server/tests/utils.test.ts
@@ -6,7 +6,7 @@ import { describe, it, vi, expect, beforeEach, afterEach } from 'vitest'
import which from 'which'
import { loggingFn, resolveNodePath } from '../src/utils.js'
-import type { ExtensionConfigManagerInterface } from '@vscode-wdio/types/config'
+import type { IExtensionConfigManager } from '@vscode-wdio/types/config'
vi.mock('vscode', async () => import('../../../tests/__mocks__/vscode.cjs'))
vi.mock('@vscode-wdio/logger', () => import('../../../tests/__mocks__/logger.js'))
@@ -51,7 +51,7 @@ describe('resolveNodePath', () => {
globalConfig: {
nodeExecutable: '',
},
- } as unknown as ExtensionConfigManagerInterface
+ } as unknown as IExtensionConfigManager
// Import which module after mocking
diff --git a/packages/vscode-wdio-api/tests/worker.test.ts b/packages/vscode-wdio-server/tests/worker.test.ts
similarity index 97%
rename from packages/vscode-wdio-api/tests/worker.test.ts
rename to packages/vscode-wdio-server/tests/worker.test.ts
index cfadc66..f92780c 100644
--- a/packages/vscode-wdio-api/tests/worker.test.ts
+++ b/packages/vscode-wdio-server/tests/worker.test.ts
@@ -8,8 +8,8 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { createWss } from '../src/utils.js'
import { WdioExtensionWorker } from '../src/worker.js'
-import type { ExtensionApi } from '@vscode-wdio/types/api'
-import type { ExtensionConfigManagerInterface } from '@vscode-wdio/types/config'
+import type { IExtensionConfigManager } from '@vscode-wdio/types/config'
+import type { ExtensionApi } from '@vscode-wdio/types/server'
import type * as WebSocket from 'ws'
// Mock dependencies
@@ -25,6 +25,9 @@ vi.mock('birpc', () => {
}
})
vi.mock('get-port')
+
+vi.mock('vscode', () => import('../../../tests/__mocks__/vscode.cjs'))
+
vi.mock('@vscode-wdio/logger', () => import('../../../tests/__mocks__/logger.js'))
vi.mock('../src/utils.js', () => {
@@ -90,7 +93,11 @@ describe('WdioExtensionWorker', () => {
vi.mocked(createBirpc).mockReturnValue(mockBirpc)
// Create worker instance
- worker = new WdioExtensionWorker({} as unknown as ExtensionConfigManagerInterface, '#1', '/test/path')
+ worker = new WdioExtensionWorker(
+ { globalConfig: { workerIdleTimeout: 600 } } as unknown as IExtensionConfigManager,
+ '#1',
+ '/test/path'
+ )
})
afterEach(() => {
@@ -387,7 +394,7 @@ describe('WdioExtensionWorker', () => {
;(worker as any)._workerConnected = true
// Verify RPC is returned
- expect(worker.rpc).toBe(mockBirpc)
+ expect(worker.rpc.loadWdioConfig).toBe(mockBirpc.loadWdioConfig)
})
it('should throw error when not connected', () => {
diff --git a/packages/vscode-wdio-api/tsconfig.json b/packages/vscode-wdio-server/tsconfig.json
similarity index 100%
rename from packages/vscode-wdio-api/tsconfig.json
rename to packages/vscode-wdio-server/tsconfig.json
diff --git a/packages/vscode-wdio-test/package.json b/packages/vscode-wdio-test/package.json
index c0ee549..3a34372 100644
--- a/packages/vscode-wdio-test/package.json
+++ b/packages/vscode-wdio-test/package.json
@@ -15,7 +15,7 @@
"clean": "shx rm -rf out dist coverage"
},
"dependencies": {
- "@vscode-wdio/api": "workspace:*",
+ "@vscode-wdio/server": "workspace:*",
"@vscode-wdio/config": "workspace:*",
"@vscode-wdio/constants": "workspace:*",
"@vscode-wdio/logger": "workspace:*",
diff --git a/packages/vscode-wdio-test/src/converter.ts b/packages/vscode-wdio-test/src/converter.ts
index 88bb483..197894a 100644
--- a/packages/vscode-wdio-test/src/converter.ts
+++ b/packages/vscode-wdio-test/src/converter.ts
@@ -1,7 +1,7 @@
import path from 'node:path'
import * as vscode from 'vscode'
-import type { ReadSpecsResult } from '@vscode-wdio/types/api'
+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
diff --git a/packages/vscode-wdio-test/src/manager.ts b/packages/vscode-wdio-test/src/manager.ts
index 0b2fd27..198ace7 100644
--- a/packages/vscode-wdio-test/src/manager.ts
+++ b/packages/vscode-wdio-test/src/manager.ts
@@ -9,11 +9,8 @@ import { MetadataRepository } from './metadata.js'
import { TestRepository } from './repository.js'
import { createRunProfile } from './utils.js'
import type { ExtensionConfigManager } from '@vscode-wdio/config'
-import type { ServerManagerInterface as ServerManager } from '@vscode-wdio/types/api'
-import type {
- RepositoryManagerInterface,
- TestRepositoryInterface as TestRepositoryInterface,
-} from '@vscode-wdio/types/test'
+import type { IWorkerManager } from '@vscode-wdio/types/server'
+import type { IRepositoryManager, ITestRepository } from '@vscode-wdio/types/test'
/**
* workspace -- managed by this class
@@ -25,8 +22,8 @@ import type {
const LOADING_TEST_ITEM_ID = '_resolving'
-export class RepositoryManager extends MetadataRepository implements RepositoryManagerInterface {
- private readonly _repos = new Set()
+export class RepositoryManager extends MetadataRepository implements IRepositoryManager {
+ private readonly _repos = new Set()
private _loadingTestItem: vscode.TestItem
private _workspaceTestItems: vscode.TestItem[] = []
private _wdioConfigTestItems: vscode.TestItem[] = []
@@ -36,7 +33,7 @@ export class RepositoryManager extends MetadataRepository implements RepositoryM
constructor(
public readonly controller: vscode.TestController,
public readonly configManager: ExtensionConfigManager,
- private readonly serverManager: ServerManager
+ private readonly workerManager: IWorkerManager
) {
super()
this._loadingTestItem = this.controller.createTestItem(LOADING_TEST_ITEM_ID, 'Resolving WebdriverIO Tests...')
@@ -66,7 +63,7 @@ export class RepositoryManager extends MetadataRepository implements RepositoryM
)
)
.then(() => this.registerToTestController())
- .then(() => this.serverManager.reorganize(configManager.getWdioConfigPaths()))
+ .then(() => this.workerManager.reorganize(configManager.getWdioConfigPaths()))
})
}
@@ -188,8 +185,8 @@ export class RepositoryManager extends MetadataRepository implements RepositoryM
workspaceTestItem.children.add(configItem)
this._wdioConfigTestItems.push(configItem)
- const worker = await this.serverManager.getConnection(wdioConfigPath)
- const repo = new TestRepository(this.controller, worker, wdioConfigPath, configItem)
+ const worker = await this.workerManager.getConnection(wdioConfigPath)
+ const repo = new TestRepository(this.controller, worker, wdioConfigPath, configItem, this.workerManager)
this._repos.add(repo)
configItem.description = relative(workspaceTestItem.uri!.fsPath, dirname(wdioConfigPath))
diff --git a/packages/vscode-wdio-test/src/metadata.ts b/packages/vscode-wdio-test/src/metadata.ts
index 03c3c06..2c597b6 100644
--- a/packages/vscode-wdio-test/src/metadata.ts
+++ b/packages/vscode-wdio-test/src/metadata.ts
@@ -1,7 +1,7 @@
-import type { MetadataRepositoryInterface, TestItemMetadata } from '@vscode-wdio/types/test'
+import type { IMetadataRepository, TestItemMetadata } from '@vscode-wdio/types/test'
import type * as vscode from 'vscode'
-export class MetadataRepository implements MetadataRepositoryInterface {
+export class MetadataRepository implements IMetadataRepository {
private static testMetadataRepository = new WeakMap()
public getMetadata(testItem: vscode.TestItem) {
const metadata = MetadataRepository.testMetadataRepository.get(testItem)
diff --git a/packages/vscode-wdio-test/src/reporter.ts b/packages/vscode-wdio-test/src/reporter.ts
index cdb0b40..5906bce 100644
--- a/packages/vscode-wdio-test/src/reporter.ts
+++ b/packages/vscode-wdio-test/src/reporter.ts
@@ -2,7 +2,7 @@ import { log } from '@vscode-wdio/logger'
import * as vscode from 'vscode'
import type { ResultSet, TestSuite, Test } from '@vscode-wdio/types/reporter'
-import type { TestRepositoryInterface } from '@vscode-wdio/types/test'
+import type { ITestRepository } from '@vscode-wdio/types/test'
/**
* TestReporter class for handling WebdriverIO test results and updating VSCode TestItems
@@ -15,7 +15,7 @@ export class TestReporter {
* @param _run The current test run
*/
constructor(
- private readonly _repository: TestRepositoryInterface,
+ private readonly _repository: ITestRepository,
private readonly _run: vscode.TestRun
) {}
diff --git a/packages/vscode-wdio-test/src/repository.ts b/packages/vscode-wdio-test/src/repository.ts
index 0febe7d..9d0fa00 100644
--- a/packages/vscode-wdio-test/src/repository.ts
+++ b/packages/vscode-wdio-test/src/repository.ts
@@ -9,26 +9,49 @@ import { convertPathToUri, convertTestData } from './converter.js'
import { MetadataRepository } from './metadata.js'
import { filterSpecsByPaths } from './utils.js'
-import type { WdioExtensionWorkerInterface } from '@vscode-wdio/types/api'
-import type { VscodeTestData, TestRepositoryInterface } from '@vscode-wdio/types/test'
+import type { IWorkerManager, IWdioExtensionWorker } from '@vscode-wdio/types/server'
+import type { VscodeTestData, ITestRepository } from '@vscode-wdio/types/test'
import type * as vscode from 'vscode'
+class WorkerProxy extends MetadataRepository {
+ private _worker: IWdioExtensionWorker | undefined
+ constructor(
+ private readonly _wdioConfigPath: string,
+ worker: IWdioExtensionWorker,
+ private workerManager: IWorkerManager
+ ) {
+ super()
+ this._worker = worker
+ this._worker.on('shutdown', () => {
+ this._worker = undefined
+ })
+ }
+
+ async getWorker() {
+ if (!this._worker) {
+ this._worker = await this.workerManager.getConnection(this._wdioConfigPath)
+ }
+ return this._worker
+ }
+}
+
/**
* TestRepository class that manages all WebdriverIO tests at
* the single WebdriverIO configuration file
*/
-export class TestRepository extends MetadataRepository implements TestRepositoryInterface {
+export class TestRepository extends WorkerProxy implements ITestRepository {
private _specPatterns: string[] = []
private _fileMap = new Map()
private _framework: string | undefined = undefined
constructor(
public readonly controller: vscode.TestController,
- public readonly worker: WdioExtensionWorkerInterface,
+ _worker: IWdioExtensionWorker,
public readonly wdioConfigPath: string,
- private _wdioConfigTestItem: vscode.TestItem
+ private _wdioConfigTestItem: vscode.TestItem,
+ workerManager: IWorkerManager
) {
- super()
+ super(wdioConfigPath, _worker, workerManager)
}
public get specPatterns() {
@@ -47,7 +70,8 @@ export class TestRepository extends MetadataRepository implements TestRepository
*/
public async discoverAllTests(): Promise {
try {
- const config = await this.worker.rpc.loadWdioConfig({ configFilePath: this.wdioConfigPath })
+ const worker = await this.getWorker()
+ const config = await worker.rpc.loadWdioConfig({ configFilePath: this.wdioConfigPath })
if (!config) {
return
@@ -80,7 +104,8 @@ export class TestRepository extends MetadataRepository implements TestRepository
*/
public async reloadSpecFiles(filePaths: string[] = []): Promise {
try {
- const config = await this.worker.rpc.loadWdioConfig({ configFilePath: this.wdioConfigPath })
+ const worker = await this.getWorker()
+ const config = await worker.rpc.loadWdioConfig({ configFilePath: this.wdioConfigPath })
if (!config) {
return
}
@@ -166,7 +191,8 @@ export class TestRepository extends MetadataRepository implements TestRepository
this._fileMap.clear()
}
log.debug(`Spec files registration is started for: ${specs.length} files.`)
- const testData = await this.worker.rpc.readSpecs({ specs })
+ const worker = await this.getWorker()
+ const testData = await worker.rpc.readSpecs({ specs })
const fileTestItems = (
await Promise.all(
@@ -323,7 +349,7 @@ export class TestRepository extends MetadataRepository implements TestRepository
// The path of the Spec file is the third one, as it is the next level after Workspace,WdioConfig.
const candidatePath = key.split(TEST_ID_SEPARATOR)[2]
if (normalizedSpecFilePath === normalizePath(candidatePath)) {
- log.trace(`Detected spec file :${value}`)
+ log.trace(`Detected spec file :${normalizedSpecFilePath}`)
return value
}
}
diff --git a/packages/vscode-wdio-test/src/runHandler.ts b/packages/vscode-wdio-test/src/runHandler.ts
index fdb4d5e..9ff67b3 100644
--- a/packages/vscode-wdio-test/src/runHandler.ts
+++ b/packages/vscode-wdio-test/src/runHandler.ts
@@ -1,13 +1,13 @@
import { dirname } from 'node:path'
-import { DebugRunner, TestRunner } from '@vscode-wdio/api'
import { log } from '@vscode-wdio/logger'
+import { DebugRunner, TestRunner } from '@vscode-wdio/server'
import * as vscode from 'vscode'
import { TestReporter } from './reporter.js'
import { getRootTestItem } from './utils.js'
-import type { ExtensionConfigManagerInterface } from '@vscode-wdio/types/config'
+import type { IExtensionConfigManager } from '@vscode-wdio/types/config'
import type { RepositoryManager } from './manager.js'
class TestQueue {
@@ -27,7 +27,7 @@ class TestQueue {
}
export function createHandler(
- configManager: ExtensionConfigManagerInterface,
+ configManager: IExtensionConfigManager,
repositoryManager: RepositoryManager,
isDebug = false
) {
@@ -78,7 +78,7 @@ export function createHandler(
try {
const runner = !isDebug
- ? new TestRunner(testData.repository.worker)
+ ? new TestRunner(await testData.repository.getWorker())
: new DebugRunner(
configManager,
getWorkspaceFolder.call(repositoryManager, configManager, testData.testItem),
@@ -132,7 +132,7 @@ function conversionCucumberStep(this: RepositoryManager, testItem: vscode.TestIt
function getWorkspaceFolder(
this: RepositoryManager,
- configManager: ExtensionConfigManagerInterface,
+ configManager: IExtensionConfigManager,
testItem: vscode.TestItem
) {
if (!configManager.isMultiWorkspace) {
diff --git a/packages/vscode-wdio-test/tests/converter.test.ts b/packages/vscode-wdio-test/tests/converter.test.ts
index 8f8ddb6..98f598e 100644
--- a/packages/vscode-wdio-test/tests/converter.test.ts
+++ b/packages/vscode-wdio-test/tests/converter.test.ts
@@ -4,7 +4,7 @@ import { describe, it, expect, vi } from 'vitest'
import * as vscode from 'vscode'
import { convertPathToUri, convertTestData, isCucumberFeatureFile } from '../src/converter.js'
-import type { ReadSpecsResult } from '@vscode-wdio/types/api'
+import type { ReadSpecsResult } from '@vscode-wdio/types/server'
// Mock dependencies
vi.mock('vscode', () => import('../../../tests/__mocks__/vscode.cjs'))
diff --git a/packages/vscode-wdio-test/tests/manager.test.ts b/packages/vscode-wdio-test/tests/manager.test.ts
index a5f4d50..4378c33 100644
--- a/packages/vscode-wdio-test/tests/manager.test.ts
+++ b/packages/vscode-wdio-test/tests/manager.test.ts
@@ -1,8 +1,8 @@
import { join } from 'node:path'
-import { ServerManager } from '@vscode-wdio/api'
import { ExtensionConfigManager } from '@vscode-wdio/config'
import { TEST_ID_SEPARATOR } from '@vscode-wdio/constants'
+import { WdioWorkerManager } from '@vscode-wdio/server'
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import * as vscode from 'vscode'
@@ -10,8 +10,8 @@ import { mockCreateTestItem, MockTestItemCollection } from '../../../tests/utils
import { RepositoryManager } from '../src/manager.js'
import { TestRepository } from '../src/repository.js'
-import type { WdioExtensionWorkerInterface } from '@vscode-wdio/types/api'
import type { WorkspaceData } from '@vscode-wdio/types/config'
+import type { IWdioExtensionWorker } from '@vscode-wdio/types/server'
// Mock dependencies
vi.mock('vscode', async () => {
@@ -38,7 +38,7 @@ vi.mock('../src/utils.js', () => {
describe('RepositoryManager', () => {
let fakeWorkspaceFolder: vscode.WorkspaceFolder
let fakeWorkspaces: WorkspaceData[]
- let serverManager: ServerManager
+ let workerManager: WdioWorkerManager
let clearTestsStub: ReturnType
let discoverAllTestsStub: ReturnType
let controller: vscode.TestController
@@ -77,8 +77,10 @@ describe('RepositoryManager', () => {
]
// Setup ServerManager mock
- serverManager = new ServerManager(configManager)
- vi.spyOn(serverManager, 'getConnection').mockResolvedValue({} as unknown as WdioExtensionWorkerInterface)
+ workerManager = new WdioWorkerManager(configManager)
+ vi.spyOn(workerManager, 'getConnection').mockResolvedValue({
+ on: vi.fn(),
+ } as unknown as IWdioExtensionWorker)
// Stub configManager
vi.spyOn(configManager, 'workspaces', 'get').mockReturnValue(fakeWorkspaces)
@@ -90,7 +92,7 @@ describe('RepositoryManager', () => {
vi.spyOn(TestRepository.prototype, 'discoverAllTests').mockImplementation(discoverAllTestsStub)
vi.spyOn(TestRepository.prototype, 'clearTests').mockImplementation(clearTestsStub)
- repositoryManager = new RepositoryManager(controller, configManager, serverManager)
+ repositoryManager = new RepositoryManager(controller, configManager, workerManager)
})
afterEach(() => {
@@ -113,7 +115,7 @@ describe('RepositoryManager', () => {
expect((repositoryManager as any)._workspaceTestItems.length).toBe(1)
expect((repositoryManager as any)._wdioConfigTestItems.length).toBe(1)
- expect(serverManager.getConnection).toHaveBeenCalledWith(fakeWorkspaces[0].wdioConfigFiles[0])
+ expect(workerManager.getConnection).toHaveBeenCalledWith(fakeWorkspaces[0].wdioConfigFiles[0])
})
})
@@ -159,7 +161,9 @@ describe('RepositoryManager', () => {
},
]
vi.spyOn(configManager, 'workspaces', 'get').mockReturnValue(fakeWorkspaces)
- vi.spyOn(serverManager, 'getConnection').mockResolvedValue({} as unknown as WdioExtensionWorkerInterface)
+ vi.spyOn(workerManager, 'getConnection').mockResolvedValue({
+ on: vi.fn(),
+ } as unknown as IWdioExtensionWorker)
await repositoryManager.initialize()
repositoryManager.registerToTestController()
diff --git a/packages/vscode-wdio-test/tests/repository.test.ts b/packages/vscode-wdio-test/tests/repository.test.ts
index b871d98..5d7728e 100644
--- a/packages/vscode-wdio-test/tests/repository.test.ts
+++ b/packages/vscode-wdio-test/tests/repository.test.ts
@@ -7,7 +7,7 @@ import * as vscode from 'vscode'
import { mockCreateTestItem, MockTestItemCollection } from '../../../tests/utils.js'
import { TestRepository } from '../src/repository.js'
-import type { WdioConfig, WdioExtensionWorkerInterface } from '@vscode-wdio/types/api'
+import type { IWorkerManager, WdioConfig, IWdioExtensionWorker } from '@vscode-wdio/types/server'
// Mock dependencies
vi.mock('vscode', async () => import('../../../tests/__mocks__/vscode.cjs'))
@@ -26,10 +26,11 @@ describe('TestRepository', () => {
let testController: vscode.TestController
let wdioConfigTestItem: vscode.TestItem
let testRepository: TestRepository
- let mockWorker: WdioExtensionWorkerInterface
+ let mockWorker: IWdioExtensionWorker
let readFile: ReturnType
let readSpecsStub: ReturnType
let runProfileDisposeStub: ReturnType
+ let workerManager: IWorkerManager
beforeEach(() => {
vi.resetAllMocks()
@@ -54,6 +55,7 @@ describe('TestRepository', () => {
])
mockWorker = {
+ on: vi.fn(),
rpc: {
loadWdioConfig: vi.fn().mockResolvedValue({
framework: 'mocha',
@@ -61,7 +63,7 @@ describe('TestRepository', () => {
}),
readSpecs: readSpecsStub,
},
- } as unknown as WdioExtensionWorkerInterface
+ } as unknown as IWdioExtensionWorker
readFile = vi.fn()
class MockTestRepository extends TestRepository {
@@ -70,8 +72,16 @@ describe('TestRepository', () => {
}
}
+ workerManager = vi.fn() as unknown as IWorkerManager
+
// Create repository with mocked dependencies
- testRepository = new MockTestRepository(testController, mockWorker, mockWdioConfigPath, wdioConfigTestItem)
+ testRepository = new MockTestRepository(
+ testController,
+ mockWorker,
+ mockWdioConfigPath,
+ wdioConfigTestItem,
+ workerManager
+ )
testRepository.setMetadata(wdioConfigTestItem, {
uri: mockWdioConfigUri,
@@ -94,10 +104,10 @@ describe('TestRepository', () => {
// Group 1: Initialization and basic functionality
describe('Initialization and Resource Management', () => {
- it('should initialize with provided dependencies', () => {
+ it('should initialize with provided dependencies', async () => {
// Verify
expect(testRepository.controller).toBe(testController)
- expect(testRepository.worker).toBe(mockWorker)
+ expect(await testRepository.getWorker()).toBe(mockWorker)
expect(testRepository.wdioConfigPath).toBe(mockWdioConfigPath)
})
@@ -126,7 +136,13 @@ describe('TestRepository', () => {
it('should throw error if framework is accessed before loading config', () => {
// Create new instance without loading config
- const repo = new TestRepository(testController, mockWorker, mockWdioConfigPath, wdioConfigTestItem)
+ const repo = new TestRepository(
+ testController,
+ mockWorker,
+ mockWdioConfigPath,
+ wdioConfigTestItem,
+ workerManager
+ )
// Verify
expect(() => repo.framework).toThrow('The configuration for WebdriverIO is not loaded')
diff --git a/packages/vscode-wdio-test/tests/runHandler.test.ts b/packages/vscode-wdio-test/tests/runHandler.test.ts
index fa9bcbd..b2bf3ba 100644
--- a/packages/vscode-wdio-test/tests/runHandler.test.ts
+++ b/packages/vscode-wdio-test/tests/runHandler.test.ts
@@ -1,11 +1,11 @@
-import { TestRunner } from '@vscode-wdio/api'
import { log } from '@vscode-wdio/logger'
+import { TestRunner } from '@vscode-wdio/server'
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { createTestItem } from '../../../tests/utils.js'
import { TestReporter } from '../src/reporter.js'
import { createHandler } from '../src/runHandler.js'
-import type { ExtensionConfigManagerInterface } from '@vscode-wdio/types/config'
+import type { IExtensionConfigManager } from '@vscode-wdio/types/config'
import type { TestItemMetadata } from '@vscode-wdio/types/test'
import type * as vscode from 'vscode'
import type { RepositoryManager } from '../src/index.js'
@@ -50,7 +50,7 @@ vi.mock('../src/utils.js', async (importActual) => {
}
})
-vi.mock('@vscode-wdio/api', () => {
+vi.mock('@vscode-wdio/server', () => {
const TestRunner = vi.fn()
TestRunner.prototype.run = vi.fn()
TestRunner.prototype.stdout = null
@@ -70,7 +70,7 @@ vi.mock('../../src/config/index.js', () => ({
describe('Run Handler', () => {
let mockTestRun: vscode.TestRun
let mockToken: vscode.CancellationToken
- let mockConfigManager: ExtensionConfigManagerInterface
+ let mockConfigManager: IExtensionConfigManager
let runHandler: ReturnType
let testItemMap: WeakMap
let mockRepositoryManager: RepositoryManager
@@ -97,7 +97,7 @@ describe('Run Handler', () => {
globalConfig: {
showOutput: true,
},
- } as unknown as ExtensionConfigManagerInterface
+ } as unknown as IExtensionConfigManager
testItemMap = new WeakMap()
const mockGetMetadata = vi.fn().mockImplementation((testItem: vscode.TestItem) => testItemMap.get(testItem))
@@ -242,7 +242,7 @@ describe('Run Handler', () => {
// Setup
const mockSpecTestData = createTestItem('spec1', {
isConfigFile: true,
- repository: { worker: {}, framework: 'mocha' },
+ repository: { getWorker: vi.fn(), framework: 'mocha' },
})
testItemMap.set(mockSpecTestData.testItem, mockSpecTestData.metadata)
@@ -277,7 +277,7 @@ describe('Run Handler', () => {
// Setup
const mockSpecTestData = createTestItem('spec1', {
isSpecFile: true,
- repository: { worker: {} },
+ repository: { getWorker: vi.fn() },
})
testItemMap.set(mockSpecTestData.testItem, mockSpecTestData.metadata)
@@ -332,7 +332,7 @@ describe('Run Handler', () => {
const mockParentTestData = createTestItem('scenario1', {
type: 'scenario',
isTestcase: true,
- repository: { framework: 'cucumber', worker: {} },
+ repository: { framework: 'cucumber', getWorker: vi.fn() },
})
const mockStepTestData = createTestItem(
@@ -340,7 +340,7 @@ describe('Run Handler', () => {
{
type: 'step',
isTestcase: true,
- repository: { framework: 'cucumber', worker: {} },
+ repository: { framework: 'cucumber', getWorker: vi.fn() },
},
mockParentTestData.testItem
)
@@ -371,7 +371,7 @@ describe('Run Handler', () => {
// Setup
const mockTestData = createTestItem('test1', {
isSpecFile: true,
- repository: { framework: 'mocha', worker: {} },
+ repository: { framework: 'mocha', getWorker: vi.fn() },
})
testItemMap.set(mockTestData.testItem, mockTestData.metadata)
diff --git a/packages/vscode-wdio-types/package.json b/packages/vscode-wdio-types/package.json
index 52378b8..f8ef287 100644
--- a/packages/vscode-wdio-types/package.json
+++ b/packages/vscode-wdio-types/package.json
@@ -8,10 +8,10 @@
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
- "./api": {
- "importSource": "./src/api.ts",
- "types": "./dist/api.d.ts",
- "import": "./dist/api.js"
+ "./server": {
+ "importSource": "./src/server.ts",
+ "types": "./dist/server.d.ts",
+ "import": "./dist/server.js"
},
"./config": {
"importSource": "./src/config.ts",
diff --git a/packages/vscode-wdio-types/src/config.ts b/packages/vscode-wdio-types/src/config.ts
index 8ed5a38..7a1a50c 100644
--- a/packages/vscode-wdio-types/src/config.ts
+++ b/packages/vscode-wdio-types/src/config.ts
@@ -1,7 +1,6 @@
-import type EventEmitter from 'node:events'
import type { DEFAULT_CONFIG_VALUES } from '@vscode-wdio/constants'
import type * as vscode from 'vscode'
-import type { WebdriverIOConfig } from './utils.js'
+import type { ITypedEventEmitter, WebdriverIOConfig } from './utils.js'
export type ConfigPropertyNames = typeof DEFAULT_CONFIG_VALUES extends Record ? K[] : never
@@ -10,7 +9,13 @@ export type WorkspaceData = {
wdioConfigFiles: string[]
}
-export interface ExtensionConfigManagerInterface extends EventEmitter, vscode.Disposable {
+type ToEventConfig = {
+ [K in keyof T as `update:${string & K}`]: T[K]
+}
+
+export type WebdriverIOConfigEvent = ToEventConfig
+
+export interface IExtensionConfigManager extends ITypedEventEmitter, vscode.Disposable {
isMultiWorkspace: boolean
globalConfig: WebdriverIOConfig
workspaces: WorkspaceData[]
diff --git a/packages/vscode-wdio-types/src/index.ts b/packages/vscode-wdio-types/src/index.ts
index e1fb76f..07b5a77 100644
--- a/packages/vscode-wdio-types/src/index.ts
+++ b/packages/vscode-wdio-types/src/index.ts
@@ -1,4 +1,4 @@
-export type * from './api.js'
+export type * from './server.js'
export type * from './config.js'
export type * from './reporter.js'
export type * from './test.js'
diff --git a/packages/vscode-wdio-types/src/api.ts b/packages/vscode-wdio-types/src/server.ts
similarity index 65%
rename from packages/vscode-wdio-types/src/api.ts
rename to packages/vscode-wdio-types/src/server.ts
index d382a8e..79b229e 100644
--- a/packages/vscode-wdio-types/src/api.ts
+++ b/packages/vscode-wdio-types/src/server.ts
@@ -1,8 +1,7 @@
-import type EventEmitter from 'node:events'
import type * as vscode from 'vscode'
import type { ResultSet } from './reporter.js'
import type { TestData } from './test.js'
-import type { NumericLogLevel } from './utils.js'
+import type { NumericLogLevel, ITypedEventEmitter } from './utils.js'
export type WdioConfig = {
specs: string[]
@@ -120,28 +119,79 @@ export interface WorkerRunnerOptions {
astCollect: boolean
}
-export interface WdioExtensionWorkerInterface extends EventEmitter {
+export interface IWdioExtensionWorker extends ITypedEventEmitter {
cid: string
rpc: WorkerApi
+ idleMonitor: IWorkerIdleMonitor
start(): Promise
waitForStart(): Promise
stop(): Promise
isConnected(): boolean
ensureConnected(): Promise
- emit(event: K, data: WdioExtensionWorkerEvents[K]): boolean
- on(
- event: K,
- listener: (data: WdioExtensionWorkerEvents[K]) => void
- ): this
}
export interface WdioExtensionWorkerEvents {
stdout: string
stderr: string
+ idleTimeout: undefined
+ shutdown: undefined
}
-export interface ServerManagerInterface extends vscode.Disposable {
+export interface IWorkerManager extends vscode.Disposable {
start(configPaths: string[]): Promise
- getConnection(configPaths: string): Promise
+ getConnection(configPaths: string): Promise
reorganize(configPaths: string[]): Promise
}
+
+export interface IWorkerIdleMonitor {
+ /**
+ * Start monitoring for idle timeout
+ */
+ start(): void
+
+ /**
+ * Stop monitoring and clear any pending timeout
+ */
+ stop(): void
+
+ /**
+ * Reset the idle timer (called when worker is accessed)
+ */
+ resetTimer(): void
+
+ /**
+ * Update the idle timeout configuration
+ * @param timeout New timeout value in seconds (0 or negative to disable)
+ */
+ updateTimeout(timeout: number): void
+
+ /**
+ * Pause the idle timer (called when RPC operation starts)
+ */
+ pauseTimer(): void
+
+ /**
+ * Resume the idle timer (called when RPC operation completes)
+ */
+ resumeTimer(): void
+
+ /**
+ * Check if monitoring is currently active
+ */
+ isActive(): boolean
+
+ /**
+ * Add event listener for idle timeout events
+ * @param event Event name ('idleTimeout')
+ * @param listener Event listener function
+ */
+ on(event: 'idleTimeout', listener: () => void): this
+}
+
+export interface WorkerIdleMonitorOptions {
+ /**
+ * Idle timeout in seconds
+ * Set to 0 or negative value to disable timeout
+ */
+ idleTimeout: number
+}
diff --git a/packages/vscode-wdio-types/src/test.ts b/packages/vscode-wdio-types/src/test.ts
index f57d949..55d6e2e 100644
--- a/packages/vscode-wdio-types/src/test.ts
+++ b/packages/vscode-wdio-types/src/test.ts
@@ -1,11 +1,11 @@
import type * as vscode from 'vscode'
-import type { WdioExtensionWorkerInterface } from './api.js'
-import type { ExtensionConfigManagerInterface } from './config.js'
+import type { IExtensionConfigManager } from './config.js'
+import type { IWdioExtensionWorker } from './server.js'
-export interface RepositoryManagerInterface extends vscode.Disposable {
+export interface IRepositoryManager extends vscode.Disposable {
readonly controller: vscode.TestController
- readonly configManager: ExtensionConfigManagerInterface
- readonly repos: TestRepositoryInterface[]
+ readonly configManager: IExtensionConfigManager
+ readonly repos: ITestRepository[]
initialize(): Promise
addWdioConfig(workspaceUri: vscode.Uri, wdioConfigPath: string): Promise
@@ -14,12 +14,12 @@ export interface RepositoryManagerInterface extends vscode.Disposable {
refreshTests(): Promise
}
-export interface TestRepositoryInterface extends MetadataRepositoryInterface, vscode.Disposable {
+export interface ITestRepository extends IMetadataRepository, vscode.Disposable {
readonly controller: vscode.TestController
- readonly worker: WdioExtensionWorkerInterface
readonly wdioConfigPath: string
specPatterns: string[]
framework: string
+ getWorker(): Promise
discoverAllTests(): Promise
reloadSpecFiles(filePaths?: string[]): Promise
removeSpecFile(specPath: string): void
@@ -87,7 +87,7 @@ export type TestItemMetadata = {
isConfigFile: boolean
isSpecFile: boolean
isTestcase: boolean
- repository?: TestRepositoryInterface // only workspace dose not have repository
+ repository?: ITestRepository // only workspace dose not have repository
runProfiles?: vscode.TestRunProfile[]
type?: TestType
}
@@ -95,8 +95,8 @@ export type TestItemMetadata = {
export type TestItemMetadataWithRepository = Omit &
Required>
-export interface MetadataRepositoryInterface {
+export interface IMetadataRepository {
getMetadata(testItem: vscode.TestItem): TestItemMetadata
- getRepository(testItem: vscode.TestItem): TestRepositoryInterface
+ getRepository(testItem: vscode.TestItem): ITestRepository
setMetadata(testItem: vscode.TestItem, metadata: TestItemMetadata): void
}
diff --git a/packages/vscode-wdio-types/src/utils.ts b/packages/vscode-wdio-types/src/utils.ts
index 70fd70d..2d2e330 100644
--- a/packages/vscode-wdio-types/src/utils.ts
+++ b/packages/vscode-wdio-types/src/utils.ts
@@ -1,9 +1,10 @@
+import type { EventEmitter } from 'node:events'
import type { LOG_LEVEL } from '@vscode-wdio/constants'
export type { WebdriverIOConfig } from '@vscode-wdio/constants'
export type WdioLogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'silent'
-export interface LoggerInterface {
+export interface ILogger {
trace(message: unknown): void
debug(message: unknown): void
info(message: unknown): void
@@ -14,3 +15,8 @@ export interface LoggerInterface {
type ValueOf = T[keyof T]
export type NumericLogLevel = ValueOf
+
+export interface ITypedEventEmitter> extends EventEmitter {
+ emit(event: K, data: Events[K]): boolean
+ on(event: K, listener: (data: Events[K]) => void | Promise): this
+}
diff --git a/packages/vscode-wdio-types/src/worker.ts b/packages/vscode-wdio-types/src/worker.ts
index 2b6b649..8eaf279 100644
--- a/packages/vscode-wdio-types/src/worker.ts
+++ b/packages/vscode-wdio-types/src/worker.ts
@@ -1,6 +1,6 @@
import type { WebSocket } from 'ws'
import type { SourceRange, TestData } from './test.js'
-import type { LoggerInterface } from './utils.js'
+import type { ILogger } from './utils.js'
export type TestCodeParser = (fileContent: string, uri: string) => TestData[]
@@ -31,7 +31,7 @@ export type { SourceRange, TestData }
export interface WorkerMetaContext {
cwd: string
- log: LoggerInterface
+ log: ILogger
ws: WebSocket
shutdownRequested: boolean
pendingCalls: Array<() => void>
diff --git a/packages/vscode-wdio-utils/src/index.ts b/packages/vscode-wdio-utils/src/index.ts
index 5bc87de..cba5f39 100644
--- a/packages/vscode-wdio-utils/src/index.ts
+++ b/packages/vscode-wdio-utils/src/index.ts
@@ -1,2 +1,18 @@
+import { EventEmitter } from 'node:events'
+
+import type { ITypedEventEmitter } from '@vscode-wdio/types'
+
export * from './normalize.js'
export * from './watcher.js'
+
+export class TypedEventEmitter>
+ extends EventEmitter
+ implements ITypedEventEmitter {
+ emit(event: K, data: Events[K]): boolean {
+ return super.emit(event as string, data)
+ }
+
+ on(event: K, listener: (data: Events[K]) => void | Promise): this {
+ return super.on(event as string, listener) as this
+ }
+}
diff --git a/packages/vscode-wdio-worker/src/client.ts b/packages/vscode-wdio-worker/src/client.ts
index 69d56f5..d4d0de1 100644
--- a/packages/vscode-wdio-worker/src/client.ts
+++ b/packages/vscode-wdio-worker/src/client.ts
@@ -6,7 +6,7 @@ import { WebSocket } from 'ws'
import { createWorker } from './handler.js'
import { getLogger } from './logger.js'
import type { NumericLogLevel } from '@vscode-wdio/types'
-import type { ExtensionApi, WorkerApi } from '@vscode-wdio/types/api'
+import type { ExtensionApi, WorkerApi } from '@vscode-wdio/types/server'
export function createRpcClient(cid: string, url: string) {
let rpc: ExtensionApi | null = null
diff --git a/packages/vscode-wdio-worker/src/handler.ts b/packages/vscode-wdio-worker/src/handler.ts
index a18c3f7..94c569f 100644
--- a/packages/vscode-wdio-worker/src/handler.ts
+++ b/packages/vscode-wdio-worker/src/handler.ts
@@ -3,10 +3,11 @@ import { normalizePath } from '@vscode-wdio/utils/node'
import { getLauncherInstance } from './cli.js'
import { parse } from './parsers/index.js'
import { runTest } from './test.js'
-import type { LoadConfigOptions, WdioConfig, WorkerApi } from '@vscode-wdio/types/api'
+import type { LoadConfigOptions, WdioConfig, WorkerApi } from '@vscode-wdio/types/server'
import type { WorkerMetaContext } from '@vscode-wdio/types/worker'
export function createWorker(context: WorkerMetaContext): WorkerApi {
+ let _shutdownRequest: Promise | null
return {
/**
* Run WebdriverIO tests
@@ -31,25 +32,18 @@ export function createWorker(context: WorkerMetaContext): WorkerApi {
context.log.info('Shutting down worker process')
context.shutdownRequested = true
context.log.info('Worker received shutdown request')
-
- // Implement safe shutdown procedure
- try {
- // Give pending tasks a chance to complete
- if (context.pendingCalls.length > 0) {
- await new Promise((resolve) => setTimeout(resolve, 500))
- }
-
- // Close WebSocket connection
- context.ws.close()
-
- // Set safety timeout in case WebSocket doesn't close
+ if (context.pendingCalls.length > 0) {
+ await new Promise((resolve) => setTimeout(resolve, 500))
+ }
+ _shutdownRequest = new Promise((resolve) => {
+ // close after this request is returned.
setTimeout(() => {
+ context.ws.close()
+ resolve()
process.exit(0)
- }, 2000)
- } catch (error) {
- console.error('Error during shutdown:', error)
- process.exit(1)
- }
+ }, 500)
+ })
+ context.log.info('Worker shutdown requested!')
},
}
}
diff --git a/packages/vscode-wdio-worker/src/logger.ts b/packages/vscode-wdio-worker/src/logger.ts
index 4348a5a..ccdb8c9 100644
--- a/packages/vscode-wdio-worker/src/logger.ts
+++ b/packages/vscode-wdio-worker/src/logger.ts
@@ -1,15 +1,15 @@
import { LOG_LEVEL } from '@vscode-wdio/constants'
-import type { LoggerInterface, NumericLogLevel } from '@vscode-wdio/types'
-import type { ExtensionApi } from '@vscode-wdio/types/api'
+import type { ILogger, NumericLogLevel } from '@vscode-wdio/types'
+import type { ExtensionApi } from '@vscode-wdio/types/server'
-const weakLoggers = new WeakMap()
+const weakLoggers = new WeakMap()
export function getLogger(client: ExtensionApi) {
const logger = weakLoggers.get(client)
if (logger) {
return logger
}
- class Logger implements LoggerInterface {
+ class Logger implements ILogger {
constructor(private readonly _client: ExtensionApi) {}
private log(loglevel: NumericLogLevel, message: unknown) {
diff --git a/packages/vscode-wdio-worker/src/parsers/index.ts b/packages/vscode-wdio-worker/src/parsers/index.ts
index b39340f..12150dc 100644
--- a/packages/vscode-wdio-worker/src/parsers/index.ts
+++ b/packages/vscode-wdio-worker/src/parsers/index.ts
@@ -2,7 +2,7 @@ import * as fs from 'node:fs/promises'
import path from 'node:path'
import { getAstParser, getCucumberParser } from './utils.js'
-import type { ReadSpecsOptions } from '@vscode-wdio/types/api'
+import type { ReadSpecsOptions } from '@vscode-wdio/types/server'
import type { WorkerMetaContext } from '@vscode-wdio/types/worker'
async function parseFeatureFile(context: WorkerMetaContext, contents: string, normalizeSpecPath: string) {
diff --git a/packages/vscode-wdio-worker/src/test.ts b/packages/vscode-wdio-worker/src/test.ts
index f71b055..b524376 100644
--- a/packages/vscode-wdio-worker/src/test.ts
+++ b/packages/vscode-wdio-worker/src/test.ts
@@ -4,9 +4,9 @@ import { dirname, isAbsolute, join, resolve } from 'node:path'
import { getLauncherInstance } from './cli.js'
import { getTempConfigCreator, isWindows } from './utils.js'
-import type { RunTestOptions, TestResultData } from '@vscode-wdio/types/api'
import type { ResultSet } from '@vscode-wdio/types/reporter'
-import type { LoggerInterface } from '@vscode-wdio/types/utils'
+import type { RunTestOptions, TestResultData } from '@vscode-wdio/types/server'
+import type { ILogger } from '@vscode-wdio/types/utils'
import type { WorkerMetaContext } from '@vscode-wdio/types/worker'
import type { RunCommandArguments } from '@wdio/cli'
@@ -149,7 +149,7 @@ async function getOutputDir(this: WorkerMetaContext) {
}
}
-async function extractResultJson(log: LoggerInterface, outputDir: string | undefined): Promise {
+async function extractResultJson(log: ILogger, outputDir: string | undefined): Promise {
if (outputDir) {
try {
await fs.access(outputDir, fs.constants.R_OK)
@@ -174,7 +174,7 @@ async function extractResultJson(log: LoggerInterface, outputDir: string | undef
return []
}
-async function removeResultDir(log: LoggerInterface, outputDir: string) {
+async function removeResultDir(log: ILogger, outputDir: string) {
try {
log.debug('Remove all files...')
await fs.rm(outputDir, { recursive: true, force: true })
diff --git a/packages/vscode-wdio-worker/tests/handler.test.ts b/packages/vscode-wdio-worker/tests/handler.test.ts
index b8f23d5..4996471 100644
--- a/packages/vscode-wdio-worker/tests/handler.test.ts
+++ b/packages/vscode-wdio-worker/tests/handler.test.ts
@@ -4,7 +4,7 @@ import { getLauncherInstance } from '../src/cli.js'
import { createWorker } from '../src/handler.js'
import * as parsers from '../src/parsers/index.js'
import * as test from '../src/test.js'
-import type { LoadConfigOptions, WorkerApi } from '@vscode-wdio/types/api'
+import type { LoadConfigOptions, WorkerApi } from '@vscode-wdio/types/server'
import type { WorkerMetaContext } from '@vscode-wdio/types/worker'
import type { WebSocket } from 'ws'
@@ -143,11 +143,14 @@ describe('handler', () => {
})
it('should close WebSocket connection during shutdown', async () => {
+ vi.useFakeTimers()
+
// Act
await workerApi.shutdown()
-
+ vi.advanceTimersByTime(1000)
// Assert
expect(mockContext.ws.close).toHaveBeenCalled()
+ vi.useRealTimers()
})
it('should set a safety timeout during shutdown', async () => {
@@ -155,10 +158,10 @@ describe('handler', () => {
await workerApi.shutdown()
// Assert
- expect(mockSetTimeout).toHaveBeenCalledWith(expect.any(Function), 2000)
+ expect(mockSetTimeout).toHaveBeenCalledWith(expect.any(Function), 500)
// Verify the safety timeout callback will exit with code 0
- const safetyCallback = timeoutCallbacks.find((tc) => tc.ms === 2000)?.callback
+ const safetyCallback = timeoutCallbacks.find((tc) => tc.ms === 500)?.callback
if (safetyCallback) {
safetyCallback()
expect(mockExit).toHaveBeenCalledWith(0)
@@ -167,20 +170,6 @@ describe('handler', () => {
}
})
- it('should handle errors during shutdown', async () => {
- // Arrange - Make ws.close throw an error
- mockContext.ws.close = vi.fn().mockImplementation(() => {
- throw new Error('WebSocket close error')
- })
-
- // Act
- await workerApi.shutdown()
-
- // Assert
- expect(mockConsoleError).toHaveBeenCalledWith('Error during shutdown:', expect.any(Error))
- expect(mockExit).toHaveBeenCalledWith(1)
- })
-
describe('loadWdioConfig', () => {
// Create a mock context
const mockContext: WorkerMetaContext = {
diff --git a/packages/vscode-wdio-worker/tests/index.test.ts b/packages/vscode-wdio-worker/tests/index.test.ts
index fc4170d..f2d1a4e 100644
--- a/packages/vscode-wdio-worker/tests/index.test.ts
+++ b/packages/vscode-wdio-worker/tests/index.test.ts
@@ -2,8 +2,8 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { createRpcClient } from '../src/client.js'
import { startWorker } from '../src/index.js'
-import type { ExtensionApi } from '@vscode-wdio/types/api'
-import type { LoggerInterface } from '@vscode-wdio/types/utils'
+import type { ExtensionApi } from '@vscode-wdio/types/server'
+import type { ILogger } from '@vscode-wdio/types/utils'
import type { WebSocket } from 'ws'
// Mock the modules
@@ -53,7 +53,7 @@ describe('worker/index', () => {
vi.mocked(createRpcClient).mockReturnValue({
ws: mockWs as unknown as WebSocket,
client: vi.fn() as unknown as ExtensionApi,
- log: mockLog as unknown as LoggerInterface,
+ log: mockLog as unknown as ILogger,
})
})
diff --git a/packages/vscode-wdio-worker/tests/logger.test.ts b/packages/vscode-wdio-worker/tests/logger.test.ts
index e42c0ad..1e7e4d7 100644
--- a/packages/vscode-wdio-worker/tests/logger.test.ts
+++ b/packages/vscode-wdio-worker/tests/logger.test.ts
@@ -2,7 +2,7 @@ import { LOG_LEVEL } from '@vscode-wdio/constants'
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { getLogger } from '../src/logger.js'
-import type { ExtensionApi } from '@vscode-wdio/types/api'
+import type { ExtensionApi } from '@vscode-wdio/types/server'
describe('Logger', () => {
// Mock the ExtensionApi client
diff --git a/packages/vscode-wdio-worker/tests/parsers/index.test.ts b/packages/vscode-wdio-worker/tests/parsers/index.test.ts
index 2a11eb6..28d15c3 100644
--- a/packages/vscode-wdio-worker/tests/parsers/index.test.ts
+++ b/packages/vscode-wdio-worker/tests/parsers/index.test.ts
@@ -7,7 +7,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { parse } from '../../src/parsers/index.js'
import { getAstParser, getCucumberParser } from '../../src/parsers/utils.js'
-import type { ReadSpecsOptions } from '@vscode-wdio/types/api'
+import type { ReadSpecsOptions } from '@vscode-wdio/types/server'
import type { WorkerMetaContext, TestData, CucumberTestData } from '@vscode-wdio/types/worker'
// Mock fs module
diff --git a/packages/vscode-webdriverio/package.json b/packages/vscode-webdriverio/package.json
index 125630c..9d1fc8e 100644
--- a/packages/vscode-webdriverio/package.json
+++ b/packages/vscode-webdriverio/package.json
@@ -45,7 +45,7 @@
"clean": "shx rm -rf out dist coverage"
},
"devDependencies": {
- "@vscode-wdio/api": "workspace:*",
+ "@vscode-wdio/server": "workspace:*",
"@vscode-wdio/config": "workspace:*",
"@vscode-wdio/constants": "workspace:*",
"@vscode-wdio/logger": "workspace:*",
@@ -100,7 +100,12 @@
"default": [
"**/*wdio*.conf*.{ts,js,mjs,cjs,cts,mts}"
],
- "description": "Glob pattern for WebdriverIO configuration file"
+ "markdownDescription": "Glob pattern for WebdriverIO configuration file"
+ },
+ "webdriverio.workerIdleTimeout": {
+ "markdownDescription": "If no processing is performed in the Worker for the set amount of time(defined by seconds), the Worker is terminated. If processing is requested again, it will be started automatically.",
+ "type": "number",
+ "default": 600
},
"webdriverio.logLevel": {
"type": "string",
@@ -113,12 +118,12 @@
"silent"
],
"default": "info",
- "description": "Set the logLevel"
+ "markdownDescription": "Set the logLevel"
},
"webdriverio.showOutput": {
"type": "boolean",
"default": true,
- "description": "Show WebdriverIO output in the test result"
+ "markdownDescription": "Show WebdriverIO output in the test result"
}
}
}
diff --git a/packages/vscode-webdriverio/src/extension.ts b/packages/vscode-webdriverio/src/extension.ts
index 2bceaf3..fc515ca 100644
--- a/packages/vscode-webdriverio/src/extension.ts
+++ b/packages/vscode-webdriverio/src/extension.ts
@@ -1,7 +1,7 @@
-import { ServerManager } from '@vscode-wdio/api'
import { ConfigFileWatcher, ExtensionConfigManager } from '@vscode-wdio/config'
import { EXTENSION_ID } from '@vscode-wdio/constants'
import { log } from '@vscode-wdio/logger'
+import { WdioWorkerManager } from '@vscode-wdio/server'
import { RepositoryManager, TestfileWatcher } from '@vscode-wdio/test'
import * as vscode from 'vscode'
@@ -25,7 +25,7 @@ class WdioExtension implements vscode.Disposable {
constructor(
private controller = vscode.tests.createTestController(EXTENSION_ID, 'WebdriverIO'),
private configManager = new ExtensionConfigManager(),
- private serverManager = new ServerManager(configManager)
+ private workerManager = new WdioWorkerManager(configManager)
) {}
async activate() {
@@ -35,14 +35,14 @@ class WdioExtension implements vscode.Disposable {
const configPaths = this.configManager.getWdioConfigPaths()
// Start worker process asynchronously
- const starting = this.serverManager.start(configPaths)
+ const starting = this.workerManager.start(configPaths)
// Create Manages and watchers
- const repositoryManager = new RepositoryManager(this.controller, this.configManager, this.serverManager)
+ const repositoryManager = new RepositoryManager(this.controller, this.configManager, this.workerManager)
const testfileWatcher = new TestfileWatcher(repositoryManager)
const configFileWatcher = new ConfigFileWatcher(
this.configManager,
- this.serverManager,
+ this.workerManager,
repositoryManager,
testfileWatcher
)
@@ -52,7 +52,7 @@ class WdioExtension implements vscode.Disposable {
testfileWatcher,
configFileWatcher,
repositoryManager,
- this.serverManager,
+ this.workerManager,
this.configManager,
this.controller,
]
diff --git a/packages/vscode-webdriverio/tests/extension.test.ts b/packages/vscode-webdriverio/tests/extension.test.ts
index 2fefc62..450550e 100644
--- a/packages/vscode-webdriverio/tests/extension.test.ts
+++ b/packages/vscode-webdriverio/tests/extension.test.ts
@@ -1,6 +1,6 @@
-import { ServerManager } from '@vscode-wdio/api'
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'
@@ -25,12 +25,12 @@ vi.mock('vscode', async () => {
})
vi.mock('@vscode-wdio/logger', () => import('../../../tests/__mocks__/logger.js'))
-vi.mock('@vscode-wdio/api', () => {
- const ServerManager = vi.fn()
- ServerManager.prototype.start = vi.fn(() => Promise.resolve())
- ServerManager.prototype.dispose = vi.fn(() => Promise.resolve())
+vi.mock('@vscode-wdio/server', () => {
+ const WdioWorkerManager = vi.fn()
+ WdioWorkerManager.prototype.start = vi.fn(() => Promise.resolve())
+ WdioWorkerManager.prototype.dispose = vi.fn(() => Promise.resolve())
- return { ServerManager }
+ return { WdioWorkerManager }
})
vi.mock('@vscode-wdio/config', () => {
const ExtensionConfigManager = vi.fn()
@@ -95,7 +95,7 @@ describe('extension', () => {
// vi.mocked(TestRunner).mock.instances[0].run
//
expect(vi.mocked(ExtensionConfigManager).mock.instances[0].initialize).toHaveBeenCalled()
- expect(vi.mocked(ServerManager).mock.instances[0].start).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()
@@ -106,7 +106,7 @@ describe('extension', () => {
it('should handle server start error gracefully', async () => {
// Arrange
const errorMessage = 'Failed to start server'
- vi.mocked(ServerManager.prototype.start).mockRejectedValueOnce(new Error(errorMessage))
+ vi.mocked(WdioWorkerManager.prototype.start).mockRejectedValueOnce(new Error(errorMessage))
// Act
await activate(fakeContext)
@@ -118,9 +118,9 @@ describe('extension', () => {
)
})
- it('should continue activation even when serverManager.start rejects', async () => {
+ it('should continue activation even when workerManager.start rejects', async () => {
// Arrange
- vi.mocked(ServerManager.prototype.start).mockRejectedValueOnce(new Error('Server failed'))
+ vi.mocked(WdioWorkerManager.prototype.start).mockRejectedValueOnce(new Error('Server failed'))
// Act
await activate(fakeContext)
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8e5c839..ff4ad68 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -159,39 +159,7 @@ importers:
specifier: ^7.7.2
version: 7.7.2
- packages/vscode-wdio-api:
- dependencies:
- '@vscode-wdio/constants':
- specifier: workspace:*
- version: link:../vscode-wdio-constants
- '@vscode-wdio/logger':
- specifier: workspace:*
- version: link:../vscode-wdio-logger
- '@vscode-wdio/utils':
- specifier: workspace:*
- version: link:../vscode-wdio-utils
- birpc:
- specifier: ^2.3.0
- version: 2.4.0
- get-port:
- specifier: ^7.1.0
- version: 7.1.0
- which:
- specifier: ^5.0.0
- version: 5.0.0
- ws:
- specifier: ^8.18.1
- version: 8.18.2
- devDependencies:
- '@types/which':
- specifier: ^3.0.4
- version: 3.0.4
- '@types/ws':
- specifier: ^8.18.1
- version: 8.18.1
- '@vscode-wdio/types':
- specifier: workspace:*
- version: link:../vscode-wdio-types
+ infra/xvfb-patch: {}
packages/vscode-wdio-config:
dependencies:
@@ -240,11 +208,42 @@ importers:
specifier: ^9.13.0
version: 9.15.0
- packages/vscode-wdio-test:
+ packages/vscode-wdio-server:
dependencies:
- '@vscode-wdio/api':
+ '@vscode-wdio/constants':
specifier: workspace:*
- version: link:../vscode-wdio-api
+ version: link:../vscode-wdio-constants
+ '@vscode-wdio/logger':
+ specifier: workspace:*
+ version: link:../vscode-wdio-logger
+ '@vscode-wdio/utils':
+ specifier: workspace:*
+ version: link:../vscode-wdio-utils
+ birpc:
+ specifier: ^2.3.0
+ version: 2.4.0
+ get-port:
+ specifier: ^7.1.0
+ version: 7.1.0
+ which:
+ specifier: ^5.0.0
+ version: 5.0.0
+ ws:
+ specifier: ^8.18.1
+ version: 8.18.2
+ devDependencies:
+ '@types/which':
+ specifier: ^3.0.4
+ version: 3.0.4
+ '@types/ws':
+ specifier: ^8.18.1
+ version: 8.18.1
+ '@vscode-wdio/types':
+ specifier: workspace:*
+ version: link:../vscode-wdio-types
+
+ packages/vscode-wdio-test:
+ dependencies:
'@vscode-wdio/config':
specifier: workspace:*
version: link:../vscode-wdio-config
@@ -254,6 +253,9 @@ importers:
'@vscode-wdio/logger':
specifier: workspace:*
version: link:../vscode-wdio-logger
+ '@vscode-wdio/server':
+ specifier: workspace:*
+ version: link:../vscode-wdio-server
'@vscode-wdio/utils':
specifier: workspace:*
version: link:../vscode-wdio-utils
@@ -327,9 +329,6 @@ importers:
packages/vscode-webdriverio:
devDependencies:
- '@vscode-wdio/api':
- specifier: workspace:*
- version: link:../vscode-wdio-api
'@vscode-wdio/config':
specifier: workspace:*
version: link:../vscode-wdio-config
@@ -342,6 +341,9 @@ importers:
'@vscode-wdio/reporter':
specifier: workspace:*
version: link:../vscode-wdio-reporter
+ '@vscode-wdio/server':
+ specifier: workspace:*
+ version: link:../vscode-wdio-server
'@vscode-wdio/test':
specifier: workspace:*
version: link:../vscode-wdio-test