Skip to content

Commit 7365f92

Browse files
committed
Console log unit test update
1 parent c2e1858 commit 7365f92

File tree

2 files changed

+91
-51
lines changed

2 files changed

+91
-51
lines changed

packages/service/src/session.ts

Lines changed: 60 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ const log = logger('@wdio/devtools-service:SessionCapturer')
2323
/**
2424
* Generic helper to strip ANSI escape codes from text
2525
*/
26-
const stripAnsi = (text: string): string => text.replace(ANSI_REGEX, '')
26+
const stripAnsiCodes = (text: string): string => text.replace(ANSI_REGEX, '')
2727

2828
/**
2929
* Generic helper to detect log level from text content
3030
*/
3131
const detectLogLevel = (text: string): LogLevel => {
32-
const cleanText = stripAnsi(text).toLowerCase()
32+
const cleanText = stripAnsiCodes(text).toLowerCase()
3333

3434
// Check log level patterns in priority order
3535
for (const { level, pattern } of LOG_LEVEL_PATTERNS) {
@@ -53,7 +53,7 @@ const detectLogLevel = (text: string): LogLevel => {
5353
/**
5454
* Generic helper to create a console log entry
5555
*/
56-
const createLogEntry = (
56+
const createConsoleLogEntry = (
5757
type: LogLevel,
5858
args: any[],
5959
source: (typeof LOG_SOURCES)[keyof typeof LOG_SOURCES]
@@ -66,7 +66,7 @@ const createLogEntry = (
6666

6767
export class SessionCapturer {
6868
#ws: WebSocket | undefined
69-
#isInjected = false
69+
#isScriptInjected = false
7070
#originalConsoleMethods: Record<
7171
(typeof CONSOLE_METHODS)[number],
7272
typeof console.log
@@ -121,14 +121,14 @@ export class SessionCapturer {
121121
}
122122

123123
this.#patchConsole()
124-
this.#patchProcessOutput()
124+
this.#interceptProcessStreams()
125125
}
126126

127127
#patchConsole() {
128128
CONSOLE_METHODS.forEach((method) => {
129129
const originalMethod = this.#originalConsoleMethods[method]
130-
console[method] = (...args: any[]) => {
131-
const serializedArgs = args.map((arg) =>
130+
console[method] = (...consoleArgs: any[]) => {
131+
const serializedArgs = consoleArgs.map((arg) =>
132132
typeof arg === 'object' && arg !== null
133133
? (() => {
134134
try {
@@ -140,7 +140,7 @@ export class SessionCapturer {
140140
: String(arg)
141141
)
142142

143-
const logEntry = createLogEntry(
143+
const logEntry = createConsoleLogEntry(
144144
method,
145145
serializedArgs,
146146
LOG_SOURCES.TEST
@@ -149,50 +149,61 @@ export class SessionCapturer {
149149
this.sendUpstream('consoleLogs', [logEntry])
150150

151151
this.#isCapturingConsole = true
152-
const result = originalMethod.apply(console, args)
152+
const result = originalMethod.apply(console, consoleArgs)
153153
this.#isCapturingConsole = false
154154
return result
155155
}
156156
})
157157
}
158158

159-
#patchProcessOutput() {
160-
const captureOutput = (data: string | Uint8Array) => {
161-
const text = typeof data === 'string' ? data : data.toString()
162-
if (!text?.trim()) {
159+
#interceptProcessStreams() {
160+
const captureTerminalOutput = (outputData: string | Uint8Array) => {
161+
const outputText =
162+
typeof outputData === 'string' ? outputData : outputData.toString()
163+
if (!outputText?.trim()) {
163164
return
164165
}
165166

166-
text
167+
outputText
167168
.split('\n')
168169
.filter((line) => line.trim())
169170
.forEach((line) => {
170-
const logEntry = createLogEntry(
171+
const logEntry = createConsoleLogEntry(
171172
detectLogLevel(line),
172-
[stripAnsi(line)],
173+
[stripAnsiCodes(line)],
173174
LOG_SOURCES.TERMINAL
174175
)
175176
this.consoleLogs.push(logEntry)
176177
this.sendUpstream('consoleLogs', [logEntry])
177178
})
178179
}
179180

180-
const patchStream = (
181+
const interceptStreamWrite = (
181182
stream: NodeJS.WriteStream,
182-
originalWrite: (...args: any[]) => boolean
183+
originalWriteMethod: (...args: any[]) => boolean
183184
) => {
184-
const self = this
185-
stream.write = function (data: any, ...rest: any[]): boolean {
186-
const result = originalWrite.call(stream, data, ...rest)
187-
if (data && !self.#isCapturingConsole) {
188-
captureOutput(data)
185+
const capturer = this
186+
stream.write = function (chunk: any, ...additionalArgs: any[]): boolean {
187+
const writeResult = originalWriteMethod.call(
188+
stream,
189+
chunk,
190+
...additionalArgs
191+
)
192+
if (chunk && !capturer.#isCapturingConsole) {
193+
captureTerminalOutput(chunk)
189194
}
190-
return result
195+
return writeResult
191196
} as any
192197
}
193198

194-
patchStream(process.stdout, this.#originalProcessMethods.stdoutWrite)
195-
patchStream(process.stderr, this.#originalProcessMethods.stderrWrite)
199+
interceptStreamWrite(
200+
process.stdout,
201+
this.#originalProcessMethods.stdoutWrite
202+
)
203+
interceptStreamWrite(
204+
process.stderr,
205+
this.#originalProcessMethods.stderrWrite
206+
)
196207
}
197208

198209
#restoreConsole() {
@@ -217,7 +228,7 @@ export class SessionCapturer {
217228
error: Error | undefined,
218229
callSource?: string
219230
) {
220-
const sourceFile =
231+
const sourceFileLocation =
221232
parse(new Error(''))
222233
.filter((frame) => Boolean(frame.getFileName()))
223234
.map((frame) =>
@@ -235,34 +246,40 @@ export class SessionCapturer {
235246
!fileName.includes('/dist/')
236247
)
237248
.shift() || ''
238-
const absPath = sourceFile.startsWith('file://')
239-
? url.fileURLToPath(sourceFile)
240-
: sourceFile
241-
const sourceFilePath = absPath.split(':')[0]
242-
const fileExist = await fs.access(sourceFilePath).then(
249+
const absolutePath = sourceFileLocation.startsWith('file://')
250+
? url.fileURLToPath(sourceFileLocation)
251+
: sourceFileLocation
252+
const sourceFilePath = absolutePath.split(':')[0]
253+
const doesFileExist = await fs.access(sourceFilePath).then(
243254
() => true,
244255
() => false
245256
)
246-
if (sourceFile && !this.sources.has(sourceFile) && fileExist) {
257+
if (
258+
sourceFileLocation &&
259+
!this.sources.has(sourceFileLocation) &&
260+
doesFileExist
261+
) {
247262
const sourceCode = await fs.readFile(sourceFilePath, 'utf-8')
248263
this.sources.set(sourceFilePath, sourceCode.toString())
249264
this.sendUpstream('sources', { [sourceFilePath]: sourceCode.toString() })
250265
}
251-
const newCommand: CommandLog = {
266+
const commandLogEntry: CommandLog = {
252267
command,
253268
args,
254269
result,
255270
error,
256271
timestamp: Date.now(),
257-
callSource: callSource ?? absPath
272+
callSource: callSource ?? absolutePath
258273
}
259274
try {
260-
newCommand.screenshot = await browser.takeScreenshot()
261-
} catch (shotErr) {
262-
log.warn(`failed to capture screenshot: ${(shotErr as Error).message}`)
275+
commandLogEntry.screenshot = await browser.takeScreenshot()
276+
} catch (screenshotError) {
277+
log.warn(
278+
`failed to capture screenshot: ${(screenshotError as Error).message}`
279+
)
263280
}
264-
this.commandsLog.push(newCommand)
265-
this.sendUpstream('commands', [newCommand])
281+
this.commandsLog.push(commandLogEntry)
282+
this.sendUpstream('commands', [commandLogEntry])
266283

267284
/**
268285
* capture trace and write to file on commands that could trigger a page transition
@@ -273,7 +290,7 @@ export class SessionCapturer {
273290
}
274291

275292
async injectScript(browser: WebdriverIO.Browser) {
276-
if (this.#isInjected) {
293+
if (this.#isScriptInjected) {
277294
log.info('Script already injected, skipping')
278295
return
279296
}
@@ -284,7 +301,7 @@ export class SessionCapturer {
284301
)
285302
}
286303

287-
this.#isInjected = true
304+
this.#isScriptInjected = true
288305
log.info('Injecting devtools script...')
289306
const script = await resolve('@wdio/devtools-script', import.meta.url)
290307
const source = (await fs.readFile(url.fileURLToPath(script))).toString()
@@ -297,7 +314,7 @@ export class SessionCapturer {
297314
}
298315

299316
async #captureTrace(browser: WebdriverIO.Browser) {
300-
if (!this.#isInjected) {
317+
if (!this.#isScriptInjected) {
301318
log.warn('Script not injected, skipping trace capture')
302319
return
303320
}

packages/service/tests/session.test.ts

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
22
import { SessionCapturer } from '../src/session.js'
33
import { WebSocket } from 'ws'
44
import fs from 'node:fs/promises'
5+
import { LOG_SOURCES } from '../src/constants.js'
56

67
vi.mock('ws')
78
vi.mock('node:fs/promises')
@@ -211,7 +212,7 @@ describe('SessionCapturer', () => {
211212
const logEntry = capturer.consoleLogs[initialLength]
212213
expect(logEntry.type).toBe('log')
213214
expect(logEntry.args).toEqual(['Log message'])
214-
expect(logEntry.source).toBe('test')
215+
expect(logEntry.source).toBe(LOG_SOURCES.TEST)
215216
expect(logEntry.timestamp).toBeDefined()
216217

217218
// Validate console.info capture with multiple arguments
@@ -222,19 +223,19 @@ describe('SessionCapturer', () => {
222223
'with multiple',
223224
'arguments'
224225
])
225-
expect(infoEntry.source).toBe('test')
226+
expect(infoEntry.source).toBe(LOG_SOURCES.TEST)
226227

227228
// Validate console.warn capture
228229
const warnEntry = capturer.consoleLogs[initialLength + 2]
229230
expect(warnEntry.type).toBe('warn')
230231
expect(warnEntry.args).toEqual(['Warning message'])
231-
expect(warnEntry.source).toBe('test')
232+
expect(warnEntry.source).toBe(LOG_SOURCES.TEST)
232233

233234
// Validate console.error capture
234235
const errorEntry = capturer.consoleLogs[initialLength + 3]
235236
expect(errorEntry.type).toBe('error')
236237
expect(errorEntry.args).toEqual(['Error message'])
237-
expect(errorEntry.source).toBe('test')
238+
expect(errorEntry.source).toBe(LOG_SOURCES.TEST)
238239
})
239240

240241
/**
@@ -317,7 +318,7 @@ describe('SessionCapturer', () => {
317318
expect(sentData.scope).toBe('consoleLogs')
318319
expect(sentData.data).toHaveLength(1)
319320
expect(sentData.data[0].args).toEqual(['Test message'])
320-
expect(sentData.data[0].source).toBe('test')
321+
expect(sentData.data[0].source).toBe(LOG_SOURCES.TEST)
321322

322323
capturer.cleanup()
323324

@@ -356,11 +357,33 @@ describe('SessionCapturer', () => {
356357
)
357358

358359
const browserLogs = capturer.consoleLogs.filter(
359-
(log) => log.source === 'browser'
360+
(log) => log.source === LOG_SOURCES.BROWSER
360361
)
361362
expect(browserLogs).toHaveLength(2)
362-
expect(browserLogs[0].source).toBe('browser')
363-
expect(browserLogs[1].source).toBe('browser')
363+
expect(browserLogs[0].source).toBe(LOG_SOURCES.BROWSER)
364+
expect(browserLogs[1].source).toBe(LOG_SOURCES.BROWSER)
365+
})
366+
367+
/**
368+
* Test: Terminal logs are captured with proper log level detection
369+
*/
370+
it('should capture terminal logs with correct log levels and source', () => {
371+
const capturer = new SessionCapturer()
372+
const initialLength = capturer.consoleLogs.length
373+
374+
process.stdout.write('INFO: Test message\n')
375+
process.stderr.write('ERROR: Test error\n')
376+
377+
const terminalLogs = capturer.consoleLogs.slice(initialLength)
378+
expect(terminalLogs.length).toBeGreaterThanOrEqual(2)
379+
380+
const infoLog = terminalLogs.find((log) => log.type === 'info')
381+
const errorLog = terminalLogs.find((log) => log.type === 'error')
382+
383+
expect(infoLog?.source).toBe(LOG_SOURCES.TERMINAL)
384+
expect(errorLog?.source).toBe(LOG_SOURCES.TERMINAL)
385+
386+
capturer.cleanup()
364387
})
365388
})
366389

0 commit comments

Comments
 (0)