Skip to content

Commit 7ce32e2

Browse files
committed
enhance environment safety and stabilize reruns
1 parent abefa3d commit 7ce32e2

File tree

13 files changed

+124
-73
lines changed

13 files changed

+124
-73
lines changed

packages/selenium-ide/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@
111111
"@seleniumhq/code-export-python-pytest": "^4.0.0-alpha.4",
112112
"@seleniumhq/code-export-ruby-rspec": "^4.0.0-alpha.3",
113113
"@seleniumhq/get-driver": "^4.0.0-alpha.3",
114-
"@seleniumhq/side-api": "^4.0.0-alpha.39",
114+
"@seleniumhq/side-api": "^4.0.0-alpha.40",
115115
"@seleniumhq/side-model": "^4.0.0-alpha.5",
116116
"@seleniumhq/side-runtime": "^4.0.0-alpha.33",
117117
"dnd-core": "^16.0.1",

packages/selenium-ide/src/browser/helpers/preload-electron.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import api from 'browser/api'
22
import { contextBridge } from 'electron'
33
import preload from './preload'
44

5-
export default preload(api, () => {
5+
export const cb = () => new Promise<void>((resolve) => {
66
/**
77
* Binds our API on initialization
88
*/
@@ -11,5 +11,8 @@ export default preload(api, () => {
1111
* Expose it in the main context
1212
*/
1313
contextBridge.exposeInMainWorld('sideAPI', window.sideAPI)
14+
resolve()
1415
})
15-
})
16+
});
17+
18+
export default preload(api, cb)

packages/selenium-ide/src/browser/helpers/preload.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,15 @@
1717
import type { Api } from '@seleniumhq/side-api'
1818
import { BrowserApiMutators } from 'browser/api'
1919
import mutators from 'browser/api/mutator'
20-
import { noop } from 'lodash/fp'
2120

2221
export type NestedPartial<API> = {
2322
[K in keyof API]?: API[K] extends Record<string, unknown>
2423
? NestedPartial<API[K]>
2524
: API[K]
2625
}
2726

