selenium-webdriver 4.0.0-alpha.4 → 4.0.0-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +27 -0
- package/.prettierrc +4 -0
- package/CHANGES.md +22 -1
- package/README.md +4 -4
- package/chrome.js +72 -590
- package/chromium.js +1024 -0
- package/devtools/CDPConnection.js +37 -0
- package/devtools/generator/protocol-dts-generator.js +429 -0
- package/edge.js +124 -86
- package/firefox.js +201 -202
- package/http/index.js +131 -115
- package/http/util.js +56 -64
- package/ie.js +180 -104
- package/index.js +246 -199
- package/io/exec.js +34 -39
- package/io/index.js +141 -151
- package/io/zip.js +75 -65
- package/lib/README +5 -0
- package/lib/actions.js +138 -121
- package/lib/atoms/find-elements.js +123 -0
- package/lib/atoms/get-attribute.js +79 -60
- package/lib/atoms/is-displayed.js +72 -73
- package/{example/chrome_mobile_emulation.js → lib/atoms/make-atoms-module.js} +16 -23
- package/lib/by.js +192 -65
- package/lib/capabilities.js +82 -78
- package/lib/command.js +18 -20
- package/lib/error.js +134 -168
- package/lib/http.js +324 -230
- package/lib/input.js +325 -302
- package/lib/logging.js +127 -138
- package/lib/promise.js +60 -66
- package/lib/proxy.js +31 -41
- package/lib/session.js +12 -15
- package/lib/symbols.js +3 -4
- package/lib/until.js +162 -162
- package/lib/webdriver.js +733 -522
- package/net/index.js +30 -36
- package/net/portprober.js +31 -144
- package/opera.js +406 -0
- package/package.json +18 -11
- package/proxy.js +2 -2
- package/remote/index.js +190 -187
- package/safari.js +40 -49
- package/LICENSE +0 -202
- package/NOTICE +0 -2
- package/example/chrome_android.js +0 -42
- package/example/firefox_channels.js +0 -77
- package/example/google_search.js +0 -50
- package/example/google_search_test.js +0 -72
- package/example/headless.js +0 -55
- package/example/logging.js +0 -68
- package/testing/index.js +0 -498
package/firefox.js
CHANGED
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
* .setFirefoxOptions(options)
|
|
80
80
|
* .build();
|
|
81
81
|
*
|
|
82
|
-
* On all platforms, you may
|
|
82
|
+
* On all platforms, you may configure WebDriver to use a Firefox specific
|
|
83
83
|
* executable:
|
|
84
84
|
*
|
|
85
85
|
* let options = new firefox.Options()
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
*
|
|
90
90
|
* You may customize the Firefox binary and profile when running against a
|
|
91
91
|
* remote Selenium server. Your custom profile will be packaged as a zip and
|
|
92
|
-
*
|
|
92
|
+
* transferred to the remote host for use. The profile will be transferred
|
|
93
93
|
* _once for each new session_. The performance impact should be minimal if
|
|
94
94
|
* you've only configured a few extra browser preferences. If you have a large
|
|
95
95
|
* profile with several extensions, you should consider installing it on the
|
|
@@ -114,25 +114,18 @@
|
|
|
114
114
|
* [PATH]: http://en.wikipedia.org/wiki/PATH_%28variable%29
|
|
115
115
|
*/
|
|
116
116
|
|
|
117
|
-
'use strict'
|
|
118
|
-
|
|
119
|
-
const path = require('path');
|
|
120
|
-
const url = require('url');
|
|
121
|
-
|
|
122
|
-
const Symbols = require('./lib/symbols');
|
|
123
|
-
const command = require('./lib/command');
|
|
124
|
-
const exec = require('./io/exec');
|
|
125
|
-
const http = require('./http');
|
|
126
|
-
const httpUtil = require('./http/util');
|
|
127
|
-
const io = require('./io');
|
|
128
|
-
const net = require('./net');
|
|
129
|
-
const portprober = require('./net/portprober');
|
|
130
|
-
const remote = require('./remote');
|
|
131
|
-
const webdriver = require('./lib/webdriver');
|
|
132
|
-
const zip = require('./io/zip');
|
|
133
|
-
const {Browser, Capabilities} = require('./lib/capabilities');
|
|
134
|
-
const {Zip} = require('./io/zip');
|
|
117
|
+
'use strict'
|
|
135
118
|
|
|
119
|
+
const path = require('path')
|
|
120
|
+
const Symbols = require('./lib/symbols')
|
|
121
|
+
const command = require('./lib/command')
|
|
122
|
+
const http = require('./http')
|
|
123
|
+
const io = require('./io')
|
|
124
|
+
const remote = require('./remote')
|
|
125
|
+
const webdriver = require('./lib/webdriver')
|
|
126
|
+
const zip = require('./io/zip')
|
|
127
|
+
const { Browser, Capabilities } = require('./lib/capabilities')
|
|
128
|
+
const { Zip } = require('./io/zip')
|
|
136
129
|
|
|
137
130
|
/**
|
|
138
131
|
* Thrown when there an add-on is malformed.
|
|
@@ -141,13 +134,12 @@ const {Zip} = require('./io/zip');
|
|
|
141
134
|
class AddonFormatError extends Error {
|
|
142
135
|
/** @param {string} msg The error message. */
|
|
143
136
|
constructor(msg) {
|
|
144
|
-
super(msg)
|
|
137
|
+
super(msg)
|
|
145
138
|
/** @override */
|
|
146
|
-
this.name = this.constructor.name
|
|
139
|
+
this.name = this.constructor.name
|
|
147
140
|
}
|
|
148
141
|
}
|
|
149
142
|
|
|
150
|
-
|
|
151
143
|
/**
|
|
152
144
|
* Installs an extension to the given directory.
|
|
153
145
|
* @param {string} extension Path to the xpi extension file to install.
|
|
@@ -156,56 +148,53 @@ class AddonFormatError extends Error {
|
|
|
156
148
|
* installed.
|
|
157
149
|
*/
|
|
158
150
|
async function installExtension(extension, dir) {
|
|
159
|
-
|
|
160
|
-
|
|
151
|
+
const ext = extension.slice(-4)
|
|
152
|
+
if (ext !== '.xpi' && ext !== '.zip') {
|
|
153
|
+
throw Error('File name does not end in ".zip" or ".xpi": ' + ext)
|
|
161
154
|
}
|
|
162
155
|
|
|
163
|
-
let archive = await zip.load(extension)
|
|
156
|
+
let archive = await zip.load(extension)
|
|
164
157
|
if (!archive.has('manifest.json')) {
|
|
165
|
-
throw new AddonFormatError(`Couldn't find manifest.json in ${extension}`)
|
|
158
|
+
throw new AddonFormatError(`Couldn't find manifest.json in ${extension}`)
|
|
166
159
|
}
|
|
167
160
|
|
|
168
|
-
let buf = await archive.getFile('manifest.json')
|
|
169
|
-
let parsedJSON = JSON.parse(buf.toString('utf8'))
|
|
170
|
-
|
|
161
|
+
let buf = await archive.getFile('manifest.json')
|
|
162
|
+
let parsedJSON = JSON.parse(buf.toString('utf8'))
|
|
163
|
+
|
|
171
164
|
let { browser_specific_settings } =
|
|
172
165
|
/** @type {{browser_specific_settings:{gecko:{id:string}}}} */
|
|
173
|
-
parsedJSON
|
|
166
|
+
parsedJSON
|
|
174
167
|
|
|
175
|
-
if (
|
|
176
|
-
browser_specific_settings &&
|
|
177
|
-
browser_specific_settings.gecko
|
|
178
|
-
) {
|
|
168
|
+
if (browser_specific_settings && browser_specific_settings.gecko) {
|
|
179
169
|
/* browser_specific_settings is an alternative to applications
|
|
180
170
|
* It is meant to facilitate cross-browser plugins since Firefox48
|
|
181
171
|
* see https://bugzilla.mozilla.org/show_bug.cgi?id=1262005
|
|
182
172
|
*/
|
|
183
|
-
parsedJSON.applications = browser_specific_settings
|
|
173
|
+
parsedJSON.applications = browser_specific_settings
|
|
184
174
|
}
|
|
185
175
|
|
|
186
176
|
let { applications } =
|
|
187
177
|
/** @type {{applications:{gecko:{id:string}}}} */
|
|
188
|
-
parsedJSON
|
|
178
|
+
parsedJSON
|
|
189
179
|
if (!(applications && applications.gecko && applications.gecko.id)) {
|
|
190
|
-
throw new AddonFormatError(`Could not find add-on ID for ${extension}`)
|
|
180
|
+
throw new AddonFormatError(`Could not find add-on ID for ${extension}`)
|
|
191
181
|
}
|
|
192
182
|
|
|
193
|
-
await io.copy(extension, `${path.join(dir, applications.gecko.id)}.xpi`)
|
|
194
|
-
return applications.gecko.id
|
|
183
|
+
await io.copy(extension, `${path.join(dir, applications.gecko.id)}.xpi`)
|
|
184
|
+
return applications.gecko.id
|
|
195
185
|
}
|
|
196
186
|
|
|
197
|
-
|
|
198
187
|
class Profile {
|
|
199
188
|
constructor() {
|
|
200
189
|
/** @private {?string} */
|
|
201
|
-
this.template_ = null
|
|
190
|
+
this.template_ = null
|
|
202
191
|
|
|
203
192
|
/** @private {!Array<string>} */
|
|
204
|
-
this.extensions_ = []
|
|
193
|
+
this.extensions_ = []
|
|
205
194
|
}
|
|
206
195
|
|
|
207
|
-
addExtensions(/** !Array<string> */paths) {
|
|
208
|
-
this.extensions_ = this.extensions_.concat(...paths)
|
|
196
|
+
addExtensions(/** !Array<string> */ paths) {
|
|
197
|
+
this.extensions_ = this.extensions_.concat(...paths)
|
|
209
198
|
}
|
|
210
199
|
|
|
211
200
|
/**
|
|
@@ -214,13 +203,12 @@ class Profile {
|
|
|
214
203
|
*/
|
|
215
204
|
[Symbols.serialize]() {
|
|
216
205
|
if (this.template_ || this.extensions_.length) {
|
|
217
|
-
return buildProfile(this.template_, this.extensions_)
|
|
206
|
+
return buildProfile(this.template_, this.extensions_)
|
|
218
207
|
}
|
|
219
|
-
return undefined
|
|
208
|
+
return undefined
|
|
220
209
|
}
|
|
221
210
|
}
|
|
222
211
|
|
|
223
|
-
|
|
224
212
|
/**
|
|
225
213
|
* @param {?string} template path to an existing profile to use as a template.
|
|
226
214
|
* @param {!Array<string>} extensions paths to extensions to install in the new
|
|
@@ -228,31 +216,33 @@ class Profile {
|
|
|
228
216
|
* @return {!Promise<string>} a promise for the base64 encoded profile.
|
|
229
217
|
*/
|
|
230
218
|
async function buildProfile(template, extensions) {
|
|
231
|
-
let dir = template
|
|
219
|
+
let dir = template
|
|
232
220
|
|
|
233
221
|
if (extensions.length) {
|
|
234
|
-
dir = await io.tmpDir()
|
|
222
|
+
dir = await io.tmpDir()
|
|
235
223
|
if (template) {
|
|
236
224
|
await io.copyDir(
|
|
237
|
-
|
|
238
|
-
|
|
225
|
+
/** @type {string} */ (template),
|
|
226
|
+
dir,
|
|
227
|
+
/(parent\.lock|lock|\.parentlock)/
|
|
228
|
+
)
|
|
239
229
|
}
|
|
240
230
|
|
|
241
|
-
const extensionsDir = path.join(dir, 'extensions')
|
|
242
|
-
await io.mkdir(extensionsDir)
|
|
231
|
+
const extensionsDir = path.join(dir, 'extensions')
|
|
232
|
+
await io.mkdir(extensionsDir)
|
|
243
233
|
|
|
244
234
|
for (let i = 0; i < extensions.length; i++) {
|
|
245
|
-
await installExtension(extensions[i], extensionsDir)
|
|
235
|
+
await installExtension(extensions[i], extensionsDir)
|
|
246
236
|
}
|
|
247
237
|
}
|
|
248
238
|
|
|
249
|
-
let zip = new Zip
|
|
250
|
-
return zip
|
|
251
|
-
|
|
252
|
-
|
|
239
|
+
let zip = new Zip()
|
|
240
|
+
return zip
|
|
241
|
+
.addDir(dir)
|
|
242
|
+
.then(() => zip.toBuffer())
|
|
243
|
+
.then((buf) => buf.toString('base64'))
|
|
253
244
|
}
|
|
254
245
|
|
|
255
|
-
|
|
256
246
|
/**
|
|
257
247
|
* Configuration options for the FirefoxDriver.
|
|
258
248
|
*/
|
|
@@ -262,8 +252,8 @@ class Options extends Capabilities {
|
|
|
262
252
|
* capabilities to initialize this instance from.
|
|
263
253
|
*/
|
|
264
254
|
constructor(other) {
|
|
265
|
-
super(other)
|
|
266
|
-
this.setBrowserName(Browser.FIREFOX)
|
|
255
|
+
super(other)
|
|
256
|
+
this.setBrowserName(Browser.FIREFOX)
|
|
267
257
|
}
|
|
268
258
|
|
|
269
259
|
/**
|
|
@@ -271,12 +261,12 @@ class Options extends Capabilities {
|
|
|
271
261
|
* @private
|
|
272
262
|
*/
|
|
273
263
|
firefoxOptions_() {
|
|
274
|
-
let options = this.get('moz:firefoxOptions')
|
|
264
|
+
let options = this.get('moz:firefoxOptions')
|
|
275
265
|
if (!options) {
|
|
276
|
-
options = {}
|
|
277
|
-
this.set('moz:firefoxOptions', options)
|
|
266
|
+
options = {}
|
|
267
|
+
this.set('moz:firefoxOptions', options)
|
|
278
268
|
}
|
|
279
|
-
return options
|
|
269
|
+
return options
|
|
280
270
|
}
|
|
281
271
|
|
|
282
272
|
/**
|
|
@@ -284,11 +274,11 @@ class Options extends Capabilities {
|
|
|
284
274
|
* @private
|
|
285
275
|
*/
|
|
286
276
|
profile_() {
|
|
287
|
-
let options = this.firefoxOptions_()
|
|
277
|
+
let options = this.firefoxOptions_()
|
|
288
278
|
if (!options.profile) {
|
|
289
|
-
options.profile = new Profile()
|
|
279
|
+
options.profile = new Profile()
|
|
290
280
|
}
|
|
291
|
-
return options.profile
|
|
281
|
+
return options.profile
|
|
292
282
|
}
|
|
293
283
|
|
|
294
284
|
/**
|
|
@@ -300,10 +290,10 @@ class Options extends Capabilities {
|
|
|
300
290
|
*/
|
|
301
291
|
addArguments(...args) {
|
|
302
292
|
if (args.length) {
|
|
303
|
-
let options = this.firefoxOptions_()
|
|
304
|
-
options.args = options.args ? options.args.concat(...args) : args
|
|
293
|
+
let options = this.firefoxOptions_()
|
|
294
|
+
options.args = options.args ? options.args.concat(...args) : args
|
|
305
295
|
}
|
|
306
|
-
return this
|
|
296
|
+
return this
|
|
307
297
|
}
|
|
308
298
|
|
|
309
299
|
/**
|
|
@@ -312,7 +302,7 @@ class Options extends Capabilities {
|
|
|
312
302
|
* @return {!Options} A self reference.
|
|
313
303
|
*/
|
|
314
304
|
headless() {
|
|
315
|
-
return this.addArguments('-headless')
|
|
305
|
+
return this.addArguments('-headless')
|
|
316
306
|
}
|
|
317
307
|
|
|
318
308
|
/**
|
|
@@ -324,15 +314,15 @@ class Options extends Capabilities {
|
|
|
324
314
|
* @throws {TypeError} if width or height is unspecified, not a number, or
|
|
325
315
|
* less than or equal to 0.
|
|
326
316
|
*/
|
|
327
|
-
windowSize({width, height}) {
|
|
317
|
+
windowSize({ width, height }) {
|
|
328
318
|
function checkArg(arg) {
|
|
329
319
|
if (typeof arg !== 'number' || arg <= 0) {
|
|
330
|
-
throw TypeError('Arguments must be {width, height} with numbers > 0')
|
|
320
|
+
throw TypeError('Arguments must be {width, height} with numbers > 0')
|
|
331
321
|
}
|
|
332
322
|
}
|
|
333
|
-
checkArg(width)
|
|
334
|
-
checkArg(height)
|
|
335
|
-
return this.addArguments(`--width=${width}`, `--height=${height}`)
|
|
323
|
+
checkArg(width)
|
|
324
|
+
checkArg(height)
|
|
325
|
+
return this.addArguments(`--width=${width}`, `--height=${height}`)
|
|
336
326
|
}
|
|
337
327
|
|
|
338
328
|
/**
|
|
@@ -342,8 +332,8 @@ class Options extends Capabilities {
|
|
|
342
332
|
* @return {!Options} A self reference.
|
|
343
333
|
*/
|
|
344
334
|
addExtensions(...paths) {
|
|
345
|
-
this.profile_().addExtensions(paths)
|
|
346
|
-
return this
|
|
335
|
+
this.profile_().addExtensions(paths)
|
|
336
|
+
return this
|
|
347
337
|
}
|
|
348
338
|
|
|
349
339
|
/**
|
|
@@ -354,18 +344,21 @@ class Options extends Capabilities {
|
|
|
354
344
|
*/
|
|
355
345
|
setPreference(key, value) {
|
|
356
346
|
if (typeof key !== 'string') {
|
|
357
|
-
throw TypeError(`key must be a string, but got ${typeof key}`)
|
|
347
|
+
throw TypeError(`key must be a string, but got ${typeof key}`)
|
|
358
348
|
}
|
|
359
|
-
if (
|
|
360
|
-
|
|
361
|
-
|
|
349
|
+
if (
|
|
350
|
+
typeof value !== 'string' &&
|
|
351
|
+
typeof value !== 'number' &&
|
|
352
|
+
typeof value !== 'boolean'
|
|
353
|
+
) {
|
|
362
354
|
throw TypeError(
|
|
363
|
-
|
|
355
|
+
`value must be a string, number, or boolean, but got ${typeof value}`
|
|
356
|
+
)
|
|
364
357
|
}
|
|
365
|
-
let options = this.firefoxOptions_()
|
|
366
|
-
options.prefs = options.prefs || {}
|
|
367
|
-
options.prefs[key] = value
|
|
368
|
-
return this
|
|
358
|
+
let options = this.firefoxOptions_()
|
|
359
|
+
options.prefs = options.prefs || {}
|
|
360
|
+
options.prefs[key] = value
|
|
361
|
+
return this
|
|
369
362
|
}
|
|
370
363
|
|
|
371
364
|
/**
|
|
@@ -379,10 +372,10 @@ class Options extends Capabilities {
|
|
|
379
372
|
*/
|
|
380
373
|
setProfile(profile) {
|
|
381
374
|
if (typeof profile !== 'string') {
|
|
382
|
-
throw TypeError(`profile must be a string, but got ${typeof profile}`)
|
|
375
|
+
throw TypeError(`profile must be a string, but got ${typeof profile}`)
|
|
383
376
|
}
|
|
384
|
-
this.profile_().template_ = profile
|
|
385
|
-
return this
|
|
377
|
+
this.profile_().template_ = profile
|
|
378
|
+
return this
|
|
386
379
|
}
|
|
387
380
|
|
|
388
381
|
/**
|
|
@@ -395,14 +388,13 @@ class Options extends Capabilities {
|
|
|
395
388
|
*/
|
|
396
389
|
setBinary(binary) {
|
|
397
390
|
if (binary instanceof Channel || typeof binary === 'string') {
|
|
398
|
-
this.firefoxOptions_().binary = binary
|
|
399
|
-
return this
|
|
391
|
+
this.firefoxOptions_().binary = binary
|
|
392
|
+
return this
|
|
400
393
|
}
|
|
401
|
-
throw TypeError('binary must be a string path or Channel object')
|
|
394
|
+
throw TypeError('binary must be a string path or Channel object')
|
|
402
395
|
}
|
|
403
396
|
}
|
|
404
397
|
|
|
405
|
-
|
|
406
398
|
/**
|
|
407
399
|
* Enum of available command contexts.
|
|
408
400
|
*
|
|
@@ -414,14 +406,12 @@ class Options extends Capabilities {
|
|
|
414
406
|
* @enum {string}
|
|
415
407
|
*/
|
|
416
408
|
const Context = {
|
|
417
|
-
CONTENT:
|
|
418
|
-
CHROME:
|
|
419
|
-
}
|
|
420
|
-
|
|
409
|
+
CONTENT: 'content',
|
|
410
|
+
CHROME: 'chrome',
|
|
411
|
+
}
|
|
421
412
|
|
|
422
413
|
const GECKO_DRIVER_EXE =
|
|
423
|
-
|
|
424
|
-
|
|
414
|
+
process.platform === 'win32' ? 'geckodriver.exe' : 'geckodriver'
|
|
425
415
|
|
|
426
416
|
/**
|
|
427
417
|
* _Synchronously_ attempts to locate the geckodriver executable on the current
|
|
@@ -430,27 +420,28 @@ const GECKO_DRIVER_EXE =
|
|
|
430
420
|
* @return {?string} the located executable, or `null`.
|
|
431
421
|
*/
|
|
432
422
|
function locateSynchronously() {
|
|
433
|
-
return io.findInPath(GECKO_DRIVER_EXE, true)
|
|
423
|
+
return io.findInPath(GECKO_DRIVER_EXE, true)
|
|
434
424
|
}
|
|
435
425
|
|
|
436
|
-
|
|
437
426
|
/**
|
|
438
427
|
* @return {string} .
|
|
439
428
|
* @throws {Error}
|
|
440
429
|
*/
|
|
441
430
|
function findGeckoDriver() {
|
|
442
|
-
let exe = locateSynchronously()
|
|
431
|
+
let exe = locateSynchronously()
|
|
443
432
|
if (!exe) {
|
|
444
433
|
throw Error(
|
|
445
|
-
'The ' +
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
434
|
+
'The ' +
|
|
435
|
+
GECKO_DRIVER_EXE +
|
|
436
|
+
' executable could not be found on the current ' +
|
|
437
|
+
'PATH. Please download the latest version from ' +
|
|
438
|
+
'https://github.com/mozilla/geckodriver/releases/ ' +
|
|
439
|
+
'and ensure it can be found on your PATH.'
|
|
440
|
+
)
|
|
449
441
|
}
|
|
450
|
-
return exe
|
|
442
|
+
return exe
|
|
451
443
|
}
|
|
452
444
|
|
|
453
|
-
|
|
454
445
|
/**
|
|
455
446
|
* @param {string} file Path to the file to find, relative to the program files
|
|
456
447
|
* root.
|
|
@@ -460,24 +451,24 @@ function findGeckoDriver() {
|
|
|
460
451
|
function findInProgramFiles(file) {
|
|
461
452
|
let files = [
|
|
462
453
|
process.env['PROGRAMFILES'] || 'C:\\Program Files',
|
|
463
|
-
process.env['PROGRAMFILES(X86)'] || 'C:\\Program Files (x86)'
|
|
464
|
-
].map(prefix => path.join(prefix, file))
|
|
465
|
-
return io.exists(files[0]).then(function(exists) {
|
|
466
|
-
return exists
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
454
|
+
process.env['PROGRAMFILES(X86)'] || 'C:\\Program Files (x86)',
|
|
455
|
+
].map((prefix) => path.join(prefix, file))
|
|
456
|
+
return io.exists(files[0]).then(function (exists) {
|
|
457
|
+
return exists
|
|
458
|
+
? files[0]
|
|
459
|
+
: io.exists(files[1]).then(function (exists) {
|
|
460
|
+
return exists ? files[1] : null
|
|
461
|
+
})
|
|
462
|
+
})
|
|
470
463
|
}
|
|
471
464
|
|
|
472
|
-
|
|
473
465
|
/** @enum {string} */
|
|
474
466
|
const ExtensionCommand = {
|
|
475
467
|
GET_CONTEXT: 'getContext',
|
|
476
468
|
SET_CONTEXT: 'setContext',
|
|
477
469
|
INSTALL_ADDON: 'install addon',
|
|
478
470
|
UNINSTALL_ADDON: 'uninstall addon',
|
|
479
|
-
}
|
|
480
|
-
|
|
471
|
+
}
|
|
481
472
|
|
|
482
473
|
/**
|
|
483
474
|
* Creates a command executor with support for Marionette's custom commands.
|
|
@@ -485,40 +476,42 @@ const ExtensionCommand = {
|
|
|
485
476
|
* @return {!command.Executor} The new command executor.
|
|
486
477
|
*/
|
|
487
478
|
function createExecutor(serverUrl) {
|
|
488
|
-
let client = serverUrl.then(url => new http.HttpClient(url))
|
|
489
|
-
let executor = new http.Executor(client)
|
|
490
|
-
configureExecutor(executor)
|
|
491
|
-
return executor
|
|
479
|
+
let client = serverUrl.then((url) => new http.HttpClient(url))
|
|
480
|
+
let executor = new http.Executor(client)
|
|
481
|
+
configureExecutor(executor)
|
|
482
|
+
return executor
|
|
492
483
|
}
|
|
493
484
|
|
|
494
|
-
|
|
495
485
|
/**
|
|
496
486
|
* Configures the given executor with Firefox-specific commands.
|
|
497
487
|
* @param {!http.Executor} executor the executor to configure.
|
|
498
488
|
*/
|
|
499
489
|
function configureExecutor(executor) {
|
|
500
490
|
executor.defineCommand(
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
491
|
+
ExtensionCommand.GET_CONTEXT,
|
|
492
|
+
'GET',
|
|
493
|
+
'/session/:sessionId/moz/context'
|
|
494
|
+
)
|
|
504
495
|
|
|
505
496
|
executor.defineCommand(
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
497
|
+
ExtensionCommand.SET_CONTEXT,
|
|
498
|
+
'POST',
|
|
499
|
+
'/session/:sessionId/moz/context'
|
|
500
|
+
)
|
|
509
501
|
|
|
510
502
|
executor.defineCommand(
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
503
|
+
ExtensionCommand.INSTALL_ADDON,
|
|
504
|
+
'POST',
|
|
505
|
+
'/session/:sessionId/moz/addon/install'
|
|
506
|
+
)
|
|
514
507
|
|
|
515
508
|
executor.defineCommand(
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
509
|
+
ExtensionCommand.UNINSTALL_ADDON,
|
|
510
|
+
'POST',
|
|
511
|
+
'/session/:sessionId/moz/addon/uninstall'
|
|
512
|
+
)
|
|
519
513
|
}
|
|
520
514
|
|
|
521
|
-
|
|
522
515
|
/**
|
|
523
516
|
* Creates {@link selenium-webdriver/remote.DriverService} instances that manage
|
|
524
517
|
* a [geckodriver](https://github.com/mozilla/geckodriver) server in a child
|
|
@@ -530,8 +523,8 @@ class ServiceBuilder extends remote.DriverService.Builder {
|
|
|
530
523
|
* the builder will attempt to locate the geckodriver on the system PATH.
|
|
531
524
|
*/
|
|
532
525
|
constructor(opt_exe) {
|
|
533
|
-
super(opt_exe || findGeckoDriver())
|
|
534
|
-
this.setLoopback(true)
|
|
526
|
+
super(opt_exe || findGeckoDriver())
|
|
527
|
+
this.setLoopback(true) // Required.
|
|
535
528
|
}
|
|
536
529
|
|
|
537
530
|
/**
|
|
@@ -542,11 +535,10 @@ class ServiceBuilder extends remote.DriverService.Builder {
|
|
|
542
535
|
* @return {!ServiceBuilder} A self reference.
|
|
543
536
|
*/
|
|
544
537
|
enableVerboseLogging(opt_trace) {
|
|
545
|
-
return this.addArguments(opt_trace ? '-vv' : '-v')
|
|
538
|
+
return this.addArguments(opt_trace ? '-vv' : '-v')
|
|
546
539
|
}
|
|
547
540
|
}
|
|
548
541
|
|
|
549
|
-
|
|
550
542
|
/**
|
|
551
543
|
* A WebDriver client for Firefox.
|
|
552
544
|
*/
|
|
@@ -575,25 +567,24 @@ class Driver extends webdriver.WebDriver {
|
|
|
575
567
|
*/
|
|
576
568
|
static createSession(opt_config, opt_executor) {
|
|
577
569
|
let caps =
|
|
578
|
-
|
|
579
|
-
? opt_config : new Options(opt_config);
|
|
570
|
+
opt_config instanceof Capabilities ? opt_config : new Options(opt_config)
|
|
580
571
|
|
|
581
|
-
let executor
|
|
582
|
-
let onQuit
|
|
572
|
+
let executor
|
|
573
|
+
let onQuit
|
|
583
574
|
|
|
584
575
|
if (opt_executor instanceof http.Executor) {
|
|
585
|
-
executor = opt_executor
|
|
586
|
-
configureExecutor(executor)
|
|
576
|
+
executor = opt_executor
|
|
577
|
+
configureExecutor(executor)
|
|
587
578
|
} else if (opt_executor instanceof remote.DriverService) {
|
|
588
|
-
executor = createExecutor(opt_executor.start())
|
|
589
|
-
onQuit = () => opt_executor.kill()
|
|
579
|
+
executor = createExecutor(opt_executor.start())
|
|
580
|
+
onQuit = () => opt_executor.kill()
|
|
590
581
|
} else {
|
|
591
|
-
let service = new ServiceBuilder().build()
|
|
592
|
-
executor = createExecutor(service.start())
|
|
593
|
-
onQuit = () => service.kill()
|
|
582
|
+
let service = new ServiceBuilder().build()
|
|
583
|
+
executor = createExecutor(service.start())
|
|
584
|
+
onQuit = () => service.kill()
|
|
594
585
|
}
|
|
595
586
|
|
|
596
|
-
return /** @type {!Driver} */(super.createSession(executor, caps, onQuit))
|
|
587
|
+
return /** @type {!Driver} */ (super.createSession(executor, caps, onQuit))
|
|
597
588
|
}
|
|
598
589
|
|
|
599
590
|
/**
|
|
@@ -601,8 +592,7 @@ class Driver extends webdriver.WebDriver {
|
|
|
601
592
|
* implementation.
|
|
602
593
|
* @override
|
|
603
594
|
*/
|
|
604
|
-
setFileDetector() {
|
|
605
|
-
}
|
|
595
|
+
setFileDetector() {}
|
|
606
596
|
|
|
607
597
|
/**
|
|
608
598
|
* Get the context that is currently in effect.
|
|
@@ -610,7 +600,7 @@ class Driver extends webdriver.WebDriver {
|
|
|
610
600
|
* @return {!Promise<Context>} Current context.
|
|
611
601
|
*/
|
|
612
602
|
getContext() {
|
|
613
|
-
return this.execute(new command.Command(ExtensionCommand.GET_CONTEXT))
|
|
603
|
+
return this.execute(new command.Command(ExtensionCommand.GET_CONTEXT))
|
|
614
604
|
}
|
|
615
605
|
|
|
616
606
|
/**
|
|
@@ -629,8 +619,11 @@ class Driver extends webdriver.WebDriver {
|
|
|
629
619
|
*/
|
|
630
620
|
setContext(ctx) {
|
|
631
621
|
return this.execute(
|
|
632
|
-
|
|
633
|
-
|
|
622
|
+
new command.Command(ExtensionCommand.SET_CONTEXT).setParameter(
|
|
623
|
+
'context',
|
|
624
|
+
ctx
|
|
625
|
+
)
|
|
626
|
+
)
|
|
634
627
|
}
|
|
635
628
|
|
|
636
629
|
/**
|
|
@@ -641,18 +634,19 @@ class Driver extends webdriver.WebDriver {
|
|
|
641
634
|
*
|
|
642
635
|
* @param {string} path Path on the local filesystem to the web extension to
|
|
643
636
|
* install.
|
|
644
|
-
* @param {boolean} temporary Flag indicating whether the extension should be
|
|
637
|
+
* @param {boolean} temporary Flag indicating whether the extension should be
|
|
645
638
|
* installed temporarily - gets removed on restart
|
|
646
639
|
* @return {!Promise<string>} A promise that will resolve to an ID for the
|
|
647
640
|
* newly installed addon.
|
|
648
641
|
* @see #uninstallAddon
|
|
649
642
|
*/
|
|
650
|
-
async installAddon(path, temporary=false) {
|
|
651
|
-
let buf = await io.read(path)
|
|
643
|
+
async installAddon(path, temporary = false) {
|
|
644
|
+
let buf = await io.read(path)
|
|
652
645
|
return this.execute(
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
646
|
+
new command.Command(ExtensionCommand.INSTALL_ADDON)
|
|
647
|
+
.setParameter('addon', buf.toString('base64'))
|
|
648
|
+
.setParameter('temporary', temporary)
|
|
649
|
+
)
|
|
656
650
|
}
|
|
657
651
|
|
|
658
652
|
/**
|
|
@@ -664,14 +658,16 @@ class Driver extends webdriver.WebDriver {
|
|
|
664
658
|
* @see #installAddon
|
|
665
659
|
*/
|
|
666
660
|
async uninstallAddon(id) {
|
|
667
|
-
id = await Promise.resolve(id)
|
|
661
|
+
id = await Promise.resolve(id)
|
|
668
662
|
return this.execute(
|
|
669
|
-
|
|
670
|
-
|
|
663
|
+
new command.Command(ExtensionCommand.UNINSTALL_ADDON).setParameter(
|
|
664
|
+
'id',
|
|
665
|
+
id
|
|
666
|
+
)
|
|
667
|
+
)
|
|
671
668
|
}
|
|
672
669
|
}
|
|
673
670
|
|
|
674
|
-
|
|
675
671
|
/**
|
|
676
672
|
* Provides methods for locating the executable for a Firefox release channel
|
|
677
673
|
* on Windows and MacOS. For other systems (i.e. Linux), Firefox will always
|
|
@@ -685,10 +681,10 @@ class Channel {
|
|
|
685
681
|
* @param {string} win32 The path to check when running on Windows.
|
|
686
682
|
*/
|
|
687
683
|
constructor(darwin, win32) {
|
|
688
|
-
/** @private @const */ this.darwin_ = darwin
|
|
689
|
-
/** @private @const */ this.win32_ = win32
|
|
684
|
+
/** @private @const */ this.darwin_ = darwin
|
|
685
|
+
/** @private @const */ this.win32_ = win32
|
|
690
686
|
/** @private {Promise<string>} */
|
|
691
|
-
this.found_ = null
|
|
687
|
+
this.found_ = null
|
|
692
688
|
}
|
|
693
689
|
|
|
694
690
|
/**
|
|
@@ -702,43 +698,44 @@ class Channel {
|
|
|
702
698
|
*/
|
|
703
699
|
locate() {
|
|
704
700
|
if (this.found_) {
|
|
705
|
-
return this.found_
|
|
701
|
+
return this.found_
|
|
706
702
|
}
|
|
707
703
|
|
|
708
|
-
let found
|
|
704
|
+
let found
|
|
709
705
|
switch (process.platform) {
|
|
710
706
|
case 'darwin':
|
|
711
|
-
found = io
|
|
712
|
-
|
|
713
|
-
|
|
707
|
+
found = io
|
|
708
|
+
.exists(this.darwin_)
|
|
709
|
+
.then((exists) => (exists ? this.darwin_ : io.findInPath('firefox')))
|
|
710
|
+
break
|
|
714
711
|
|
|
715
712
|
case 'win32':
|
|
716
|
-
found = findInProgramFiles(this.win32_)
|
|
717
|
-
|
|
718
|
-
|
|
713
|
+
found = findInProgramFiles(this.win32_).then(
|
|
714
|
+
(found) => found || io.findInPath('firefox.exe')
|
|
715
|
+
)
|
|
716
|
+
break
|
|
719
717
|
|
|
720
718
|
default:
|
|
721
|
-
found = Promise.resolve(io.findInPath('firefox'))
|
|
722
|
-
break
|
|
719
|
+
found = Promise.resolve(io.findInPath('firefox'))
|
|
720
|
+
break
|
|
723
721
|
}
|
|
724
722
|
|
|
725
|
-
this.found_ = found.then(found => {
|
|
723
|
+
this.found_ = found.then((found) => {
|
|
726
724
|
if (found) {
|
|
727
725
|
// TODO: verify version info.
|
|
728
|
-
return found
|
|
726
|
+
return found
|
|
729
727
|
}
|
|
730
|
-
throw Error('Could not locate Firefox on the current system')
|
|
731
|
-
})
|
|
732
|
-
return this.found_
|
|
728
|
+
throw Error('Could not locate Firefox on the current system')
|
|
729
|
+
})
|
|
730
|
+
return this.found_
|
|
733
731
|
}
|
|
734
732
|
|
|
735
733
|
/** @return {!Promise<string>} */
|
|
736
734
|
[Symbols.serialize]() {
|
|
737
|
-
return this.locate()
|
|
735
|
+
return this.locate()
|
|
738
736
|
}
|
|
739
737
|
}
|
|
740
738
|
|
|
741
|
-
|
|
742
739
|
/**
|
|
743
740
|
* Firefox's developer channel.
|
|
744
741
|
* @const
|
|
@@ -746,7 +743,8 @@ class Channel {
|
|
|
746
743
|
*/
|
|
747
744
|
Channel.AURORA = new Channel(
|
|
748
745
|
'/Applications/FirefoxDeveloperEdition.app/Contents/MacOS/firefox-bin',
|
|
749
|
-
'Firefox Developer Edition\\firefox.exe'
|
|
746
|
+
'Firefox Developer Edition\\firefox.exe'
|
|
747
|
+
)
|
|
750
748
|
|
|
751
749
|
/**
|
|
752
750
|
* Firefox's beta channel. Note this is provided mainly for convenience as
|
|
@@ -757,7 +755,8 @@ Channel.AURORA = new Channel(
|
|
|
757
755
|
*/
|
|
758
756
|
Channel.BETA = new Channel(
|
|
759
757
|
'/Applications/Firefox.app/Contents/MacOS/firefox-bin',
|
|
760
|
-
'Mozilla Firefox\\firefox.exe'
|
|
758
|
+
'Mozilla Firefox\\firefox.exe'
|
|
759
|
+
)
|
|
761
760
|
|
|
762
761
|
/**
|
|
763
762
|
* Firefox's release channel.
|
|
@@ -766,7 +765,8 @@ Channel.BETA = new Channel(
|
|
|
766
765
|
*/
|
|
767
766
|
Channel.RELEASE = new Channel(
|
|
768
767
|
'/Applications/Firefox.app/Contents/MacOS/firefox-bin',
|
|
769
|
-
'Mozilla Firefox\\firefox.exe'
|
|
768
|
+
'Mozilla Firefox\\firefox.exe'
|
|
769
|
+
)
|
|
770
770
|
|
|
771
771
|
/**
|
|
772
772
|
* Firefox's nightly release channel.
|
|
@@ -775,15 +775,14 @@ Channel.RELEASE = new Channel(
|
|
|
775
775
|
*/
|
|
776
776
|
Channel.NIGHTLY = new Channel(
|
|
777
777
|
'/Applications/Firefox Nightly.app/Contents/MacOS/firefox-bin',
|
|
778
|
-
'Nightly\\firefox.exe'
|
|
779
|
-
|
|
778
|
+
'Nightly\\firefox.exe'
|
|
779
|
+
)
|
|
780
780
|
|
|
781
781
|
// PUBLIC API
|
|
782
782
|
|
|
783
|
-
|
|
784
|
-
exports.
|
|
785
|
-
exports.
|
|
786
|
-
exports.
|
|
787
|
-
exports.
|
|
788
|
-
exports.
|
|
789
|
-
exports.locateSynchronously = locateSynchronously;
|
|
783
|
+
exports.Channel = Channel
|
|
784
|
+
exports.Context = Context
|
|
785
|
+
exports.Driver = Driver
|
|
786
|
+
exports.Options = Options
|
|
787
|
+
exports.ServiceBuilder = ServiceBuilder
|
|
788
|
+
exports.locateSynchronously = locateSynchronously
|