Skip to content

Commit

Permalink
enhance environment safety and stabilize reruns
Browse files Browse the repository at this point in the history
  • Loading branch information
toddtarsi committed Dec 29, 2023
1 parent abefa3d commit 7ce32e2
Show file tree
Hide file tree
Showing 13 changed files with 124 additions and 73 deletions.
2 changes: 1 addition & 1 deletion packages/selenium-ide/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@
"@seleniumhq/code-export-python-pytest": "^4.0.0-alpha.4",
"@seleniumhq/code-export-ruby-rspec": "^4.0.0-alpha.3",
"@seleniumhq/get-driver": "^4.0.0-alpha.3",
"@seleniumhq/side-api": "^4.0.0-alpha.39",
"@seleniumhq/side-api": "^4.0.0-alpha.40",
"@seleniumhq/side-model": "^4.0.0-alpha.5",
"@seleniumhq/side-runtime": "^4.0.0-alpha.33",
"dnd-core": "^16.0.1",
Expand Down
7 changes: 5 additions & 2 deletions packages/selenium-ide/src/browser/helpers/preload-electron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import api from 'browser/api'
import { contextBridge } from 'electron'
import preload from './preload'

export default preload(api, () => {
export const cb = () => new Promise<void>((resolve) => {
/**
* Binds our API on initialization
*/
Expand All @@ -11,5 +11,8 @@ export default preload(api, () => {
* Expose it in the main context
*/
contextBridge.exposeInMainWorld('sideAPI', window.sideAPI)
resolve()
})
})
});

export default preload(api, cb)
16 changes: 12 additions & 4 deletions packages/selenium-ide/src/browser/helpers/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,15 @@
import type { Api } from '@seleniumhq/side-api'
import { BrowserApiMutators } from 'browser/api'
import mutators from 'browser/api/mutator'
import { noop } from 'lodash/fp'

export type NestedPartial<API> = {
[K in keyof API]?: API[K] extends Record<string, unknown>
? NestedPartial<API[K]>
: API[K]
}

