selenium-webdriver 3.3.0 → 4.0.0-alpha.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.
Files changed (117) hide show
  1. package/CHANGES.md +273 -0
  2. package/NOTICE +1 -1
  3. package/README.md +28 -36
  4. package/chrome.js +221 -146
  5. package/edge.js +43 -118
  6. package/example/chrome_android.js +8 -9
  7. package/example/chrome_mobile_emulation.js +9 -10
  8. package/example/firefox_channels.js +23 -19
  9. package/example/google_search.js +3 -3
  10. package/example/google_search_test.js +39 -27
  11. package/example/headless.js +55 -0
  12. package/example/logging.js +40 -7
  13. package/firefox.js +769 -0
  14. package/http/index.js +41 -4
  15. package/http/util.js +11 -6
  16. package/ie.js +57 -98
  17. package/index.js +182 -115
  18. package/io/index.js +59 -0
  19. package/io/zip.js +214 -0
  20. package/jasmine.json +11 -0
  21. package/lib/actions.js +68 -63
  22. package/lib/atoms/get-attribute.js +9 -0
  23. package/lib/atoms/is-displayed.js +73 -78
  24. package/lib/by.js +9 -1
  25. package/lib/capabilities.js +303 -201
  26. package/lib/command.js +28 -50
  27. package/lib/error.js +113 -42
  28. package/lib/http.js +167 -135
  29. package/lib/input.js +1044 -8
  30. package/lib/promise.js +222 -3331
  31. package/lib/proxy.js +134 -39
  32. package/lib/test/bootstrap_jasmine.js +28 -0
  33. package/lib/test/data/actions/click.html +24 -0
  34. package/lib/test/data/actions/drag.html +77 -0
  35. package/lib/test/data/actions/record_click.html +21 -0
  36. package/lib/test/data/chrome/download.html +2 -0
  37. package/lib/test/data/click_tests/disabled_element.html +12 -0
  38. package/lib/test/data/content-editable.html +10 -0
  39. package/lib/test/data/firefox/webextension.xpi +0 -0
  40. package/lib/test/data/inputs.html +42 -0
  41. package/lib/test/data/key_logger.html +34 -0
  42. package/lib/test/data/nestedElements.html +10 -1
  43. package/lib/test/data/scrolling_tests/page_with_partially_hidden_element.html +14 -0
  44. package/lib/test/data/selectPage.html +12 -0
  45. package/lib/test/data/simpleTest.html +5 -0
  46. package/lib/test/data/single_text_input.html +12 -0
  47. package/lib/test/data/upload_invisible.html +45 -0
  48. package/lib/test/fileserver.js +23 -19
  49. package/lib/test/index.js +30 -244
  50. package/lib/test/resources.js +0 -1
  51. package/lib/webdriver.js +770 -939
  52. package/net/index.js +21 -25
  53. package/net/portprober.js +51 -68
  54. package/package.json +6 -4
  55. package/remote/index.js +18 -30
  56. package/safari.js +42 -129
  57. package/test/actions_test.js +173 -18
  58. package/test/builder_test.js +106 -0
  59. package/test/chrome/devtools_test.js +93 -0
  60. package/test/chrome/options_test.js +48 -154
  61. package/test/chrome/service_test.js +7 -7
  62. package/test/cookie_test.js +77 -63
  63. package/test/element_finding_test.js +206 -201
  64. package/test/execute_script_test.js +115 -114
  65. package/test/fingerprint_test.js +16 -15
  66. package/test/firefox_test.js +251 -0
  67. package/test/http/http_test.js +18 -1
  68. package/test/http/util_test.js +2 -2
  69. package/test/{io_test.js → io/io_test.js} +40 -1
  70. package/test/io/zip_test.js +127 -0
  71. package/test/lib/by_test.js +21 -0
  72. package/test/lib/error_test.js +55 -8
  73. package/test/lib/http_test.js +18 -130
  74. package/test/lib/input_test.js +1379 -0
  75. package/test/lib/promise_test.js +473 -908
  76. package/test/lib/webdriver_test.js +357 -825
  77. package/test/logging_test.js +37 -43
  78. package/test/page_loading_test.js +70 -86
  79. package/test/proxy_test.js +64 -72
  80. package/test/rect_test.js +14 -25
  81. package/test/remote_test.js +13 -32
  82. package/test/safari_test.js +7 -71
  83. package/test/stale_element_test.js +20 -23
  84. package/test/tag_name_test.js +10 -9
  85. package/test/upload_test.js +27 -35
  86. package/test/window_test.js +72 -85
  87. package/testing/index.js +386 -315
  88. package/.npmignore +0 -2
  89. package/example/async_await_test.js +0 -68
  90. package/example/google_search_generator.js +0 -50
  91. package/example/parallel_flows.js +0 -54
  92. package/firefox/binary.js +0 -344
  93. package/firefox/extension.js +0 -187
  94. package/firefox/index.js +0 -722
  95. package/firefox/profile.js +0 -430
  96. package/lib/atoms/getAttribute.js +0 -12
  97. package/lib/events.js +0 -210
  98. package/lib/firefox/webdriver.json +0 -70
  99. package/lib/firefox/webdriver.xpi +0 -0
  100. package/lib/test/data/firefox/jetpack-sample.xpi +0 -0
  101. package/lib/test/data/firefox/sample.xpi +0 -0
  102. package/lib/test/promise.js +0 -79
  103. package/opera.js +0 -405
  104. package/phantomjs.js +0 -282
  105. package/test/firefox/extension_test.js +0 -96
  106. package/test/firefox/firefox_test.js +0 -226
  107. package/test/firefox/profile_test.js +0 -185
  108. package/test/lib/events_test.js +0 -177
  109. package/test/lib/promise_aplus_test.js +0 -78
  110. package/test/lib/promise_error_test.js +0 -884
  111. package/test/lib/promise_flow_test.js +0 -2288
  112. package/test/lib/promise_generator_test.js +0 -310
  113. package/test/phantomjs/execute_phantomjs_test.js +0 -59
  114. package/test/session_test.js +0 -54
  115. package/test/testing/assert_test.js +0 -373
  116. package/test/testing/index_test.js +0 -224
  117. package/testing/assert.js +0 -378
