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/binary.js CHANGED
@@ -47,27 +47,17 @@ const NO_FOCUS_LIB_AMD64 = isDevMode ?
47
47
  const X_IGNORE_NO_FOCUS_LIB = 'x_ignore_nofocus.so';
48
48
 
49
49
 
50
- let foundBinary = null;
51
- let foundDevBinary = null;
52
-
53
-
54
50
  /**
55
- * Checks the default Windows Firefox locations in Program Files.
56
- *
57
- * @param {boolean=} opt_dev Whether to find the Developer Edition.
51
+ * @param {string} file Path to the file to find, relative to the program files
52
+ * root.
58
53
  * @return {!Promise<?string>} A promise for the located executable.
59
54
  * The promise will resolve to {@code null} if Firefox was not found.
60
55
  */
61
- function defaultWindowsLocation(opt_dev) {
62
- var files = [
56
+ function findInProgramFiles(file) {
57
+ let files = [
63
58
  process.env['PROGRAMFILES'] || 'C:\\Program Files',
64
59
  process.env['PROGRAMFILES(X86)'] || 'C:\\Program Files (x86)'
65
- ].map(function(prefix) {
66
- if (opt_dev) {
67
- return path.join(prefix, 'Firefox Developer Edition\\firefox.exe');
68
- }
69
- return path.join(prefix, 'Mozilla Firefox\\firefox.exe');
70
- });
60
+ ].map(prefix => path.join(prefix, file));
71
61
  return io.exists(files[0]).then(function(exists) {
72
62
  return exists ? files[0] : io.exists(files[1]).then(function(exists) {
73
63
  return exists ? files[1] : null;
@@ -77,49 +67,104 @@ function defaultWindowsLocation(opt_dev) {
77
67
 
78
68
 
79
69
  /**
80
- * Locates the Firefox binary for the current system.
70
+ * Provides methods for locating the executable for a Firefox release channel
71
+ * on Windows and MacOS. For other systems (i.e. Linux), Firefox will always
72
+ * be located on the system PATH.
81
73
  *
82
- * @param {boolean=} opt_dev Whether to find the Developer Edition. This only
83
- * used on Windows and OSX.
84
- * @return {!Promise<string>} A promise for the located binary. The promise will
85
- * be rejected if Firefox cannot be located.
74
+ * @final
86
75
  */
87
- function findFirefox(opt_dev) {
88
- if (opt_dev && foundDevBinary) {
89
- return foundDevBinary;
76
+ class Channel {
77
+ /**
78
+ * @param {string} darwin The path to check when running on MacOS.
79
+ * @param {string} win32 The path to check when running on Windows.
80
+ */
81
+ constructor(darwin, win32) {
82
+ /** @private @const */ this.darwin_ = darwin;
83
+ /** @private @const */ this.win32_ = win32;
84
+ /** @private {Promise<string>} */
85
+ this.found_ = null;
90
86
  }
91
87
 
92
- if (!opt_dev && foundBinary) {
93
- return foundBinary;
88
+ /**
89
+ * Attempts to locate the Firefox executable for this release channel. This
90
+ * will first check the default installation location for the channel before
91
+ * checking the user's PATH. The returned promise will be rejected if Firefox
92
+ * can not be found.
93
+ *
94
+ * @return {!Promise<string>} A promise for the location of the located
95
+ * Firefox executable.
96
+ */
97
+ locate() {
98
+ if (this.found_) {
99
+ return this.found_;
100
+ }
101
+
102
+ let found;
103
+ switch (process.platform) {
104
+ case 'darwin':
105
+ found = io.exists(this.darwin_)
106
+ .then(exists => exists ? this.darwin_ : io.findInPath('firefox'));
107
+ break;
108
+
109
+ case 'win32':
110
+ found = findInProgramFiles(this.win32_)
111
+ .then(found => found || io.findInPath('firefox.exe'));
112
+ break;
113
+
114
+ default:
115
+ found = Promise.resolve(io.findInPath('firefox'));
116
+ break;
117
+ }
118
+
119
+ this.found_ = found.then(found => {
120
+ if (found) {
121
+ // TODO: verify version info.
122
+ return found;
123
+ }
124
+ throw Error('Could not locate Firefox on the current system');
125
+ });
126
+ return this.found_;
94
127
  }
128
+ }
95
129
 
96
- let found;
97
- if (process.platform === 'darwin') {
98
- let exe = opt_dev
99
- ? '/Applications/FirefoxDeveloperEdition.app/Contents/MacOS/firefox-bin'
100
- : '/Applications/Firefox.app/Contents/MacOS/firefox-bin';
101
- found = io.exists(exe).then(exists => exists ? exe : null);
102
130
 
103
- } else if (process.platform === 'win32') {
104
- found = defaultWindowsLocation(opt_dev);
131
+ /**
132
+ * Firefox's developer channel.
133
+ * @const
134
+ * @see <https://www.mozilla.org/en-US/firefox/channel/desktop/#aurora>
135
+ */
136
+ Channel.AURORA = new Channel(
137
+ '/Applications/FirefoxDeveloperEdition.app/Contents/MacOS/firefox-bin',
138
+ 'Firefox Developer Edition\\firefox.exe');
105
139
 
106
- } else {
107
- found = Promise.resolve(io.findInPath('firefox'));
108
- }
140
+ /**
141
+ * Firefox's beta channel. Note this is provided mainly for convenience as
142
+ * the beta channel has the same installation location as the main release
143
+ * channel.
144
+ * @const
145
+ * @see <https://www.mozilla.org/en-US/firefox/channel/desktop/#beta>
146
+ */
147
+ Channel.BETA = new Channel(
148
+ '/Applications/Firefox.app/Contents/MacOS/firefox-bin',
149
+ 'Mozilla Firefox\\firefox.exe');
109
150
 
110
- found = found.then(found => {
111
- if (found) {
112
- return found;
113
- }
114
- throw Error('Could not locate Firefox on the current system');
115
- });
151
+ /**
152
+ * Firefox's release channel.
153
+ * @const
154
+ * @see <https://www.mozilla.org/en-US/firefox/desktop/>
155
+ */
156
+ Channel.RELEASE = new Channel(
157
+ '/Applications/Firefox.app/Contents/MacOS/firefox-bin',
158
+ 'Mozilla Firefox\\firefox.exe');
116
159
 
117
- if (opt_dev) {
118
- return foundDevBinary = found;
119
- } else {
120
- return foundBinary = found;
121
- }
122
- }
160
+ /**
161
+ * Firefox's nightly release channel.
162
+ * @const
163
+ * @see <https://www.mozilla.org/en-US/firefox/channel/desktop/#nightly>
164
+ */
165
+ Channel.NIGHTLY = new Channel(
166
+ '/Applications/FirefoxNightly.app/Contents/MacOS/firefox-bin',
167
+ 'Nightly\\firefox.exe');
123
168
 
124
169
 
125
170
  /**
@@ -151,20 +196,23 @@ function installNoFocusLibs(profileDir) {
151
196
  * use with WebDriver.
152
197
  *
153
198
  * If created _without_ a path for the Firefox binary to use, this class will
154
- * attempt to find Firefox when {@link #launch()} is called. For OSX and
199
+ * attempt to find Firefox when {@link #launch()} is called. For MacOS and
155
200
  * Windows, this class will look for Firefox in the current platform's default
156
- * installation location (e.g. /Applications/Firefox.app on OSX). For all other
157
- * platforms, the Firefox executable must be available on your system `PATH`.
201
+ * installation location (e.g. /Applications/Firefox.app on MacOS). For all
202
+ * other platforms, the Firefox executable must be available on your system
203
+ * `PATH`.
158
204
  *
159
205
  * @final
160
206
  */
161
207
  class Binary {
162
208
  /**
163
- * @param {string=} opt_exe Path to the Firefox binary to use.
209
+ * @param {?(string|Channel)=} opt_exeOrChannel Either the path to a specific
210
+ * Firefox binary to use, or a {@link Channel} instance that describes
211
+ * how to locate the desired Firefox version.
164
212
  */
165
- constructor(opt_exe) {
166
- /** @private {(string|undefined)} */
167
- this.exe_ = opt_exe;
213
+ constructor(opt_exeOrChannel) {
214
+ /** @private {?(string|Channel)} */
215
+ this.exe_ = opt_exeOrChannel || null;
168
216
 
169
217
  /** @private {!Array.<string>} */
170
218
  this.args_ = [];
@@ -187,7 +235,7 @@ class Binary {
187
235
  * on the current system.
188
236
  */
189
237
  getExe() {
190
- return this.exe_;
238
+ return typeof this.exe_ === 'string' ? this.exe_ : undefined;
191
239
  }
192
240
 
193
241
  /**
@@ -223,6 +271,8 @@ class Binary {
223
271
  *
224
272
  * @param {boolean=} opt_use Whether to use the developer edition. Defaults to
225
273
  * true.
274
+ * @deprecated Use the {@link Channel} class to indicate the desired Firefox
275
+ * version when creating a new binary: `new Binary(Channel.AURORA)`.
226
276
  */
227
277
  useDevEdition(opt_use) {
228
278
  this.devEdition_ = opt_use === undefined || !!opt_use;
@@ -238,7 +288,13 @@ class Binary {
238
288
  * used by this instance.
239
289
  */
240
290
  locate() {
241
- return Promise.resolve(this.exe_ || findFirefox(this.devEdition_));
291
+ if (typeof this.exe_ === 'string') {
292
+ return Promise.resolve(this.exe_);
293
+ } else if (this.exe_ instanceof Channel) {
294
+ return this.exe_.locate();
295
+ }
296
+ let channel = this.devEdition_ ? Channel.AURORA : Channel.RELEASE;
297
+ return channel.locate();
242
298
  }
243
299
 
244
300
  /**
@@ -284,4 +340,5 @@ class Binary {
284
340
 
285
341
 
286
342
  exports.Binary = Binary;
343
+ exports.Channel = Channel;
287
344
 
@@ -19,12 +19,12 @@
19
19
 
20
20
  'use strict';
21
21
 
22
- const AdmZip = require('adm-zip'),
23
- fs = require('fs'),
22
+ const fs = require('fs'),
24
23
  path = require('path'),
25
24
  xml = require('xml2js');
26
25
 
27
26
  const io = require('../io');
27
+ const zip = require('../io/zip');
28
28
 
29
29
 
30
30
  /**
@@ -56,11 +56,7 @@ function install(extension, dir) {
56
56
  if (!details.unpack) {
57
57
  return io.copy(extension, dst + '.xpi').then(() => details.id);
58
58
  } else {
59
- return Promise.resolve().then(function() {
60
- // TODO: find an async library for inflating a zip archive.
61
- new AdmZip(extension).extractAllTo(dst, true);
62
- return details.id;
63
- });
59
+ return zip.unzip(extension, dst).then(() => details.id);
64
60
  }
65
61
  } else {
66
62
  return io.copyDir(extension, dst).then(() => details.id);
@@ -79,105 +75,146 @@ var AddonDetails;
79
75
  var RdfRoot;
80
76
 
81
77
 
82
-
83
78
  /**
84
79
  * Extracts the details needed to install an add-on.
85
80
  * @param {string} addonPath Path to the extension directory.
86
81
  * @return {!Promise<!AddonDetails>} A promise for the add-on details.
87
82
  */
88
83
  function getDetails(addonPath) {
89
- return readManifest(addonPath).then(function(doc) {
90
- var em = getNamespaceId(doc, 'http://www.mozilla.org/2004/em-rdf#');
91
- var rdf = getNamespaceId(
92
- doc, 'http://www.w3.org/1999/02/22-rdf-syntax-ns#');
93
-
94
- var description = doc[rdf + 'RDF'][rdf + 'Description'][0];
95
- var details = {
96
- id: getNodeText(description, em + 'id'),
97
- name: getNodeText(description, em + 'name'),
98
- version: getNodeText(description, em + 'version'),
99
- unpack: getNodeText(description, em + 'unpack') || false
100
- };
101
-
102
- if (typeof details.unpack === 'string') {
103
- details.unpack = details.unpack.toLowerCase() === 'true';
84
+ return io.stat(addonPath).then((stats) => {
85
+ if (stats.isDirectory()) {
86
+ return parseDirectory(addonPath);
87
+ } else if (addonPath.slice(-4) === '.xpi') {
88
+ return parseXpiFile(addonPath);
89
+ } else {
90
+ throw Error('Add-on path is not an xpi or a directory: ' + addonPath);
104
91
  }
92
+ });
105
93
 
106
- if (!details.id) {
107
- throw new AddonFormatError('Could not find add-on ID for ' + addonPath);
108
- }
94
+ /**
95
+ * Parse an install.rdf for a Firefox add-on.
96
+ * @param {string} rdf The contents of install.rdf for the add-on.
97
+ * @return {!Promise<!AddonDetails>} A promise for the add-on details.
98
+ */
99
+ function parseInstallRdf(rdf) {
100
+ return parseXml(rdf).then(function(doc) {
101
+ var em = getNamespaceId(doc, 'http://www.mozilla.org/2004/em-rdf#');
102
+ var rdf = getNamespaceId(
103
+ doc, 'http://www.w3.org/1999/02/22-rdf-syntax-ns#');
104
+
105
+ var description = doc[rdf + 'RDF'][rdf + 'Description'][0];
106
+ var details = {
107
+ id: getNodeText(description, em + 'id'),
108
+ name: getNodeText(description, em + 'name'),
109
+ version: getNodeText(description, em + 'version'),
110
+ unpack: getNodeText(description, em + 'unpack') || false
111
+ };
112
+
113
+ if (typeof details.unpack === 'string') {
114
+ details.unpack = details.unpack.toLowerCase() === 'true';
115
+ }
109
116
 
110
- return details;
111
- });
117
+ if (!details.id) {
118
+ throw new AddonFormatError('Could not find add-on ID for ' + addonPath);
119
+ }
112
120
 
113
- function getNodeText(node, name) {
114
- return node[name] && node[name][0] || '';
115
- }
121
+ return details;
122
+ });
116
123
 
117
- function getNamespaceId(doc, url) {
118
- var keys = Object.keys(doc);
119
- if (keys.length !== 1) {
120
- throw new AddonFormatError('Malformed manifest for add-on ' + addonPath);
124
+ function parseXml(text) {
125
+ return new Promise((resolve, reject) => {
126
+ xml.parseString(text, (err, data) => {
127
+ if (err) {
128
+ reject(err);
129
+ } else {
130
+ resolve(data);
131
+ }
132
+ });
133
+ });
121
134
  }
122
135
 
123
- var namespaces = /** @type {!RdfRoot} */(doc[keys[0]]).$;
124
- var id = '';
125
- Object.keys(namespaces).some(function(ns) {
126
- if (namespaces[ns] !== url) {
127
- return false;
128
- }
136
+ function getNodeText(node, name) {
137
+ return node[name] && node[name][0] || '';
138
+ }
129
139
 
130
- if (ns.indexOf(':') != -1) {
131
- id = ns.split(':')[1] + ':';
140
+ function getNamespaceId(doc, url) {
141
+ var keys = Object.keys(doc);
142
+ if (keys.length !== 1) {
143
+ throw new AddonFormatError('Malformed manifest for add-on ' + addonPath);
132
144
  }
133
- return true;
134
- });
135
- return id;
136
- }
137
- }
138
145
 
146
+ var namespaces = /** @type {!RdfRoot} */(doc[keys[0]]).$;
147
+ var id = '';
148
+ Object.keys(namespaces).some(function(ns) {
149
+ if (namespaces[ns] !== url) {
150
+ return false;
151
+ }
139
152
 
140
- /**
141
- * Reads the manifest for a Firefox add-on.
142
- * @param {string} addonPath Path to a Firefox add-on as a xpi or an extension.
143
- * @return {!Promise<!Object>} A promise for the parsed manifest.
144
- */
145
- function readManifest(addonPath) {
146
- var manifest;
153
+ if (ns.indexOf(':') != -1) {
154
+ id = ns.split(':')[1] + ':';
155
+ }
156
+ return true;
157
+ });
158
+ return id;
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Parse a manifest for a Firefox WebExtension.
164
+ * @param {{
165
+ * name: string,
166
+ * version: string,
167
+ * applications: {gecko:{id:string}}
168
+ * }} json JSON representation of the manifest.
169
+ * @return {!AddonDetails} The add-on details.
170
+ */
171
+ function parseManifestJson({name, version, applications}) {
172
+ if (!(applications && applications.gecko && applications.gecko.id)) {
173
+ throw new AddonFormatError('Could not find add-on ID for ' + addonPath);
174
+ }
147
175
 
148
- if (addonPath.slice(-4) === '.xpi') {
149
- manifest = new Promise((resolve, reject) => {
150
- let zip = new AdmZip(addonPath);
176
+ return {id: applications.gecko.id, name, version, unpack: false};
177
+ }
151
178
 
152
- if (!zip.getEntry('install.rdf')) {
153
- reject(new AddonFormatError(
154
- 'Could not find install.rdf in ' + addonPath));
155
- return;
179
+ function parseXpiFile(filePath) {
180
+ return zip.load(filePath).then(archive => {
181
+ if (archive.has('install.rdf')) {
182
+ return archive.getFile('install.rdf')
183
+ .then(buf => parseInstallRdf(buf.toString('utf8')));
156
184
  }
157
185
 
158
- zip.readAsTextAsync('install.rdf', resolve);
159
- });
160
- } else {
161
- manifest = io.stat(addonPath).then(function(stats) {
162
- if (!stats.isDirectory()) {
163
- throw Error(
164
- 'Add-on path is niether a xpi nor a directory: ' + addonPath);
186
+ if (archive.has('manifest.json')) {
187
+ return archive.getFile('manifest.json')
188
+ .then(buf => JSON.parse(buf.toString('utf8')))
189
+ .then(parseManifestJson);
165
190
  }
166
- return io.read(path.join(addonPath, 'install.rdf'));
191
+
192
+ throw new AddonFormatError(
193
+ `Couldn't find install.rdf or manifest.json in ${filePath}`);
167
194
  });
168
195
  }
169
196
 
170
- return manifest.then(function(content) {
171
- return new Promise((resolve, reject) => {
172
- xml.parseString(content, (err, data) => {
173
- if (err) {
174
- reject(err);
175
- } else {
176
- resolve(data);
177
- }
178
- });
179
- });
180
- });
197
+ function parseDirectory(dirPath) {
198
+ const rdfPath = path.join(dirPath, 'install.rdf');
199
+ const jsonPath = path.join(dirPath, 'manifest.json');
200
+ return io.exists(rdfPath)
201
+ .then(rdfExists => {
202
+ if (rdfExists) {
203
+ return io.read(rdfPath)
204
+ .then(buf => parseInstallRdf(buf.toString('utf8')));
205
+ }
206
+ return io.exists(jsonPath)
207
+ .then(jsonExists => {
208
+ if (jsonExists) {
209
+ return io.read(jsonPath)
210
+ .then(buf => JSON.parse(buf.toString('utf8')))
211
+ .then(parseManifestJson);
212
+ }
213
+ throw new AddonFormatError(
214
+ `Couldn't find install.rdf or manifest.json in ${dirPath}`);
215
+ });
216
+ })
217
+ }
181
218
  }
182
219
 
183
220