Skip to content

Commit f1e21f7

Browse files
committed
Adding persist session handling to ide
1 parent 10f9e32 commit f1e21f7

File tree

10 files changed

+278
-159
lines changed

10 files changed

+278
-159
lines changed

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,12 @@ const performSubscription = async (
2121
})
2222
}
2323
window.sideAPI.state.onMutate.addListener(handler)
24-
const removeHandler = () => window.sideAPI.state.onMutate.removeListener(handler)
25-
window.addEventListener('beforeunload', removeHandler)
24+
const removeHandler = () => {
25+
try {
26+
window.sideAPI.state.onMutate.removeListener(handler)
27+
} catch (e) {}
28+
};
29+
// window.addEventListener('beforeunload', removeHandler)
2630
return removeHandler
2731
}, [])
2832
}

packages/selenium-ide/src/main/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,12 @@ app.on('activate', async () => {
5959
}
6060
})
6161

62-
app.on('before-quit', async () => {
63-
await session.system.beforeQuit()
62+
app.on('before-quit', async (e) => {
63+
e.preventDefault()
64+
const successfulExit = await session.system.beforeQuit()
65+
if (successfulExit) {
66+
app.exit(0)
67+
}
6468
})
6569

6670
app.on('window-all-closed', async () => {

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,12 @@ export default class DriverController extends BaseController {
7676
},
7777
server,
7878
windowAPI: {
79-
setWindowSize: async (_executor, width, height) => {
80-
const window = this.session.windows.getLastPlaybackWindow()
79+
setWindowSize: async (executor, width, height) => {
80+
const handle = await executor.driver.getWindowHandle()
81+
const window = await this.session.windows.getPlaybackWindowByHandle(handle)
82+
if (!window) {
83+
throw new Error('Failed to find playback window')
84+
}
8185
const pbWinCount = this.session.windows.playbackWindows.length
8286
const b = await window.getBounds()
8387
const calcNewX = b.x + Math.floor(b.width / 2) - Math.floor(width / 2)

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

Lines changed: 132 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,14 @@ import {
66
} from '@seleniumhq/side-runtime'
77
import { WebDriverExecutorHooks } from '@seleniumhq/side-runtime/src/webdriver'
88
import { hasID } from '@seleniumhq/side-api/dist/helpers/hasID'
9+
import { randomUUID } from 'crypto'
10+
import { session } from 'electron'
911
import { Session } from 'main/types'
12+
import { cpus } from 'os'
1013
import BaseController from '../Base'
1114

15+
const parallelExecutions = Math.floor(cpus().length / 2)
16+
1217
/**
1318
* This is a wrapper on the Playback construct of the side-runtime. Once again,
1419
* I am ashamed. When hoisted, the underlying playback thing is accessed at
@@ -27,48 +32,67 @@ export default class PlaybackController extends BaseController {
2732
playRange = [0, -1]
2833
playingSuite = ''
2934
playingTest = ''
30-
playback: Playback | null = null
35+
playbacks: Playback[] = []
36+
testQueue: string[] = []
3137
variables: Variables = new Variables()
3238

3339
onBeforePlay: NonNullable<WebDriverExecutorHooks['onBeforePlay']> = async ({
3440
driver: executor,
3541
}) => {
3642
const { driver } = executor
3743
const { windows } = this.session
38-
const playbackWindow = await windows.getPlaybackWindow()
39-
40-
let success = false
41-
if (playbackWindow) {
42-
// Figure out playback window from document.title and url match
44+
const UUID = randomUUID()
45+
const registerWindow = async (windowID: number) => {
46+
let success = false
4347
const handles = await driver.getAllWindowHandles()
4448
for (let i = 0, ii = handles.length; i !== ii; i++) {
45-
try {
46-
await driver.switchTo().window(handles[i])
47-
const title = await driver.getTitle()
48-
const url = await driver.getCurrentUrl()
49-
if (
50-
title === playbackWindow.getTitle() &&
51-
url === playbackWindow.webContents.getURL()
52-
) {
53-
success = true
54-
break
55-
}
56-
} catch (e) {
57-
console.warn('Failed to switch to window', e)
49+
const handle = handles[i]
50+
await driver.switchTo().window(handle)
51+
const title = await driver.getTitle()
52+
if (title === UUID) {
53+
await windows.registerPlaybackWindowHandle(handle, windowID)
54+
success = true
55+
break
5856
}
5957
}
58+
if (!success) {
59+
throw new Error('Failed to switch to playback window')
60+
}
61+
}
62+
63+
if (this.session.playback.playingSuite) {
64+
const window = await windows.openPlaybackWindow({
65+
show: false,
66+
title: UUID,
67+
})
68+
await registerWindow(window.id)
69+
await windows.arrangeWindow(
70+
window,
71+
'windowSizePlayback',
72+
'windowPositionPlayback'
73+
)
74+
window.show()
75+
return
6076
}
61-
if (!success) {
62-
await windows.initializePlaybackWindow()
63-
await this.onBeforePlay({ driver: executor })
77+
78+
const playbackWindow = await windows.getPlaybackWindow()
79+
if (playbackWindow) {
80+
const handle = await windows.getPlaybackWindowHandleByID(
81+
playbackWindow.id
82+
)
83+
if (handle) {
84+
await driver.switchTo().window(handle)
85+
return
86+
}
6487
}
88+
89+
const window = await windows.openPlaybackWindow({ title: UUID })
90+
await registerWindow(window.id)
6591
}
6692

6793
async pause() {
6894
this.isPlaying = false
69-
if (this.playback) {
70-
await this.playback.pause()
71-
}
95+
this.playbacks.forEach((playback) => playback.pause())
7296
}
7397

7498
async stop() {
@@ -82,9 +106,9 @@ export default class PlaybackController extends BaseController {
82106
}
83107

84108
async resume() {
85-
if (this.playback) {
109+
if (this.playbacks.length) {
86110
this.isPlaying = true
87-
this.playback.resume()
111+
this.playbacks.forEach((playback) => playback.resume())
88112
} else {
89113
const sessionState = await this.session.state.get()
90114
await this.play(sessionState.state.activeTestID)
@@ -98,28 +122,31 @@ export default class PlaybackController extends BaseController {
98122
/**
99123
* Create playback if none exists
100124
*/
101-
if (!this.playback) {
102-
const playback = new Playback({
103-
baseUrl: this.session.projects.project.url,
104-
executor: await this.session.driver.build({}),
105-
getTestByName: (name: string) => this.session.tests.getByName(name),
106-
logger: console,
107-
variables: this.variables,
108-
options: {
109-
delay: this.session.projects.project.delay || 0
110-
}
111-
})
112-
const EE = playback['event-emitter']
113-
EE.addListener(
114-
PlaybackEvents.PLAYBACK_STATE_CHANGED,
115-
this.handlePlaybackStateChanged
116-
)
117-
EE.addListener(
118-
PlaybackEvents.COMMAND_STATE_CHANGED,
119-
this.handleCommandStateChanged
120-
)
121-
this.playback = playback
122-
}
125+
// const browser = 'electron'
126+
const playback = new Playback({
127+
baseUrl: this.session.projects.project.url,
128+
executor: await this.session.driver.build({}),
129+
getTestByName: (name: string) => this.session.tests.getByName(name),
130+
logger: console,
131+
variables: new Variables(),
132+
options: {
133+
delay: this.session.projects.project.delay || 0,
134+
},
135+
})
136+
console.log('playback init')
137+
await playback.init()
138+
console.log('playback cookies deleted')
139+
140+
const EE = playback['event-emitter']
141+
EE.addListener(
142+
PlaybackEvents.PLAYBACK_STATE_CHANGED,
143+
this.handlePlaybackStateChanged(playback, testID)
144+
)
145+
EE.addListener(
146+
PlaybackEvents.COMMAND_STATE_CHANGED,
147+
this.handleCommandStateChanged
148+
)
149+
this.playbacks.push(playback)
123150
/**
124151
* If not ending at end of test, use playTo command
125152
* or playSingleCommand if just one command specified.
@@ -128,12 +155,12 @@ export default class PlaybackController extends BaseController {
128155
if (playRange[1] !== -1) {
129156
const test = this.session.tests.getByID(testID)
130157
if (playRange[0] === playRange[1]) {
131-
this.playback.playSingleCommand(test.commands[playRange[0]])
158+
playback.playSingleCommand(test.commands[playRange[0]])
132159
} else {
133-
this.playback.playTo(test, playRange[1], playRange[0])
160+
playback.playTo(test, playRange[1], playRange[0])
134161
}
135162
} else {
136-
this.playback.play(this.session.tests.getByID(testID), {
163+
playback.play(this.session.tests.getByID(testID), {
137164
startingCommandIndex: playRange[0],
138165
})
139166
}
@@ -145,28 +172,32 @@ export default class PlaybackController extends BaseController {
145172
state: { activeSuiteID },
146173
} = await this.session.state.get()
147174
this.playingSuite = activeSuiteID
148-
const tests = suites.find(hasID(activeSuiteID))?.tests ?? []
149-
this.play(tests[0])
175+
const suite = suites.find(hasID(activeSuiteID))
176+
this.testQueue = suite?.tests ?? []
177+
if (suite?.parallel) {
178+
for (let i = 0; i < parallelExecutions; i++) {
179+
this.playNextTest()
180+
}
181+
} else {
182+
this.playNextTest()
183+
}
150184
}
151185

152186
async playNextTest() {
153-
const {
154-
project: { suites },
155-
state: { activeSuiteID, activeTestID },
156-
} = await this.session.state.get()
157-
const tests = suites.find(hasID(activeSuiteID))?.tests ?? []
158-
const nextTestIndex = tests.indexOf(activeTestID) + 1
159-
const nextTest = tests[nextTestIndex]
187+
const nextTest = this.testQueue.shift()
160188
if (nextTest) {
161-
this.session.api.state.onMutate.dispatchEvent('state.setActiveTest', {
162-
params: [nextTest],
163-
})
164-
this.play(nextTest)
165-
} else {
166-
this.playingSuite = ''
167-
this.session.api.state.onMutate.dispatchEvent('playback.stop', {
168-
params: [],
169-
})
189+
const {
190+
project: { suites },
191+
state: { activeSuiteID },
192+
} = await this.session.state.get()
193+
const suite = suites.find(hasID(activeSuiteID))
194+
const persistSession = suite?.persistSession ?? false
195+
if (!persistSession) {
196+
await session.defaultSession.clearStorageData({
197+
storages: ['cookies', 'localstorage'],
198+
})
199+
}
200+
await this.play(nextTest)
170201
}
171202
}
172203

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

184-
handlePlaybackStateChanged = (
185-
e: PlaybackEventShapes['PLAYBACK_STATE_CHANGED']
186-
) => {
187-
const testName = this.session.tests.getByID(this.playingTest)?.name
188-
console.debug(`Playing state changed ${e.state} for test ${testName}`)
189-
switch (e.state) {
190-
case 'aborted':
191-
case 'errored':
192-
case 'failed':
193-
case 'finished':
194-
case 'stopped':
195-
const playback = this.playback as Playback
196-
playback.cleanup()
197-
this.playback = null
198-
this.variables = new Variables()
199-
if (this.playingSuite) {
200-
this.playNextTest()
201-
}
215+
handlePlaybackStateChanged =
216+
(playback: Playback, testID: string) =>
217+
async (e: PlaybackEventShapes['PLAYBACK_STATE_CHANGED']) => {
218+
const testName = this.session.tests.getByID(testID)?.name
219+
console.debug(
220+
`Playing state changed ${e.state} for test ${testName}`,
221+
this.playingSuite
222+
)
223+
switch (e.state) {
224+
case 'aborted':
225+
case 'errored':
226+
case 'failed':
227+
case 'finished':
228+
case 'stopped':
229+
if (this.playingSuite) {
230+
try {
231+
await playback.executor.driver.close()
232+
} catch (e) {}
233+
this.playbacks.splice(this.playbacks.indexOf(playback), 1)
234+
await playback.cleanup()
235+
if (!this.testQueue.length && !this.playbacks.length) {
236+
this.playingSuite = ''
237+
this.session.api.state.onMutate.dispatchEvent('playback.stop', {
238+
params: [],
239+
})
240+
} else {
241+
this.playNextTest()
242+
}
243+
}
244+
}
245+
this.session.api.playback.onPlayUpdate.dispatchEvent(e)
202246
}
203-
this.session.api.playback.onPlayUpdate.dispatchEvent(e)
204-
}
205247
}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { randomUUID } from 'crypto'
99
import RecentProjects from './Recent'
1010
import BaseController from '../Base'
1111
import { isAutomated } from 'main/util'
12+
import { session } from 'electron'
1213

1314
export default class ProjectsController {
1415
constructor(session: Session) {
@@ -43,6 +44,9 @@ export default class ProjectsController {
4344
if (this.loaded) return
4445
this.filepath = filepath
4546
this.project = project
47+
await session.defaultSession.clearStorageData({
48+
storages: ['cookies', 'localstorage'],
49+
})
4650
await this.executeHook('onProjectLoaded')
4751
this.loaded = true
4852
}
@@ -206,7 +210,7 @@ export default class ProjectsController {
206210
async doSaveChangesConfirm(): Promise<boolean> {
207211
if (await this.projectHasChanged()) {
208212
const confirmationStatus = await this.session.dialogs.showMessageBox(
209-
'Save changes before leaving?',
213+
'Save changes before closing project?',
210214
['Cancel', 'Save and Continue', 'Continue without Saving']
211215
)
212216
switch (confirmationStatus) {

0 commit comments

Comments
 (0)