export default (api: Api, cb = noop) =>
(
export default (api: Api, ...cbs: (() => void)[]) =>
async (
apiSubset: NestedPartial<Api> & {
mutators: NestedPartial<BrowserApiMutators>
} = {
Expand All @@ -35,5 +34,14 @@ export default (api: Api, cb = noop) =>
},
) => {
window.sideAPI = apiSubset as Api & { mutators: BrowserApiMutators }
cb()
if (cbs?.length) {
for (const cb of cbs) {
await cb()
}
}

return cbs.reduce((acc, cb) => () => {
acc()
cb()
})
}
64 changes: 35 additions & 29 deletions packages/selenium-ide/src/browser/windows/PlaybackWindow/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,38 +17,44 @@
import { RecorderPreprocessor } from '@seleniumhq/side-api'
import api from 'browser/api'
import apiMutators from 'browser/api/mutator'
import preload from 'browser/helpers/preload-electron'
import preload from 'browser/helpers/preload'
import {cb as ElectronCallback} from 'browser/helpers/preload-electron'
import { webFrame } from 'electron'
import Recorder from './preload/recorder'

const recorderProcessors: RecorderPreprocessor[] = []
preload({
plugins: {
addRecorderPreprocessor: (fn) => {
recorderProcessors.push(fn)
async function main() {
const preloads = await api.plugins.getPreloads()
for (const preload of preloads) {
eval(preload)
}
window.addEventListener('DOMContentLoaded', async () => {
webFrame.executeJavaScript(`
Object.defineProperty(navigator, 'webdriver', {
get () {
return true
}
})
`)
setTimeout(async () => {
console.debug('Initializing the recorder')
const recorder = new Recorder(window, recorderProcessors)
recorder.attach()
}, 500)
})
}

preload(api, ElectronCallback, main)(
{
plugins: {
addRecorderPreprocessor: (fn) => {
recorderProcessors.push(fn)
},
},
recorder: api.recorder,
mutators: {
plugins: {},
recorder: apiMutators.recorder,
},
},
recorder: api.recorder,
mutators: {
plugins: {},
recorder: apiMutators.recorder,
},
})
window.addEventListener('DOMContentLoaded', async () => {
const plugins = await api.plugins.listPreloadPaths()
for (const plugin of plugins) {
__non_webpack_require__(plugin)
}
webFrame.executeJavaScript(`
Object.defineProperty(navigator, 'webdriver', {
get () {
return true
}
})
`)
setTimeout(async () => {
console.debug('Initializing the recorder')
const recorder = new Recorder(window, recorderProcessors)
recorder.attach()
}, 500)
})
)
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ export const createBidiAPIBindings = async (
console.log('Playback javascript exception', entry)
})

const scriptManager = await getScriptManager(null as any, driver as any)
const handle = await driver.getWindowHandle() as any
const scriptManager = await getScriptManager(handle, driver as any)
await scriptManager.addPreloadScript(
playbackWindowBidiPreload as any,
[],
null
false
)
const pluginPreloads = await session.plugins.listPreloadPaths()
pluginPreloads.forEach(async (preloadPath) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,10 @@ export default class PlaybackController extends BaseController {
const browserInfo = this.session.store.get('browserInfo')
const allowMultiplePlaybacks =
(await this.isParallel()) && this.testQueue.length
const makeNewPlayback = !this.playbacks.length || allowMultiplePlaybacks
const makeNewPlayback = !this.playbacks.length || allowMultiplePlaybacks
let playback: Playback
if (makeNewPlayback) {
const playback = new Playback({
playback = new Playback({
baseUrl: this.session.projects.project.url,
executor: await this.session.driver.build({
browser: browserInfo.browser,
Expand Down Expand Up @@ -171,28 +172,24 @@ export default class PlaybackController extends BaseController {
} else {
await playback.executor.driver.switchTo().newWindow('window')
}
const state = await this.session.state.get()
const currentCommand = getActiveCommand(state)
const url =
currentCommand.command === 'open'
? new URL(currentCommand.target as string, state.project.url).href
: state.project.url
playback.executor.doOpen(url)
return playback
} else {
playback = this.playbacks[0]
await this.claimPlaybackWindow(playback)
}
return this.playbacks[0]
return playback
}

async claimPlaybackWindow(playback: Playback) {
const executor = playback.executor
const driver = executor.driver
const handle = await driver.getWindowHandle()
const existingWindow = await this.session.windows.getPlaybackWindowByHandle(
handle
)
if (existingWindow && existingWindow.isVisible()) {
return
}
try {
const handle = await driver.getWindowHandle()
const existingWindow =
await this.session.windows.getPlaybackWindowByHandle(handle)
if (existingWindow && existingWindow.isVisible()) {
return
}
} catch (windowDoesNotExist) {}
let success = false
const UUID = randomUUID()
const window = await this.session.windows.openPlaybackWindow({
Expand Down Expand Up @@ -220,6 +217,13 @@ export default class PlaybackController extends BaseController {
if (!success) {
throw new Error('Failed to switch to playback window')
}
const state = await this.session.state.get()
const currentCommand = getActiveCommand(state)
const url =
currentCommand.command === 'open'
? new URL(currentCommand.target as string, state.project.url).href
: state.project.url
playback.executor.doOpen(url)
}

async play(testID: string, playRange = PlaybackController.defaultPlayRange) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { PluginShape } from '@seleniumhq/side-api'
import { correctPluginPaths, loadPlugins } from '@seleniumhq/side-runtime'
import { ipcMain } from 'electron'
import { readFile } from 'fs/promises'
import BaseController from '../Base'
import path from 'node:path'

Expand All @@ -18,25 +19,45 @@ export default class PluginsController extends BaseController {
const systemPlugins = this.session.store.get('plugins')
const projectPath = this.session.projects.filepath as string
const activeProject = await this.session.projects.getActive()
return systemPlugins
.concat(correctPluginPaths(projectPath, activeProject.plugins))
return systemPlugins.concat(
correctPluginPaths(projectPath, activeProject.plugins)
)
}

async getPreloads() {
const preloadPaths = (await this.listPreloadPaths())
.map((preloadPath) => {
try {
const path = __non_webpack_require__.resolve(preloadPath) as string
return path
} catch (e) {
return null
}
})
.filter(Boolean) as string[]

return Promise.all(
preloadPaths.map((preloadPath) => readFile(preloadPath, 'utf-8'))
)
}

async listPreloadPaths() {
const list = await this.list();
const list = await this.list()
return list.map((pluginPath) => {
const actualPluginPath = __non_webpack_require__.resolve(pluginPath)
const preloadPath = path.join(actualPluginPath, '..', 'preload', 'index.js')
const preloadPath = path.join(
actualPluginPath,
'..',
'preload',
'index.js'
)
return preloadPath
})
}

async onProjectLoaded() {
const pluginPaths = await this.list()
const plugins = await loadPlugins(
pluginPaths,
__non_webpack_require__
)
const plugins = await loadPlugins(pluginPaths, __non_webpack_require__)
plugins.forEach((plugin, index) => {
const pluginPath = pluginPaths[index]
return this.load(pluginPath, plugin)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const windowLoaderFactoryMap: WindowLoaderFactoryMap = Object.fromEntries(
devTools: !isAutomated,
...(windowConfig?.webPreferences ?? {}),
preload: hasPreload ? preloadPath : undefined,
sandbox: hasPreload ? false : undefined,
sandbox: true,
},
...options,
})
Expand Down Expand Up @@ -288,7 +288,7 @@ export default class WindowsController extends BaseController {

async removePlaybackWIndow(window: Electron.BrowserWindow) {
this.playbackWindows.splice(this.playbackWindows.indexOf(window), 1)
if (this.playbackWindows.length === 0) {
if (this.playbackWindows.length === 1) {
if (this.session.state.state.status === 'recording') {
await this.session.api.recorder.stop()
}
Expand Down
2 changes: 1 addition & 1 deletion packages/side-api/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@seleniumhq/side-api",
"version": "4.0.0-alpha.39",
"version": "4.0.0-alpha.40",
"private": false,
"description": "Selenium IDE API command shapes and such",
"author": "Todd Tarsi <[email protected]>",
Expand Down
4 changes: 4 additions & 0 deletions packages/side-api/src/commands/plugins/getPreloads.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* Get the active plugin preload scripts
*/
export type Shape = () => Promise<string[]>
4 changes: 4 additions & 0 deletions packages/side-api/src/commands/plugins/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Shape as AddPreloadScript } from './addPreloadScript'
import type { Shape as AddRecorderPreprocessor } from './addRecorderPreprocessor'
import type { Shape as GetPreloads } from './getPreloads'
import type { Shape as List } from './list'
import type { Shape as ListPreloadPaths } from './listPreloadPaths'
import type { Shape as ProjectCreate } from './projectCreate'
Expand All @@ -8,6 +9,7 @@ import type { Shape as ProjectEdit } from './projectEdit'

import * as addPreloadScript from './addPreloadScript'
import * as addRecorderPreprocessor from './addRecorderPreprocessor'
import * as getPreloads from './getPreloads'
import * as list from './list'
import * as listPreloadPaths from './listPreloadPaths'
import * as projectCreate from './projectCreate'
Expand All @@ -17,6 +19,7 @@ import * as projectEdit from './projectEdit'
export const commands = {
addPreloadScript,
addRecorderPreprocessor,
getPreloads,
list,
listPreloadPaths,
projectCreate,
Expand All @@ -29,6 +32,7 @@ export const commands = {
export type Shape = {
addPreloadScript: AddPreloadScript
addRecorderPreprocessor: AddRecorderPreprocessor
getPreloads: GetPreloads
list: List
listPreloadPaths: ListPreloadPaths
projectCreate: ProjectCreate
Expand Down
2 changes: 1 addition & 1 deletion packages/side-example-suite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"@seleniumhq/code-export-python-pytest": "4.0.0-alpha.3"
},
"devDependencies": {
"@seleniumhq/side-api": "^4.0.0-alpha.39"
"@seleniumhq/side-api": "^4.0.0-alpha.40"
},
"repository": {
"type": "git",
Expand Down
10 changes: 5 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 7ce32e2

Please sign in to comment.