Skip to content

Commit

Permalink
Adding persist session handling to ide
Browse files Browse the repository at this point in the history
  • Loading branch information
toddtarsi committed Nov 6, 2023
1 parent 10f9e32 commit f1e21f7
Show file tree
Hide file tree
Showing 10 changed files with 278 additions and 159 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,12 @@ const performSubscription = async (
})
}
window.sideAPI.state.onMutate.addListener(handler)
const removeHandler = () => window.sideAPI.state.onMutate.removeListener(handler)
window.addEventListener('beforeunload', removeHandler)
const removeHandler = () => {
try {
window.sideAPI.state.onMutate.removeListener(handler)
} catch (e) {}
};
// window.addEventListener('beforeunload', removeHandler)
return removeHandler
}, [])
}
Expand Down
8 changes: 6 additions & 2 deletions packages/selenium-ide/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,12 @@ app.on('activate', async () => {
}
})

app.on('before-quit', async () => {
await session.system.beforeQuit()
app.on('before-quit', async (e) => {
e.preventDefault()
const successfulExit = await session.system.beforeQuit()
if (successfulExit) {
app.exit(0)
}
})

app.on('window-all-closed', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,12 @@ export default class DriverController extends BaseController {
},
server,
windowAPI: {
setWindowSize: async (_executor, width, height) => {
const window = this.session.windows.getLastPlaybackWindow()
setWindowSize: async (executor, width, height) => {
const handle = await executor.driver.getWindowHandle()
const window = await this.session.windows.getPlaybackWindowByHandle(handle)
if (!window) {
throw new Error('Failed to find playback window')
}
const pbWinCount = this.session.windows.playbackWindows.length
const b = await window.getBounds()
const calcNewX = b.x + Math.floor(b.width / 2) - Math.floor(width / 2)
Expand Down
222 changes: 132 additions & 90 deletions packages/selenium-ide/src/main/session/controllers/Playback/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@ import {
} from '@seleniumhq/side-runtime'
import { WebDriverExecutorHooks } from '@seleniumhq/side-runtime/src/webdriver'
import { hasID } from '@seleniumhq/side-api/dist/helpers/hasID'
import { randomUUID } from 'crypto'
import { session } from 'electron'
import { Session } from 'main/types'
import { cpus } from 'os'
import BaseController from '../Base'

const parallelExecutions = Math.floor(cpus().length / 2)

/**
* This is a wrapper on the Playback construct of the side-runtime. Once again,
* I am ashamed. When hoisted, the underlying playback thing is accessed at
Expand All @@ -27,48 +32,67 @@ export default class PlaybackController extends BaseController {
playRange = [0, -1]
playingSuite = ''
playingTest = ''
playback: Playback | null = null
playbacks: Playback[] = []
testQueue: string[] = []
variables: Variables = new Variables()

onBeforePlay: NonNullable<WebDriverExecutorHooks['onBeforePlay']> = async ({
driver: executor,
}) => {
const { driver } = executor
const { windows } = this.session
const playbackWindow = await windows.getPlaybackWindow()

let success = false
if (playbackWindow) {
// Figure out playback window from document.title and url match
const UUID = randomUUID()
const registerWindow = async (windowID: number) => {
let success = false
const handles = await driver.getAllWindowHandles()
for (let i = 0, ii = handles.length; i !== ii; i++) {
try {
await driver.switchTo().window(handles[i])
const title = await driver.getTitle()
const url = await driver.getCurrentUrl()
if (
title === playbackWindow.getTitle() &&
url === playbackWindow.webContents.getURL()
) {
success = true
break
}
} catch (e) {
console.warn('Failed to switch to window', e)
const handle = handles[i]
await driver.switchTo().window(handle)
const title = await driver.getTitle()
if (title === UUID) {
await windows.registerPlaybackWindowHandle(handle, windowID)
success = true
break
}
}
if (!success) {
throw new Error('Failed to switch to playback window')
}
}

if (this.session.playback.playingSuite) {
const window = await windows.openPlaybackWindow({
show: false,
title: UUID,
})
await registerWindow(window.id)
await windows.arrangeWindow(
window,
'windowSizePlayback',
'windowPositionPlayback'
)
window.show()
return
}
if (!success) {
await windows.initializePlaybackWindow()
await this.onBeforePlay({ driver: executor })

const playbackWindow = await windows.getPlaybackWindow()
if (playbackWindow) {
const handle = await windows.getPlaybackWindowHandleByID(
playbackWindow.id
)
if (handle) {
await driver.switchTo().window(handle)
return
}
}

const window = await windows.openPlaybackWindow({ title: UUID })
await registerWindow(window.id)
}

async pause() {
this.isPlaying = false
if (this.playback) {
await this.playback.pause()
}
this.playbacks.forEach((playback) => playback.pause())
}

async stop() {
Expand All @@ -82,9 +106,9 @@ export default class PlaybackController extends BaseController {
}

async resume() {
if (this.playback) {
if (this.playbacks.length) {
this.isPlaying = true
this.playback.resume()
this.playbacks.forEach((playback) => playback.resume())
} else {
const sessionState = await this.session.state.get()
await this.play(sessionState.state.activeTestID)
Expand All @@ -98,28 +122,31 @@ export default class PlaybackController extends BaseController {
/**
* Create playback if none exists
*/
if (!this.playback) {
const playback = new Playback({
baseUrl: this.session.projects.project.url,
executor: await this.session.driver.build({}),
getTestByName: (name: string) => this.session.tests.getByName(name),
logger: console,
variables: this.variables,
options: {
delay: this.session.projects.project.delay || 0
}
})
const EE = playback['event-emitter']
EE.addListener(
PlaybackEvents.PLAYBACK_STATE_CHANGED,
this.handlePlaybackStateChanged
)
EE.addListener(
PlaybackEvents.COMMAND_STATE_CHANGED,
this.handleCommandStateChanged
)
this.playback = playback
}
// const browser = 'electron'
const playback = new Playback({
baseUrl: this.session.projects.project.url,
executor: await this.session.driver.build({}),
getTestByName: (name: string) => this.session.tests.getByName(name),
logger: console,
variables: new Variables(),
options: {
delay: this.session.projects.project.delay || 0,
},
})
console.log('playback init')
await playback.init()
console.log('playback cookies deleted')

const EE = playback['event-emitter']
EE.addListener(
PlaybackEvents.PLAYBACK_STATE_CHANGED,
this.handlePlaybackStateChanged(playback, testID)
)
EE.addListener(
PlaybackEvents.COMMAND_STATE_CHANGED,
this.handleCommandStateChanged
)
this.playbacks.push(playback)
/**
* If not ending at end of test, use playTo command
* or playSingleCommand if just one command specified.
Expand All @@ -128,12 +155,12 @@ export default class PlaybackController extends BaseController {
if (playRange[1] !== -1) {
const test = this.session.tests.getByID(testID)
if (playRange[0] === playRange[1]) {
this.playback.playSingleCommand(test.commands[playRange[0]])
playback.playSingleCommand(test.commands[playRange[0]])
} else {
this.playback.playTo(test, playRange[1], playRange[0])
playback.playTo(test, playRange[1], playRange[0])
}
} else {
this.playback.play(this.session.tests.getByID(testID), {
playback.play(this.session.tests.getByID(testID), {
startingCommandIndex: playRange[0],
})
}
Expand All @@ -145,28 +172,32 @@ export default class PlaybackController extends BaseController {
state: { activeSuiteID },
} = await this.session.state.get()
this.playingSuite = activeSuiteID
const tests = suites.find(hasID(activeSuiteID))?.tests ?? []
this.play(tests[0])
const suite = suites.find(hasID(activeSuiteID))
this.testQueue = suite?.tests ?? []
if (suite?.parallel) {
for (let i = 0; i < parallelExecutions; i++) {
this.playNextTest()
}
} else {
this.playNextTest()
}
}

async playNextTest() {
const {
project: { suites },
state: { activeSuiteID, activeTestID },
} = await this.session.state.get()
const tests = suites.find(hasID(activeSuiteID))?.tests ?? []
const nextTestIndex = tests.indexOf(activeTestID) + 1
const nextTest = tests[nextTestIndex]
const nextTest = this.testQueue.shift()
if (nextTest) {
this.session.api.state.onMutate.dispatchEvent('state.setActiveTest', {
params: [nextTest],
})
this.play(nextTest)
} else {
this.playingSuite = ''
this.session.api.state.onMutate.dispatchEvent('playback.stop', {
params: [],
})
const {
project: { suites },
state: { activeSuiteID },
} = await this.session.state.get()
const suite = suites.find(hasID(activeSuiteID))
const persistSession = suite?.persistSession ?? false
if (!persistSession) {
await session.defaultSession.clearStorageData({
storages: ['cookies', 'localstorage'],
})
}
await this.play(nextTest)
}
}

Expand All @@ -181,25 +212,36 @@ export default class PlaybackController extends BaseController {
console.debug(`${e.state} ${niceString}`)
}

handlePlaybackStateChanged = (
e: PlaybackEventShapes['PLAYBACK_STATE_CHANGED']
) => {
const testName = this.session.tests.getByID(this.playingTest)?.name
console.debug(`Playing state changed ${e.state} for test ${testName}`)
switch (e.state) {
case 'aborted':
case 'errored':
case 'failed':
case 'finished':
case 'stopped':
const playback = this.playback as Playback
playback.cleanup()
this.playback = null
this.variables = new Variables()
if (this.playingSuite) {
this.playNextTest()
}
handlePlaybackStateChanged =
(playback: Playback, testID: string) =>
async (e: PlaybackEventShapes['PLAYBACK_STATE_CHANGED']) => {
const testName = this.session.tests.getByID(testID)?.name
console.debug(
`Playing state changed ${e.state} for test ${testName}`,
this.playingSuite
)
switch (e.state) {
case 'aborted':
case 'errored':
case 'failed':
case 'finished':
case 'stopped':
if (this.playingSuite) {
try {
await playback.executor.driver.close()
} catch (e) {}
this.playbacks.splice(this.playbacks.indexOf(playback), 1)
await playback.cleanup()
if (!this.testQueue.length && !this.playbacks.length) {
this.playingSuite = ''
this.session.api.state.onMutate.dispatchEvent('playback.stop', {
params: [],
})
} else {
this.playNextTest()
}
}
}
this.session.api.playback.onPlayUpdate.dispatchEvent(e)
}
this.session.api.playback.onPlayUpdate.dispatchEvent(e)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { randomUUID } from 'crypto'
import RecentProjects from './Recent'
import BaseController from '../Base'
import { isAutomated } from 'main/util'
import { session } from 'electron'

export default class ProjectsController {
constructor(session: Session) {
Expand Down Expand Up @@ -43,6 +44,9 @@ export default class ProjectsController {
if (this.loaded) return
this.filepath = filepath
this.project = project
await session.defaultSession.clearStorageData({
storages: ['cookies', 'localstorage'],
})
await this.executeHook('onProjectLoaded')
this.loaded = true
}
Expand Down Expand Up @@ -206,7 +210,7 @@ export default class ProjectsController {
async doSaveChangesConfirm(): Promise<boolean> {
if (await this.projectHasChanged()) {
const confirmationStatus = await this.session.dialogs.showMessageBox(
'Save changes before leaving?',
'Save changes before closing project?',
['Cancel', 'Save and Continue', 'Continue without Saving']
)
switch (confirmationStatus) {
Expand Down
Loading

0 comments on commit f1e21f7

Please sign in to comment.