selenium-webdriver 3.0.1 → 3.5.0

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 (67) hide show
  1. package/CHANGES.md +92 -0
  2. package/NOTICE +1 -1
  3. package/README.md +11 -14
  4. package/chrome.js +125 -36
  5. package/edge.js +3 -3
  6. package/example/async_await_test.js +68 -0
  7. package/example/chrome_headless.js +47 -0
  8. package/example/chrome_mobile_emulation.js +1 -1
  9. package/example/firefox_channels.js +73 -0
  10. package/example/google_search_test.js +1 -1
  11. package/firefox/binary.js +114 -57
  12. package/firefox/extension.js +118 -81
  13. package/firefox/index.js +67 -135
  14. package/firefox/profile.js +13 -49
  15. package/ie.js +2 -2
  16. package/index.js +6 -3
  17. package/io/index.js +59 -0
  18. package/io/zip.js +214 -0
  19. package/lib/README +2 -3
  20. package/lib/actions.js +4 -4
  21. package/lib/atoms/getAttribute.js +9 -11
  22. package/lib/atoms/is-displayed.js +102 -0
  23. package/lib/by.js +9 -1
  24. package/lib/capabilities.js +5 -5
  25. package/lib/command.js +2 -0
  26. package/lib/error.js +29 -4
  27. package/lib/events.js +1 -1
  28. package/lib/firefox/webdriver.json +2 -1
  29. package/lib/http.js +84 -69
  30. package/lib/input.js +1 -1
  31. package/lib/promise.js +205 -81
  32. package/lib/symbols.js +1 -1
  33. package/lib/test/data/click_tests/disabled_element.html +12 -0
  34. package/lib/test/data/firefox/webextension.xpi +0 -0
  35. package/lib/test/data/key_logger.html +34 -0
  36. package/lib/test/data/nestedElements.html +10 -1
  37. package/lib/test/data/single_text_input.html +12 -0
  38. package/lib/test/index.js +9 -35
  39. package/lib/until.js +1 -1
  40. package/lib/webdriver.js +130 -34
  41. package/net/portprober.js +1 -1
  42. package/opera.js +2 -2
  43. package/package.json +2 -2
  44. package/phantomjs.js +2 -2
  45. package/remote/index.js +17 -11
  46. package/safari.js +47 -8
  47. package/test/firefox/extension_test.js +41 -17
  48. package/test/firefox/firefox_test.js +70 -23
  49. package/test/firefox/profile_test.js +14 -59
  50. package/test/http/http_test.js +18 -0
  51. package/test/{io_test.js → io/io_test.js} +40 -1
  52. package/test/io/zip_test.js +128 -0
  53. package/test/lib/by_test.js +21 -0
  54. package/test/lib/error_test.js +20 -1
  55. package/test/lib/http_test.js +41 -2
  56. package/test/lib/promise_test.js +19 -0
  57. package/test/lib/until_test.js +28 -13
  58. package/test/lib/webdriver_test.js +107 -3
  59. package/test/page_loading_test.js +0 -6
  60. package/test/rect_test.js +60 -0
  61. package/test/remote_test.js +3 -3
  62. package/test/session_test.js +68 -23
  63. package/testing/index.js +16 -5
  64. package/lib/atoms/isDisplayed.js +0 -106
  65. package/lib/firefox/amd64/libnoblur64.so +0 -0
  66. package/lib/firefox/i386/libnoblur.so +0 -0
  67. package/lib/firefox/webdriver.xpi +0 -0
package/firefox/index.js CHANGED
@@ -26,28 +26,31 @@
26
26
  *
27
27
  * __Customizing the Firefox Profile__
28
28
  *
