Skip to content

Commit b466334

Browse files
committed
feat: reporter output to TestResult screen
1 parent bafa3bf commit b466334

11 files changed

Lines changed: 170 additions & 196 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@
8989
"webdriverio.showOutput": {
9090
"type": "boolean",
9191
"default": true,
92-
"description": "Show WebdriverIO output in terminal"
92+
"description": "Show WebdriverIO output in the test result"
9393
}
9494
}
9595
}

src/api/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
export { serverManager } from './manager.js'
2-
export { runWdio } from './run.js'
2+
export { TestRunner } from './run.js'
33
export type * from './types.js'
4-
export type { WdioExtensionWorker } from './worker.js'

src/api/manager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ class ServerManager implements vscode.Disposable {
5252
private async startWorker(id: number, configPaths: string) {
5353
const strId = `#${String(id)}`
5454
const server = new WdioExtensionWorker(strId, configPaths)
55-
await server._start()
55+
await server.start()
5656
log.debug(`[server manager] server was resisted: ${configPaths}`)
5757
this._serverPool.set(configPaths, server)
5858
return server

src/api/run.ts

Lines changed: 76 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,99 @@
1-
import * as fs from 'node:fs'
2-
import * as os from 'node:os'
3-
import * as path from 'node:path'
4-
51
import { TEST_ID_SEPARATOR } from '../constants.js'
6-
import {
7-
isConfig,
8-
isSpec,
9-
isTestcase,
10-
isWdioTestItem,
11-
type SpecFileTestItem,
12-
type WdioConfigTestItem,
13-
type TestcaseTestItem,
14-
} from '../test/index.js'
2+
import { isConfig, isSpec, isTestcase, isWdioTestItem } from '../test/index.js'
153
import { log } from '../utils/logger.js'
164

175
import type * as vscode from 'vscode'
186
import type { RunTestOptions } from './types.js'
7+
import type { WdioExtensionWorkerInterface } from './types.js'
198
import type { ResultSet } from '../reporter/types.js'
9+
import type { SpecFileTestItem, WdioConfigTestItem, TestcaseTestItem } from '../test/index.js'
2010

21-
export async function runWdio(test: vscode.TestItem) {
22-
if (!isWdioTestItem(test)) {
23-
throw new Error("The metadata for TestItem is not set. This is extension's bug.")
24-
}
11+
type Listeners = {
12+
stdout: (data: string) => void
13+
stderr: (data: string) => void
14+
}
2515

26-
if (!isConfig(test) && !isSpec(test) && !isTestcase(test)) {
27-
throw new Error('Workspace TestItem is not valid.')
28-
}
29-
const isCucumberTestItems = checkCucumberTestItems(test)
16+
export class TestRunner {
17+
public stdout = ''
18+
private _stderr = ''
19+
private _listeners: Listeners | undefined
3020

31-
const cucumberSpecs = isCucumberTestItems && isTestcase(test) ? getCucumberSpec(test) : undefined
21+
constructor(private _worker: WdioExtensionWorkerInterface) {}
22+
public async run(test: vscode.TestItem) {
23+
if (!isWdioTestItem(test)) {
24+
throw new Error("The metadata for TestItem is not set. This is extension's bug.")
25+
}
3226

33-
const _specs = isSpec(test) || isTestcase(test) ? getSpec(test) : undefined
27+
if (!isConfig(test) && !isSpec(test) && !isTestcase(test)) {
28+
throw new Error('Workspace TestItem is not valid.')
29+
}
30+
const isCucumberTestItems = checkCucumberTestItems(test)
3431

35-
const specs = !cucumberSpecs ? _specs : cucumberSpecs
32+
const cucumberSpecs = isCucumberTestItems && isTestcase(test) ? getCucumberSpec(test) : undefined
3633

37-
const grep = !isCucumberTestItems && isTestcase(test) ? getGrep(test) : undefined
38-
const range = !isCucumberTestItems && isTestcase(test) ? getRange(test) : undefined
39-
const outputDir = getOutputDir()
34+
const _specs = isSpec(test) || isTestcase(test) ? getSpec(test) : undefined
4035

41-
try {
42-
const testOptions: RunTestOptions = {
43-
outputDir,
44-
configPath: test.metadata.repository.wdioConfigPath,
45-
specs,
46-
grep,
47-
range,
48-
}
36+
const specs = !cucumberSpecs ? _specs : cucumberSpecs
4937

50-
log.trace(`REQUEST: ${JSON.stringify(testOptions, null, 2)}`)
51-
await test.metadata.repository.worker.ensureConnected()
52-
const result = await test.metadata.repository.worker.rpc.runTest(testOptions)
38+
const grep = !isCucumberTestItems && isTestcase(test) ? getGrep(test) : undefined
39+
const range = !isCucumberTestItems && isTestcase(test) ? getRange(test) : undefined
5340

54-
const resultData = parseJson<ResultSet[]>(result.stdout)
55-
log.trace(`RESULT: ${JSON.stringify(resultData, null, 2)}`)
41+
try {
42+
const testOptions: RunTestOptions = {
43+
configPath: test.metadata.repository.wdioConfigPath,
44+
specs,
45+
grep,
46+
range,
47+
}
5648

57-
return {
58-
success: result.success,
59-
duration: 0,
60-
detail: resultData,
61-
errorMessage: result.error,
49+
log.trace(`REQUEST: ${JSON.stringify(testOptions, null, 2)}`)
50+
await this._worker.ensureConnected()
51+
this.setListener()
52+
const result = await this._worker.rpc.runTest(testOptions)
53+
this.removeListener()
54+
55+
const resultData = parseJson<ResultSet[]>(result.json)
56+
log.trace(`RESULT: ${JSON.stringify(resultData, null, 2)}`)
57+
58+
return {
59+
success: result.success,
60+
duration: 0,
61+
detail: resultData,
62+
log: result.stdout,
63+
errorMessage: result.error,
64+
}
65+
} catch (error) {
66+
const _error = error as Error
67+
return {
68+
success: false,
69+
errorMessage: _error.message,
70+
detail: [],
71+
}
6272
}
63-
} catch (error) {
64-
const _error = error as Error
65-
return {
66-
success: false,
67-
errorMessage: _error.message,
68-
detail: [],
73+
}
74+
75+
private setListener() {
76+
this._listeners = {
77+
stdout: (data: string) => this.stdoutListener(data),
78+
stderr: (data: string) => this.stderrListener(data),
6979
}
80+
this._worker.on('stdout', this._listeners.stdout)
81+
this._worker.on('stderr', this._listeners.stderr)
7082
}
71-
}
7283

73-
function getOutputDir() {
74-
const resultRootDir = path.join(os.tmpdir(), 'vscode-webdriverio')
75-
try {
76-
fs.mkdirSync(resultRootDir, { recursive: true })
77-
const outputDir = fs.mkdtempSync(path.join(resultRootDir, 'result-'))
78-
return outputDir
79-
} catch (error) {
80-
const errorMessage = error instanceof Error ? error.message : String(error)
81-
log.debug(`Failed to create output directory: ${errorMessage}`)
82-
log.debug('Fallback to extract data from stdout.')
83-
return
84+
private removeListener() {
85+
if (this._listeners) {
86+
this._worker.removeListener('stdout', this._listeners.stdout)
87+
this._worker.removeListener('stderr', this._listeners.stderr)
88+
}
89+
}
90+
91+
private stdoutListener(data: string) {
92+
this.stdout += data + '\n'
93+
}
94+
95+
private stderrListener(data: string) {
96+
this._stderr += data + '\n'
8497
}
8598
}
8699

src/api/types.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type EventEmitter from 'node:events'
12
import type * as vscode from 'vscode'
23
import type { TestData } from '../test/index.js'
34
import type { NumericLogLevel } from '../types.js'
@@ -57,8 +58,6 @@ export type ExtensionApi = {
5758

5859
// Test run options
5960
export interface RunTestOptions {
60-
// Path to the test result files
61-
outputDir?: string
6261
// Path to WebdriverIO config file
6362
configPath: string
6463
// Spec files to run (optional)
@@ -75,9 +74,12 @@ export interface TestResult {
7574
success: boolean
7675
// Test output text
7776
stdout: string
77+
// Test stderr
7878
stderr?: string
7979
// Error message if any
8080
error?: string
81+
// The detail test result (this is set the stringified json data)
82+
json: string
8183
}
8284

8385
export interface EventReady {
@@ -116,5 +118,24 @@ export interface WorkerRunnerOptions {
116118
astCollect: boolean
117119
}
118120

121+
export interface WdioExtensionWorkerInterface extends EventEmitter {
122+
cid: string
123+
rpc: WorkerApi
124+
start(): Promise<void>
125+
stop(): Promise<void>
126+
isConnected(): boolean
127+
ensureConnected(): Promise<void>
128+
emit<K extends keyof WdioExtensionWorkerEvents>(event: K, data: WdioExtensionWorkerEvents[K]): boolean
129+
on<K extends keyof WdioExtensionWorkerEvents>(
130+
event: K,
131+
listener: (data: WdioExtensionWorkerEvents[K]) => void
132+
): this
133+
}
134+
135+
export interface WdioExtensionWorkerEvents {
136+
stdout: string
137+
stderr: string
138+
}
139+
119140
export type { TestData }
120141
export type { TestType, SourceRange, SourcePosition } from '../test/index.js'

src/api/worker.ts

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { spawn, type ChildProcess } from 'node:child_process'
2+
import EventEmitter from 'node:events'
23
import { createServer as createHttpServer, type Server } from 'node:http'
34
import { resolve } from 'node:path'
45
import * as v8 from 'node:v8'
@@ -11,15 +12,11 @@ import { LOG_LEVEL } from '../constants.js'
1112
import { log } from '../utils/logger.js'
1213

1314
import type * as vscode from 'vscode'
14-
import type { ExtensionApi, WorkerApi } from './types.js'
15+
import type { ExtensionApi, WdioExtensionWorkerInterface, WorkerApi } from './types.js'
1516
import type { NumericLogLevel } from '../types.js'
1617

1718
const WORKER_PATH = resolve(__dirname, 'worker/index.cjs')
18-
19-
/**
20-
* Manages the WebdriverIO worker process
21-
*/
22-
export class WdioExtensionWorker {
19+
export class WdioExtensionWorker extends EventEmitter implements WdioExtensionWorkerInterface {
2320
public cid: string
2421
private _workerProcess: ChildProcess | null = null
2522
private _workerRpc: WorkerApi | null = null
@@ -31,6 +28,7 @@ export class WdioExtensionWorker {
3128
private _cwd: string
3229

3330
constructor(cid: string = '#0', cwd: string) {
31+
super()
3432
this.cid = cid
3533
this._cwd = cwd
3634

@@ -49,7 +47,7 @@ export class WdioExtensionWorker {
4947
/**
5048
* Start the worker process
5149
*/
52-
async _start(): Promise<void> {
50+
public async start(): Promise<void> {
5351
if (this._workerProcess) {
5452
log.debug('Worker already running')
5553
return
@@ -99,13 +97,9 @@ export class WdioExtensionWorker {
9997

10098
private setListeners(wp: ChildProcess) {
10199
// Handle process output
102-
wp.stdout?.on('data', (data) => {
103-
log.debug(`[Worker stdout] ${data.toString().trim()}`)
104-
})
100+
wp.stdout?.on('data', (data) => this.workerOutHandler('stdout', data))
105101

106-
wp.stderr?.on('data', (data) => {
107-
log.debug(`[Worker stderr] ${data.toString().trim()}`)
108-
})
102+
wp.stderr?.on('data', (data) => this.workerOutHandler('stderr', data))
109103

110104
// Handle process exit
111105
wp.on('exit', (code) => {
@@ -116,6 +110,12 @@ export class WdioExtensionWorker {
116110
})
117111
}
118112

113+
private workerOutHandler(event: 'stdout' | 'stderr', data: any) {
114+
const payload = data.toString().trim()
115+
log.debug(`[Worker${this.cid} ${event}] ${payload}`)
116+
this.emit(event, payload)
117+
}
118+
119119
/**
120120
* Connect to worker via WebSocket
121121
*/
@@ -167,7 +167,7 @@ export class WdioExtensionWorker {
167167
/**
168168
* Stop the worker process
169169
*/
170-
async stop(): Promise<void> {
170+
public async stop(): Promise<void> {
171171
let shutdownSucceeded = false
172172

173173
// Set a timeout for graceful shutdown
@@ -241,7 +241,7 @@ export class WdioExtensionWorker {
241241
/**
242242
* Get worker RPC interface
243243
*/
244-
get rpc(): WorkerApi {
244+
public get rpc(): WorkerApi {
245245
if (!this._workerRpc || !this._workerConnected) {
246246
throw new Error('Worker not connected')
247247
}
@@ -251,17 +251,17 @@ export class WdioExtensionWorker {
251251
/**
252252
* Check if worker is connected
253253
*/
254-
isConnected(): boolean {
254+
public isConnected(): boolean {
255255
return this._workerConnected
256256
}
257257

258258
/**
259259
* Restart worker if it's not connected
260260
*/
261-
async ensureConnected(): Promise<void> {
261+
public async ensureConnected(): Promise<void> {
262262
if (!this.isConnected()) {
263263
await this.stop()
264-
await this._start()
264+
await this.start()
265265
}
266266
}
267267

@@ -278,7 +278,7 @@ export class WdioExtensionWorker {
278278
log.warn('Worker health check failed: unexpected response')
279279
await this.ensureConnected()
280280
} else {
281-
log.trace('Worker health check success!')
281+
log.trace(`[${this.cid}] Worker health check success!`)
282282
}
283283
} catch (error) {
284284
log.warn(`Worker health check failed: ${error instanceof Error ? error.message : String(error)}`)
@@ -290,6 +290,17 @@ export class WdioExtensionWorker {
290290
dispose: () => clearInterval(interval),
291291
})
292292
}
293+
294+
// emit<K extends keyof WdioExtensionWorkerEvents>(event: K, data: WdioExtensionWorkerEvents[K]): boolean {
295+
// return super.emit(event, data)
296+
// }
297+
298+
// on<K extends keyof WdioExtensionWorkerEvents>(
299+
// event: K,
300+
// listener: (data: WdioExtensionWorkerEvents[K]) => void
301+
// ): this {
302+
// return super.on(event, listener)
303+
// }
293304
}
294305

295306
function createRpcServer(): ExtensionApi {

src/extension.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ class WdioExtension implements vscode.Disposable {
4949
configManager,
5050
]
5151

52-
// await new Promise((resolve) => setTimeout(resolve, 3000))
5352
await repositoryManager.initialize()
5453
Promise.all(
5554
repositoryManager.repos.map(async (repo) => {

src/reporter/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export default class VscodeJsonReporter extends WDIOReporter {
2323
constructor(options: Reporters.Options) {
2424
super(options)
2525
if (!options.outputDir) {
26-
options.stdout = true
26+
options.stdout = false
2727
}
2828
this._outputDir = options.outputDir
2929
}
@@ -63,7 +63,6 @@ export default class VscodeJsonReporter extends WDIOReporter {
6363
onRunnerEnd(runner: RunnerStats) {
6464
const json = this.#prepareJson(runner)
6565

66-
this.write(JSON.stringify(json))
6766
this.writeFile(runner.cid, JSON.stringify(json, null, 2))
6867
}
6968

0 commit comments

Comments
 (0)