selenium-webdriver 3.1.0 → 3.6.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 +112 -0
- package/NOTICE +1 -1
- package/README.md +5 -11
- package/chrome.js +122 -33
- package/edge.js +2 -2
- package/example/async_await_test.js +69 -0
- package/example/chrome_android.js +2 -3
- package/example/chrome_mobile_emulation.js +3 -4
- package/example/firefox_channels.js +3 -3
- package/example/google_search.js +3 -3
- package/example/google_search_generator.js +2 -5
- package/example/google_search_test.js +5 -6
- package/example/headless.js +55 -0
- package/example/logging.js +3 -3
- package/example/parallel_flows.js +4 -7
- package/firefox/binary.js +3 -0
- package/firefox/extension.js +118 -81
- package/firefox/index.js +117 -263
- package/firefox/profile.js +18 -137
- package/ie.js +2 -2
- package/index.js +4 -1
- package/io/index.js +59 -0
- package/io/zip.js +214 -0
- package/lib/atoms/get-attribute.js +9 -0
- package/lib/atoms/is-displayed.js +73 -78
- package/lib/by.js +8 -0
- package/lib/capabilities.js +61 -22
- package/lib/command.js +2 -0
- package/lib/error.js +32 -2
- package/lib/http.js +53 -44
- package/lib/promise.js +72 -25
- package/lib/test/data/click_tests/disabled_element.html +12 -0
- package/lib/test/data/content-editable.html +10 -0
- package/lib/test/data/firefox/webextension.xpi +0 -0
- package/lib/test/data/key_logger.html +34 -0
- package/lib/test/data/nestedElements.html +10 -1
- package/lib/test/data/scrolling_tests/page_with_partially_hidden_element.html +14 -0
- package/lib/test/data/single_text_input.html +12 -0
- package/lib/test/index.js +4 -36
- package/lib/webdriver.js +147 -55
- package/net/portprober.js +1 -1
- package/opera.js +1 -1
- package/package.json +2 -2
- package/phantomjs.js +2 -2
- package/remote/index.js +21 -14
- package/safari.js +2 -2
- package/test/firefox/extension_test.js +41 -17
- package/test/firefox/firefox_test.js +37 -19
- package/test/firefox/profile_test.js +14 -59
- package/test/http/http_test.js +18 -0
- package/test/{io_test.js → io/io_test.js} +40 -1
- package/test/io/zip_test.js +128 -0
- package/test/lib/by_test.js +21 -0
- package/test/lib/error_test.js +21 -1
- package/test/lib/http_test.js +11 -3
- package/test/lib/webdriver_test.js +107 -3
- package/test/page_loading_test.js +0 -6
- package/test/remote_test.js +3 -3
- package/test/session_test.js +68 -23
- package/testing/index.js +2 -3
- package/lib/atoms/getAttribute.js +0 -12
- package/lib/firefox/amd64/libnoblur64.so +0 -0
- package/lib/firefox/i386/libnoblur.so +0 -0
- package/lib/firefox/webdriver.json +0 -69
- package/lib/firefox/webdriver.xpi +0 -0
|
@@ -0,0 +1,55 @@
|
|
|
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 or Firefox in headless mode.
|
|
20
|
+
*
|
|
21
|
+
* To run with Chrome, 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
|
+
* SELENIUM_BROWSER=chrome node selenium-webdriver/example/headless.js
|
|
26
|
+
*
|
|
27
|
+
* To run with Firefox, ensure you have Firefox 57+ installed and that
|
|
28
|
+
* geckodriver 0.19.0+ is present on your system PATH:
|
|
29
|
+
* <https://github.com/mozilla/geckodriver/releases>
|
|
30
|
+
*
|
|
31
|
+
* SELENIUM_BROWSER=firefox node selenium-webdriver/example/headless.js
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
const chrome = require('../chrome');
|
|
35
|
+
const firefox = require('../firefox');
|
|
36
|
+
const {Builder, By, Key, until} = require('..');
|
|
37
|
+
|
|
38
|
+
const width = 640;
|
|
39
|
+
const height = 480;
|
|
40
|
+
|
|
41
|
+
let driver = new Builder()
|
|
42
|
+
.forBrowser('chrome')
|
|
43
|
+
.setChromeOptions(
|
|
44
|
+
new chrome.Options().headless().windowSize({width, height}))
|
|
45
|
+
.setFirefoxOptions(
|
|
46
|
+
new firefox.Options().headless().windowSize({width, height}))
|
|
47
|
+
.build();
|
|
48
|
+
|
|
49
|
+
driver.get('http://www.google.com/ncr')
|
|
50
|
+
.then(_ =>
|
|
51
|
+
driver.findElement(By.name('q')).sendKeys('webdriver', Key.RETURN))
|
|
52
|
+
.then(_ => driver.wait(until.titleIs('webdriver - Google Search'), 1000))
|
|
53
|
+
.then(
|
|
54
|
+
_ => driver.quit(),
|
|
55
|
+
e => driver.quit().then(() => { throw e; }));
|
package/example/logging.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
'use strict';
|
|
23
23
|
|
|
24
|
-
const {Builder, By, logging, until} = require('..');
|
|
24
|
+
const {Builder, By, Key, logging, until} = require('..');
|
|
25
25
|
|
|
26
26
|
logging.installConsoleHandler();
|
|
27
27
|
logging.getLogger('webdriver.http').setLevel(logging.Level.ALL);
|
|
@@ -29,7 +29,7 @@ logging.getLogger('webdriver.http').setLevel(logging.Level.ALL);
|
|
|
29
29
|
var driver = new Builder().forBrowser('firefox').build();
|
|
30
30
|
|
|
31
31
|
driver.get('http://www.google.com/ncr')
|
|
32
|
-
.then(_ =>
|
|
33
|
-
|
|
32
|
+
.then(_ =>
|
|
33
|
+
driver.findElement(By.name('q')).sendKeys('webdriver', Key.RETURN))
|
|
34
34
|
.then(_ => driver.wait(until.titleIs('webdriver - Google Search'), 1000))
|
|
35
35
|
.then(_ => driver.quit());
|
|
@@ -23,18 +23,16 @@
|
|
|
23
23
|
* (see <https://github.com/SeleniumHQ/selenium/issues/2969>).
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
By = webdriver.By,
|
|
28
|
-
until = webdriver.until;
|
|
26
|
+
const {Builder, By, Key, promise, until} = require('..');
|
|
29
27
|
|
|
30
28
|
for (var i = 0; i < 3; i++) {
|
|
31
29
|
(function(n) {
|
|
32
|
-
var flow = new
|
|
30
|
+
var flow = new promise.ControlFlow()
|
|
33
31
|
.on('uncaughtException', function(e) {
|
|
34
32
|
console.log('uncaughtException in flow %d: %s', n, e);
|
|
35
33
|
});
|
|
36
34
|
|
|
37
|
-
var driver = new
|
|
35
|
+
var driver = new Builder().
|
|
38
36
|
forBrowser('firefox').
|
|
39
37
|
setControlFlow(flow). // Comment out this line to see the difference.
|
|
40
38
|
build();
|
|
@@ -44,8 +42,7 @@ for (var i = 0; i < 3; i++) {
|
|
|
44
42
|
driver.manage().window().setPosition(300 * i, 400 * i);
|
|
45
43
|
|
|
46
44
|
driver.get('http://www.google.com');
|
|
47
|
-
driver.findElement(By.name('q')).sendKeys('webdriver');
|
|
48
|
-
driver.findElement(By.name('btnG')).click();
|
|
45
|
+
driver.findElement(By.name('q')).sendKeys('webdriver', Key.RETURN);
|
|
49
46
|
driver.wait(until.titleIs('webdriver - Google Search'), 1000);
|
|
50
47
|
|
|
51
48
|
driver.quit();
|
package/firefox/binary.js
CHANGED
|
@@ -203,6 +203,8 @@ function installNoFocusLibs(profileDir) {
|
|
|
203
203
|
* `PATH`.
|
|
204
204
|
*
|
|
205
205
|
* @final
|
|
206
|
+
* @deprecated This class will be removed in 4.0. Use the binary management
|
|
207
|
+
* functions available on the {@link ./index.Options firefox.Options} class.
|
|
206
208
|
*/
|
|
207
209
|
class Binary {
|
|
208
210
|
/**
|
|
@@ -242,6 +244,7 @@ class Binary {
|
|
|
242
244
|
* Add arguments to the command line used to start Firefox.
|
|
243
245
|
* @param {...(string|!Array.<string>)} var_args Either the arguments to add
|
|
244
246
|
* as varargs, or the arguments as an array.
|
|
247
|
+
* @deprecated Use {@link ./index.Options#addArguments}.
|
|
245
248
|
*/
|
|
246
249
|
addArguments(var_args) {
|
|
247
250
|
for (var i = 0; i < arguments.length; i++) {
|
package/firefox/extension.js
CHANGED
|
@@ -19,12 +19,12 @@
|
|
|
19
19
|
|
|
20
20
|
'use strict';
|
|
21
21
|
|
|
22
|
-
const
|
|
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
|
|
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
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
107
|
-
|
|
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
|
-
|
|
111
|
-
|
|
117
|
+
if (!details.id) {
|
|
118
|
+
throw new AddonFormatError('Could not find add-on ID for ' + addonPath);
|
|
119
|
+
}
|
|
112
120
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
121
|
+
return details;
|
|
122
|
+
});
|
|
116
123
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
131
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
let zip = new AdmZip(addonPath);
|
|
176
|
+
return {id: applications.gecko.id, name, version, unpack: false};
|
|
177
|
+
}
|
|
151
178
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
191
|
+
|
|
192
|
+
throw new AddonFormatError(
|
|
193
|
+
`Couldn't find install.rdf or manifest.json in ${filePath}`);
|
|
167
194
|
});
|
|
168
195
|
}
|
|
169
196
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
|