package/chrome.js CHANGED
@@ -36,6 +36,20 @@
36
36
  * a unique browser session with a clean user profile (unless otherwise
37
37
  * configured through the {@link Options} class).
38
38
  *
39
+ * __Headless Chrome__ <a id="headless"></a>
40
+ *
41
+ * To start Chrome in headless mode, simply call
42
+ * {@linkplain Options#headless Options.headless()}. Note, starting in headless
43
+ * mode currently also disables GPU acceleration.
44
+ *
45
+ * let chrome = require('selenium-webdriver/chrome');
46
+ * let {Builder} = require('selenium-webdriver');
47
+ *
48
+ * let driver = new Builder()
49
+ * .forBrowser('chrome')
50
+ * .setChromeOptions(new chrome.Options().headless())
51
+ * .build();
52
+ *
39
53
  * __Customizing the ChromeDriver Server__ <a id="custom-server"></a>
40
54
  *
41
55
  * By default, every Chrome session will use a single driver service, which is
@@ -114,19 +128,20 @@
114
128
 
115
129
  'use strict';
116
130
 
117
- const fs = require('fs'),
118
- util = require('util');
131
+ const fs = require('fs');
132
+ const util = require('util');
119
133
 
120
- const http = require('./http'),
121
- io = require('./io'),
122
- {Capabilities, Capability} = require('./lib/capabilities'),
123
- command = require('./lib/command'),
124
- logging = require('./lib/logging'),
125
- promise = require('./lib/promise'),
126
- Symbols = require('./lib/symbols'),
127
- webdriver = require('./lib/webdriver'),
128
- portprober = require('./net/portprober'),
129
- remote = require('./remote');
134
+ const http = require('./http');
135
+ const io = require('./io');
136
+ const {Browser, Capabilities, Capability} = require('./lib/capabilities');
137
+ const command = require('./lib/command');
138
+ const error = require('./lib/error');
139
+ const logging = require('./lib/logging');
140
+ const promise = require('./lib/promise');
141
+ const Symbols = require('./lib/symbols');
142
+ const webdriver = require('./lib/webdriver');
143
+ const portprober = require('./net/portprober');
144
+ const remote = require('./remote');
130
145
 
131
146
 
132
147
  /**
@@ -143,7 +158,10 @@ const CHROMEDRIVER_EXE =
143
158
  * @enum {string}
144
159
  */
145
160
  const Command = {
146
- LAUNCH_APP: 'launchApp'
161
+ LAUNCH_APP: 'launchApp',
162
+ GET_NETWORK_CONDITIONS: 'getNetworkConditions',
163
+ SET_NETWORK_CONDITIONS: 'setNetworkConditions',
164
+ SEND_DEVTOOLS_COMMAND: 'sendDevToolsCommand',
147
165
  };
148
166
 
149
167
 
@@ -169,6 +187,29 @@ function configureExecutor(executor) {
169
187
  Command.LAUNCH_APP,
170
188
  'POST',
171
189
  '/session/:sessionId/chromium/launch_app');
190
+ executor.defineCommand(
191
+ Command.GET_NETWORK_CONDITIONS,
192
+ 'GET',
193
+ '/session/:sessionId/chromium/network_conditions');
194
+ executor.defineCommand(
195
+ Command.SET_NETWORK_CONDITIONS,
196
+ 'POST',
197
+ '/session/:sessionId/chromium/network_conditions');
198
+ executor.defineCommand(
199
+ Command.SEND_DEVTOOLS_COMMAND,
200
+ 'POST',
201
+ '/session/:sessionId/chromium/send_command');
202
+ }
203
+
204
+
205
+ /**
206
+ * _Synchronously_ attempts to locate the chromedriver executable on the current
207
+ * system.
208
+ *
209
+ * @return {?string} the located executable, or `null`.
210
+ */
211
+ function locateSynchronously() {
212
+ return io.findInPath(CHROMEDRIVER_EXE, true);
172
213
  }
