selenium-webdriver 3.4.0 → 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.
package/CHANGES.md CHANGED
@@ -1,3 +1,31 @@
1
+ ## v3.5.0
2
+
3
+ ### Notice
4
+
5
+ Native support for Firefox 45 (ESR) has been removed. Users will have to connect
6
+ to a remote Selenium server that supports Firefox 45.
7
+
8
+ ### Changes
9
+
10
+ * Removed native support for Firefox 46 and older.
11
+ - The `SELENIUM_MARIONETTE` enviornment variable no longer has an effect.
12
+ - `selenium-webdriver/firefox.Capability.MARIONETTE` is deprecated.
13
+ - `selenium-webdriver/firefox.Options#useGeckoDriver()` is deprecated and now a no-op.
14
+ * `firefox.Options` will no longer discard the `"moz:firefoxOptions"` set in
15
+ user provided capabilities (via `Builder.withCapabilities({})`). When both
16
+ are used, the settings in `firefox.Options` will be applied _last_.
17
+ * Added `chrome.Options#headless()` and `chrome.Options#windowSize()`, which
18
+ may be used to start Chrome in headless mode (requires Chrome 59+) and to set
19
+ the initial window size, respectively.
20
+
21
+
22
+ ### Changes for W3C WebDriver Spec Compliance
23
+
24
+ * Added `error.WebDriverError#remoteStacktrace` to capture the stacktrace
25
+ reported by a remote WebDriver endpoint (if any).
26
+ * Fixed `WebElement#sendKeys` to send text as a string instead of an array of
27
+ strings.
28
+
1
29
  ## v3.4.0
2
30
 
3
31
  ### Notice