29
- * The {@link Profile} class may be used to configure the browser profile used
30
- * with WebDriver, with functions to install additional
29
+ * The {@linkplain Profile} class may be used to configure the browser profile
30
+ * used with WebDriver, with functions to install additional
31
31
  * {@linkplain Profile#addExtension extensions}, configure browser
32
32
  * {@linkplain Profile#setPreference preferences}, and more. For example, you
33
33
  * may wish to include Firebug:
34
34
  *
35
- * var firefox = require('selenium-webdriver/firefox');
35
+ * const {Builder} = require('selenium-webdriver');
36
+ * const firefox = require('selenium-webdriver/firefox');
36
37
  *
37
- * var profile = new firefox.Profile();
38
+ * let profile = new firefox.Profile();
38
39
  * profile.addExtension('/path/to/firebug.xpi');
39
40
  * profile.setPreference('extensions.firebug.showChromeErrors', true);
40
41
  *
41
- * var options = new firefox.Options().setProfile(profile);
42
- * var driver = new firefox.Driver(options);
42
+ * let options = new firefox.Options().setProfile(profile);
43
+ * let driver = new Builder()
44
+ * .forBrowser('firefox')
45
+ * .setFirefoxOptions(options)
46
+ * .build();
43
47
  *
44
- * The {@link Profile} class may also be used to configure WebDriver based on a
45
- * pre-existing browser profile:
48
+ * The {@linkplain Profile} class may also be used to configure WebDriver based
49
+ * on a pre-existing browser profile:
46
50
  *
47
- * var profile = new firefox.Profile(
51
+ * let profile = new firefox.Profile(
48
52
  * '/usr/local/home/bob/.mozilla/firefox/3fgog75h.testing');
49
- * var options = new firefox.Options().setProfile(profile);
50
- * var driver = new firefox.Driver(options);
53
+ * let options = new firefox.Options().setProfile(profile);
51
54
  *
52
55
  * The FirefoxDriver will _never_ modify a pre-existing profile; instead it will
53
56
  * create a copy for it to modify. By extension, there are certain browser
@@ -56,21 +59,35 @@
56
59
  *
57
60
  * __Using a Custom Firefox Binary__
58
61
  *
59
- * On Windows and OSX, the FirefoxDriver will search for Firefox in its
62
+ * On Windows and MacOS, the FirefoxDriver will search for Firefox in its
60
63
  * default installation location:
61
64
  *
62
- * * Windows: C:\Program Files and C:\Program Files (x86).
63
- * * Mac OS X: /Applications/Firefox.app
65
+ * - Windows: C:\Program Files and C:\Program Files (x86).
66
+ * - MacOS: /Applications/Firefox.app
64
67
  *
65
- * For Linux, Firefox will be located on the PATH: `$(where firefox)`.
68
+ * For Linux, Firefox will always be located on the PATH: `$(where firefox)`.
66
69
  *
67
- * You can configure WebDriver to start use a custom Firefox installation with
68
- * the {@link Binary} class:
70
+ * Several methods are provided for starting Firefox with a custom executable.
71
+ * First, on Windows and MacOS, you may configure WebDriver to check the default
72
+ * install location for a non-release channel. If the requested channel cannot
73
+ * be found in its default location, WebDriver will fallback to searching your
74
+ * PATH. _Note:_ on Linux, Firefox is _always_ located on your path, regardless
75
+ * of the requested channel.
69
76
  *
70
- * var firefox = require('selenium-webdriver/firefox');
71
- * var binary = new firefox.Binary('/my/firefox/install/dir/firefox-bin');
72
- * var options = new firefox.Options().setBinary(binary);
73
- * var driver = new firefox.Driver(options);
77
+ * const {Builder} = require('selenium-webdriver');
78
+ * const firefox = require('selenium-webdriver/firefox');
79
+ *
80
+ * let options = new firefox.Options().setBinary(firefox.Channel.NIGHTLY);
81
+ * let driver = new Builder()
82
+ * .forBrowser('firefox')
83
+ * .setFirefoxOptions(options)
84
+ * .build();
85
+ *
86
+ * On all platforms, you may configrue WebDriver to use a Firefox specific
87
+ * executable:
88
+ *
89
+ * let options = new firefox.Options()
90
+ * .setBinary('/my/firefox/install/dir/firefox-bin');
74
91
  *
75
92
  * __Remote Testing__
76
93
  *
@@ -84,27 +101,19 @@
84
101
  * binaries are never copied to remote machines and must be referenced by
85
102
  * installation path.
86
103
  *
87
- * var options = new firefox.Options()
104
+ * const {Builder} = require('selenium-webdriver');
105
+ * const firefox = require('selenium-webdriver/firefox');
106
+ *
107
+ * let options = new firefox.Options()
88
108
  * .setProfile('/profile/path/on/remote/host')
89
109
  * .setBinary('/install/dir/on/remote/host/firefox-bin');
90
110
  *
91
- * var driver = new (require('selenium-webdriver')).Builder()
111
+ * let driver = new Builder()
92
112
  * .forBrowser('firefox')
93
113
  * .usingServer('http://127.0.0.1:4444/wd/hub')
94
114
  * .setFirefoxOptions(options)
95
115
  * .build();
96
116
  *
97
- * __Testing Older Versions of Firefox__
98
- *
99
- * To test versions of Firefox prior to Firefox 47, you must disable the use of
100
- * the geckodriver using the {@link Options} class.
101
- *
102
- * var options = new firefox.Options().useGeckoDriver(false);
103
- * var driver = new firefox.Driver(options);
104
- *
105
- * Alternatively, you may disable the geckodriver at runtime by setting the
106
- * environment variable `SELENIUM_MARIONETTE=false`.
107
- *
108
117
  * [geckodriver release]: https://github.com/mozilla/geckodriver/releases/
109
118
  * [PATH]: http://en.wikipedia.org/wiki/PATH_%28variable%29
110
119
  */
@@ -113,7 +122,7 @@
113
122
 
114
123
  const url = require('url');
115
124
 
116
- const Binary = require('./binary').Binary,
125
+ const {Binary, Channel} = require('./binary'),
117
126
  Profile = require('./profile').Profile,
118
127
  decodeProfile = require('./profile').decode,
119
128
  http = require('../http'),
@@ -144,8 +153,8 @@ const Capability = {
144
153
  BINARY: 'firefox_binary',
145
154
 
146
155
  /**
147
- * Specifies whether to use Mozilla's Marionette, or the legacy FirefoxDriver
148
- * from the Selenium project. Defaults to false.
156
+ * @deprecated This will be removed in a future release. There is no
157
+ * replacement.
149
158
  */
150
159
  MARIONETTE: 'marionette',
151
160
 
@@ -174,9 +183,6 @@ class Options {
174
183
 
175
184
  /** @private {?capabilities.ProxyConfig} */
176
185
  this.proxy_ = null;
177
-
178
- /** @private {boolean} */
179
- this.marionette_ = true;
180
186
  }
181
187
 
182
188
  /**
@@ -196,16 +202,24 @@ class Options {
196
202
  }
197
203
 
198
204
  /**
199
- * Sets the binary to use. The binary may be specified as the path to a Firefox
200
- * executable, or as a {@link Binary} object.
205
+ * Sets the binary to use. The binary may be specified as the path to a
206
+ * Firefox executable, a specific {@link Channel}, or as a {@link Binary}
207
+ * object.
201
208
  *
202
- * @param {(string|!Binary)} binary The binary to use.
209
+ * @param {(string|!Binary|!Channel)} binary The binary to use.
203
210
  * @return {!Options} A self reference.
211
+ * @throws {TypeError} If `binary` is an invalid type.
204
212
  */
205
213
  setBinary(binary) {
206
- if (typeof binary === 'string') {
214
+ if (typeof binary === 'string' || binary instanceof Channel) {
207
215
  binary = new Binary(binary);
208
216
  }
217
+
218
+ if (!(binary instanceof Binary)) {
219
+ throw TypeError(
220
+ 'binary must be a string path, Channel, or Binary object');
221
+ }
222
+
209
223
  this.binary_ = binary;
210
224
  return this;
211
225
  }
@@ -232,14 +246,10 @@ class Options {
232
246
  }
233
247
 
234
248
  /**
235
- * Sets whether to use Mozilla's geckodriver to drive the browser. This option
236
- * is enabled by default and required for Firefox 47+.
237
- *
238
- * @param {boolean} enable Whether to enable the geckodriver.
239
- * @see https://github.com/mozilla/geckodriver
249
+ * @deprecated This method has been deprecated and will be removed in the next
250
+ * release. It is a no-op.
240
251
  */
241
252
  useGeckoDriver(enable) {
242
- this.marionette_ = enable;
243
253
  return this;
244
254
  }
245
255
 
@@ -262,7 +272,6 @@ class Options {
262
272
  if (this.profile_) {
263
273
  caps.set(Capability.PROFILE, this.profile_);
264
274
  }
265
- caps.set(Capability.MARIONETTE, this.marionette_);
266
275
  return caps;
267
276
  }
268
277
  }
@@ -298,33 +307,13 @@ function findGeckoDriver() {
298
307
  throw Error(
299
308
  'The ' + GECKO_DRIVER_EXE + ' executable could not be found on the current ' +
300
309
  'PATH. Please download the latest version from ' +
301
- 'https://github.com/mozilla/geckodriver/releases/' +
302
- 'WebDriver and ensure it can be found on your PATH.');
310
+ 'https://github.com/mozilla/geckodriver/releases/ ' +
311
+ 'and ensure it can be found on your PATH.');
303
312
  }
304
313
  return exe;
305
314
  }
306
315
 
307
316
 
308
- /**
309
- * @param {(Profile|string)} profile The profile to prepare.
310
- * @param {number} port The port the FirefoxDriver should listen on.
311
- * @return {!Promise<string>} a promise for the path to the profile directory.
312
- */
313
- function prepareProfile(profile, port) {
314
- if (typeof profile === 'string') {
315
- return decodeProfile(/** @type {string} */(profile)).then(dir => {
316
- profile = new Profile(dir);
317
- profile.setPreference('webdriver_firefox_port', port);
318
- return profile.writeToDisk();
319
- });
320
- }
321
-
322
- profile = profile || new Profile;
323
- profile.setPreference('webdriver_firefox_port', port);
324
- return profile.writeToDisk();
325
- }
326
-
327
-
328
317
  function normalizeProxyConfiguration(config) {
329
318
  if ('manual' === config.proxyType) {
330
319
  if (config.ftpProxy && !config.ftpProxyPort) {
@@ -457,7 +446,7 @@ var DriverSpec;
457
446
  * @return {DriverSpec}
458
447
  */
459
448
  function createGeckoDriver(executor, caps, profile, binary) {
460
- let firefoxOptions = {};
449
+ let firefoxOptions = caps.get('moz:firefoxOptions') || {};
461
450
  caps.set('moz:firefoxOptions', firefoxOptions);
462
451
 
463
452
  if (binary) {
@@ -529,47 +518,6 @@ function createGeckoDriver(executor, caps, profile, binary) {
529
518
  }
530
519
 
531
520
 
532
- /**
533
- * @param {!capabilities.Capabilities} caps
534
- * @param {Profile} profile
535
- * @param {!Binary} binary
536
- * @return {DriverSpec}
537
- */
538
- function createLegacyDriver(caps, profile, binary, flow) {
539
- profile = profile || new Profile;
540
-
541
- let freePort = portprober.findFreePort();
542
- let preparedProfile =
543
- freePort.then(port => prepareProfile(profile, port));
544
- let command = preparedProfile.then(dir => binary.launch(dir));
545
-
546
- let serverUrl = command.then(() => freePort)
547
- .then(function(/** number */port) {
548
- let serverUrl = url.format({
549
- protocol: 'http',
550
- hostname: net.getLoopbackAddress(),
551
- port: port + '',
552
- pathname: '/hub'
553
- });
554
- let ready = httpUtil.waitForServer(serverUrl, 45 * 1000);
555
- return ready.then(() => serverUrl);
556
- });
557
-
558
- return {
559
- executor: createExecutor(serverUrl),
560
- capabilities: caps,
561
- onQuit: function() {
562
- return command.then(command => {
563
- command.kill();
564
- return preparedProfile.then(io.rmDir)
565
- .then(() => command.result(),
566
- () => command.result());
567
- });
568
- }
569
- };
570
- }
571
-
572
-
573
521
  /**
574
522
  * A WebDriver client for Firefox.
575
523
  */
@@ -619,26 +567,9 @@ class Driver extends webdriver.WebDriver {
619
567
  caps.delete(Capability.PROFILE);
620
568
  }
621
569
 
622
- // Users must now explicitly disable marionette to use the legacy
623
- // FirefoxDriver.
624
- let noMarionette =
625
- caps.get(Capability.MARIONETTE) === false
626
- || /^0|false$/i.test(process.env['SELENIUM_MARIONETTE']);
627
- let useMarionette = !noMarionette;
628
-
629
- let spec;
630
- if (useMarionette) {
631
- spec = createGeckoDriver(opt_executor, caps, profile, binary);
632
- } else {
633
- if (opt_executor) {
634
- throw Error('You may not use a custom command executor with the legacy'
635
- + ' FirefoxDriver');
636
- }
637
- spec = createLegacyDriver(caps, profile, binary, opt_flow);
638
- }
639
-
640
- return /** @type {!Driver} */(webdriver.WebDriver.createSession(
641
- spec.executor, spec.capabilities, opt_flow, this, spec.onQuit));
570
+ let spec = createGeckoDriver(opt_executor, caps, profile, binary);
571
+ return /** @type {!Driver} */(super.createSession(
572
+ spec.executor, spec.capabilities, opt_flow, spec.onQuit));
642
573
  }
643
574
 
644
575
  /**
@@ -687,6 +618,7 @@ class Driver extends webdriver.WebDriver {
687
618
 
688
619
 
689
620
  exports.Binary = Binary;
621
+ exports.Channel = Channel;
690
622
  exports.Context = Context;
691
623
  exports.Driver = Driver;
692
624
  exports.Options = Options;
@@ -22,14 +22,14 @@
22
22
 
23
23
  'use strict';
24
24
 
25
- const AdmZip = require('adm-zip'),
26
- fs = require('fs'),
25
+ const fs = require('fs'),
27
26
  path = require('path'),
28
27
  vm = require('vm');
29
28
 
30
29
  const isDevMode = require('../lib/devmode'),
31
30
  Symbols = require('../lib/symbols'),
32
31
  io = require('../io'),
32
+ {Zip, unzip} = require('../io/zip'),
33
33
  extension = require('./extension');
34
34
 
35
35
 
@@ -38,17 +38,6 @@ const WEBDRIVER_PREFERENCES_PATH = isDevMode
38
38
  ? path.join(__dirname, '../../../firefox-driver/webdriver.json')
39
39
  : path.join(__dirname, '../lib/firefox/webdriver.json');
40
40
 
41
- /** @const */
42
- const WEBDRIVER_EXTENSION_PATH = isDevMode
43
- ? path.join(__dirname,
44
- '../../../../build/javascript/firefox-driver/webdriver.xpi')
45
- : path.join(__dirname, '../lib/firefox/webdriver.xpi');
46
-
47
- /** @const */
48
- const WEBDRIVER_EXTENSION_NAME = 'fxdriver@googlecode.com';
49
-
50
-
51
-
52
41
  /** @type {Object} */
53
42
  var defaultPreferences = null;
54
43
 
@@ -130,13 +119,10 @@ function writeUserPrefs(prefs, dir) {
130
119
  * @param {!Array.<string>} extensions The extensions to install, as a
131
120
  * path to an unpacked extension directory or a path to a xpi file.
132
121
  * @param {string} dir The profile directory to install to.
133
- * @param {boolean=} opt_excludeWebDriverExt Whether to skip installation of
134
- * the default WebDriver extension.
135
122
  * @return {!Promise<string>} A promise for the main profile directory
136
123
  * once all extensions have been installed.
137
124
  */
138
- function installExtensions(extensions, dir, opt_excludeWebDriverExt) {
139
- var hasWebDriver = !!opt_excludeWebDriverExt;
125
+ function installExtensions(extensions, dir) {
140
126
  var next = 0;
141
127
  var extensionDir = path.join(dir, 'extensions');
142
128
 
@@ -145,11 +131,7 @@ function installExtensions(extensions, dir, opt_excludeWebDriverExt) {
145
131
 
146
132
  function installNext() {
147
133
  if (next >= extensions.length) {
148
- if (hasWebDriver) {
149
- fulfill(dir);
150
- } else {
151
- install(WEBDRIVER_EXTENSION_PATH);
152
- }
134
+ fulfill(dir);
153
135
  } else {
154
136
  install(extensions[next++]);
155
137
  }
@@ -157,7 +139,6 @@ function installExtensions(extensions, dir, opt_excludeWebDriverExt) {
157
139
 
158
140
  function install(ext) {
159
141
  extension.install(ext, extensionDir).then(function(id) {
160
- hasWebDriver = hasWebDriver || (id === WEBDRIVER_EXTENSION_NAME);
161
142
  installNext();
162
143
  }, reject);
163
144
  }
@@ -173,14 +154,10 @@ function installExtensions(extensions, dir, opt_excludeWebDriverExt) {
173
154
  */
174
155
  function decode(data) {
175
156
  return io.tmpFile().then(function(file) {
176
- var buf = new Buffer(data, 'base64');
157
+ let buf = new Buffer(data, 'base64');
177
158
  return io.write(file, buf)
178
159
  .then(io.tmpDir)
179
- .then(function(dir) {
180
- var zip = new AdmZip(file);
181
- zip.extractAllTo(dir); // Sync only? Why?? :-(
182
- return dir;
183
- });
160
+ .then(dir => unzip(file, dir));
184
161
  });
185
162
  }
186
163
 
@@ -353,14 +330,10 @@ class Profile {
353
330
 
354
331
  /**
355
332
  * Writes this profile to disk.
356
- * @param {boolean=} opt_excludeWebDriverExt Whether to exclude the WebDriver
357
- * extension from the generated profile. Used to reduce the size of an
358
- * {@link #encode() encoded profile} since the server will always install
359
- * the extension itself.
360
333
  * @return {!Promise<string>} A promise for the path to the new profile
361
334
  * directory.
362
335
  */
363
- writeToDisk(opt_excludeWebDriverExt) {
336
+ writeToDisk() {
364
337
  var profileDir = io.tmpDir();
365
338
  if (this.template_) {
366
339
  profileDir = profileDir.then(function(dir) {
@@ -382,7 +355,7 @@ class Profile {
382
355
  return profileDir.then(function(dir) {
383
356
  return writeUserPrefs(prefs, dir);
384
357
  }).then(function(dir) {
385
- return installExtensions(extensions, dir, !!opt_excludeWebDriverExt);
358
+ return installExtensions(extensions, dir);
386
359
  });
387
360
  }
388
361
 
@@ -395,20 +368,11 @@ class Profile {
395
368
  *
396
369
  */
397
370
  encode() {
398
- return this.writeToDisk(true).then(function(dir) {
399
- var zip = new AdmZip();
400
- zip.addLocalFolder(dir, '');
401
- // Stored compression, see https://en.wikipedia.org/wiki/Zip_(file_format)
402
- zip.getEntries().forEach(function(entry) {
403
- entry.header.method = 0;
404
- });
405
-
406
- return io.tmpFile().then(function(file) {
407
- zip.writeZip(file); // Sync! Why oh why :-(
408
- return io.read(file);
409
- });
410
- }).then(function(data) {
411
- return data.toString('base64');
371
+ return this.writeToDisk().then(function(dir) {
372
+ let zip = new Zip;
373
+ return zip.addDir(dir)
374
+ .then(() => zip.toBuffer())
375
+ .then(buf => buf.toString('base64'));
412
376
  });
413
377
  }
414
378
 
package/ie.js CHANGED
@@ -420,8 +420,8 @@ class Driver extends webdriver.WebDriver {
420
420
  var client = service.start().then(url => new http.HttpClient(url));
421
421
  var executor = new http.Executor(client);
422
422
 
423
- return /** @type {!Driver} */(webdriver.WebDriver.createSession(
424
- executor, caps, opt_flow, this, () => service.kill()));
423
+ return /** @type {!Driver} */(super.createSession(
424
+ executor, caps, opt_flow, () => service.kill()));
425
425
  }
426
426
 
427
427
  /**
package/index.js CHANGED
@@ -58,7 +58,7 @@ var seleniumServer;
58
58
  * Starts an instance of the Selenium server if not yet running.
59
59
  * @param {string} jar Path to the server jar to use.
60
60
  * @return {!Promise<string>} A promise for the server's
61
- * addrss once started.
61
+ * address once started.
62
62
  */
63
63
  function startSeleniumServer(jar) {
64
64
  if (!seleniumServer) {
@@ -131,7 +131,10 @@ const THENABLE_DRIVERS = new Map;
131
131
  function createDriver(ctor, ...args) {
132
132
  let thenableWebDriverProxy = THENABLE_DRIVERS.get(ctor);
133
133
  if (!thenableWebDriverProxy) {
134
- /** @implements {ThenableWebDriver} */
134
+ /**
135
+ * @extends {WebDriver} // Needed since `ctor` is dynamically typed.
136
+ * @implements {ThenableWebDriver}
137
+ */
135
138
  thenableWebDriverProxy = class extends ctor {
136
139
  /**
137
140
  * @param {!IThenable<!Session>} session
@@ -160,7 +163,7 @@ function createDriver(ctor, ...args) {
160
163
  /** @override */
161
164
  this.catch = pd.then.bind(pd);
162
165
  }
163
- }
166
+ };
164
167
  promise.CancellableThenable.addImplementation(thenableWebDriverProxy);
165
168
  THENABLE_DRIVERS.set(ctor, thenableWebDriverProxy);
166
169
  }
package/io/index.js CHANGED
@@ -298,3 +298,62 @@ exports.mkdir = function(aPath) {
298
298
  });
299
299
  });
300
300
  };
301
+
302
+
303
+ /**
304
+ * Recursively creates a directory and any ancestors that do not yet exist.
305
+ *
306
+ * @param {string} dir The directory path to create.
307
+ * @return {!Promise<string>} A promise that will resolve with the path of the
308
+ * created directory.
309
+ */
310
+ exports.mkdirp = function mkdirp(dir) {
311
+ return checkedCall(callback => {
312
+ fs.mkdir(dir, undefined, err => {
313
+ if (!err) {
314
+ callback(null, dir);
315
+ return;
316
+ }
317
+
318
+ switch (err.code) {
319
+ case 'EEXIST':
320
+ callback(null, dir);
321
+ return;
322
+ case 'ENOENT':
323
+ return mkdirp(path.dirname(dir))
324
+ .then(() => mkdirp(dir))
325
+ .then(() => callback(null, dir), err => callback(err));
326
+ default:
327
+ callback(err);
328
+ return;
329
+ }
330
+ });
331
+ });
332
+ };
333
+
334
+
335
+ /**
336
+ * Recursively walks a directory, returning a promise that will resolve with
337
+ * a list of all files/directories seen.
338
+ *
339
+ * @param {string} rootPath the directory to walk.
340
+ * @return {!Promise<!Array<{path: string, dir: boolean}>>} a promise that will
341
+ * resolve with a list of entries seen. For each entry, the recorded path
342
+ * will be relative to `rootPath`.
343
+ */
344
+ exports.walkDir = function(rootPath) {
345
+ let seen = [];
346
+ return (function walk(dir) {
347
+ return checkedCall(callback => fs.readdir(dir, callback))
348
+ .then(files => Promise.all(files.map(file => {
349
+ file = path.join(dir, file);
350
+ return checkedCall(cb => fs.stat(file, cb)).then(stats => {
351
+ seen.push({
352
+ path: path.relative(rootPath, file),
353
+ dir: stats.isDirectory()
354
+ });
355
+ return stats.isDirectory() && walk(file);
356
+ });
357
+ })));
358
+ })(rootPath).then(() => seen);
359
+ };