Skip to content

Commit a56118d

Browse files
committed
Updated browser context injection
1 parent dce5e45 commit a56118d

File tree

2 files changed

+43
-7
lines changed

2 files changed

+43
-7
lines changed

packages/service/src/constants.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,14 @@ export const DEFAULT_LAUNCH_CAPS: WebdriverIO.Capabilities = {
1515
}
1616

1717
export const INTERNAL_COMMANDS = [
18-
'$', '$$', 'emit', 'browsingContextLocateNodes', 'browsingContextNavigate',
18+
'emit', 'browsingContextLocateNodes', 'browsingContextNavigate',
1919
'waitUntil', 'getTitle', 'getUrl', 'getWindowSize', 'setWindowSize', 'deleteSession',
2020
'findElementFromShadowRoot', 'findElementsFromShadowRoot', 'waitForExist', 'browsingContextGetTree',
21-
'scriptCallFunction', 'getElement', 'execute'
21+
'scriptCallFunction', 'getElement', 'execute', 'findElement'
22+
]
23+
24+
export const CONTEXT_CHANGE_COMMANDS = [
25+
'url', 'back', 'forward', 'refresh', 'switchFrame', 'newWindow', 'createWindow', 'closeWindow'
2226
]
2327

2428
export const SPEC_FILE_PATTERN = /(test|spec|features)[\\/].*\.(js|ts)$/i

packages/service/src/index.ts

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { DevToolsAppLauncher } from './launcher.js'
1313
import { getBrowserObject } from './utils.ts'
1414
import { parse } from 'stack-trace'
1515
import { type TraceLog, TraceType } from './types.ts'
16-
import { INTERNAL_COMMANDS, SPEC_FILE_PATTERN } from './constants.ts'
16+
import { INTERNAL_COMMANDS, SPEC_FILE_PATTERN, CONTEXT_CHANGE_COMMANDS } from './constants.ts'
1717

1818
export const launcher = DevToolsAppLauncher
1919

@@ -86,6 +86,9 @@ export default class DevToolsHookService implements Services.ServiceInstance {
8686
*/
8787
captureType = TraceType.Testrunner
8888

89+
// This is used to track if the injection script is currently being injected
90+
#injecting = false
91+
8992
before(caps: Capabilities.W3CCapabilities, __: string[], browser: WebdriverIO.Browser) {
9093
this.#browser = browser
9194

@@ -99,6 +102,7 @@ export default class DevToolsHookService implements Services.ServiceInstance {
99102
options: browser.options,
100103
capabilities: browser.capabilities as Capabilities.W3CCapabilities,
101104
}))
105+
this.#ensureInjected('session-start')
102106

103107
/**
104108
* create a new session capturer instance with the devtools options
@@ -156,9 +160,6 @@ export default class DevToolsHookService implements Services.ServiceInstance {
156160
async beforeCommand(command: string, args: string[]) {
157161
if (!this.#browser) { return }
158162

159-
// Always inject the script to support iframe detection etc.
160-
await this.#sessionCapturer.injectScript(getBrowserObject(this.#browser))
161-
162163
/**
163164
* propagate url change to devtools app
164165
*/
@@ -173,9 +174,12 @@ export default class DevToolsHookService implements Services.ServiceInstance {
173174
const stack = parse(new Error('')).reverse()
174175
const source = stack.find((frame) => {
175176
const file = frame.getFileName()
176-
// Only consider frames from user spec/test files
177+
// Only consider command frames from user spec/test files
177178
return file && SPEC_FILE_PATTERN.test(file)
178179
})
180+
log.debug('Command: ', command)
181+
log.debug('Source: ', JSON.stringify(source))
182+
log.debug('Stack: ', JSON.stringify(stack))
179183

180184
if (source && this.#commandStack.length === 0 && !INTERNAL_COMMANDS.includes(command)) {
181185
const cmdSig = JSON.stringify({
@@ -192,6 +196,9 @@ export default class DevToolsHookService implements Services.ServiceInstance {
192196
}
193197

194198
afterCommand(command: keyof WebDriverCommands, args: any[], result: any, error?: Error) {
199+
// Skip bookkeeping for internal injection calls
200+
if (this.#injecting) return
201+
195202
/* Ensure that the command is captured only if it matches the last command in the stack.
196203
* This prevents capturing commands that are not top-level user commands.
197204
*/
@@ -201,6 +208,11 @@ export default class DevToolsHookService implements Services.ServiceInstance {
201208
return this.#sessionCapturer.afterCommand(this.#browser, command, args, result, error)
202209
}
203210
}
211+
212+
// Re-inject AFTER context-changing commands complete so new documents/frames are instrumented
213+
if (CONTEXT_CHANGE_COMMANDS.includes(command)) {
214+
void this.#ensureInjected(`context-change:${command}`)
215+
}
204216
}
205217

206218
/**
@@ -232,4 +244,24 @@ export default class DevToolsHookService implements Services.ServiceInstance {
232244
await fs.writeFile(traceFilePath, JSON.stringify(traceLog))
233245
log.info(`DevTools trace saved to ${traceFilePath}`)
234246
}
247+
248+
async #ensureInjected(reason: string) {
249+
if (!this.#browser) return
250+
if (this.#injecting) return
251+
try {
252+
this.#injecting = true
253+
// Cheap marker check (no heavy stack work)
254+
const markerPresent = await this.#browser.execute(() => {
255+
return Boolean((window as any).__WDIO_DEVTOOLS_MARK)
256+
})
257+
if (markerPresent) {
258+
return
259+
}
260+
await this.#sessionCapturer.injectScript(getBrowserObject(this.#browser))
261+
} catch (err) {
262+
log.warn(`[inject] failed (reason=${reason}): ${(err as Error).message}`)
263+
} finally {
264+
this.#injecting = false
265+
}
266+
}
235267
}

0 commit comments

Comments
 (0)