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.
Files changed (52) hide show
  1. package/.eslintrc.js +27 -0
  2. package/.prettierrc +4 -0
  3. package/CHANGES.md +22 -1
  4. package/README.md +4 -4
  5. package/chrome.js +72 -590
  6. package/chromium.js +1024 -0
  7. package/devtools/CDPConnection.js +37 -0
  8. package/devtools/generator/protocol-dts-generator.js +429 -0
  9. package/edge.js +124 -86
  10. package/firefox.js +201 -202
  11. package/http/index.js +131 -115
  12. package/http/util.js +56 -64
  13. package/ie.js +180 -104
  14. package/index.js +246 -199
  15. package/io/exec.js +34 -39
  16. package/io/index.js +141 -151
  17. package/io/zip.js +75 -65
  18. package/lib/README +5 -0
  19. package/lib/actions.js +138 -121
  20. package/lib/atoms/find-elements.js +123 -0
  21. package/lib/atoms/get-attribute.js +79 -60
  22. package/lib/atoms/is-displayed.js +72 -73
  23. package/{example/chrome_mobile_emulation.js → lib/atoms/make-atoms-module.js} +16 -23
  24. package/lib/by.js +192 -65
  25. package/lib/capabilities.js +82 -78
  26. package/lib/command.js +18 -20
  27. package/lib/error.js +134 -168
  28. package/lib/http.js +324 -230
  29. package/lib/input.js +325 -302
  30. package/lib/logging.js +127 -138
  31. package/lib/promise.js +60 -66
  32. package/lib/proxy.js +31 -41
  33. package/lib/session.js +12 -15
  34. package/lib/symbols.js +3 -4
  35. package/lib/until.js +162 -162
  36. package/lib/webdriver.js +733 -522
  37. package/net/index.js +30 -36
  38. package/net/portprober.js +31 -144
  39. package/opera.js +406 -0
  40. package/package.json +18 -11
  41. package/proxy.js +2 -2
  42. package/remote/index.js +190 -187
  43. package/safari.js +40 -49
  44. package/LICENSE +0 -202
  45. package/NOTICE +0 -2
  46. package/example/chrome_android.js +0 -42
  47. package/example/firefox_channels.js +0 -77
  48. package/example/google_search.js +0 -50
  49. package/example/google_search_test.js +0 -72
  50. package/example/headless.js +0 -55
  51. package/example/logging.js +0 -68
  52. package/testing/index.js +0 -498
