Skip to content

Commit 42c79a2

Browse files
cdifinocorevo
authored andcommitted
Connected Mode (#823)
* Fixed side-runner foreach command * Fixed Test for foreach in Selianize Fixed the for each test. * connection mode ui changes Connected Mode Implementation Fixed check for connection ID bug Fixed prettier bug and changed description of pop up Fixed prettier bug Fixed prettier bug Plugin Manager Fix, modified dialog, window focus addition Closure Prettier Prettier Prettier Restart to welcome sccreen when entering connected mode Prettier Prettier Prettier Prettier Prettier Prettier Prettier debug travis fail restart capabiulity everytime is starting a connection prettier travis bugs travis bugs * documentation of connected mode * deleted end connection * change saving message * fix bug * prettier * deleted a unwanted file * changing errors to response with error code * Revert "Changing errors to response with error code" * Use promise to refactor the code (#12) * Use promise to refactor the code * Change the position of skipping save. * remove the restart payload from connect call. * Hide the force close endpoint. * Fix a launch problem when IDE is already in non-control mode. * convert all throw to error response. * fixing indentation issues causing build failure * adding space in comments to retrigger the build * using prettier to re-indent * Revert "using prettier to re-indent" * Revert "retrigger the build" * Revert "fixing indentation issues causing build failure" * removing unused import * indentation changes for build * bug fixes * fixing error message * reindent using eslint instead of prettier * Updated document to use goog:chromeOptions (#757) * Added first pass on command schema validation with error handling for code export and displaying the message in the GUI. * fixed typo on code-export: targret into target (#784) * Moved the isOptional declaration for arguments to the command object. * Added validation support for ignored commands, other commands with optional args (e.g., executeScript). Also added the test name to the error for easier user reference (e.g., when exporting a suite of tests that has a command error) * Added side-model package with Commands and ArgTypes * Stopped mixing ES5 & ES6 exports and added a test to verify that the index is exporting the correct thing * Updated everything to use side-model instead of direct calls to models/Command/etc * Config for stale bot, for managing issues * Remove stale bot config since it is on master now * Added linting rule for the `.only` filter applied to tests/suites * Removed .only filter from selianize index test * Added linting rules from eslint-plugin-node * Added master remnants to gitignore * Added installing the website deps to the CI config * Added userAgent checks to side-utils * Updated the beforeEach hook for each language to check the user agent to determine the browser name and default to Chrome if it's not available * Added environment checks to side-utils * Added side-utils to selenium-ide * Exposed environment from side-utils * Updated selenium-ide/common/utils to use side-utils * Added lowercasing to the JS beforeEach hook when using the browserName from the user agent * Added check for `window` before setting the userAgent in side-utils/user-agent * Catch undefined error for window in side-utils/user-agent * re: side-utils/user-agent: Forgot to call the function. Storing it in a variable and exposing it instead * Fixed erroneous reference change for Command. Fixes regression found when playing back all tests in a suite * Reset timesVisited to 0 when finished iterating through a forEach collection. Fixes #792 * Added test coverage for reseting timesVisited when completing a forEach loop. Also moved the setter into evaluateForEach * Added linting to release-prep script * Updated changelog * v3.12.2 * Fix for off-by-one error in nested forEach command blocks. Fixes #792 * Updated changelog * v3.12.3 * Added a seed test for nested forEach * Extending api to open SeleniumIDE and load projects Co-Authored-By: Tomer <[email protected]> * fixed typo * fix lint * Updated side-runner version of jest-environment-selenium * Added loopLimit param to repeatIf * Updated retryLimit to ignore forEach commands * prevent unregistered plugins from communicating with Selenium IDE * Updated changelog * lint fix * Revert error handling for unregistered commands due to a race condition which breaks plugins (and doesn't propagate the error) * Variable Property Access Co-authored-by: Tomer <[email protected]> * Added the ability to register a command in side-model * Wired up plugin command registration to side-model * Added the isOptional check to the target param too (with some cleanup) * Updated changelog * dont allow usage of npm * Fixed linting error * v3.13.0 * API bug (#809) Fixed check instead of req.id req.project * Fixed typo. Action reference should be Actions reference to avoid casting. (#743) * Fixed typo. Action should really be Actions reference to avoid casting. * Updating snapshot to ensure build succeeds * parent bb93dfe author cdifino <[email protected]> 1569005404 -0700 committer Nikhil Bansal <[email protected]> 1570570897 -0700 connection mode ui changes Connected Mode Implementation Fixed check for connection ID bug Fixed prettier bug and changed description of pop up Fixed prettier bug Fixed prettier bug Plugin Manager Fix, modified dialog, window focus addition Closure Prettier Prettier Prettier Restart to welcome sccreen when entering connected mode Prettier Prettier Prettier Prettier Prettier Prettier Prettier debug travis fail restart capabiulity everytime is starting a connection prettier travis bugs travis bugs documentation of connected mode deleted end connection change saving message fix bug prettier deleted a unwanted file changing errors to response with error code Revert "Changing errors to response with error code" Use promise to refactor the code (#12) * Use promise to refactor the code * Change the position of skipping save. * remove the restart payload from connect call. * Hide the force close endpoint. * Fix a launch problem when IDE is already in non-control mode. * convert all throw to error response. using prettier to re-indent adding space in comments to retrigger the build Revert "using prettier to re-indent" Revert "retrigger the build" Revert "fixing indentation issues causing build failure" removing unused import indentation changes for build bug fixes fixing error message reindent using eslint instead of prettier * fixing error message * reindent using eslint instead of prettier * connection mode ui changes Connected Mode Implementation Fixed check for connection ID bug Fixed prettier bug and changed description of pop up Fixed prettier bug Fixed prettier bug Plugin Manager Fix, modified dialog, window focus addition Closure Prettier Prettier Prettier Restart to welcome sccreen when entering connected mode Prettier Prettier Prettier Prettier Prettier Prettier Prettier debug travis fail restart capabiulity everytime is starting a connection prettier travis bugs travis bugs documentation of connected mode deleted end connection * fixing bugs added in squash * adding import back * adding import back * resolve review comments. * Fix some indention issue. * Fix the max-width of banner. * connected mode resolve comments. (#29) * resolve review comments. * Fix some indention issue. * Fix the max-width of banner. * updating the api documentation * indentation changes to fix build * fixing css indentation issues * Nbansal/feedbackchanges (#33) changing private apis to /private/ sending project in message on save
1 parent a3563b5 commit 42c79a2

File tree

10 files changed

+295
-59
lines changed

10 files changed

+295
-59
lines changed

docs/api/plugins/system.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ title: Plugins System API
44
sidebar_label: System
55
---
66

7-
The System API is the most basic API which Selenium IDE provides. It is not prefixed and can be called with `/`.
7+
The System API is the most basic API which Selenium IDE provides. It is not prefixed and can be called with `/`.
88

99
#### Opening Selenium IDE
1010
If the extension is installed, a request could be made by a plugin to open Selenium IDE.
@@ -62,4 +62,12 @@ Loads a project into the IDE, as if the user opened it, if the user has unsaved
6262
project: JSON parsed side file
6363
}
6464
```
65+
### `POST /control`
6566

67+
Start a connection from another chrome extension. When this connection is accepted by the user, Selenium IDE restarts and registers the caller, and the extension takes exclusive control of Selenium IDE until user closes Selenium IDE or another connection is accepted. When this mode is on, the save to computer functionality gets overwritten by sending the side file to the extension controlling Selenium IDE.
68+
69+
The payload of this call is identical to the payload of `POST /register` call.
70+
71+
### `POST /close`
72+
73+
When Selenium IDE is controlled by another chrome extension, the controller extension can use this API to close the IDE window. If user have any unsaved changes, it will prompt user to whether give up the changes or ignore the close. No payload is needed.

docs/introduction/code-export.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ You can see an example of this being implemented in `packages/code-export-java-j
157157

158158
Once you've got everything else in place, it's time to wire it up for use in the UI.
159159

160-
This is possible in [`packages/code-export/src/index.js`](https://github.com/SeleniumHQ/selenium-ide/blob/v3/packages/code-export/src/index.js).
160+
This is possible in [`packages/code-export/src/index.js`](https://github.com/SeleniumHQ/selenium-ide/blob/v3/packages/code-export/src/index.js).
161161

162162
You will need to add your language to `availableLanguages`.
163163

packages/selenium-ide/src/api/v1/index.js

Lines changed: 153 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
// specific language governing permissions and limitations
1616
// under the License.
1717

18+
import browser from 'webextension-polyfill'
1819
import Router from '../../router'
1920
import Manager from '../../plugin/manager'
2021
import logger from '../../neo/stores/view/Logs'
@@ -24,79 +25,199 @@ import recordRouter from './record'
2425
import exportRouter from './export'
2526
import popupRouter from './popup'
2627
import UiState from '../../neo/stores/view/UiState'
28+
import WindowSession from '../../neo/IO/window-session'
2729
import ModalState from '../../neo/stores/view/ModalState'
2830
import { loadJSProject } from '../../neo/IO/filesystem'
31+
import manager from '../../plugin/manager'
2932

3033
const router = new Router()
3134

35+
const errors = {
36+
cannotAccessInControlMode: {
37+
errorCode: 'CannotAccessInControlMode',
38+
error: 'Selenium IDE is controlled by a different extension.',
39+
},
40+
missingPlugin: {
41+
errorCode: 'MissingPlugin',
42+
error: 'Plugin is not registered.',
43+
},
44+
missingController: {
45+
errorCode: 'missingController',
46+
error: 'No controller specified.',
47+
},
48+
missingProject: {
49+
errorCode: 'MissingProject',
50+
error: 'Project is missing.',
51+
},
52+
}
53+
54+
function isFromController(req) {
55+
return Manager.controller && manager.controller.id === req.sender
56+
}
57+
58+
function controlledOnly(req, res) {
59+
if (!Manager.controller || isFromController(req)) {
60+
return Promise.resolve()
61+
} else {
62+
res(errors.cannotAccessInControlMode)
63+
return Promise.reject()
64+
}
65+
}
66+
67+
function tryOverrideControl(req) {
68+
if (!ModalState.welcomeState.completed) {
69+
ModalState.hideWelcome()
70+
}
71+
WindowSession.focusIDEWindow()
72+
return ModalState.showAlert({
73+
title: 'Assisted Control',
74+
description: `${req.name} is trying to control Selenium IDE`,
75+
confirmLabel: 'Restart and Allow access',
76+
cancelLabel: 'Deny access',
77+
}).then(r => {
78+
if (r) {
79+
const plugin = {
80+
id: req.sender,
81+
name: req.name,
82+
version: req.version,
83+
commands: req.commands,
84+
dependencies: req.dependencies,
85+
jest: req.jest,
86+
exports: req.exports,
87+
}
88+
return browser.runtime.sendMessage({ restart: true, controller: plugin })
89+
} else {
90+
return Promise.reject()
91+
}
92+
})
93+
}
94+
3295
router.get('/health', (req, res) => {
3396
res(Manager.hasPlugin(req.sender))
3497
})
3598

3699
router.post('/register', (req, res) => {
37-
const plugin = {
38-
id: req.sender,
39-
name: req.name,
40-
version: req.version,
41-
commands: req.commands,
42-
dependencies: req.dependencies,
43-
jest: req.jest,
44-
exports: req.exports,
45-
}
46-
Manager.registerPlugin(plugin)
47-
res(true)
100+
controlledOnly(req, res).then(() => {
101+
const plugin = {
102+
id: req.sender,
103+
name: req.name,
104+
version: req.version,
105+
commands: req.commands,
106+
dependencies: req.dependencies,
107+
jest: req.jest,
108+
exports: req.exports,
109+
}
110+
Manager.registerPlugin(plugin)
111+
res(true)
112+
})
48113
})
49114

50115
router.post('/log', (req, res) => {
51-
if (req.type === LogTypes.Error) {
52-
logger.error(`${Manager.getPlugin(req.sender).name}: ${req.message}`)
53-
} else if (req.type === LogTypes.Warning) {
54-
logger.warn(`${Manager.getPlugin(req.sender).name}: ${req.message}`)
55-
} else {
56-
logger.log(`${Manager.getPlugin(req.sender).name}: ${req.message}`)
57-
}
58-
res(true)
116+
controlledOnly(req, res).then(() => {
117+
if (req.type === LogTypes.Error) {
118+
logger.error(`${Manager.getPlugin(req.sender).name}: ${req.message}`)
119+
} else if (req.type === LogTypes.Warning) {
120+
logger.warn(`${Manager.getPlugin(req.sender).name}: ${req.message}`)
121+
} else {
122+
logger.log(`${Manager.getPlugin(req.sender).name}: ${req.message}`)
123+
}
124+
res(true)
125+
})
59126
})
60127

61128
router.get('/project', (_req, res) => {
62-
res({ id: UiState.project.id, name: UiState.project.name })
129+
controlledOnly(_req, res).then(() => {
130+
res({ id: UiState.project.id, name: UiState.project.name })
131+
})
63132
})
64133

65-
router.post('/project', (req, res) => {
66-
if (req.project) {
134+
router.post('/control', (req, res) => {
135+
if (isFromController(req)) {
136+
res(true)
137+
} else {
138+
tryOverrideControl(req)
139+
.then(() => res(true))
140+
.catch(() => res(false))
141+
}
142+
})
143+
144+
router.post('/close', (req, res) => {
145+
controlledOnly(req, res).then(() => {
146+
// Not allow close if is not control mode.
147+
if (!UiState.isControlled) {
148+
return res(false)
149+
}
67150
const plugin = Manager.getPlugin(req.sender)
151+
if (!plugin) return res(errors.missingPlugin)
68152
if (!UiState.isSaved()) {
69153
ModalState.showAlert({
70-
title: 'Open project without saving',
154+
title: 'Close project without saving',
71155
description: `${
72156
plugin.name
73-
} is trying to load a project, are you sure you want to load this project and lose all unsaved changes?`,
157+
} is trying to close a project, are you sure you want to load this project and lose all unsaved changes?`,
74158
confirmLabel: 'proceed',
75159
cancelLabel: 'cancel',
76160
}).then(result => {
77161
if (result) {
78-
loadJSProject(UiState.project, req.project)
79-
ModalState.completeWelcome()
162+
window.close()
163+
res(true)
80164
}
81165
})
82-
res(true)
166+
res(false)
83167
} else {
84-
ModalState.hideWelcome()
168+
window.close()
169+
res(true)
170+
}
171+
})
172+
})
173+
174+
//Using '/private/' as a prefix for internal APIs
175+
router.post('/private/close', res => {
176+
window.close()
177+
res(true)
178+
})
179+
180+
router.post('/private/connect', (req, res) => {
181+
if (req.controller && req.controller.id) {
182+
Manager.controller = req.controller
183+
UiState.startConnection()
184+
Manager.registerPlugin(req.controller)
185+
return res(true)
186+
} else {
187+
return res(errors.missingController)
188+
}
189+
})
190+
191+
router.post('/project', (req, res) => {
192+
controlledOnly(req, res).then(() => {
193+
const plugin = Manager.getPlugin(req.sender)
194+
if (!plugin) return res(errors.missingPlugin)
195+
if (!req.project) return res(errors.missingProject)
196+
197+
if (!UiState.isSaved()) {
198+
WindowSession.focusIDEWindow()
85199
ModalState.showAlert({
86-
title: 'Open project',
87-
description: `${plugin.name} is trying to load a project`,
200+
title: 'Open project without saving',
201+
description: `${
202+
plugin.name
203+
} is trying to load a project, are you sure you want to load this project and lose all unsaved changes?`,
88204
confirmLabel: 'proceed',
89205
cancelLabel: 'cancel',
90206
}).then(result => {
91207
if (result) {
92208
loadJSProject(UiState.project, req.project)
93209
ModalState.completeWelcome()
210+
res(true)
94211
}
95212
})
213+
} else {
214+
WindowSession.focusIDEWindow()
215+
loadJSProject(UiState.project, req.project)
216+
ModalState.completeWelcome()
96217
res(true)
97218
}
98-
}
99-
res(false)
219+
res(false)
220+
})
100221
})
101222

102223
router.use('/playback', playbackRouter)

packages/selenium-ide/src/background/background.js

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,17 +181,82 @@ browser.runtime.onConnect.addListener(function(m) {
181181
port = m
182182
})
183183

184+
browser.runtime.onMessage.addListener(handleInternalMessage)
185+
186+
function handleInternalMessage(message) {
187+
if (message.restart && message.controller && message.controller.id) {
188+
ideWindowId = undefined
189+
190+
browser.runtime
191+
.sendMessage({
192+
uri: '/private/close',
193+
verb: 'post',
194+
payload: null,
195+
})
196+
.then(() => {
197+
openPanel({ windowId: 0 }).then(() => {
198+
var payload = { ...message }
199+
delete payload.restart
200+
201+
const newMessage = {
202+
uri: '/private/connect',
203+
verb: 'post',
204+
payload: payload,
205+
}
206+
browser.runtime
207+
.sendMessage(newMessage)
208+
.then(
209+
browser.runtime.sendMessage(message.controller.id, {
210+
connected: true,
211+
})
212+
)
213+
.catch(() => {
214+
browser.runtime.sendMessage(
215+
message.controller.id,
216+
'Error Connecting to Selenium IDE'
217+
)
218+
})
219+
})
220+
})
221+
}
222+
}
223+
184224
browser.runtime.onMessageExternal.addListener(
185225
(message, sender, sendResponse) => {
186226
if (!message.payload) {
187227
message.payload = {}
188228
}
189-
message.payload.sender = sender.id
229+
230+
let payload = message.payload
231+
232+
payload.sender = sender.id
233+
if (message.uri.startsWith('/private/')) {
234+
return sendResponse(false)
235+
}
190236
browser.runtime
191237
.sendMessage(message)
192238
.then(sendResponse)
193239
.catch(() => {
194-
if (message.openSeleniumIDEIfClosed) {
240+
if (message.uri == '/control' && message.verb == 'post') {
241+
return openPanel({ windowId: 0 }).then(() => {
242+
const newMessage = {
243+
uri: '/private/connect',
244+
verb: 'post',
245+
payload: {
246+
controller: {
247+
id: payload.sender,
248+
name: payload.name,
249+
version: payload.version,
250+
commands: payload.commands,
251+
dependencies: payload.dependencies,
252+
jest: payload.jest,
253+
exports: payload.exports,
254+
},
255+
},
256+
}
257+
browser.runtime.sendMessage(newMessage).then(sendResponse)
258+
})
259+
} else if (message.openSeleniumIDEIfClosed) {
195260
return openPanel({ windowId: 0 }).then(() => {
196261
sendResponse(true)
197262
})

0 commit comments

Comments
 (0)