173
214
 
174
215
 
@@ -186,7 +227,7 @@ class ServiceBuilder extends remote.DriverService.Builder {
186
227
  * cannot be found on the PATH.
187
228
  */
188
229
  constructor(opt_exe) {
189
- let exe = opt_exe || io.findInPath(CHROMEDRIVER_EXE, true);
230
+ let exe = opt_exe || locateSynchronously();
190
231
  if (!exe) {
191
232
  throw Error(
192
233
  'The ChromeDriver could not be found on the current PATH. Please ' +
@@ -283,68 +324,25 @@ function getDefaultService() {
283
324
  }
284
325
 
285
326
 
286
- /**
287
- * @type {string}
288
- * @const
289
- */
290
- let OPTIONS_CAPABILITY_KEY = 'chromeOptions';
327
+ const OPTIONS_CAPABILITY_KEY = 'chromeOptions';
291
328
 
292
329
 
293
330
  /**
294
331
  * Class for managing ChromeDriver specific options.
295
332
  */
296
- class Options {
297
- constructor() {
298
- /** @private {!Object} */
299
- this.options_ = {};
300
-
301
- /** @private {!Array<(string|!Buffer)>} */
302
- this.extensions_ = [];
303
-
304
- /** @private {?logging.Preferences} */
305
- this.logPrefs_ = null;
306
-
307
- /** @private {?./lib/capabilities.ProxyConfig} */
308
- this.proxy_ = null;
309
- }
310
-
311
- /**
312
- * Extracts the ChromeDriver specific options from the given capabilities
313
- * object.
314
- * @param {!Capabilities} caps The capabilities object.
315
- * @return {!Options} The ChromeDriver options.
316
- */
317
- static fromCapabilities(caps) {
318
- let options = new Options();
319
-
320
- let o = caps.get(OPTIONS_CAPABILITY_KEY);
321
- if (o instanceof Options) {
322
- options = o;
323
- } else if (o) {
324
- options.
325
- addArguments(o.args || []).
326
- addExtensions(o.extensions || []).
327
- detachDriver(o.detach).
328
- excludeSwitches(o.excludeSwitches || []).
329
- setChromeBinaryPath(o.binary).
330
- setChromeLogFile(o.logPath).
331
- setChromeMinidumpPath(o.minidumpPath).
332
- setLocalState(o.localState).
333
- setMobileEmulation(o.mobileEmulation).
334
- setUserPreferences(o.prefs).
335
- setPerfLoggingPrefs(o.perfLoggingPrefs);
336
- }
337
-
338
- if (caps.has(Capability.PROXY)) {
339
- options.setProxy(caps.get(Capability.PROXY));
340
- }
333
+ class Options extends Capabilities {
334
+ /**
335
+ * @param {(Capabilities|Map<string, ?>|Object)=} other Another set of
336
+ * capabilities to initialize this instance from.
337
+ */
338
+ constructor(other = undefined) {
339
+ super(other);
341
340
 
342
- if (caps.has(Capability.LOGGING_PREFS)) {
343
- options.setLoggingPrefs(
344
- caps.get(Capability.LOGGING_PREFS));
345
- }
341
+ /** @private {!Object} */
342
+ this.options_ = this.get(OPTIONS_CAPABILITY_KEY) || {};
346
343
 
347
- return options;
344
+ this.setBrowserName(Browser.CHROME);
345
+ this.set(OPTIONS_CAPABILITY_KEY, this.options_);
348
346
  }
349
347
 
350
348
  /**
@@ -352,28 +350,67 @@ class Options {
352
350
  * browser. Each argument may be specified with or without the "--" prefix
353
351
  * (e.g. "--foo" and "foo"). Arguments with an associated value should be
354
352
  * delimited by an "=": "foo=bar".
355
- * @param {...(string|!Array<string>)} var_args The arguments to add.
353
+ *
354
+ * @param {...(string|!Array<string>)} args The arguments to add.
356
355
  * @return {!Options} A self reference.
357
356
  */
358
- addArguments(var_args) {
359
- let args = this.options_.args || [];
360
- args = args.concat.apply(args, arguments);
361
- if (args.length) {
362
- this.options_.args = args;
357
+ addArguments(...args) {
358
+ let newArgs = (this.options_.args || []).concat(...args);
359
+ if (newArgs.length) {
360
+ this.options_.args = newArgs;
363
361
  }
364
362
  return this;
365
363
  }
366
364
 
365
+ /**
366
+ * Configures the chromedriver to start Chrome in headless mode.
367
+ *
368
+ * > __NOTE:__ Resizing the browser window in headless mode is only supported
369
+ * > in Chrome 60. Users are encouraged to set an initial window size with
370
+ * > the {@link #windowSize windowSize({width, height})} option.
371
+ *
372
+ * > __NOTE__: For security, Chrome disables downloads by default when
373
+ * > in headless mode (to prevent sites from silently downloading files to
374
+ * > your machine). After creating a session, you may call
375
+ * > {@link ./chrome.Driver#setDownloadPath setDownloadPath} to re-enable
376
+ * > downloads, saving files in the specified directory.
377
+ *
378
+ * @return {!Options} A self reference.
379
+ */
380
+ headless() {
381
+ // TODO(jleyba): Remove `disable-gpu` once head Chrome no longer requires
382
+ // that to be set.
383
+ return this.addArguments('headless', 'disable-gpu');
384
+ }
385
+
386
+ /**
387
+ * Sets the initial window size.
388
+ *
389
+ * @param {{width: number, height: number}} size The desired window size.
390
+ * @return {!Options} A self reference.
391
+ * @throws {TypeError} if width or height is unspecified, not a number, or
392
+ * less than or equal to 0.
393
+ */
394
+ windowSize({width, height}) {
395
+ function checkArg(arg) {
396
+ if (typeof arg !== 'number' || arg <= 0) {
397
+ throw TypeError('Arguments must be {width, height} with numbers > 0');
398
+ }
399
+ }
400
+ checkArg(width);
401
+ checkArg(height);
402
+ return this.addArguments(`window-size=${width},${height}`);
403
+ }
404
+
367
405
  /**
368
406
  * List of Chrome command line switches to exclude that ChromeDriver by default
369
407
  * passes when starting Chrome. Do not prefix switches with "--".
370
408
  *
371
- * @param {...(string|!Array<string>)} var_args The switches to exclude.
409
+ * @param {...(string|!Array<string>)} args The switches to exclude.
372
410
  * @return {!Options} A self reference.
373
411
  */
374
- excludeSwitches(var_args) {
375
- let switches = this.options_.excludeSwitches || [];
376
- switches = switches.concat.apply(switches, arguments);
412
+ excludeSwitches(...args) {
413
+ let switches = (this.options_.excludeSwitches || []).concat(...args);
377
414
  if (switches.length) {
378
415
  this.options_.excludeSwitches = switches;
379
416
  }
@@ -384,13 +421,13 @@ class Options {
384
421
  * Add additional extensions to install when launching Chrome. Each extension
385
422
  * should be specified as the path to the packed CRX file, or a Buffer for an
386
423
  * extension.
387
- * @param {...(string|!Buffer|!Array<(string|!Buffer)>)} var_args The
424
+ * @param {...(string|!Buffer|!Array<(string|!Buffer)>)} args The
388
425
  * extensions to add.
389
426
  * @return {!Options} A self reference.
390
427
  */
391
- addExtensions(var_args) {
392
- this.extensions_ =
393
- this.extensions_.concat.apply(this.extensions_, arguments);
428
+ addExtensions(...args) {
429
+ let current = this.options_.extensions || [];
430
+ this.options_.extensions = current.concat(...args);
394
431
  return this;
395
432
  }
396
433
 
@@ -434,16 +471,6 @@ class Options {
434
471
  return this;
435
472
  }
436
473
 
437
- /**
438
- * Sets the logging preferences for the new session.
439
- * @param {!logging.Preferences} prefs The logging preferences.
440
- * @return {!Options} A self reference.
441
- */
442
- setLoggingPrefs(prefs) {
443
- this.logPrefs_ = prefs;
444
- return this;
445
- }
446
-
447
474
  /**
448
475
  * Sets the performance logging preferences. Options include:
449
476
  *
@@ -621,54 +648,25 @@ class Options {
621
648
  return this;
622
649
  }
623
650
 
624
- /**
625
- * Sets the proxy settings for the new session.
626
- * @param {./lib/capabilities.ProxyConfig} proxy The proxy configuration to
627
- * use.
628
- * @return {!Options} A self reference.
629
- */
630
- setProxy(proxy) {
631
- this.proxy_ = proxy;
632
- return this;
633
- }
634
-
635
- /**
636
- * Converts this options instance to a {@link Capabilities} object.
637
- * @param {Capabilities=} opt_capabilities The capabilities to merge
638
- * these options into, if any.
639
- * @return {!Capabilities} The capabilities.
640
- */
641
- toCapabilities(opt_capabilities) {
642
- let caps = opt_capabilities || Capabilities.chrome();
643
- caps.
644
- set(Capability.PROXY, this.proxy_).
645
- set(Capability.LOGGING_PREFS, this.logPrefs_).
646
- set(OPTIONS_CAPABILITY_KEY, this);
647
- return caps;
648
- }
649
-
650
651
  /**
651
652
  * Converts this instance to its JSON wire protocol representation. Note this
652
653
  * function is an implementation not intended for general use.
654
+ *
653
655
  * @return {!Object} The JSON wire protocol representation of this instance.
656
+ * @suppress {checkTypes} Suppress [] access on a struct.
654
657
  */
655
658
  [Symbols.serialize]() {
656
- let json = {};
657
- for (let key in this.options_) {
658
- if (this.options_[key] != null) {
659
- json[key] = this.options_[key];
660
- }
661
- }
662
- if (this.extensions_.length) {
663
- json.extensions = this.extensions_.map(function(extension) {
664
- if (Buffer.isBuffer(extension)) {
665
- return extension.toString('base64');
666
- }
667
- return io.read(/** @type {string} */(extension))
668
- .then(buffer => buffer.toString('base64'));
669
- });
659
+ if (this.options_.extensions && this.options_.extensions.length) {
660
+ this.options_.extensions =
661
+ this.options_.extensions.map(function(extension) {
662
+ if (Buffer.isBuffer(extension)) {
663
+ return extension.toString('base64');
664
+ }
665
+ return io.read(/** @type {string} */(extension))
666
+ .then(buffer => buffer.toString('base64'));
667
+ });
670
668
  }
671
- return json;
669
+ return super[Symbols.serialize]();
672
670
  }
673
671
  }
674
672
 
@@ -687,11 +685,9 @@ class Driver extends webdriver.WebDriver {
687
685
  * for an externally managed endpoint. If neither is provided, the
688
686
  * {@linkplain ##getDefaultService default service} will be used by
689
687
  * default.
690
- * @param {promise.ControlFlow=} opt_flow The control flow to use, or `null`
691
- * to use the currently active flow.
692
688
  * @return {!Driver} A new driver instance.
693
689
  */
694
- static createSession(opt_config, opt_serviceExecutor, opt_flow) {
690
+ static createSession(opt_config, opt_serviceExecutor) {
695
691
  let executor;
696
692
  if (opt_serviceExecutor instanceof http.Executor) {
697
693
  executor = opt_serviceExecutor;
@@ -701,12 +697,19 @@ class Driver extends webdriver.WebDriver {
701
697
  executor = createExecutor(service.start());
702
698
  }
703
699
 
704
- let caps =
705
- opt_config instanceof Options ? opt_config.toCapabilities() :
706
- (opt_config || Capabilities.chrome());
700
+ let caps = opt_config || Capabilities.chrome();
701
+
702
+ // W3C spec requires noProxy value to be an array of strings, but Chrome
703
+ // expects a single host as a string.
704
+ let proxy = caps.get(Capability.PROXY);
705
+ if (proxy && Array.isArray(proxy.noProxy)) {
706
+ proxy.noProxy = proxy.noProxy[0];
707
+ if (!proxy.noProxy) {
708
+ proxy.noProxy = undefined;
709
+ }
710
+ }
707
711
 
708
- return /** @type {!Driver} */(
709
- webdriver.WebDriver.createSession(executor, caps, opt_flow, this));
712
+ return /** @type {!Driver} */(super.createSession(executor, caps));
710
713
  }
711
714
 
712
715
  /**
@@ -719,13 +722,84 @@ class Driver extends webdriver.WebDriver {
719
722
  /**
720
723
  * Schedules a command to launch Chrome App with given ID.
721
724
  * @param {string} id ID of the App to launch.
722
- * @return {!promise.Thenable<void>} A promise that will be resolved
725
+ * @return {!Promise<void>} A promise that will be resolved
723
726
  * when app is launched.
724
727
  */
725
728
  launchApp(id) {
726
- return this.schedule(
727
- new command.Command(Command.LAUNCH_APP).setParameter('id', id),
728
- 'Driver.launchApp()');
729
+ return this.execute(
730
+ new command.Command(Command.LAUNCH_APP).setParameter('id', id));
731
+ }
732
+
733
+ /**
734
+ * Schedules a command to get Chrome network emulation settings.
735
+ * @return {!Promise} A promise that will be resolved when network
736
+ * emulation settings are retrievied.
737
+ */
738
+ getNetworkConditions() {
739
+ return this.execute(new command.Command(Command.GET_NETWORK_CONDITIONS));
740
+ }
741
+
742
+ /**
743
+ * Schedules a command to set Chrome network emulation settings.
744
+ *
745
+ * __Sample Usage:__
746
+ *
747
+ * driver.setNetworkConditions({
748
+ * offline: false,
749
+ * latency: 5, // Additional latency (ms).
750
+ * download_throughput: 500 * 1024, // Maximal aggregated download throughput.
751
+ * upload_throughput: 500 * 1024 // Maximal aggregated upload throughput.
752
+ * });
753
+ *
754
+ * @param {Object} spec Defines the network conditions to set
755
+ * @return {!Promise<void>} A promise that will be resolved when network
756
+ * emulation settings are set.
757
+ */
758
+ setNetworkConditions(spec) {
759
+ if (!spec || typeof spec !== 'object') {
760
+ throw TypeError('setNetworkConditions called with non-network-conditions parameter');
761
+ }
762
+ return this.execute(
763
+ new command.Command(Command.SET_NETWORK_CONDITIONS)
764
+ .setParameter('network_conditions', spec));
765
+ }
766
+
767
+ /**
768
+ * Sends an arbitrary devtools command to the browser.
769
+ *
770
+ * @param {string} cmd The name of the command to send.
771
+ * @param {Object=} params The command parameters.
772
+ * @return {!Promise<void>} A promise that will be resolved when the command
773
+ * has finished.
774
+ * @see <https://chromedevtools.github.io/devtools-protocol/>
775
+ */
776
+ sendDevToolsCommand(cmd, params = {}) {
777
+ return this.execute(
778
+ new command.Command(Command.SEND_DEVTOOLS_COMMAND)
779
+ .setParameter('cmd', cmd)
780
+ .setParameter('params', params));
781
+ }
782
+
783
+ /**
784
+ * Sends a DevTools command to change Chrome's download directory.
785
+ *
786
+ * @param {string} path The desired download directory.
787
+ * @return {!Promise<void>} A promise that will be resolved when the command
788
+ * has finished.
789
+ * @see #sendDevToolsCommand
790
+ */
791
+ async setDownloadPath(path) {
792
+ if (!path || typeof path !== 'string') {
793
+ throw new error.InvalidArgumentError('invalid download path');
794
+ }
795
+ const stat = await io.stat(path);
796
+ if (!stat.isDirectory()) {
797
+ throw new error.InvalidArgumentError('not a directory: ' + path);
798
+ }
799
+ return this.sendDevToolsCommand('Page.setDownloadBehavior', {
800
+ 'behavior': 'allow',
801
+ 'downloadPath': path
802
+ });
729
803
  }
730
804
  }
731
805
 
@@ -738,3 +812,4 @@ exports.Options = Options;
738
812
  exports.ServiceBuilder = ServiceBuilder;
739
813
  exports.getDefaultService = getDefaultService;
740
814
  exports.setDefaultService = setDefaultService;
815
+ exports.locateSynchronously = locateSynchronously;