28-
export default (api: Api, cb = noop) =>
29-
(
27+
export default (api: Api, ...cbs: (() => void)[]) =>
28+
async (
3029
apiSubset: NestedPartial<Api> & {
3130
mutators: NestedPartial<BrowserApiMutators>
3231
} = {
@@ -35,5 +34,14 @@ export default (api: Api, cb = noop) =>
3534
},
3635
) => {
3736
window.sideAPI = apiSubset as Api & { mutators: BrowserApiMutators }
38-
cb()
37+
if (cbs?.length) {
38+
for (const cb of cbs) {
39+
await cb()
40+
}
41+
}
42+
43+
return cbs.reduce((acc, cb) => () => {
44+
acc()
45+
cb()
46+
})
3947
}

packages/selenium-ide/src/browser/windows/PlaybackWindow/preload.ts

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,38 +17,44 @@
1717
import { RecorderPreprocessor } from '@seleniumhq/side-api'
1818
import api from 'browser/api'
1919
import apiMutators from 'browser/api/mutator'
20-
import preload from 'browser/helpers/preload-electron'
20+
import preload from 'browser/helpers/preload'
21+
import {cb as ElectronCallback} from 'browser/helpers/preload-electron'
2122
import { webFrame } from 'electron'
2223
import Recorder from './preload/recorder'
2324

2425
const recorderProcessors: RecorderPreprocessor[] = []
25-
preload({
26-
plugins: {
27-
addRecorderPreprocessor: (fn) => {
28-
recorderProcessors.push(fn)
26+
async function main() {
27+
const preloads = await api.plugins.getPreloads()
28+
for (const preload of preloads) {
29+
eval(preload)
30+
}
31+
window.addEventListener('DOMContentLoaded', async () => {
32+
webFrame.executeJavaScript(`
33+
Object.defineProperty(navigator, 'webdriver', {
34+
get () {
35+
return true
36+
}
37+
})
38+
`)
39+
setTimeout(async () => {
40+
console.debug('Initializing the recorder')
41+
const recorder = new Recorder(window, recorderProcessors)
42+
recorder.attach()
43+
}, 500)
44+
})
45+
}
46+
47+
preload(api, ElectronCallback, main)(
48+
{
49+
plugins: {
50+
addRecorderPreprocessor: (fn) => {
51+
recorderProcessors.push(fn)
52+
},
53+
},
54+
recorder: api.recorder,
55+
mutators: {
56+
plugins: {},
57+
recorder: apiMutators.recorder,
2958
},
3059
},
31-
recorder: api.recorder,
32-
mutators: {
33-
plugins: {},
34-
recorder: apiMutators.recorder,
35-
},
36-
})
37-
window.addEventListener('DOMContentLoaded', async () => {
38-
const plugins = await api.plugins.listPreloadPaths()
39-
for (const plugin of plugins) {
40-
__non_webpack_require__(plugin)
41-
}
42-
webFrame.executeJavaScript(`
43-
Object.defineProperty(navigator, 'webdriver', {
44-
get () {
45-
return true
46-
}
47-
})
48-
`)
49-
setTimeout(async () => {
50-
console.debug('Initializing the recorder')
51-
const recorder = new Recorder(window, recorderProcessors)
52-
recorder.attach()
53-
}, 500)
54-
})
60+
)

packages/selenium-ide/src/main/session/controllers/Driver/bidi.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,12 @@ export const createBidiAPIBindings = async (
2929
console.log('Playback javascript exception', entry)
3030
})
3131

32-
const scriptManager = await getScriptManager(null as any, driver as any)
32+
const handle = await driver.getWindowHandle() as any
33+
const scriptManager = await getScriptManager(handle, driver as any)
3334
await scriptManager.addPreloadScript(
3435
playbackWindowBidiPreload as any,
3536
[],
36-
null
37+
false
3738
)
3839
const pluginPreloads = await session.plugins.listPreloadPaths()
3940
pluginPreloads.forEach(async (preloadPath) => {

packages/selenium-ide/src/main/session/controllers/Playback/index.ts

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,10 @@ export default class PlaybackController extends BaseController {
138138
const browserInfo = this.session.store.get('browserInfo')
139139
const allowMultiplePlaybacks =
140140
(await this.isParallel()) && this.testQueue.length
141-
const makeNewPlayback = !this.playbacks.length || allowMultiplePlaybacks
141+
const makeNewPlayback = !this.playbacks.length || allowMultiplePlaybacks
142+
let playback: Playback
142143
if (makeNewPlayback) {
143-
const playback = new Playback({
144+
playback = new Playback({
144145
baseUrl: this.session.projects.project.url,
145146
executor: await this.session.driver.build({
146147
browser: browserInfo.browser,
@@ -171,28 +172,24 @@ export default class PlaybackController extends BaseController {
171172
} else {
172173
await playback.executor.driver.switchTo().newWindow('window')
173174
}
174-
const state = await this.session.state.get()
175-
const currentCommand = getActiveCommand(state)
176-
const url =
177-
currentCommand.command === 'open'
178-
? new URL(currentCommand.target as string, state.project.url).href
179-
: state.project.url
180-
playback.executor.doOpen(url)
181-
return playback
175+
} else {
176+
playback = this.playbacks[0]
177+
await this.claimPlaybackWindow(playback)
182178
}
183-
return this.playbacks[0]
179+
return playback
184180
}
185181

186182
async claimPlaybackWindow(playback: Playback) {
187183
const executor = playback.executor
188184
const driver = executor.driver
189-
const handle = await driver.getWindowHandle()
190-
const existingWindow = await this.session.windows.getPlaybackWindowByHandle(
191-
handle
192-
)
193-
if (existingWindow && existingWindow.isVisible()) {
194-
return
195-
}
185+
try {
186+
const handle = await driver.getWindowHandle()
187+
const existingWindow =
188+
await this.session.windows.getPlaybackWindowByHandle(handle)
189+
if (existingWindow && existingWindow.isVisible()) {
190+
return
191+
}
192+
} catch (windowDoesNotExist) {}
196193
let success = false
197194
const UUID = randomUUID()
198195
const window = await this.session.windows.openPlaybackWindow({
@@ -220,6 +217,13 @@ export default class PlaybackController extends BaseController {
220217
if (!success) {
221218
throw new Error('Failed to switch to playback window')
222219
}
220+
const state = await this.session.state.get()
221+
const currentCommand = getActiveCommand(state)
222+
const url =
223+
currentCommand.command === 'open'
224+
? new URL(currentCommand.target as string, state.project.url).href
225+
: state.project.url
226+
playback.executor.doOpen(url)
223227
}
224228

225229
async play(testID: string, playRange = PlaybackController.defaultPlayRange) {

packages/selenium-ide/src/main/session/controllers/Plugins/index.ts

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { PluginShape } from '@seleniumhq/side-api'
22
import { correctPluginPaths, loadPlugins } from '@seleniumhq/side-runtime'
33
import { ipcMain } from 'electron'
4+
import { readFile } from 'fs/promises'
45
import BaseController from '../Base'
56
import path from 'node:path'
67

@@ -18,25 +19,45 @@ export default class PluginsController extends BaseController {
1819
const systemPlugins = this.session.store.get('plugins')
1920
const projectPath = this.session.projects.filepath as string
2021
const activeProject = await this.session.projects.getActive()
21-
return systemPlugins
22-
.concat(correctPluginPaths(projectPath, activeProject.plugins))
22+
return systemPlugins.concat(
23+
correctPluginPaths(projectPath, activeProject.plugins)
24+
)
25+
}
26+
27+
async getPreloads() {
28+
const preloadPaths = (await this.listPreloadPaths())
29+
.map((preloadPath) => {
30+
try {
31+
const path = __non_webpack_require__.resolve(preloadPath) as string
32+
return path
33+
} catch (e) {
34+
return null
35+
}
36+
})
37+
.filter(Boolean) as string[]
38+
39+
return Promise.all(
40+
preloadPaths.map((preloadPath) => readFile(preloadPath, 'utf-8'))
41+
)
2342
}
2443

2544
async listPreloadPaths() {
26-
const list = await this.list();
45+
const list = await this.list()
2746
return list.map((pluginPath) => {
2847
const actualPluginPath = __non_webpack_require__.resolve(pluginPath)
29-
const preloadPath = path.join(actualPluginPath, '..', 'preload', 'index.js')
48+
const preloadPath = path.join(
49+
actualPluginPath,
50+
'..',
51+
'preload',
52+
'index.js'
53+
)
3054
return preloadPath
3155
})
3256
}
3357

3458
async onProjectLoaded() {
3559
const pluginPaths = await this.list()
36-
const plugins = await loadPlugins(
37-
pluginPaths,
38-
__non_webpack_require__
39-
)
60+
const plugins = await loadPlugins(pluginPaths, __non_webpack_require__)
4061
plugins.forEach((plugin, index) => {
4162
const pluginPath = pluginPaths[index]
4263
return this.load(pluginPath, plugin)

packages/selenium-ide/src/main/session/controllers/Windows/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ const windowLoaderFactoryMap: WindowLoaderFactoryMap = Object.fromEntries(
6060
devTools: !isAutomated,
6161
...(windowConfig?.webPreferences ?? {}),
6262
preload: hasPreload ? preloadPath : undefined,
63-
sandbox: hasPreload ? false : undefined,
63+
sandbox: true,
6464
},
6565
...options,
6666
})
@@ -288,7 +288,7 @@ export default class WindowsController extends BaseController {
288288

289289
async removePlaybackWIndow(window: Electron.BrowserWindow) {
290290
this.playbackWindows.splice(this.playbackWindows.indexOf(window), 1)
291-
if (this.playbackWindows.length === 0) {
291+
if (this.playbackWindows.length === 1) {
292292
if (this.session.state.state.status === 'recording') {
293293
await this.session.api.recorder.stop()
294294
}

packages/side-api/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@seleniumhq/side-api",
3-
"version": "4.0.0-alpha.39",
3+
"version": "4.0.0-alpha.40",
44
"private": false,
55
"description": "Selenium IDE API command shapes and such",
66
"author": "Todd Tarsi <[email protected]>",
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/**
2+
* Get the active plugin preload scripts
3+
*/
4+
export type Shape = () => Promise<string[]>

0 commit comments

Comments
 (0)