Skip to content

Commit 5325db2

Browse files
authored
fix(bidi-dialog): only accept dialogs in active browsing context (#14448)
* fix(bidi-dialog): only accept dialogs in active browsing context * test: add unit tests for dialog.accept with context validation
1 parent 3e1a01d commit 5325db2

File tree

2 files changed

+122
-0
lines changed

2 files changed

+122
-0
lines changed

packages/webdriverio/src/session/dialog.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { type local } from 'webdriver'
22
import { SessionManager } from './session.js'
3+
import { getContextManager } from './context.js'
34

45
export function getDialogManager(browser: WebdriverIO.Browser) {
56
return SessionManager.getSessionManager(browser, DialogManager)
@@ -111,6 +112,13 @@ export class Dialog {
111112
* @returns {Promise<void>}
112113
*/
113114
async accept(userText?: string) {
115+
const contextManager = getContextManager(this.#browser)
116+
const context = await contextManager.getCurrentContext()
117+
118+
if (this.#context !== context) {
119+
return
120+
}
121+
114122
await this.#browser.browsingContextHandleUserPrompt({
115123
accept: true,
116124
context: this.#context,
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest'
2+
import { remote } from '../../../src/index.js'
3+
import { Dialog } from '../../../src/session/dialog.js'
4+
import type { BrowsingContextUserPromptOpenedParameters } from '../../../../webdriver/build/bidi/localTypes.js'
5+
6+
vi.mock('../../../src/session/context.js', () => ({
7+
getContextManager: vi.fn(),
8+
}))
9+
import { getContextManager } from '../../../src/session/context.js'
10+
11+
describe('accept', () => {
12+
let browser: WebdriverIO.Browser
13+
let dialog: Dialog
14+
let mockContextManager: { getCurrentContext: () => Promise<string> }
15+
let browseStub
16+
17+
beforeEach(async () => {
18+
browser = await remote({
19+
baseUrl: 'http://foobar.com',
20+
capabilities: { browserName: 'dialog' }
21+
})
22+
23+
browseStub = vi.spyOn(browser, 'browsingContextHandleUserPrompt')
24+
.mockResolvedValue({})
25+
26+
mockContextManager = {
27+
getCurrentContext: vi.fn()
28+
};
29+
30+
(getContextManager as Mock).mockReturnValue(mockContextManager)
31+
})
32+
33+
it('should NOT call browsingContextHandleUserPrompt if contexts differ', async () => {
34+
const fakeEvent = {
35+
context: 'ctx-A',
36+
message: 'ignored',
37+
defaultValue: '',
38+
type: 'alert',
39+
} as BrowsingContextUserPromptOpenedParameters
40+
41+
dialog = new Dialog(fakeEvent, browser)
42+
43+
// simulate a *different* active context
44+
mockContextManager.getCurrentContext = vi.fn().mockResolvedValue('ctx-B')
45+
await dialog.accept('foo')
46+
47+
expect(browseStub).not.toHaveBeenCalled()
48+
})
49+
50+
it('should call browsingContextHandleUserPrompt if contexts match', async () => {
51+
const fakeEvent = {
52+
context: 'ctx-A',
53+
message: 'ignored',
54+
defaultValue: '',
55+
type: 'prompt',
56+
} as BrowsingContextUserPromptOpenedParameters
57+
58+
dialog = new Dialog(fakeEvent, browser)
59+
60+
// simulate *same* active context
61+
mockContextManager.getCurrentContext = vi.fn().mockResolvedValue('ctx-A')
62+
await dialog.accept('my input')
63+
64+
expect(browseStub).toHaveBeenCalledWith({
65+
accept: true,
66+
context: 'ctx-A',
67+
userText: 'my input'
68+
})
69+
})
70+
71+
it('should call getCurrentContext once and pass undefined userText when none provided', async () => {
72+
const fakeEvent = {
73+
context: 'ctx-A',
74+
message: 'msg',
75+
defaultValue: '',
76+
type: 'prompt',
77+
} as BrowsingContextUserPromptOpenedParameters
78+
79+
dialog = new Dialog(fakeEvent, browser)
80+
mockContextManager.getCurrentContext = vi.fn().mockResolvedValue('ctx-A')
81+
82+
await dialog.accept()
83+
84+
expect(mockContextManager.getCurrentContext).toHaveBeenCalledTimes(1)
85+
86+
expect(browseStub).toHaveBeenCalledWith({
87+
accept: true,
88+
context: 'ctx-A',
89+
userText: undefined
90+
})
91+
})
92+
93+
it('should handle any dialog type the same way when contexts match', async () => {
94+
for (const type of ['alert', 'confirm', 'prompt', 'beforeunload'] as const) {
95+
const fakeEvent = {
96+
context: 'ctx-X',
97+
message: 'm',
98+
defaultValue: '',
99+
type: type,
100+
} as BrowsingContextUserPromptOpenedParameters
101+
102+
dialog = new Dialog(fakeEvent, browser)
103+
mockContextManager.getCurrentContext = vi.fn().mockResolvedValue('ctx-X')
104+
browseStub.mockClear()
105+
106+
await dialog.accept('foo')
107+
expect(browseStub).toHaveBeenCalledWith({
108+
accept: true,
109+
context: 'ctx-X',
110+
userText: 'foo'
111+
})
112+
}
113+
})
114+
})

0 commit comments

Comments
 (0)