@@ -17,7 +45,7 @@ This release requires [geckodriver 0.15.0](https://github.com/mozilla/geckodrive
17
45
 
18
46
  * Fixed W3C response parsing, which expects response data to always be a JSON
19
47
  object with a `value` key.
20
- * Added W3C endpoints for interacting with various types of
48
+ * Added W3C endpoints for interacting with various types of
21
49
  [user prompts](https://w3c.github.io/webdriver/webdriver-spec.html#user-prompts).
22
50
  * Added W3C endpoints for remotely executing scripts.
23
51
  * Added W3C endpoints to get current window handle and all windows handles.
package/NOTICE CHANGED
@@ -1,2 +1,2 @@
1
- Copyright 2011-2016 Software Freedom Conservancy
1
+ Copyright 2011-2017 Software Freedom Conservancy
2
2
  Copyright 2004-2011 Selenium committers
package/README.md CHANGED
@@ -17,16 +17,13 @@ be placed on your system [PATH]. Apple's safaridriver is shipped with
17
17
  Safari 10 for OS X El Capitan and macOS Sierra. You will need to enable Remote
18
18
  Automation in the Develop menu of Safari 10 before testing.
19
19
 
20
- > **NOTE:** Mozilla's [geckodriver] is only required for Firefox 47+.
21
- > Everything you need for Firefox 38-46 is included with this package.
22
-
23
20
 
24
21
  | Browser | Component |
25
22
  | ----------------- | ---------------------------------- |
26
23
  | Chrome | [chromedriver(.exe)][chrome] |
27
24
  | Internet Explorer | [IEDriverServer.exe][release] |
28
25
  | Edge | [MicrosoftWebDriver.msi][edge] |
29
- | Firefox 47+ | [geckodriver(.exe)][geckodriver] |
26
+ | Firefox | [geckodriver(.exe)][geckodriver] |
30
27
  | PhantomJS | [phantomjs(.exe)][phantomjs] |
31
28
  | Opera | [operadriver(.exe)][opera] |
32
29
  | Safari | [safaridriver] |
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,19 @@
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 {Capabilities, Capability} = require('./lib/capabilities');
137
+ const command = require('./lib/command');
138
+ const logging = require('./lib/logging');
139
+ const promise = require('./lib/promise');
140
+ const Symbols = require('./lib/symbols');
141
+ const webdriver = require('./lib/webdriver');
142
+ const portprober = require('./net/portprober');
143
+ const remote = require('./remote');
130
144
 
131
145
 
132
146
  /**
@@ -293,11 +307,7 @@ function getDefaultService() {
293
307
  }
294
308
 
295
309
 
296
- /**
297
- * @type {string}
298
- * @const
299
- */
300
- let OPTIONS_CAPABILITY_KEY = 'chromeOptions';
310
+ const OPTIONS_CAPABILITY_KEY = 'chromeOptions';
301
311
 
302
312
 
303
313
  /**
@@ -362,28 +372,61 @@ class Options {
362
372
  * browser. Each argument may be specified with or without the "--" prefix
363
373
  * (e.g. "--foo" and "foo"). Arguments with an associated value should be
364
374
  * delimited by an "=": "foo=bar".
365
- * @param {...(string|!Array<string>)} var_args The arguments to add.
375
+ *
376
+ * @param {...(string|!Array<string>)} args The arguments to add.
366
377
  * @return {!Options} A self reference.
367
378
  */
368
- addArguments(var_args) {
369
- let args = this.options_.args || [];
370
- args = args.concat.apply(args, arguments);
371
- if (args.length) {
372
- this.options_.args = args;
379
+ addArguments(...args) {
380
+ let newArgs = (this.options_.args || []).concat(...args);
381
+ if (newArgs.length) {
382
+ this.options_.args = newArgs;
373
383
  }
374
384
  return this;
375
385
  }
376
386
 
387
+ /**
388
+ * Configures the chromedriver to start Chrome in headless mode.
389
+ *
390
+ * > __NOTE:__ Resizing the browser window in headless mode is only supported
391
+ * > in Chrome 60. Users are encouraged to set an initial window size with
392
+ * > the {@link #windowSize windowSize({width, height})} option.
393
+ *
394
+ * @return {!Options} A self reference.
395
+ */
396
+ headless() {
397
+ // TODO(jleyba): Remove `disable-gpu` once head Chrome no longer requires
398
+ // that to be set.
399
+ return this.addArguments('headless', 'disable-gpu');
400
+ }
401
+
402
+ /**
403
+ * Sets the initial window size.
404
+ *
405
+ * @param {{width: number, height: number}} size The desired window size.
406
+ * @return {!Options} A self reference.
407
+ * @throws {TypeError} if width or height is unspecified, not a number, or
408
+ * less than or equal to 0.
409
+ */
410
+ windowSize({width, height}) {
411
+ function checkArg(arg) {
412
+ if (typeof arg !== 'number' || arg <= 0) {
413
+ throw TypeError('Arguments must be {width, height} with numbers > 0');
414
+ }
415
+ }
416
+ checkArg(width);
417
+ checkArg(height);
418
+ return this.addArguments(`window-size=${width},${height}`);
419
+ }
420
+
377
421
  /**
378
422
  * List of Chrome command line switches to exclude that ChromeDriver by default
379
423
  * passes when starting Chrome. Do not prefix switches with "--".
380
424
  *
381
- * @param {...(string|!Array<string>)} var_args The switches to exclude.
425
+ * @param {...(string|!Array<string>)} args The switches to exclude.
382
426
  * @return {!Options} A self reference.
383
427
  */
384
- excludeSwitches(var_args) {
385
- let switches = this.options_.excludeSwitches || [];
386
- switches = switches.concat.apply(switches, arguments);
428
+ excludeSwitches(...args) {
429
+ let switches = (this.options_.excludeSwitches || []).concat(...args);
387
430
  if (switches.length) {
388
431
  this.options_.excludeSwitches = switches;
389
432
  }
@@ -394,13 +437,12 @@ class Options {
394
437
  * Add additional extensions to install when launching Chrome. Each extension
395
438
  * should be specified as the path to the packed CRX file, or a Buffer for an
396
439
  * extension.
397
- * @param {...(string|!Buffer|!Array<(string|!Buffer)>)} var_args The
440
+ * @param {...(string|!Buffer|!Array<(string|!Buffer)>)} args The
398
441
  * extensions to add.
399
442
  * @return {!Options} A self reference.
400
443
  */
401
- addExtensions(var_args) {
402
- this.extensions_ =
403
- this.extensions_.concat.apply(this.extensions_, arguments);
444
+ addExtensions(...args) {
445
+ this.extensions_ = this.extensions_.concat(...args);
404
446
  return this;
405
447
  }
406
448
 
@@ -716,7 +758,7 @@ class Driver extends webdriver.WebDriver {
716
758
  (opt_config || Capabilities.chrome());
717
759
 
718
760
  return /** @type {!Driver} */(
719
- webdriver.WebDriver.createSession(executor, caps, opt_flow, this));
761
+ super.createSession(executor, caps, opt_flow));
720
762
  }
721
763
 
722
764
  /**
@@ -737,7 +779,7 @@ class Driver extends webdriver.WebDriver {
737
779
  new command.Command(Command.LAUNCH_APP).setParameter('id', id),
738
780
  'Driver.launchApp()');
739
781
  }
740
-
782
+
741
783
  /**
742
784
  * Schedules a command to get Chrome network emulation settings.
743
785
  * @return {!promise.Thenable<T>} A promise that will be resolved
@@ -751,16 +793,16 @@ class Driver extends webdriver.WebDriver {
751
793
 
752
794
  /**
753
795
  * Schedules a command to set Chrome network emulation settings.
754
- *
796
+ *
755
797
  * __Sample Usage:__
756
- *
798
+ *
757
799
  * driver.setNetworkConditions({
758
800
  * offline: false,
759
801
  * latency: 5, // Additional latency (ms).
760
802
  * download_throughput: 500 * 1024, // Maximal aggregated download throughput.
761
803
  * upload_throughput: 500 * 1024 // Maximal aggregated upload throughput.
762
804
  * });
763
- *
805
+ *
764
806
  * @param {Object} spec Defines the network conditions to set
765
807
  * @return {!promise.Thenable<void>} A promise that will be resolved
766
808
  * when network emulation settings are set.
package/edge.js CHANGED
@@ -278,8 +278,8 @@ class Driver extends webdriver.WebDriver {
278
278
  opt_config instanceof Options ? opt_config.toCapabilities() :
279
279
  (opt_config || capabilities.Capabilities.edge());
280
280
 
281
- return /** @type {!Driver} */(webdriver.WebDriver.createSession(
282
- executor, caps, opt_flow, this, () => service.kill()));
281
+ return /** @type {!Driver} */(super.createSession(
282
+ executor, caps, opt_flow, () => service.kill()));
283
283
  }
284
284
 
285
285
  /**
@@ -0,0 +1,47 @@
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 An example of running Chrome in headless mode.
20
+ *
21
+ * Before running this script, ensure you have Chrome 59+ installed and that
22
+ * chromedriver 2.30+ is present on your system PATH:
23
+ * <https://sites.google.com/a/chromium.org/chromedriver/downloads>
24
+ *
25
+ * Usage:
26
+ * node selenium-webdriver/example/chrome_headless.js
27
+ */
28
+
29
+ const chrome = require('../chrome');
30
+ const {Builder, By, until} = require('..');
31
+
32
+ const width = 640;
33
+ const height = 480;
34
+
35
+ let driver = new Builder()
36
+ .forBrowser('chrome')
37
+ .setChromeOptions(
38
+ new chrome.Options().headless().windowSize({width, height}))
39
+ .build();
40
+
41
+ driver.get('http://www.google.com/ncr')
42
+ .then(_ => driver.findElement(By.name('q')).sendKeys('webdriver'))
43
+ .then(_ => driver.findElement(By.name('btnG')).click())
44
+ .then(_ => driver.wait(until.titleIs('webdriver - Google Search'), 1000))
45
+ .then(
46
+ _ => driver.quit(),
47
+ e => driver.quit().then(() => { throw e; }));
@@ -31,7 +31,7 @@ promise.consume(function* () {
31
31
  driver = yield new Builder()
32
32
  .forBrowser('chrome')
33
33
  .setChromeOptions(
34
- new Options().setMobileEmulation({deviceName: 'Google Nexus 5'}))
34
+ new Options().setMobileEmulation({deviceName: 'Nexus 5X'}))
35
35
  .build();
36
36
  yield driver.get('http://www.google.com/ncr');
37
37
  yield driver.findElement(By.name('q')).sendKeys('webdriver');
@@ -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 neither 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