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