package/chromium.js ADDED
@@ -0,0 +1,1024 @@
1
+ // Licensed to the Software Freedom Conservancy (SFC) under one
2
+ // or more contributor license agreements. See the NOTICE file
3
+ // distributed with this work for additional information
4
+ // regarding copyright ownership. The SFC licenses this file
5
+ // to you under the Apache License, Version 2.0 (the
6
+ // "License"); you may not use this file except in compliance
7
+ // with the License. You may obtain a copy of the License at
8
+ //
9
+ // http://www.apache.org/licenses/LICENSE-2.0
10
+ //
11
+ // Unless required by applicable law or agreed to in writing,
12
+ // software distributed under the License is distributed on an
13
+ // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ // KIND, either express or implied. See the License for the
15
+ // specific language governing permissions and limitations
16
+ // under the License.
17
+
18
+ /**
19
+ * @fileoverview Defines an abstract {@linkplain Driver WebDriver} client for
20
+ * Chromium-based web browsers. These classes should not be instantiated
21
+ * directly.
22
+ *
23
+ * There are three primary classes exported by this module:
24
+ *
25
+ * 1. {@linkplain ServiceBuilder}: configures the
26
+ * {@link selenium-webdriver/remote.DriverService remote.DriverService}
27
+ * that manages a WebDriver server child process.
28
+ *
29
+ * 2. {@linkplain Options}: defines configuration options for each new Chromium
30
+ * session, such as which {@linkplain Options#setProxy proxy} to use,
31
+ * what {@linkplain Options#addExtensions extensions} to install, or
32
+ * what {@linkplain Options#addArguments command-line switches} to use when
33
+ * starting the browser.
34
+ *
35
+ * 3. {@linkplain Driver}: the WebDriver client; each new instance will control
36
+ * a unique browser session with a clean user profile (unless otherwise
37
+ * configured through the {@link Options} class).
38
+ *
39
+ * __Headless Chromium__ <a id="headless"></a>
40
+ *
41
+ * To start the browser in headless mode, simply call
42
+ * {@linkplain Options#headless Options.headless()}.
43
+ *
44
+ * let chrome = require('selenium-webdriver/chrome');
45
+ * let {Builder} = require('selenium-webdriver');
46
+ *
47
+ * let driver = new Builder()
48
+ * .forBrowser('chrome')
49
+ * .setChromeOptions(new chrome.Options().headless())
50
+ * .build();
51
+ *
52
+ * __Customizing the Chromium WebDriver Server__ <a id="custom-server"></a>
53
+ *
54
+ * Subclasses of {@link Driver} are expected to provide a static
55
+ * getDefaultService method. By default, this method will be called every time
56
+ * a {@link Driver} instance is created to obtain the default driver service
57
+ * for that specific browser (e.g. Chrome or Chromium Edge). Subclasses are
58
+ * responsible for managing the lifetime of the default service.
59
+ *
60
+ * You may also create a {@link Driver} with its own driver service. This is
61
+ * useful if you need to capture the server's log output for a specific session:
62
+ *
63
+ * let chrome = require('selenium-webdriver/chrome');
64
+ *
65
+ * let service = new chrome.ServiceBuilder()
66
+ * .loggingTo('/my/log/file.txt')
67
+ * .enableVerboseLogging()
68
+ * .build();
69
+ *
70
+ * let options = new chrome.Options();
71
+ * // configure browser options ...
72
+ *
73
+ * let driver = chrome.Driver.createSession(options, service);
74
+ */
75
+
76
+ 'use strict'
77
+
78
+ const http = require('./http')
79
+ const io = require('./io')
80
+ const { Capabilities, Capability } = require('./lib/capabilities')
81
+ const command = require('./lib/command')
82
+ const error = require('./lib/error')
83
+ const Symbols = require('./lib/symbols')
84
+ const webdriver = require('./lib/webdriver')
85
+ const WebSocket = require('ws')
86
+ const cdp = require('./devtools/CDPConnection')
87
+ const remote = require('./remote')
88
+ const cdpTargets = ['page', 'browser']
89
+ const fs = require('fs')
90
+
91
+ /**
92
+ * Custom command names supported by Chromium WebDriver.
93
+ * @enum {string}
94
+ */
95
+ const Command = {
96
+ LAUNCH_APP: 'launchApp',
97
+ GET_NETWORK_CONDITIONS: 'getNetworkConditions',
98
+ SET_NETWORK_CONDITIONS: 'setNetworkConditions',
99
+ SEND_DEVTOOLS_COMMAND: 'sendDevToolsCommand',
100
+ SEND_AND_GET_DEVTOOLS_COMMAND: 'sendAndGetDevToolsCommand',
101
+ SET_PERMISSION: 'setPermission',
102
+ GET_CAST_SINKS: 'getCastSinks',
103
+ SET_CAST_SINK_TO_USE: 'setCastSinkToUse',
104
+ START_CAST_TAB_MIRRORING: 'setCastTabMirroring',
105
+ GET_CAST_ISSUE_MESSAGE: 'getCastIssueMessage',
106
+ STOP_CASTING: 'stopCasting',
107
+ }
108
+
109
+ /**
110
+ * Creates a command executor with support for Chromium's custom commands.
111
+ * @param {!Promise<string>} url The server's URL.
112
+ * @return {!command.Executor} The new command executor.
113
+ */
114
+ function createExecutor(url, vendorPrefix) {
115
+ let agent = new http.Agent({ keepAlive: true })
116
+ let client = url.then((url) => new http.HttpClient(url, agent))
117
+ let executor = new http.Executor(client)
118
+ configureExecutor(executor, vendorPrefix)
119
+ return executor
120
+ }
121
+
122
+ /**
123
+ * Configures the given executor with Chromium-specific commands.
124
+ * @param {!http.Executor} executor the executor to configure.
125
+ */
126
+ function configureExecutor(executor, vendorPrefix) {
127
+ executor.defineCommand(
128
+ Command.LAUNCH_APP,
129
+ 'POST',
130
+ '/session/:sessionId/chromium/launch_app'
131
+ )
132
+ executor.defineCommand(
133
+ Command.GET_NETWORK_CONDITIONS,
134
+ 'GET',
135
+ '/session/:sessionId/chromium/network_conditions'
136
+ )
137
+ executor.defineCommand(
138
+ Command.SET_NETWORK_CONDITIONS,
139
+ 'POST',
140
+ '/session/:sessionId/chromium/network_conditions'
141
+ )
142
+ executor.defineCommand(
143
+ Command.SEND_DEVTOOLS_COMMAND,
144
+ 'POST',
145
+ '/session/:sessionId/chromium/send_command'
146
+ )
147
+ executor.defineCommand(
148
+ Command.SEND_AND_GET_DEVTOOLS_COMMAND,
149
+ 'POST',
150
+ '/session/:sessionId/chromium/send_command_and_get_result'
151
+ )
152
+ executor.defineCommand(
153
+ Command.SET_PERMISSION,
154
+ 'POST',
155
+ '/session/:sessionId/permissions'
156
+ )
157
+ executor.defineCommand(
158
+ Command.GET_CAST_SINKS,
159
+ 'GET',
160
+ `/session/:sessionId/${vendorPrefix}/cast/get_sinks`
161
+ )
162
+ executor.defineCommand(
163
+ Command.SET_CAST_SINK_TO_USE,
164
+ 'POST',
165
+ `/session/:sessionId/${vendorPrefix}/cast/set_sink_to_use`
166
+ )
167
+ executor.defineCommand(
168
+ Command.START_CAST_TAB_MIRRORING,
169
+ 'POST',
170
+ `/session/:sessionId/${vendorPrefix}/cast/start_tab_mirroring`
171
+ )
172
+ executor.defineCommand(
173
+ Command.GET_CAST_ISSUE_MESSAGE,
174
+ 'GET',
175
+ `/session/:sessionId/${vendorPrefix}/cast/get_issue_message`
176
+ )
177
+ executor.defineCommand(
178
+ Command.STOP_CASTING,
179
+ 'POST',
180
+ `/session/:sessionId/${vendorPrefix}/cast/stop_casting`
181
+ )
182
+ }
183
+
184
+ /**
185
+ * Creates {@link selenium-webdriver/remote.DriverService} instances that manage
186
+ * a WebDriver server in a child process.
187
+ */
188
+ class ServiceBuilder extends remote.DriverService.Builder {
189
+ /**
190
+ * @param {string=} exe Path to the server executable to use. Subclasses
191
+ * should ensure a valid path to the appropriate exe is provided.
192
+ */
193
+ constructor(exe) {
194
+ super(exe)
195
+ this.setLoopback(true) // Required
196
+ }
197
+
198
+ /**
199
+ * Sets which port adb is listening to. _The driver will connect to adb
200
+ * if an {@linkplain Options#androidPackage Android session} is requested, but
201
+ * adb **must** be started beforehand._
202
+ *
203
+ * @param {number} port Which port adb is running on.
204
+ * @return {!ServiceBuilder} A self reference.
205
+ */
206
+ setAdbPort(port) {
207
+ return this.addArguments('--adb-port=' + port)
208
+ }
209
+
210
+ /**
211
+ * Sets the path of the log file the driver should log to. If a log file is
212
+ * not specified, the driver will log to stderr.
213
+ * @param {string} path Path of the log file to use.
214
+ * @return {!ServiceBuilder} A self reference.
215
+ */
216
+ loggingTo(path) {
217
+ return this.addArguments('--log-path=' + path)
218
+ }
219
+
220
+ /**
221
+ * Enables verbose logging.
222
+ * @return {!ServiceBuilder} A self reference.
223
+ */
224
+ enableVerboseLogging() {
225
+ return this.addArguments('--verbose')
226
+ }
227
+
228
+ /**
229
+ * Sets the number of threads the driver should use to manage HTTP requests.
230
+ * By default, the driver will use 4 threads.
231
+ * @param {number} n The number of threads to use.
232
+ * @return {!ServiceBuilder} A self reference.
233
+ */
234
+ setNumHttpThreads(n) {
235
+ return this.addArguments('--http-threads=' + n)
236
+ }
237
+
238
+ /**
239
+ * @override
240
+ */
241
+ setPath(path) {
242
+ super.setPath(path)
243
+ return this.addArguments('--url-base=' + path)
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Class for managing WebDriver options specific to a Chromium-based browser.
249
+ */
250
+ class Options extends Capabilities {
251
+ /**
252
+ * @param {(Capabilities|Map<string, ?>|Object)=} other Another set of
253
+ * capabilities to initialize this instance from.
254
+ */
255
+ constructor(other = undefined) {
256
+ super(other)
257
+
258
+ /** @private {!Object} */
259
+ this.options_ = this.get(this.CAPABILITY_KEY) || {}
260
+
261
+ this.setBrowserName(this.BROWSER_NAME_VALUE)
262
+ this.set(this.CAPABILITY_KEY, this.options_)
263
+ }
264
+
265
+ /**
266
+ * Add additional command line arguments to use when launching the browser.
267
+ * Each argument may be specified with or without the "--" prefix
268
+ * (e.g. "--foo" and "foo"). Arguments with an associated value should be
269
+ * delimited by an "=": "foo=bar".
270
+ *
271
+ * @param {...(string|!Array<string>)} args The arguments to add.
272
+ * @return {!Options} A self reference.
273
+ */
274
+ addArguments(...args) {
275
+ let newArgs = (this.options_.args || []).concat(...args)
276
+ if (newArgs.length) {
277
+ this.options_.args = newArgs
278
+ }
279
+ return this
280
+ }
281
+
282
+ /**
283
+ * Configures the driver to start the browser in headless mode.
284
+ *
285
+ * > __NOTE:__ Resizing the browser window in headless mode is only supported
286
+ * > in Chromium 60+. Users are encouraged to set an initial window size with
287
+ * > the {@link #windowSize windowSize({width, height})} option.
288
+ *
289
+ * > __NOTE__: For security, Chromium disables downloads by default when
290
+ * > in headless mode (to prevent sites from silently downloading files to
291
+ * > your machine). After creating a session, you may call
292
+ * > {@link ./chrome.Driver#setDownloadPath setDownloadPath} to re-enable
293
+ * > downloads, saving files in the specified directory.
294
+ *
295
+ * @return {!Options} A self reference.
296
+ */
297
+ headless() {
298
+ return this.addArguments('headless')
299
+ }
300
+
301
+ /**
302
+ * Sets the initial window size.
303
+ *
304
+ * @param {{width: number, height: number}} size The desired window size.
305
+ * @return {!Options} A self reference.
306
+ * @throws {TypeError} if width or height is unspecified, not a number, or
307
+ * less than or equal to 0.
308
+ */
309
+ windowSize({ width, height }) {
310
+ function checkArg(arg) {
311
+ if (typeof arg !== 'number' || arg <= 0) {
312
+ throw TypeError('Arguments must be {width, height} with numbers > 0')
313
+ }
314
+ }
315
+ checkArg(width)
316
+ checkArg(height)
317
+ return this.addArguments(`window-size=${width},${height}`)
318
+ }
319
+
320
+ /**
321
+ * List of Chrome command line switches to exclude that ChromeDriver by default
322
+ * passes when starting Chrome. Do not prefix switches with "--".
323
+ *
324
+ * @param {...(string|!Array<string>)} args The switches to exclude.
325
+ * @return {!Options} A self reference.
326
+ */
327
+ excludeSwitches(...args) {
328
+ let switches = (this.options_.excludeSwitches || []).concat(...args)
329
+ if (switches.length) {
330
+ this.options_.excludeSwitches = switches
331
+ }
332
+ return this
333
+ }
334
+
335
+ /**
336
+ * Add additional extensions to install when launching the browser. Each extension
337
+ * should be specified as the path to the packed CRX file, or a Buffer for an
338
+ * extension.
339
+ * @param {...(string|!Buffer|!Array<(string|!Buffer)>)} args The
340
+ * extensions to add.
341
+ * @return {!Options} A self reference.
342
+ */
343
+ addExtensions(...args) {
344
+ let current = this.options_.extensions || []
345
+ this.options_.extensions = current.concat(...args)
346
+ return this
347
+ }
348
+
349
+ /**
350
+ * Sets the path to the browser binary to use. On Mac OS X, this path should
351
+ * reference the actual Chromium executable, not just the application binary
352
+ * (e.g. "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome").
353
+ *
354
+ * The binary path can be absolute or relative to the WebDriver server
355
+ * executable, but it must exist on the machine that will launch the browser.
356
+ *
357
+ * @param {string} path The path to the browser binary to use.
358
+ * @return {!Options} A self reference.
359
+ */
360
+ setBinaryPath(path) {
361
+ this.options_.binary = path
362
+ return this
363
+ }
364
+
365
+ /**
366
+ * Sets whether to leave the started browser process running if the controlling
367
+ * driver service is killed before {@link webdriver.WebDriver#quit()} is
368
+ * called.
369
+ * @param {boolean} detach Whether to leave the browser running if the
370
+ * driver service is killed before the session.
371
+ * @return {!Options} A self reference.
372
+ */
373
+ detachDriver(detach) {
374
+ this.options_.detach = detach
375
+ return this
376
+ }
377
+
378
+ /**
379
+ * Sets the user preferences for Chrome's user profile. See the "Preferences"
380
+ * file in Chrome's user data directory for examples.
381
+ * @param {!Object} prefs Dictionary of user preferences to use.
382
+ * @return {!Options} A self reference.
383
+ */
384
+ setUserPreferences(prefs) {
385
+ this.options_.prefs = prefs
386
+ return this
387
+ }
388
+
389
+ /**
390
+ * Sets the performance logging preferences. Options include:
391
+ *
392
+ * - `enableNetwork`: Whether or not to collect events from Network domain.
393
+ * - `enablePage`: Whether or not to collect events from Page domain.
394
+ * - `enableTimeline`: Whether or not to collect events from Timeline domain.
395
+ * Note: when tracing is enabled, Timeline domain is implicitly disabled,
396
+ * unless `enableTimeline` is explicitly set to true.
397
+ * - `tracingCategories`: A comma-separated string of Chromium tracing
398
+ * categories for which trace events should be collected. An unspecified
399
+ * or empty string disables tracing.
400
+ * - `bufferUsageReportingInterval`: The requested number of milliseconds
401
+ * between DevTools trace buffer usage events. For example, if 1000, then
402
+ * once per second, DevTools will report how full the trace buffer is. If
403
+ * a report indicates the buffer usage is 100%, a warning will be issued.
404
+ *
405
+ * @param {{enableNetwork: boolean,
406
+ * enablePage: boolean,
407
+ * enableTimeline: boolean,
408
+ * tracingCategories: string,
409
+ * bufferUsageReportingInterval: number}} prefs The performance
410
+ * logging preferences.
411
+ * @return {!Options} A self reference.
412
+ */
413
+ setPerfLoggingPrefs(prefs) {
414
+ this.options_.perfLoggingPrefs = prefs
415
+ return this
416
+ }
417
+
418
+ /**
419
+ * Sets preferences for the "Local State" file in Chrome's user data
420
+ * directory.
421
+ * @param {!Object} state Dictionary of local state preferences.
422
+ * @return {!Options} A self reference.
423
+ */
424
+ setLocalState(state) {
425
+ this.options_.localState = state
426
+ return this
427
+ }
428
+
429
+ /**
430
+ * Sets the name of the activity hosting a Chrome-based Android WebView. This
431
+ * option must be set to connect to an [Android WebView](
432
+ * https://chromedriver.chromium.org/getting-started/getting-started---android)
433
+ *
434
+ * @param {string} name The activity name.
435
+ * @return {!Options} A self reference.
436
+ */
437
+ androidActivity(name) {
438
+ this.options_.androidActivity = name
439
+ return this
440
+ }
441
+
442
+ /**
443
+ * Sets the device serial number to connect to via ADB. If not specified, the
444
+ * WebDriver server will select an unused device at random. An error will be
445
+ * returned if all devices already have active sessions.
446
+ *
447
+ * @param {string} serial The device serial number to connect to.
448
+ * @return {!Options} A self reference.
449
+ */
450
+ androidDeviceSerial(serial) {
451
+ this.options_.androidDeviceSerial = serial
452
+ return this
453
+ }
454
+
455
+ /**
456
+ * Sets the package name of the Chrome or WebView app.
457
+ *
458
+ * @param {?string} pkg The package to connect to, or `null` to disable Android
459
+ * and switch back to using desktop browser.
460
+ * @return {!Options} A self reference.
461
+ */
462
+ androidPackage(pkg) {
463
+ this.options_.androidPackage = pkg
464
+ return this
465
+ }
466
+
467
+ /**
468
+ * Sets the process name of the Activity hosting the WebView (as given by
469
+ * `ps`). If not specified, the process name is assumed to be the same as
470
+ * {@link #androidPackage}.
471
+ *
472
+ * @param {string} processName The main activity name.
473
+ * @return {!Options} A self reference.
474
+ */
475
+ androidProcess(processName) {
476
+ this.options_.androidProcess = processName
477
+ return this
478
+ }
479
+
480
+ /**
481
+ * Sets whether to connect to an already-running instead of the specified
482
+ * {@linkplain #androidProcess app} instead of launching the app with a clean
483
+ * data directory.
484
+ *
485
+ * @param {boolean} useRunning Whether to connect to a running instance.
486
+ * @return {!Options} A self reference.
487
+ */
488
+ androidUseRunningApp(useRunning) {
489
+ this.options_.androidUseRunningApp = useRunning
490
+ return this
491
+ }
492
+
493
+ /**
494
+ * Sets the path to the browser's log file. This path should exist on the machine
495
+ * that will launch the browser.
496
+ * @param {string} path Path to the log file to use.
497
+ * @return {!Options} A self reference.
498
+ */
499
+ setBrowserLogFile(path) {
500
+ this.options_.logPath = path
501
+ return this
502
+ }
503
+
504
+ /**
505
+ * Sets the directory to store browser minidumps in. This option is only
506
+ * supported when the driver is running on Linux.
507
+ * @param {string} path The directory path.
508
+ * @return {!Options} A self reference.
509
+ */
510
+ setBrowserMinidumpPath(path) {
511
+ this.options_.minidumpPath = path
512
+ return this
513
+ }
514
+
515
+ /**
516
+ * Configures the browser to emulate a mobile device. For more information, refer
517
+ * to the ChromeDriver project page on [mobile emulation][em]. Configuration
518
+ * options include:
519
+ *
520
+ * - `deviceName`: The name of a pre-configured [emulated device][devem]
521
+ * - `width`: screen width, in pixels
522
+ * - `height`: screen height, in pixels
523
+ * - `pixelRatio`: screen pixel ratio
524
+ *
525
+ * __Example 1: Using a Pre-configured Device__
526
+ *
527
+ * let options = new chrome.Options().setMobileEmulation(
528
+ * {deviceName: 'Google Nexus 5'});
529
+ *
530
+ * let driver = chrome.Driver.createSession(options);
531
+ *
532
+ * __Example 2: Using Custom Screen Configuration__
533
+ *
534
+ * let options = new chrome.Options().setMobileEmulation({deviceMetrics: {
535
+ * width: 360,
536
+ * height: 640,
537
+ * pixelRatio: 3.0
538
+ * }});
539
+ *
540
+ * let driver = chrome.Driver.createSession(options);
541
+ *
542
+ *
543
+ * [em]: https://chromedriver.chromium.org/mobile-emulation
544
+ * [devem]: https://developer.chrome.com/devtools/docs/device-mode
545
+ *
546
+ * @param {?({deviceName: string}|
547
+ * {width: number, height: number, pixelRatio: number})} config The
548
+ * mobile emulation configuration, or `null` to disable emulation.
549
+ * @return {!Options} A self reference.
550
+ */
551
+ setMobileEmulation(config) {
552
+ this.options_.mobileEmulation = config
553
+ return this
554
+ }
555
+
556
+ /**
557
+ * Converts this instance to its JSON wire protocol representation. Note this
558
+ * function is an implementation not intended for general use.
559
+ *
560
+ * @return {!Object} The JSON wire protocol representation of this instance.
561
+ * @suppress {checkTypes} Suppress [] access on a struct.
562
+ */
563
+ [Symbols.serialize]() {
564
+ if (this.options_.extensions && this.options_.extensions.length) {
565
+ this.options_.extensions = this.options_.extensions.map(function (
566
+ extension
567
+ ) {
568
+ if (Buffer.isBuffer(extension)) {
569
+ return extension.toString('base64')
570
+ }
571
+ return io
572
+ .read(/** @type {string} */ (extension))
573
+ .then((buffer) => buffer.toString('base64'))
574
+ })
575
+ }
576
+ return super[Symbols.serialize]()
577
+ }
578
+ }
579
+
580
+ /**
581
+ * Creates a new WebDriver client for Chromium-based browsers.
582
+ */
583
+ class Driver extends webdriver.WebDriver {
584
+ /**
585
+ * Creates a new session with the WebDriver server.
586
+ *
587
+ * @param {(Capabilities|Options)=} opt_config The configuration options.
588
+ * @param {(remote.DriverService|http.Executor)=} opt_serviceExecutor Either
589
+ * a DriverService to use for the remote end, or a preconfigured executor
590
+ * for an externally managed endpoint. If neither is provided, the
591
+ * {@linkplain ##getDefaultService default service} will be used by
592
+ * default.
593
+ * @return {!Driver} A new driver instance.
594
+ */
595
+ static createSession(caps, opt_serviceExecutor) {
596
+ let executor
597
+ let onQuit
598
+ if (opt_serviceExecutor instanceof http.Executor) {
599
+ executor = opt_serviceExecutor
600
+ configureExecutor(executor, this.VENDOR_COMMAND_PREFIX)
601
+ } else {
602
+ let service = opt_serviceExecutor || this.getDefaultService()
603
+ executor = createExecutor(service.start(), this.VENDOR_COMMAND_PREFIX)
604
+ onQuit = () => service.kill()
605
+ }
606
+
607
+ // W3C spec requires noProxy value to be an array of strings, but Chromium
608
+ // expects a single host as a string.
609
+ let proxy = caps.get(Capability.PROXY)
610
+ if (proxy && Array.isArray(proxy.noProxy)) {
611
+ proxy.noProxy = proxy.noProxy[0]
612
+ if (!proxy.noProxy) {
613
+ proxy.noProxy = undefined
614
+ }
615
+ }
616
+
617
+ return /** @type {!Driver} */ (super.createSession(executor, caps, onQuit))
618
+ }
619
+
620
+ /**
621
+ * This function is a no-op as file detectors are not supported by this
622
+ * implementation.
623
+ * @override
624
+ */
625
+ setFileDetector() {}
626
+
627
+ /**
628
+ * Schedules a command to launch Chrome App with given ID.
629
+ * @param {string} id ID of the App to launch.
630
+ * @return {!Promise<void>} A promise that will be resolved
631
+ * when app is launched.
632
+ */
633
+ launchApp(id) {
634
+ return this.execute(
635
+ new command.Command(Command.LAUNCH_APP).setParameter('id', id)
636
+ )
637
+ }
638
+
639
+ /**
640
+ * Schedules a command to get Chromium network emulation settings.
641
+ * @return {!Promise} A promise that will be resolved when network
642
+ * emulation settings are retrievied.
643
+ */
644
+ getNetworkConditions() {
645
+ return this.execute(new command.Command(Command.GET_NETWORK_CONDITIONS))
646
+ }
647
+
648
+ /**
649
+ * Schedules a command to set Chromium network emulation settings.
650
+ *
651
+ * __Sample Usage:__
652
+ *
653
+ * driver.setNetworkConditions({
654
+ * offline: false,
655
+ * latency: 5, // Additional latency (ms).
656
+ * download_throughput: 500 * 1024, // Maximal aggregated download throughput.
657
+ * upload_throughput: 500 * 1024 // Maximal aggregated upload throughput.
658
+ * });
659
+ *
660
+ * @param {Object} spec Defines the network conditions to set
661
+ * @return {!Promise<void>} A promise that will be resolved when network
662
+ * emulation settings are set.
663
+ */
664
+ setNetworkConditions(spec) {
665
+ if (!spec || typeof spec !== 'object') {
666
+ throw TypeError(
667
+ 'setNetworkConditions called with non-network-conditions parameter'
668
+ )
669
+ }
670
+ return this.execute(
671
+ new command.Command(Command.SET_NETWORK_CONDITIONS).setParameter(
672
+ 'network_conditions',
673
+ spec
674
+ )
675
+ )
676
+ }
677
+
678
+ /**
679
+ * Sends an arbitrary devtools command to the browser.
680
+ *
681
+ * @param {string} cmd The name of the command to send.
682
+ * @param {Object=} params The command parameters.
683
+ * @return {!Promise<void>} A promise that will be resolved when the command
684
+ * has finished.
685
+ * @see <https://chromedevtools.github.io/devtools-protocol/>
686
+ */
687
+ sendDevToolsCommand(cmd, params = {}) {
688
+ return this.execute(
689
+ new command.Command(Command.SEND_DEVTOOLS_COMMAND)
690
+ .setParameter('cmd', cmd)
691
+ .setParameter('params', params)
692
+ )
693
+ }
694
+
695
+ /**
696
+ * ends an arbitrary devtools command to the browser and get the result.
697
+ *
698
+ * @param {string} cmd The name of the command to send.
699
+ * @param {Object=} params The command parameters.
700
+ * @return {!Promise<void>} A promise that will be resolved when the command
701
+ * has finished.
702
+ * @see <https://chromedevtools.github.io/devtools-protocol/>
703
+ */
704
+ sendAndGetDevToolsCommand(cmd, params = {}) {
705
+ return this.execute(
706
+ new command.Command(Command.SEND_AND_GET_DEVTOOLS_COMMAND)
707
+ .setParameter('cmd', cmd)
708
+ .setParameter('params', params)
709
+ )
710
+ }
711
+
712
+ /**
713
+ * Creates a new WebSocket connection.
714
+ * @return {!Promise<resolved>} A new CDP instance.
715
+ */
716
+ async createCDPConnection(target) {
717
+ const caps = await this.getCapabilities()
718
+ const seOptions = caps['map_'].get('se:options') || new Map()
719
+ const vendorInfo =
720
+ caps['map_'].get(this.VENDOR_COMMAND_PREFIX + ':chromeOptions') ||
721
+ new Map()
722
+ const debuggerUrl = seOptions['cdp'] || vendorInfo['debuggerAddress']
723
+ this._wsUrl = await this.getWsUrl(debuggerUrl, target)
724
+
725
+ return new Promise((resolve, reject) => {
726
+ try {
727
+ this._wsConnection = new WebSocket(this._wsUrl)
728
+ } catch (err) {
729
+ reject(err)
730
+ return
731
+ }
732
+
733
+ this._wsConnection.on('open', () => {
734
+ this._cdpConnection = new cdp.CdpConnection(this._wsConnection)
735
+ resolve(this._cdpConnection)
736
+ })
737
+
738
+ this._wsConnection.on('error', (error) => {
739
+ reject(error)
740
+ })
741
+ })
742
+ }
743
+
744
+ /**
745
+ * Retrieves 'webSocketDebuggerUrl' by sending a http request using debugger address
746
+ * @param {string} debuggerAddress
747
+ * @param {string} target
748
+ * @return {string} Returns parsed webSocketDebuggerUrl obtained from the http request
749
+ */
750
+ async getWsUrl(debuggerAddress, target) {
751
+ if (target && cdpTargets.indexOf(target.toLowerCase()) === -1) {
752
+ throw new error.InvalidArgumentError('invalid target value')
753
+ }
754
+ let path = '/json/version'
755
+
756
+ if (target === 'page') {
757
+ path = '/json'
758
+ }
759
+ let request = new http.Request('GET', path)
760
+ let client = new http.HttpClient('http://' + debuggerAddress)
761
+ let response = await client.send(request)
762
+ let url = JSON.parse(response.body)['webSocketDebuggerUrl']
763
+ if (target.toLowerCase() === 'page') {
764
+ url = JSON.parse(response.body)[0]['webSocketDebuggerUrl']
765
+ }
766
+
767
+ return url
768
+ }
769
+
770
+ /**
771
+ * Sets a listener for Fetch.authRequired event from CDP
772
+ * If event is triggered, it enter username and password
773
+ * and allows the test to move forward
774
+ * @param {string} username
775
+ * @param {string} password
776
+ * @param connection CDP Connection
777
+ */
778
+ async register(username, password, connection) {
779
+ await connection.execute("Network.setCacheDisabled", this.getRandomNumber(1, 10), {
780
+ cacheDisabled: true,
781
+ }, null);
782
+
783
+ this._wsConnection.on('message', (message) => {
784
+ const params = JSON.parse(message)
785
+
786
+ if (params.method === 'Fetch.authRequired') {
787
+ const requestParams = params['params']
788
+ connection.execute('Fetch.continueWithAuth', this.getRandomNumber(1, 10), {
789
+ requestId: requestParams['requestId'],
790
+ authChallengeResponse: {
791
+ response: 'ProvideCredentials',
792
+ username: username,
793
+ password: password,
794
+ }})
795
+ } else if (params.method === 'Fetch.requestPaused') {
796
+ const requestPausedParams = params['params']
797
+ connection.execute('Fetch.continueRequest', this.getRandomNumber(1, 10), {
798
+ requestId: requestPausedParams['requestId'],
799
+ })
800
+ }
801
+ })
802
+
803
+ await connection.execute('Fetch.enable', 1, {
804
+ handleAuthRequests: true,
805
+ }, null)
806
+ }
807
+
808
+ /**
809
+ *
810
+ * @param connection
811
+ * @param callback
812
+ * @returns {Promise<void>}
813
+ */
814
+ async onLogEvent(connection, callback) {
815
+ await connection.execute('Runtime.enable', this.getRandomNumber(1, 10), {}, null)
816
+
817
+ this._wsConnection.on('message', (message) => {
818
+ const params = JSON.parse(message)
819
+
820
+ if (params.method === 'Runtime.consoleAPICalled') {
821
+ const consoleEventParams = params['params']
822
+ let event = {
823
+ type: consoleEventParams['type'],
824
+ timestamp: new Date(consoleEventParams['timestamp']),
825
+ args: consoleEventParams['args']
826
+ }
827
+
828
+ callback(event)
829
+ }
830
+ })
831
+ }
832
+
833
+ /**
834
+ * Set a permission state to the given value.
835
+ *
836
+ * @param {string} name A name of the permission to update.
837
+ * @param {('granted'|'denied'|'prompt')} state State to set permission to.
838
+ * @returns {!Promise<Object>} A promise that will be resolved when the
839
+ * command has finished.
840
+ * @see <https://w3c.github.io/permissions/#permission-registry> for valid
841
+ * names
842
+ */
843
+ setPermission(name, state) {
844
+ return this.execute(
845
+ new command.Command(Command.SET_PERMISSION)
846
+ .setParameter('descriptor', { name })
847
+ .setParameter('state', state)
848
+ )
849
+ }
850
+
851
+ /**
852
+ *
853
+ * @param connection
854
+ * @param callback
855
+ * @returns {Promise<void>}
856
+ */
857
+ async onLogException(connection, callback) {
858
+ await connection.execute('Runtime.enable', this.getRandomNumber(1, 10), {}, null)
859
+
860
+ this._wsConnection.on('message', (message) => {
861
+ const params = JSON.parse(message)
862
+
863
+ if (params.method === 'Runtime.exceptionThrown') {
864
+ const exceptionEventParams = params['params']
865
+ let event = {
866
+ exceptionDetails: exceptionEventParams['exceptionDetails'],
867
+ timestamp: new Date(exceptionEventParams['timestamp']),
868
+ }
869
+
870
+ callback(event)
871
+ }
872
+ })
873
+ }
874
+
875
+ /**
876
+ * @param connection
877
+ * @param callback
878
+ * @returns {Promise<void>}
879
+ */
880
+ async logMutationEvents(connection, callback) {
881
+ await connection.execute('Runtime.enable', this.getRandomNumber(1, 10), {}, null)
882
+ await connection.execute('Page.enable', this.getRandomNumber(1, 10), {}, null)
883
+
884
+ await connection.execute('Runtime.addBinding', this.getRandomNumber(1, 10), {
885
+ name: '__webdriver_attribute',
886
+ }, null)
887
+
888
+ const mutationListener = fs.readFileSync('../../cdp-support/mutation-listener.js', 'utf-8').toString()
889
+
890
+ this.executeScript(mutationListener)
891
+
892
+ await connection.execute('Page.addScriptToEvaluateOnNewDocument', this.getRandomNumber(1, 10), {
893
+ source: mutationListener,
894
+ }, null)
895
+
896
+ this._wsConnection.on('message', async (message) => {
897
+ const params = JSON.parse(message)
898
+ if (params.method === 'Runtime.bindingCalled') {
899
+ let payload = JSON.parse(params['params']['payload'])
900
+ let elements = await this.findElements({css: "*[data-__webdriver_id=" + payload['target']})
901
+
902
+ if (elements.length === 0) {
903
+ return
904
+ }
905
+
906
+ let event = {
907
+ element: elements[0],
908
+ attribute_name: payload['name'],
909
+ current_value: payload['value'],
910
+ old_value: payload['oldValue']
911
+ }
912
+ callback(event)
913
+ }
914
+ })
915
+ }
916
+
917
+ /**
918
+ * Sends a DevTools command to change the browser's download directory.
919
+ *
920
+ * @param {string} path The desired download directory.
921
+ * @return {!Promise<void>} A promise that will be resolved when the command
922
+ * has finished.
923
+ * @see #sendDevToolsCommand
924
+ */
925
+ async setDownloadPath(path) {
926
+ if (!path || typeof path !== 'string') {
927
+ throw new error.InvalidArgumentError('invalid download path')
928
+ }
929
+ const stat = await io.stat(path)
930
+ if (!stat.isDirectory()) {
931
+ throw new error.InvalidArgumentError('not a directory: ' + path)
932
+ }
933
+ return this.sendDevToolsCommand('Page.setDownloadBehavior', {
934
+ behavior: 'allow',
935
+ downloadPath: path,
936
+ })
937
+ }
938
+
939
+ /**
940
+ * Returns the list of cast sinks (Cast devices) available to the Chrome media router.
941
+ *
942
+ * @return {!promise.Thenable<void>} A promise that will be resolved with an array of Strings
943
+ * containing the friendly device names of available cast sink targets.
944
+ */
945
+ getCastSinks() {
946
+ return this.schedule(
947
+ new command.Command(Command.GET_CAST_SINKS),
948
+ 'Driver.getCastSinks()'
949
+ )
950
+ }
951
+
952
+ /**
953
+ * Selects a cast sink (Cast device) as the recipient of media router intents (connect or play).
954
+ *
955
+ * @param {String} deviceName name of the target device.
956
+ * @return {!promise.Thenable<void>} A promise that will be resolved
957
+ * when the target device has been selected to respond further webdriver commands.
958
+ */
959
+ setCastSinkToUse(deviceName) {
960
+ return this.schedule(
961
+ new command.Command(Command.SET_CAST_SINK_TO_USE).setParameter(
962
+ 'sinkName',
963
+ deviceName
964
+ ),
965
+ 'Driver.setCastSinkToUse(' + deviceName + ')'
966
+ )
967
+ }
968
+
969
+ /**
970
+ * Initiates tab mirroring for the current browser tab on the specified device.
971
+ *
972
+ * @param {String} deviceName name of the target device.
973
+ * @return {!promise.Thenable<void>} A promise that will be resolved
974
+ * when the mirror command has been issued to the device.
975
+ */
976
+ startCastTabMirroring(deviceName) {
977
+ return this.schedule(
978
+ new command.Command(Command.START_CAST_TAB_MIRRORING).setParameter(
979
+ 'sinkName',
980
+ deviceName
981
+ ),
982
+ 'Driver.startCastTabMirroring(' + deviceName + ')'
983
+ )
984
+ }
985
+
986
+ /**
987
+ * Returns an error message when there is any issue in a Cast session.
988
+ * @return {!promise.Thenable<void>} A promise that will be resolved
989
+ * when the mirror command has been issued to the device.
990
+ */
991
+ getCastIssueMessage() {
992
+ return this.schedule(
993
+ new command.Command(Command.GET_CAST_ISSUE_MESSAGE),
994
+ 'Driver.getCastIssueMessage()'
995
+ )
996
+ }
997
+
998
+ /**
999
+ * Stops casting from media router to the specified device, if connected.
1000
+ *
1001
+ * @param {String} deviceName name of the target device.
1002
+ * @return {!promise.Thenable<void>} A promise that will be resolved
1003
+ * when the stop command has been issued to the device.
1004
+ */
1005
+ stopCasting(deviceName) {
1006
+ return this.schedule(
1007
+ new command.Command(Command.STOP_CASTING).setParameter(
1008
+ 'sinkName',
1009
+ deviceName
1010
+ ),
1011
+ 'Driver.stopCasting(' + deviceName + ')'
1012
+ )
1013
+ }
1014
+
1015
+ getRandomNumber(min, max) {
1016
+ return Math.floor(Math.random() * (max - min + 1) + min)
1017
+ }
1018
+ }
1019
+
1020
+ // PUBLIC API
1021
+
1022
+ exports.Driver = Driver
1023
+ exports.Options = Options
1024
+ exports.ServiceBuilder = ServiceBuilder