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