Skip to content

Commit 0066fba

Browse files
committed
Iframe bug fix and package update
1 parent 9e0de32 commit 0066fba

File tree

8 files changed

+126
-53
lines changed

8 files changed

+126
-53
lines changed

packages/app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@wdio/devtools-app",
3-
"version": "1.0.0",
3+
"version": "1.0.1",
44
"description": "Browser devtools extension for debugging WebdriverIO tests.",
55
"type": "module",
66
"exports": "./src/index.ts",

packages/app/src/components/workbench/actions.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,13 @@ export class DevtoolsActions extends Element {
4848
(a, b) => a.timestamp - b.timestamp
4949
)
5050

51-
if (!entries.length || !mutations.length) {
51+
if (!entries.length) {
5252
return html`<wdio-devtools-placeholder></wdio-devtools-placeholder>`
5353
}
54+
const baselineTimestamp = entries[0]?.timestamp ?? 0
5455

5556
return entries.map((entry) => {
56-
const elapsedTime = entry.timestamp - mutations[0].timestamp
57+
const elapsedTime = entry.timestamp - baselineTimestamp
5758

5859
if ('command' in entry) {
5960
return html`

packages/app/src/components/workbench/list.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,17 +95,18 @@ export class DevtoolsList extends Element {
9595
}
9696

9797
render() {
98-
const isArrayList = Array.isArray(this.list)
98+
const list = this.list ?? {}
99+
const isArrayList = Array.isArray(list)
99100

100-
if (this.list === null) {
101+
if (list === null) {
101102
return null
102103
}
103-
if (isArrayList && (this.list as unknown[]).length === 0) {
104+
if (isArrayList && (list as unknown[]).length === 0) {
104105
return null
105106
}
106107
if (
107108
!isArrayList &&
108-
Object.keys(this.list as Record<string, unknown>).length === 0
109+
Object.keys(list as Record<string, unknown>).length === 0
109110
) {
110111
return null
111112
}

packages/backend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@wdio/devtools-backend",
3-
"version": "1.0.0",
3+
"version": "1.0.1",
44
"description": "Backend service to spin up WebdriverIO Devtools",
55
"author": "Christian Bromann <[email protected]>",
66
"license": "MIT",

packages/service/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@wdio/devtools-service",
3-
"version": "10.0.0",
3+
"version": "10.0.1",
44
"description": "Hook up WebdriverIO with DevTools",
55
"author": "Christian Bromann <[email protected]>",
66
"type": "module",

packages/service/src/constants.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ export const CONTEXT_CHANGE_COMMANDS = [
5050
/**
5151
* Existing pattern (kept for any external consumers)
5252
*/
53-
export const SPEC_FILE_PATTERN = /(test|spec|features)[\\/].*\.(js|ts)$/i
53+
export const SPEC_FILE_PATTERN =
54+
/\/(test|spec|features|pageobjects|@wdio\/expect-webdriverio)\//i
5455

5556
/**
5657
* Parser options

packages/service/src/index.ts

Lines changed: 73 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ export const launcher = DevToolsAppLauncher
2323

2424
const log = logger('@wdio/devtools-service')
2525

26+
type CommandFrame = {
27+
command: string
28+
callSource?: string
29+
}
30+
2631
/**
2732
* Setup WebdriverIO Devtools hook for standalone instances
2833
*/
@@ -87,7 +92,7 @@ export default class DevToolsHookService implements Services.ServiceInstance {
8792
* This is used to capture the command stack to ensure that we only capture
8893
* commands that are top-level user commands.
8994
*/
90-
#commandStack: string[] = []
95+
#commandStack: CommandFrame[] = []
9196

9297
// This is used to capture the last command signature to avoid duplicate captures
9398
#lastCommandSig: string | null = null
@@ -101,13 +106,34 @@ export default class DevToolsHookService implements Services.ServiceInstance {
101106
// This is used to track if the injection script is currently being injected
102107
#injecting = false
103108

104-
before(
109+
async before(
105110
caps: Capabilities.W3CCapabilities,
106111
__: string[],
107112
browser: WebdriverIO.Browser
108113
) {
109114
this.#browser = browser
110115

116+
/**
117+
* create a new session capturer instance with the devtools options
118+
*/
119+
const wdioCaps = caps as Capabilities.W3CCapabilities & {
120+
'wdio:devtoolsOptions'?: any
121+
}
122+
this.#sessionCapturer = new SessionCapturer(
123+
wdioCaps['wdio:devtoolsOptions']
124+
)
125+
126+
/**
127+
* Block until injection completes BEFORE any test commands
128+
*/
129+
try {
130+
await this.#injectScriptSync(browser)
131+
} catch (err) {
132+
log.error(
133+
`Failed to inject script at session start: ${(err as Error).message}`
134+
)
135+
}
136+
111137
/**
112138
* propagate session metadata at the beginning of the session
113139
*/
@@ -121,17 +147,6 @@ export default class DevToolsHookService implements Services.ServiceInstance {
121147
capabilities: browser.capabilities as Capabilities.W3CCapabilities
122148
})
123149
)
124-
this.#ensureInjected('session-start')
125-
126-
/**
127-
* create a new session capturer instance with the devtools options
128-
*/
129-
const wdioCaps = caps as Capabilities.W3CCapabilities & {
130-
'wdio:devtoolsOptions'?: any
131-
}
132-
this.#sessionCapturer = new SessionCapturer(
133-
wdioCaps['wdio:devtoolsOptions']
134-
)
135150
}
136151

137152
// The method signature is corrected to use W3CCapabilities
@@ -216,14 +231,37 @@ export default class DevToolsHookService implements Services.ServiceInstance {
216231
this.#commandStack.length === 0 &&
217232
!INTERNAL_COMMANDS.includes(command)
218233
) {
234+
const rawFile = source.getFileName() ?? undefined
235+
let absPath = rawFile
236+
237+
if (rawFile?.startsWith('file://')) {
238+
try {
239+
const url = new URL(rawFile)
240+
absPath = decodeURIComponent(url.pathname)
241+
} catch {
242+
absPath = rawFile
243+
}
244+
}
245+
246+
if (absPath?.includes('?')) {
247+
absPath = absPath.split('?')[0]
248+
}
249+
250+
const line = source.getLineNumber() ?? undefined
251+
const column = source.getColumnNumber() ?? undefined
252+
const callSource =
253+
absPath !== undefined
254+
? `${absPath}:${line ?? 0}:${column ?? 0}`
255+
: undefined
256+
219257
const cmdSig = JSON.stringify({
220258
command,
221259
args,
222-
src: source.getFileName() + ':' + source.getLineNumber()
260+
src: callSource
223261
})
224262

225263
if (this.#lastCommandSig !== cmdSig) {
226-
this.#commandStack.push(command)
264+
this.#commandStack.push({ command, callSource })
227265
this.#lastCommandSig = cmdSig
228266
}
229267
}
@@ -243,15 +281,17 @@ export default class DevToolsHookService implements Services.ServiceInstance {
243281
/* Ensure that the command is captured only if it matches the last command in the stack.
244282
* This prevents capturing commands that are not top-level user commands.
245283
*/
246-
if (this.#commandStack[this.#commandStack.length - 1] === command) {
284+
const frame = this.#commandStack[this.#commandStack.length - 1]
285+
if (frame?.command === command) {
247286
this.#commandStack.pop()
248287
if (this.#browser) {
249288
return this.#sessionCapturer.afterCommand(
250289
this.#browser,
251290
command,
252291
args,
253292
result,
254-
error
293+
error,
294+
frame.callSource
255295
)
256296
}
257297
}
@@ -295,16 +335,27 @@ export default class DevToolsHookService implements Services.ServiceInstance {
295335
log.info(`DevTools trace saved to ${traceFilePath}`)
296336
}
297337

298-
async #ensureInjected(reason: string) {
299-
if (!this.#browser) {
300-
return
338+
/**
339+
* Synchronous injection that blocks until complete
340+
*/
341+
async #injectScriptSync(browser: WebdriverIO.Browser) {
342+
if (!browser.isBidi) {
343+
throw new SevereServiceError(
344+
`Can not set up devtools for session with id "${browser.sessionId}" because it doesn't support WebDriver Bidi`
345+
)
301346
}
302-
if (this.#injecting) {
347+
348+
await this.#sessionCapturer.injectScript(getBrowserObject(browser))
349+
log.info('✓ Devtools preload script active')
350+
}
351+
352+
async #ensureInjected(reason: string) {
353+
// Keep this for re-injection after context changes
354+
if (!this.#browser || this.#injecting) {
303355
return
304356
}
305357
try {
306358
this.#injecting = true
307-
// Cheap marker check (no heavy stack work)
308359
const markerPresent = await this.#browser.execute(() => {
309360
return Boolean((window as any).__WDIO_DEVTOOLS_MARK)
310361
})

packages/service/src/session.ts

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,9 @@ export class SessionCapturer {
6060
command: keyof WebDriverCommands,
6161
args: any[],
6262
result: any,
63-
error: Error | undefined
63+
error: Error | undefined,
64+
callSource?: string
6465
) {
65-
const timestamp = Date.now()
6666
const sourceFile =
6767
parse(new Error(''))
6868
.filter((frame) => Boolean(frame.getFileName()))
@@ -99,8 +99,8 @@ export class SessionCapturer {
9999
args,
100100
result,
101101
error,
102-
timestamp,
103-
callSource: absPath
102+
timestamp: Date.now(),
103+
callSource: callSource ?? absPath
104104
}
105105
try {
106106
newCommand.screenshot = await browser.takeScreenshot()
@@ -120,6 +120,7 @@ export class SessionCapturer {
120120

121121
async injectScript(browser: WebdriverIO.Browser) {
122122
if (this.#isInjected) {
123+
log.info('Script already injected, skipping')
123124
return
124125
}
125126

@@ -130,38 +131,56 @@ export class SessionCapturer {
130131
}
131132

132133
this.#isInjected = true
134+
log.info('Injecting devtools script...')
133135
const script = await resolve('@wdio/devtools-script', import.meta.url)
134136
const source = (await fs.readFile(url.fileURLToPath(script))).toString()
135137
const functionDeclaration = `async () => { ${source} }`
136138

137139
await browser.scriptAddPreloadScript({
138140
functionDeclaration
139141
})
142+
log.info('✓ Script injected successfully')
140143
}
141144

142145
async #captureTrace(browser: WebdriverIO.Browser) {
143-
/**
144-
* only capture trace if script was injected
145-
*/
146146
if (!this.#isInjected) {
147+
log.warn('Script not injected, skipping trace capture')
147148
return
148149
}
149150

150-
const { mutations, traceLogs, consoleLogs, metadata } =
151-
await browser.execute(() => window.wdioTraceCollector.getTraceData())
152-
this.metadata = metadata
151+
try {
152+
const collectorExists = await browser.execute(
153+
() => typeof window.wdioTraceCollector !== 'undefined'
154+
)
153155

154-
if (Array.isArray(mutations)) {
155-
this.mutations.push(...(mutations as TraceMutation[]))
156-
this.sendUpstream('mutations', mutations)
157-
}
158-
if (Array.isArray(traceLogs)) {
159-
this.traceLogs.push(...traceLogs)
160-
this.sendUpstream('logs', traceLogs)
161-
}
162-
if (Array.isArray(consoleLogs)) {
163-
this.consoleLogs.push(...(consoleLogs as ConsoleLogs[]))
164-
this.sendUpstream('consoleLogs', consoleLogs)
156+
if (!collectorExists) {
157+
log.warn(
158+
'wdioTraceCollector not loaded yet - page loaded before preload script took effect'
159+
)
160+
return
161+
}
162+
163+
const { mutations, traceLogs, consoleLogs, metadata } =
164+
await browser.execute(() => window.wdioTraceCollector.getTraceData())
165+
this.metadata = metadata
166+
167+
if (Array.isArray(mutations)) {
168+
this.mutations.push(...(mutations as TraceMutation[]))
169+
this.sendUpstream('mutations', mutations)
170+
}
171+
if (Array.isArray(traceLogs)) {
172+
this.traceLogs.push(...traceLogs)
173+
this.sendUpstream('logs', traceLogs)
174+
}
175+
if (Array.isArray(consoleLogs)) {
176+
this.consoleLogs.push(...(consoleLogs as ConsoleLogs[]))
177+
this.sendUpstream('consoleLogs', consoleLogs)
178+
}
179+
180+
this.sendUpstream('metadata', metadata)
181+
log.info(`✓ Sent metadata upstream, WS state: ${this.#ws?.readyState}`)
182+
} catch (err) {
183+
log.error(`Failed to capture trace: ${(err as Error).message}`)
165184
}
166185
}
167186

0 commit comments

Comments
 (0)