selenium-webdriver 4.0.0-alpha.4 → 4.0.0-alpha.8
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/.eslintrc.js +27 -0
- package/.prettierrc +4 -0
- package/CHANGES.md +22 -1
- package/README.md +4 -4
- package/chrome.js +72 -590
- package/chromium.js +1024 -0
- package/devtools/CDPConnection.js +37 -0
- package/devtools/generator/protocol-dts-generator.js +429 -0
- package/edge.js +124 -86
- package/firefox.js +201 -202
- package/http/index.js +131 -115
- package/http/util.js +56 -64
- package/ie.js +180 -104
- package/index.js +246 -199
- package/io/exec.js +34 -39
- package/io/index.js +141 -151
- package/io/zip.js +75 -65
- package/lib/README +5 -0
- package/lib/actions.js +138 -121
- package/lib/atoms/find-elements.js +123 -0
- package/lib/atoms/get-attribute.js +79 -60
- package/lib/atoms/is-displayed.js +72 -73
- package/{example/chrome_mobile_emulation.js → lib/atoms/make-atoms-module.js} +16 -23
- package/lib/by.js +192 -65
- package/lib/capabilities.js +82 -78
- package/lib/command.js +18 -20
- package/lib/error.js +134 -168
- package/lib/http.js +324 -230
- package/lib/input.js +325 -302
- package/lib/logging.js +127 -138
- package/lib/promise.js +60 -66
- package/lib/proxy.js +31 -41
- package/lib/session.js +12 -15
- package/lib/symbols.js +3 -4
- package/lib/until.js +162 -162
- package/lib/webdriver.js +733 -522
- package/net/index.js +30 -36
- package/net/portprober.js +31 -144
- package/opera.js +406 -0
- package/package.json +18 -11
- package/proxy.js +2 -2
- package/remote/index.js +190 -187
- package/safari.js +40 -49
- package/LICENSE +0 -202
- package/NOTICE +0 -2
- package/example/chrome_android.js +0 -42
- package/example/firefox_channels.js +0 -77
- package/example/google_search.js +0 -50
- package/example/google_search_test.js +0 -72
- package/example/headless.js +0 -55
- package/example/logging.js +0 -68
- package/testing/index.js +0 -498
package/chromium.js
ADDED
|
@@ -0,0 +1,1024 @@
|
|
|
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 Defines an abstract {@linkplain Driver WebDriver} client for
|
|
20
|
+
* Chromium-based web browsers. These classes should not be instantiated
|
|
21
|
+
* directly.
|
|
22
|
+
*
|
|
23
|
+
* There are three primary classes exported by this module:
|
|
24
|
+
*
|
|
25
|
+
* 1. {@linkplain ServiceBuilder}: configures the
|
|
26
|
+
* {@link selenium-webdriver/remote.DriverService remote.DriverService}
|
|
27
|
+
* that manages a WebDriver server child process.
|
|
28
|
+
*
|
|
29
|
+
* 2. {@linkplain Options}: defines configuration options for each new Chromium
|
|
30
|
+
* session, such as which {@linkplain Options#setProxy proxy} to use,
|
|
31
|
+
* what {@linkplain Options#addExtensions extensions} to install, or
|
|
32
|
+
* what {@linkplain Options#addArguments command-line switches} to use when
|
|
33
|
+
* starting the browser.
|
|
34
|
+
*
|
|
35
|
+
* 3. {@linkplain Driver}: the WebDriver client; each new instance will control
|
|
36
|
+
* a unique browser session with a clean user profile (unless otherwise
|
|
37
|
+
* configured through the {@link Options} class).
|
|
38
|
+
*
|
|
39
|
+
* __Headless Chromium__ <a id="headless"></a>
|
|
40
|
+
*
|
|
41
|
+
* To start the browser in headless mode, simply call
|
|
42
|
+
* {@linkplain Options#headless Options.headless()}.
|
|
43
|
+
*
|
|
44
|
+
* let chrome = require('selenium-webdriver/chrome');
|
|
45
|
+
* let {Builder} = require('selenium-webdriver');
|
|
46
|
+
*
|
|
47
|
+
* let driver = new Builder()
|
|
48
|
+
* .forBrowser('chrome')
|
|
49
|
+
* .setChromeOptions(new chrome.Options().headless())
|
|
50
|
+
* .build();
|
|
51
|
+
*
|
|
52
|
+
* __Customizing the Chromium WebDriver Server__ <a id="custom-server"></a>
|
|
53
|
+
*
|
|
54
|
+
* Subclasses of {@link Driver} are expected to provide a static
|
|
55
|
+
* getDefaultService method. By default, this method will be called every time
|
|
56
|
+
* a {@link Driver} instance is created to obtain the default driver service
|
|
57
|
+
* for that specific browser (e.g. Chrome or Chromium Edge). Subclasses are
|
|
58
|
+
* responsible for managing the lifetime of the default service.
|
|
59
|
+
*
|
|
60
|
+
* You may also create a {@link Driver} with its own driver service. This is
|
|
61
|
+
* useful if you need to capture the server's log output for a specific session:
|
|
62
|
+
*
|
|
63
|
+
* let chrome = require('selenium-webdriver/chrome');
|
|
64
|
+
*
|
|
65
|
+
* let service = new chrome.ServiceBuilder()
|
|
66
|
+
* .loggingTo('/my/log/file.txt')
|
|
67
|
+
* .enableVerboseLogging()
|
|
68
|
+
* .build();
|
|
69
|
+
*
|
|
70
|
+
* let options = new chrome.Options();
|
|
71
|
+
* // configure browser options ...
|
|
72
|
+
*
|
|
73
|
+
* let driver = chrome.Driver.createSession(options, service);
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
'use strict'
|
|
77
|
+
|
|
78
|
+
const http = require('./http')
|
|
79
|
+
const io = require('./io')
|
|
80
|
+
const { Capabilities, Capability } = require('./lib/capabilities')
|
|
81
|
+
const command = require('./lib/command')
|
|
82
|
+
const error = require('./lib/error')
|
|
83
|
+
const Symbols = require('./lib/symbols')
|
|
84
|
+
const webdriver = require('./lib/webdriver')
|
|
85
|
+
const WebSocket = require('ws')
|
|
86
|
+
const cdp = require('./devtools/CDPConnection')
|
|
87
|
+
const remote = require('./remote')
|
|
88
|
+
const cdpTargets = ['page', 'browser']
|
|
89
|
+
const fs = require('fs')
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Custom command names supported by Chromium WebDriver.
|
|
93
|
+
* @enum {string}
|
|
94
|
+
*/
|
|
95
|
+
const Command = {
|
|
96
|
+
LAUNCH_APP: 'launchApp',
|
|
97
|
+
GET_NETWORK_CONDITIONS: 'getNetworkConditions',
|
|
98
|
+
SET_NETWORK_CONDITIONS: 'setNetworkConditions',
|
|
99
|
+
SEND_DEVTOOLS_COMMAND: 'sendDevToolsCommand',
|
|
100
|
+
SEND_AND_GET_DEVTOOLS_COMMAND: 'sendAndGetDevToolsCommand',
|
|
101
|
+
SET_PERMISSION: 'setPermission',
|
|
102
|
+
GET_CAST_SINKS: 'getCastSinks',
|
|
103
|
+
SET_CAST_SINK_TO_USE: 'setCastSinkToUse',
|
|
104
|
+
START_CAST_TAB_MIRRORING: 'setCastTabMirroring',
|
|
105
|
+
GET_CAST_ISSUE_MESSAGE: 'getCastIssueMessage',
|
|
106
|
+
STOP_CASTING: 'stopCasting',
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Creates a command executor with support for Chromium's custom commands.
|
|
111
|
+
* @param {!Promise<string>} url The server's URL.
|
|
112
|
+
* @return {!command.Executor} The new command executor.
|
|
113
|
+
*/
|
|
114
|
+
function createExecutor(url, vendorPrefix) {
|
|
115
|
+
let agent = new http.Agent({ keepAlive: true })
|
|
116
|
+
let client = url.then((url) => new http.HttpClient(url, agent))
|
|
117
|
+
let executor = new http.Executor(client)
|
|
118
|
+
configureExecutor(executor, vendorPrefix)
|
|
119
|
+
return executor
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Configures the given executor with Chromium-specific commands.
|
|
124
|
+
* @param {!http.Executor} executor the executor to configure.
|
|
125
|
+
*/
|
|
126
|
+
function configureExecutor(executor, vendorPrefix) {
|
|
127
|
+
executor.defineCommand(
|
|
128
|
+
Command.LAUNCH_APP,
|
|
129
|
+
'POST',
|
|
130
|
+
'/session/:sessionId/chromium/launch_app'
|
|
131
|
+
)
|
|
132
|
+
executor.defineCommand(
|
|
133
|
+
Command.GET_NETWORK_CONDITIONS,
|
|
134
|
+
'GET',
|
|
135
|
+
'/session/:sessionId/chromium/network_conditions'
|
|
136
|
+
)
|
|
137
|
+
executor.defineCommand(
|
|
138
|
+
Command.SET_NETWORK_CONDITIONS,
|
|
139
|
+
'POST',
|
|
140
|
+
'/session/:sessionId/chromium/network_conditions'
|
|
141
|
+
)
|
|
142
|
+
executor.defineCommand(
|
|
143
|
+
Command.SEND_DEVTOOLS_COMMAND,
|
|
144
|
+
'POST',
|
|
145
|
+
'/session/:sessionId/chromium/send_command'
|
|
146
|
+
)
|
|
147
|
+
executor.defineCommand(
|
|
148
|
+
Command.SEND_AND_GET_DEVTOOLS_COMMAND,
|
|
149
|
+
'POST',
|
|
150
|
+
'/session/:sessionId/chromium/send_command_and_get_result'
|
|
151
|
+
)
|
|
152
|
+
executor.defineCommand(
|
|
153
|
+
Command.SET_PERMISSION,
|
|
154
|
+
'POST',
|
|
155
|
+
'/session/:sessionId/permissions'
|
|
156
|
+
)
|
|
157
|
+
executor.defineCommand(
|
|
158
|
+
Command.GET_CAST_SINKS,
|
|
159
|
+
'GET',
|
|
160
|
+
`/session/:sessionId/${vendorPrefix}/cast/get_sinks`
|
|
161
|
+
)
|
|
162
|
+
executor.defineCommand(
|
|
163
|
+
Command.SET_CAST_SINK_TO_USE,
|
|
164
|
+
'POST',
|
|
165
|
+
`/session/:sessionId/${vendorPrefix}/cast/set_sink_to_use`
|
|
166
|
+
)
|
|
167
|
+
executor.defineCommand(
|
|
168
|
+
Command.START_CAST_TAB_MIRRORING,
|
|
169
|
+
'POST',
|
|
170
|
+
`/session/:sessionId/${vendorPrefix}/cast/start_tab_mirroring`
|
|
171
|
+
)
|
|
172
|
+
executor.defineCommand(
|
|
173
|
+
Command.GET_CAST_ISSUE_MESSAGE,
|
|
174
|
+
'GET',
|
|
175
|
+
`/session/:sessionId/${vendorPrefix}/cast/get_issue_message`
|
|
176
|
+
)
|
|
177
|
+
executor.defineCommand(
|
|
178
|
+
Command.STOP_CASTING,
|
|
179
|
+
'POST',
|
|
180
|
+
`/session/:sessionId/${vendorPrefix}/cast/stop_casting`
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Creates {@link selenium-webdriver/remote.DriverService} instances that manage
|
|
186
|
+
* a WebDriver server in a child process.
|
|
187
|
+
*/
|
|
188
|
+
class ServiceBuilder extends remote.DriverService.Builder {
|
|
189
|
+
/**
|
|
190
|
+
* @param {string=} exe Path to the server executable to use. Subclasses
|
|
191
|
+
* should ensure a valid path to the appropriate exe is provided.
|
|
192
|
+
*/
|
|
193
|
+
constructor(exe) {
|
|
194
|
+
super(exe)
|
|
195
|
+
this.setLoopback(true) // Required
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Sets which port adb is listening to. _The driver will connect to adb
|
|
200
|
+
* if an {@linkplain Options#androidPackage Android session} is requested, but
|
|
201
|
+
* adb **must** be started beforehand._
|
|
202
|
+
*
|
|
203
|
+
* @param {number} port Which port adb is running on.
|
|
204
|
+
* @return {!ServiceBuilder} A self reference.
|
|
205
|
+
*/
|
|
206
|
+
setAdbPort(port) {
|
|
207
|
+
return this.addArguments('--adb-port=' + port)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Sets the path of the log file the driver should log to. If a log file is
|
|
212
|
+
* not specified, the driver will log to stderr.
|
|
213
|
+
* @param {string} path Path of the log file to use.
|
|
214
|
+
* @return {!ServiceBuilder} A self reference.
|
|
215
|
+
*/
|
|
216
|
+
loggingTo(path) {
|
|
217
|
+
return this.addArguments('--log-path=' + path)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Enables verbose logging.
|
|
222
|
+
* @return {!ServiceBuilder} A self reference.
|
|
223
|
+
*/
|
|
224
|
+
enableVerboseLogging() {
|
|
225
|
+
return this.addArguments('--verbose')
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Sets the number of threads the driver should use to manage HTTP requests.
|
|
230
|
+
* By default, the driver will use 4 threads.
|
|
231
|
+
* @param {number} n The number of threads to use.
|
|
232
|
+
* @return {!ServiceBuilder} A self reference.
|
|
233
|
+
*/
|
|
234
|
+
setNumHttpThreads(n) {
|
|
235
|
+
return this.addArguments('--http-threads=' + n)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* @override
|
|
240
|
+
*/
|
|
241
|
+
setPath(path) {
|
|
242
|
+
super.setPath(path)
|
|
243
|
+
return this.addArguments('--url-base=' + path)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Class for managing WebDriver options specific to a Chromium-based browser.
|
|
249
|
+
*/
|
|
250
|
+
class Options extends Capabilities {
|
|
251
|
+
/**
|
|
252
|
+
* @param {(Capabilities|Map<string, ?>|Object)=} other Another set of
|
|
253
|
+
* capabilities to initialize this instance from.
|
|
254
|
+
*/
|
|
255
|
+
constructor(other = undefined) {
|
|
256
|
+
super(other)
|
|
257
|
+
|
|
258
|
+
/** @private {!Object} */
|
|
259
|
+
this.options_ = this.get(this.CAPABILITY_KEY) || {}
|
|
260
|
+
|
|
261
|
+
this.setBrowserName(this.BROWSER_NAME_VALUE)
|
|
262
|
+
this.set(this.CAPABILITY_KEY, this.options_)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Add additional command line arguments to use when launching the browser.
|
|
267
|
+
* Each argument may be specified with or without the "--" prefix
|
|
268
|
+
* (e.g. "--foo" and "foo"). Arguments with an associated value should be
|
|
269
|
+
* delimited by an "=": "foo=bar".
|
|
270
|
+
*
|
|
271
|
+
* @param {...(string|!Array<string>)} args The arguments to add.
|
|
272
|
+
* @return {!Options} A self reference.
|
|
273
|
+
*/
|
|
274
|
+
addArguments(...args) {
|
|
275
|
+
let newArgs = (this.options_.args || []).concat(...args)
|
|
276
|
+
if (newArgs.length) {
|
|
277
|
+
this.options_.args = newArgs
|
|
278
|
+
}
|
|
279
|
+
return this
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Configures the driver to start the browser in headless mode.
|
|
284
|
+
*
|
|
285
|
+
* > __NOTE:__ Resizing the browser window in headless mode is only supported
|
|
286
|
+
* > in Chromium 60+. Users are encouraged to set an initial window size with
|
|
287
|
+
* > the {@link #windowSize windowSize({width, height})} option.
|
|
288
|
+
*
|
|
289
|
+
* > __NOTE__: For security, Chromium disables downloads by default when
|
|
290
|
+
* > in headless mode (to prevent sites from silently downloading files to
|
|
291
|
+
* > your machine). After creating a session, you may call
|
|
292
|
+
* > {@link ./chrome.Driver#setDownloadPath setDownloadPath} to re-enable
|
|
293
|
+
* > downloads, saving files in the specified directory.
|
|
294
|
+
*
|
|
295
|
+
* @return {!Options} A self reference.
|
|
296
|
+
*/
|
|
297
|
+
headless() {
|
|
298
|
+
return this.addArguments('headless')
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Sets the initial window size.
|
|
303
|
+
*
|
|
304
|
+
* @param {{width: number, height: number}} size The desired window size.
|
|
305
|
+
* @return {!Options} A self reference.
|
|
306
|
+
* @throws {TypeError} if width or height is unspecified, not a number, or
|
|
307
|
+
* less than or equal to 0.
|
|
308
|
+
*/
|
|
309
|
+
windowSize({ width, height }) {
|
|
310
|
+
function checkArg(arg) {
|
|
311
|
+
if (typeof arg !== 'number' || arg <= 0) {
|
|
312
|
+
throw TypeError('Arguments must be {width, height} with numbers > 0')
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
checkArg(width)
|
|
316
|
+
checkArg(height)
|
|
317
|
+
return this.addArguments(`window-size=${width},${height}`)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* List of Chrome command line switches to exclude that ChromeDriver by default
|
|
322
|
+
* passes when starting Chrome. Do not prefix switches with "--".
|
|
323
|
+
*
|
|
324
|
+
* @param {...(string|!Array<string>)} args The switches to exclude.
|
|
325
|
+
* @return {!Options} A self reference.
|
|
326
|
+
*/
|
|
327
|
+
excludeSwitches(...args) {
|
|
328
|
+
let switches = (this.options_.excludeSwitches || []).concat(...args)
|
|
329
|
+
if (switches.length) {
|
|
330
|
+
this.options_.excludeSwitches = switches
|
|
331
|
+
}
|
|
332
|
+
return this
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Add additional extensions to install when launching the browser. Each extension
|
|
337
|
+
* should be specified as the path to the packed CRX file, or a Buffer for an
|
|
338
|
+
* extension.
|
|
339
|
+
* @param {...(string|!Buffer|!Array<(string|!Buffer)>)} args The
|
|
340
|
+
* extensions to add.
|
|
341
|
+
* @return {!Options} A self reference.
|
|
342
|
+
*/
|
|
343
|
+
addExtensions(...args) {
|
|
344
|
+
let current = this.options_.extensions || []
|
|
345
|
+
this.options_.extensions = current.concat(...args)
|
|
346
|
+
return this
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Sets the path to the browser binary to use. On Mac OS X, this path should
|
|
351
|
+
* reference the actual Chromium executable, not just the application binary
|
|
352
|
+
* (e.g. "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome").
|
|
353
|
+
*
|
|
354
|
+
* The binary path can be absolute or relative to the WebDriver server
|
|
355
|
+
* executable, but it must exist on the machine that will launch the browser.
|
|
356
|
+
*
|
|
357
|
+
* @param {string} path The path to the browser binary to use.
|
|
358
|
+
* @return {!Options} A self reference.
|
|
359
|
+
*/
|
|
360
|
+
setBinaryPath(path) {
|
|
361
|
+
this.options_.binary = path
|
|
362
|
+
return this
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Sets whether to leave the started browser process running if the controlling
|
|
367
|
+
* driver service is killed before {@link webdriver.WebDriver#quit()} is
|
|
368
|
+
* called.
|
|
369
|
+
* @param {boolean} detach Whether to leave the browser running if the
|
|
370
|
+
* driver service is killed before the session.
|
|
371
|
+
* @return {!Options} A self reference.
|
|
372
|
+
*/
|
|
373
|
+
detachDriver(detach) {
|
|
374
|
+
this.options_.detach = detach
|
|
375
|
+
return this
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Sets the user preferences for Chrome's user profile. See the "Preferences"
|
|
380
|
+
* file in Chrome's user data directory for examples.
|
|
381
|
+
* @param {!Object} prefs Dictionary of user preferences to use.
|
|
382
|
+
* @return {!Options} A self reference.
|
|
383
|
+
*/
|
|
384
|
+
setUserPreferences(prefs) {
|
|
385
|
+
this.options_.prefs = prefs
|
|
386
|
+
return this
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Sets the performance logging preferences. Options include:
|
|
391
|
+
*
|
|
392
|
+
* - `enableNetwork`: Whether or not to collect events from Network domain.
|
|
393
|
+
* - `enablePage`: Whether or not to collect events from Page domain.
|
|
394
|
+
* - `enableTimeline`: Whether or not to collect events from Timeline domain.
|
|
395
|
+
* Note: when tracing is enabled, Timeline domain is implicitly disabled,
|
|
396
|
+
* unless `enableTimeline` is explicitly set to true.
|
|
397
|
+
* - `tracingCategories`: A comma-separated string of Chromium tracing
|
|
398
|
+
* categories for which trace events should be collected. An unspecified
|
|
399
|
+
* or empty string disables tracing.
|
|
400
|
+
* - `bufferUsageReportingInterval`: The requested number of milliseconds
|
|
401
|
+
* between DevTools trace buffer usage events. For example, if 1000, then
|
|
402
|
+
* once per second, DevTools will report how full the trace buffer is. If
|
|
403
|
+
* a report indicates the buffer usage is 100%, a warning will be issued.
|
|
404
|
+
*
|
|
405
|
+
* @param {{enableNetwork: boolean,
|
|
406
|
+
* enablePage: boolean,
|
|
407
|
+
* enableTimeline: boolean,
|
|
408
|
+
* tracingCategories: string,
|
|
409
|
+
* bufferUsageReportingInterval: number}} prefs The performance
|
|
410
|
+
* logging preferences.
|
|
411
|
+
* @return {!Options} A self reference.
|
|
412
|
+
*/
|
|
413
|
+
setPerfLoggingPrefs(prefs) {
|
|
414
|
+
this.options_.perfLoggingPrefs = prefs
|
|
415
|
+
return this
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Sets preferences for the "Local State" file in Chrome's user data
|
|
420
|
+
* directory.
|
|
421
|
+
* @param {!Object} state Dictionary of local state preferences.
|
|
422
|
+
* @return {!Options} A self reference.
|
|
423
|
+
*/
|
|
424
|
+
setLocalState(state) {
|
|
425
|
+
this.options_.localState = state
|
|
426
|
+
return this
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Sets the name of the activity hosting a Chrome-based Android WebView. This
|
|
431
|
+
* option must be set to connect to an [Android WebView](
|
|
432
|
+
* https://chromedriver.chromium.org/getting-started/getting-started---android)
|
|
433
|
+
*
|
|
434
|
+
* @param {string} name The activity name.
|
|
435
|
+
* @return {!Options} A self reference.
|
|
436
|
+
*/
|
|
437
|
+
androidActivity(name) {
|
|
438
|
+
this.options_.androidActivity = name
|
|
439
|
+
return this
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Sets the device serial number to connect to via ADB. If not specified, the
|
|
444
|
+
* WebDriver server will select an unused device at random. An error will be
|
|
445
|
+
* returned if all devices already have active sessions.
|
|
446
|
+
*
|
|
447
|
+
* @param {string} serial The device serial number to connect to.
|
|
448
|
+
* @return {!Options} A self reference.
|
|
449
|
+
*/
|
|
450
|
+
androidDeviceSerial(serial) {
|
|
451
|
+
this.options_.androidDeviceSerial = serial
|
|
452
|
+
return this
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Sets the package name of the Chrome or WebView app.
|
|
457
|
+
*
|
|
458
|
+
* @param {?string} pkg The package to connect to, or `null` to disable Android
|
|
459
|
+
* and switch back to using desktop browser.
|
|
460
|
+
* @return {!Options} A self reference.
|
|
461
|
+
*/
|
|
462
|
+
androidPackage(pkg) {
|
|
463
|
+
this.options_.androidPackage = pkg
|
|
464
|
+
return this
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Sets the process name of the Activity hosting the WebView (as given by
|
|
469
|
+
* `ps`). If not specified, the process name is assumed to be the same as
|
|
470
|
+
* {@link #androidPackage}.
|
|
471
|
+
*
|
|
472
|
+
* @param {string} processName The main activity name.
|
|
473
|
+
* @return {!Options} A self reference.
|
|
474
|
+
*/
|
|
475
|
+
androidProcess(processName) {
|
|
476
|
+
this.options_.androidProcess = processName
|
|
477
|
+
return this
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Sets whether to connect to an already-running instead of the specified
|
|
482
|
+
* {@linkplain #androidProcess app} instead of launching the app with a clean
|
|
483
|
+
* data directory.
|
|
484
|
+
*
|
|
485
|
+
* @param {boolean} useRunning Whether to connect to a running instance.
|
|
486
|
+
* @return {!Options} A self reference.
|
|
487
|
+
*/
|
|
488
|
+
androidUseRunningApp(useRunning) {
|
|
489
|
+
this.options_.androidUseRunningApp = useRunning
|
|
490
|
+
return this
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Sets the path to the browser's log file. This path should exist on the machine
|
|
495
|
+
* that will launch the browser.
|
|
496
|
+
* @param {string} path Path to the log file to use.
|
|
497
|
+
* @return {!Options} A self reference.
|
|
498
|
+
*/
|
|
499
|
+
setBrowserLogFile(path) {
|
|
500
|
+
this.options_.logPath = path
|
|
501
|
+
return this
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Sets the directory to store browser minidumps in. This option is only
|
|
506
|
+
* supported when the driver is running on Linux.
|
|
507
|
+
* @param {string} path The directory path.
|
|
508
|
+
* @return {!Options} A self reference.
|
|
509
|
+
*/
|
|
510
|
+
setBrowserMinidumpPath(path) {
|
|
511
|
+
this.options_.minidumpPath = path
|
|
512
|
+
return this
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Configures the browser to emulate a mobile device. For more information, refer
|
|
517
|
+
* to the ChromeDriver project page on [mobile emulation][em]. Configuration
|
|
518
|
+
* options include:
|
|
519
|
+
*
|
|
520
|
+
* - `deviceName`: The name of a pre-configured [emulated device][devem]
|
|
521
|
+
* - `width`: screen width, in pixels
|
|
522
|
+
* - `height`: screen height, in pixels
|
|
523
|
+
* - `pixelRatio`: screen pixel ratio
|
|
524
|
+
*
|
|
525
|
+
* __Example 1: Using a Pre-configured Device__
|
|
526
|
+
*
|
|
527
|
+
* let options = new chrome.Options().setMobileEmulation(
|
|
528
|
+
* {deviceName: 'Google Nexus 5'});
|
|
529
|
+
*
|
|
530
|
+
* let driver = chrome.Driver.createSession(options);
|
|
531
|
+
*
|
|
532
|
+
* __Example 2: Using Custom Screen Configuration__
|
|
533
|
+
*
|
|
534
|
+
* let options = new chrome.Options().setMobileEmulation({deviceMetrics: {
|
|
535
|
+
* width: 360,
|
|
536
|
+
* height: 640,
|
|
537
|
+
* pixelRatio: 3.0
|
|
538
|
+
* }});
|
|
539
|
+
*
|
|
540
|
+
* let driver = chrome.Driver.createSession(options);
|
|
541
|
+
*
|
|
542
|
+
*
|
|
543
|
+
* [em]: https://chromedriver.chromium.org/mobile-emulation
|
|
544
|
+
* [devem]: https://developer.chrome.com/devtools/docs/device-mode
|
|
545
|
+
*
|
|
546
|
+
* @param {?({deviceName: string}|
|
|
547
|
+
* {width: number, height: number, pixelRatio: number})} config The
|
|
548
|
+
* mobile emulation configuration, or `null` to disable emulation.
|
|
549
|
+
* @return {!Options} A self reference.
|
|
550
|
+
*/
|
|
551
|
+
setMobileEmulation(config) {
|
|
552
|
+
this.options_.mobileEmulation = config
|
|
553
|
+
return this
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Converts this instance to its JSON wire protocol representation. Note this
|
|
558
|
+
* function is an implementation not intended for general use.
|
|
559
|
+
*
|
|
560
|
+
* @return {!Object} The JSON wire protocol representation of this instance.
|
|
561
|
+
* @suppress {checkTypes} Suppress [] access on a struct.
|
|
562
|
+
*/
|
|
563
|
+
[Symbols.serialize]() {
|
|
564
|
+
if (this.options_.extensions && this.options_.extensions.length) {
|
|
565
|
+
this.options_.extensions = this.options_.extensions.map(function (
|
|
566
|
+
extension
|
|
567
|
+
) {
|
|
568
|
+
if (Buffer.isBuffer(extension)) {
|
|
569
|
+
return extension.toString('base64')
|
|
570
|
+
}
|
|
571
|
+
return io
|
|
572
|
+
.read(/** @type {string} */ (extension))
|
|
573
|
+
.then((buffer) => buffer.toString('base64'))
|
|
574
|
+
})
|
|
575
|
+
}
|
|
576
|
+
return super[Symbols.serialize]()
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Creates a new WebDriver client for Chromium-based browsers.
|
|
582
|
+
*/
|
|
583
|
+
class Driver extends webdriver.WebDriver {
|
|
584
|
+
/**
|
|
585
|
+
* Creates a new session with the WebDriver server.
|
|
586
|
+
*
|
|
587
|
+
* @param {(Capabilities|Options)=} opt_config The configuration options.
|
|
588
|
+
* @param {(remote.DriverService|http.Executor)=} opt_serviceExecutor Either
|
|
589
|
+
* a DriverService to use for the remote end, or a preconfigured executor
|
|
590
|
+
* for an externally managed endpoint. If neither is provided, the
|
|
591
|
+
* {@linkplain ##getDefaultService default service} will be used by
|
|
592
|
+
* default.
|
|
593
|
+
* @return {!Driver} A new driver instance.
|
|
594
|
+
*/
|
|
595
|
+
static createSession(caps, opt_serviceExecutor) {
|
|
596
|
+
let executor
|
|
597
|
+
let onQuit
|
|
598
|
+
if (opt_serviceExecutor instanceof http.Executor) {
|
|
599
|
+
executor = opt_serviceExecutor
|
|
600
|
+
configureExecutor(executor, this.VENDOR_COMMAND_PREFIX)
|
|
601
|
+
} else {
|
|
602
|
+
let service = opt_serviceExecutor || this.getDefaultService()
|
|
603
|
+
executor = createExecutor(service.start(), this.VENDOR_COMMAND_PREFIX)
|
|
604
|
+
onQuit = () => service.kill()
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// W3C spec requires noProxy value to be an array of strings, but Chromium
|
|
608
|
+
// expects a single host as a string.
|
|
609
|
+
let proxy = caps.get(Capability.PROXY)
|
|
610
|
+
if (proxy && Array.isArray(proxy.noProxy)) {
|
|
611
|
+
proxy.noProxy = proxy.noProxy[0]
|
|
612
|
+
if (!proxy.noProxy) {
|
|
613
|
+
proxy.noProxy = undefined
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
return /** @type {!Driver} */ (super.createSession(executor, caps, onQuit))
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* This function is a no-op as file detectors are not supported by this
|
|
622
|
+
* implementation.
|
|
623
|
+
* @override
|
|
624
|
+
*/
|
|
625
|
+
setFileDetector() {}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Schedules a command to launch Chrome App with given ID.
|
|
629
|
+
* @param {string} id ID of the App to launch.
|
|
630
|
+
* @return {!Promise<void>} A promise that will be resolved
|
|
631
|
+
* when app is launched.
|
|
632
|
+
*/
|
|
633
|
+
launchApp(id) {
|
|
634
|
+
return this.execute(
|
|
635
|
+
new command.Command(Command.LAUNCH_APP).setParameter('id', id)
|
|
636
|
+
)
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Schedules a command to get Chromium network emulation settings.
|
|
641
|
+
* @return {!Promise} A promise that will be resolved when network
|
|
642
|
+
* emulation settings are retrievied.
|
|
643
|
+
*/
|
|
644
|
+
getNetworkConditions() {
|
|
645
|
+
return this.execute(new command.Command(Command.GET_NETWORK_CONDITIONS))
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Schedules a command to set Chromium network emulation settings.
|
|
650
|
+
*
|
|
651
|
+
* __Sample Usage:__
|
|
652
|
+
*
|
|
653
|
+
* driver.setNetworkConditions({
|
|
654
|
+
* offline: false,
|
|
655
|
+
* latency: 5, // Additional latency (ms).
|
|
656
|
+
* download_throughput: 500 * 1024, // Maximal aggregated download throughput.
|
|
657
|
+
* upload_throughput: 500 * 1024 // Maximal aggregated upload throughput.
|
|
658
|
+
* });
|
|
659
|
+
*
|
|
660
|
+
* @param {Object} spec Defines the network conditions to set
|
|
661
|
+
* @return {!Promise<void>} A promise that will be resolved when network
|
|
662
|
+
* emulation settings are set.
|
|
663
|
+
*/
|
|
664
|
+
setNetworkConditions(spec) {
|
|
665
|
+
if (!spec || typeof spec !== 'object') {
|
|
666
|
+
throw TypeError(
|
|
667
|
+
'setNetworkConditions called with non-network-conditions parameter'
|
|
668
|
+
)
|
|
669
|
+
}
|
|
670
|
+
return this.execute(
|
|
671
|
+
new command.Command(Command.SET_NETWORK_CONDITIONS).setParameter(
|
|
672
|
+
'network_conditions',
|
|
673
|
+
spec
|
|
674
|
+
)
|
|
675
|
+
)
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Sends an arbitrary devtools command to the browser.
|
|
680
|
+
*
|
|
681
|
+
* @param {string} cmd The name of the command to send.
|
|
682
|
+
* @param {Object=} params The command parameters.
|
|
683
|
+
* @return {!Promise<void>} A promise that will be resolved when the command
|
|
684
|
+
* has finished.
|
|
685
|
+
* @see <https://chromedevtools.github.io/devtools-protocol/>
|
|
686
|
+
*/
|
|
687
|
+
sendDevToolsCommand(cmd, params = {}) {
|
|
688
|
+
return this.execute(
|
|
689
|
+
new command.Command(Command.SEND_DEVTOOLS_COMMAND)
|
|
690
|
+
.setParameter('cmd', cmd)
|
|
691
|
+
.setParameter('params', params)
|
|
692
|
+
)
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* ends an arbitrary devtools command to the browser and get the result.
|
|
697
|
+
*
|
|
698
|
+
* @param {string} cmd The name of the command to send.
|
|
699
|
+
* @param {Object=} params The command parameters.
|
|
700
|
+
* @return {!Promise<void>} A promise that will be resolved when the command
|
|
701
|
+
* has finished.
|
|
702
|
+
* @see <https://chromedevtools.github.io/devtools-protocol/>
|
|
703
|
+
*/
|
|
704
|
+
sendAndGetDevToolsCommand(cmd, params = {}) {
|
|
705
|
+
return this.execute(
|
|
706
|
+
new command.Command(Command.SEND_AND_GET_DEVTOOLS_COMMAND)
|
|
707
|
+
.setParameter('cmd', cmd)
|
|
708
|
+
.setParameter('params', params)
|
|
709
|
+
)
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Creates a new WebSocket connection.
|
|
714
|
+
* @return {!Promise<resolved>} A new CDP instance.
|
|
715
|
+
*/
|
|
716
|
+
async createCDPConnection(target) {
|
|
717
|
+
const caps = await this.getCapabilities()
|
|
718
|
+
const seOptions = caps['map_'].get('se:options') || new Map()
|
|
719
|
+
const vendorInfo =
|
|
720
|
+
caps['map_'].get(this.VENDOR_COMMAND_PREFIX + ':chromeOptions') ||
|
|
721
|
+
new Map()
|
|
722
|
+
const debuggerUrl = seOptions['cdp'] || vendorInfo['debuggerAddress']
|
|
723
|
+
this._wsUrl = await this.getWsUrl(debuggerUrl, target)
|
|
724
|
+
|
|
725
|
+
return new Promise((resolve, reject) => {
|
|
726
|
+
try {
|
|
727
|
+
this._wsConnection = new WebSocket(this._wsUrl)
|
|
728
|
+
} catch (err) {
|
|
729
|
+
reject(err)
|
|
730
|
+
return
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
this._wsConnection.on('open', () => {
|
|
734
|
+
this._cdpConnection = new cdp.CdpConnection(this._wsConnection)
|
|
735
|
+
resolve(this._cdpConnection)
|
|
736
|
+
})
|
|
737
|
+
|
|
738
|
+
this._wsConnection.on('error', (error) => {
|
|
739
|
+
reject(error)
|
|
740
|
+
})
|
|
741
|
+
})
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* Retrieves 'webSocketDebuggerUrl' by sending a http request using debugger address
|
|
746
|
+
* @param {string} debuggerAddress
|
|
747
|
+
* @param {string} target
|
|
748
|
+
* @return {string} Returns parsed webSocketDebuggerUrl obtained from the http request
|
|
749
|
+
*/
|
|
750
|
+
async getWsUrl(debuggerAddress, target) {
|
|
751
|
+
if (target && cdpTargets.indexOf(target.toLowerCase()) === -1) {
|
|
752
|
+
throw new error.InvalidArgumentError('invalid target value')
|
|
753
|
+
}
|
|
754
|
+
let path = '/json/version'
|
|
755
|
+
|
|
756
|
+
if (target === 'page') {
|
|
757
|
+
path = '/json'
|
|
758
|
+
}
|
|
759
|
+
let request = new http.Request('GET', path)
|
|
760
|
+
let client = new http.HttpClient('http://' + debuggerAddress)
|
|
761
|
+
let response = await client.send(request)
|
|
762
|
+
let url = JSON.parse(response.body)['webSocketDebuggerUrl']
|
|
763
|
+
if (target.toLowerCase() === 'page') {
|
|
764
|
+
url = JSON.parse(response.body)[0]['webSocketDebuggerUrl']
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
return url
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* Sets a listener for Fetch.authRequired event from CDP
|
|
772
|
+
* If event is triggered, it enter username and password
|
|
773
|
+
* and allows the test to move forward
|
|
774
|
+
* @param {string} username
|
|
775
|
+
* @param {string} password
|
|
776
|
+
* @param connection CDP Connection
|
|
777
|
+
*/
|
|
778
|
+
async register(username, password, connection) {
|
|
779
|
+
await connection.execute("Network.setCacheDisabled", this.getRandomNumber(1, 10), {
|
|
780
|
+
cacheDisabled: true,
|
|
781
|
+
}, null);
|
|
782
|
+
|
|
783
|
+
this._wsConnection.on('message', (message) => {
|
|
784
|
+
const params = JSON.parse(message)
|
|
785
|
+
|
|
786
|
+
if (params.method === 'Fetch.authRequired') {
|
|
787
|
+
const requestParams = params['params']
|
|
788
|
+
connection.execute('Fetch.continueWithAuth', this.getRandomNumber(1, 10), {
|
|
789
|
+
requestId: requestParams['requestId'],
|
|
790
|
+
authChallengeResponse: {
|
|
791
|
+
response: 'ProvideCredentials',
|
|
792
|
+
username: username,
|
|
793
|
+
password: password,
|
|
794
|
+
}})
|
|
795
|
+
} else if (params.method === 'Fetch.requestPaused') {
|
|
796
|
+
const requestPausedParams = params['params']
|
|
797
|
+
connection.execute('Fetch.continueRequest', this.getRandomNumber(1, 10), {
|
|
798
|
+
requestId: requestPausedParams['requestId'],
|
|
799
|
+
})
|
|
800
|
+
}
|
|
801
|
+
})
|
|
802
|
+
|
|
803
|
+
await connection.execute('Fetch.enable', 1, {
|
|
804
|
+
handleAuthRequests: true,
|
|
805
|
+
}, null)
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
*
|
|
810
|
+
* @param connection
|
|
811
|
+
* @param callback
|
|
812
|
+
* @returns {Promise<void>}
|
|
813
|
+
*/
|
|
814
|
+
async onLogEvent(connection, callback) {
|
|
815
|
+
await connection.execute('Runtime.enable', this.getRandomNumber(1, 10), {}, null)
|
|
816
|
+
|
|
817
|
+
this._wsConnection.on('message', (message) => {
|
|
818
|
+
const params = JSON.parse(message)
|
|
819
|
+
|
|
820
|
+
if (params.method === 'Runtime.consoleAPICalled') {
|
|
821
|
+
const consoleEventParams = params['params']
|
|
822
|
+
let event = {
|
|
823
|
+
type: consoleEventParams['type'],
|
|
824
|
+
timestamp: new Date(consoleEventParams['timestamp']),
|
|
825
|
+
args: consoleEventParams['args']
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
callback(event)
|
|
829
|
+
}
|
|
830
|
+
})
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* Set a permission state to the given value.
|
|
835
|
+
*
|
|
836
|
+
* @param {string} name A name of the permission to update.
|
|
837
|
+
* @param {('granted'|'denied'|'prompt')} state State to set permission to.
|
|
838
|
+
* @returns {!Promise<Object>} A promise that will be resolved when the
|
|
839
|
+
* command has finished.
|
|
840
|
+
* @see <https://w3c.github.io/permissions/#permission-registry> for valid
|
|
841
|
+
* names
|
|
842
|
+
*/
|
|
843
|
+
setPermission(name, state) {
|
|
844
|
+
return this.execute(
|
|
845
|
+
new command.Command(Command.SET_PERMISSION)
|
|
846
|
+
.setParameter('descriptor', { name })
|
|
847
|
+
.setParameter('state', state)
|
|
848
|
+
)
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/**
|
|
852
|
+
*
|
|
853
|
+
* @param connection
|
|
854
|
+
* @param callback
|
|
855
|
+
* @returns {Promise<void>}
|
|
856
|
+
*/
|
|
857
|
+
async onLogException(connection, callback) {
|
|
858
|
+
await connection.execute('Runtime.enable', this.getRandomNumber(1, 10), {}, null)
|
|
859
|
+
|
|
860
|
+
this._wsConnection.on('message', (message) => {
|
|
861
|
+
const params = JSON.parse(message)
|
|
862
|
+
|
|
863
|
+
if (params.method === 'Runtime.exceptionThrown') {
|
|
864
|
+
const exceptionEventParams = params['params']
|
|
865
|
+
let event = {
|
|
866
|
+
exceptionDetails: exceptionEventParams['exceptionDetails'],
|
|
867
|
+
timestamp: new Date(exceptionEventParams['timestamp']),
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
callback(event)
|
|
871
|
+
}
|
|
872
|
+
})
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/**
|
|
876
|
+
* @param connection
|
|
877
|
+
* @param callback
|
|
878
|
+
* @returns {Promise<void>}
|
|
879
|
+
*/
|
|
880
|
+
async logMutationEvents(connection, callback) {
|
|
881
|
+
await connection.execute('Runtime.enable', this.getRandomNumber(1, 10), {}, null)
|
|
882
|
+
await connection.execute('Page.enable', this.getRandomNumber(1, 10), {}, null)
|
|
883
|
+
|
|
884
|
+
await connection.execute('Runtime.addBinding', this.getRandomNumber(1, 10), {
|
|
885
|
+
name: '__webdriver_attribute',
|
|
886
|
+
}, null)
|
|
887
|
+
|
|
888
|
+
const mutationListener = fs.readFileSync('../../cdp-support/mutation-listener.js', 'utf-8').toString()
|
|
889
|
+
|
|
890
|
+
this.executeScript(mutationListener)
|
|
891
|
+
|
|
892
|
+
await connection.execute('Page.addScriptToEvaluateOnNewDocument', this.getRandomNumber(1, 10), {
|
|
893
|
+
source: mutationListener,
|
|
894
|
+
}, null)
|
|
895
|
+
|
|
896
|
+
this._wsConnection.on('message', async (message) => {
|
|
897
|
+
const params = JSON.parse(message)
|
|
898
|
+
if (params.method === 'Runtime.bindingCalled') {
|
|
899
|
+
let payload = JSON.parse(params['params']['payload'])
|
|
900
|
+
let elements = await this.findElements({css: "*[data-__webdriver_id=" + payload['target']})
|
|
901
|
+
|
|
902
|
+
if (elements.length === 0) {
|
|
903
|
+
return
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
let event = {
|
|
907
|
+
element: elements[0],
|
|
908
|
+
attribute_name: payload['name'],
|
|
909
|
+
current_value: payload['value'],
|
|
910
|
+
old_value: payload['oldValue']
|
|
911
|
+
}
|
|
912
|
+
callback(event)
|
|
913
|
+
}
|
|
914
|
+
})
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
/**
|
|
918
|
+
* Sends a DevTools command to change the browser's download directory.
|
|
919
|
+
*
|
|
920
|
+
* @param {string} path The desired download directory.
|
|
921
|
+
* @return {!Promise<void>} A promise that will be resolved when the command
|
|
922
|
+
* has finished.
|
|
923
|
+
* @see #sendDevToolsCommand
|
|
924
|
+
*/
|
|
925
|
+
async setDownloadPath(path) {
|
|
926
|
+
if (!path || typeof path !== 'string') {
|
|
927
|
+
throw new error.InvalidArgumentError('invalid download path')
|
|
928
|
+
}
|
|
929
|
+
const stat = await io.stat(path)
|
|
930
|
+
if (!stat.isDirectory()) {
|
|
931
|
+
throw new error.InvalidArgumentError('not a directory: ' + path)
|
|
932
|
+
}
|
|
933
|
+
return this.sendDevToolsCommand('Page.setDownloadBehavior', {
|
|
934
|
+
behavior: 'allow',
|
|
935
|
+
downloadPath: path,
|
|
936
|
+
})
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
/**
|
|
940
|
+
* Returns the list of cast sinks (Cast devices) available to the Chrome media router.
|
|
941
|
+
*
|
|
942
|
+
* @return {!promise.Thenable<void>} A promise that will be resolved with an array of Strings
|
|
943
|
+
* containing the friendly device names of available cast sink targets.
|
|
944
|
+
*/
|
|
945
|
+
getCastSinks() {
|
|
946
|
+
return this.schedule(
|
|
947
|
+
new command.Command(Command.GET_CAST_SINKS),
|
|
948
|
+
'Driver.getCastSinks()'
|
|
949
|
+
)
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/**
|
|
953
|
+
* Selects a cast sink (Cast device) as the recipient of media router intents (connect or play).
|
|
954
|
+
*
|
|
955
|
+
* @param {String} deviceName name of the target device.
|
|
956
|
+
* @return {!promise.Thenable<void>} A promise that will be resolved
|
|
957
|
+
* when the target device has been selected to respond further webdriver commands.
|
|
958
|
+
*/
|
|
959
|
+
setCastSinkToUse(deviceName) {
|
|
960
|
+
return this.schedule(
|
|
961
|
+
new command.Command(Command.SET_CAST_SINK_TO_USE).setParameter(
|
|
962
|
+
'sinkName',
|
|
963
|
+
deviceName
|
|
964
|
+
),
|
|
965
|
+
'Driver.setCastSinkToUse(' + deviceName + ')'
|
|
966
|
+
)
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* Initiates tab mirroring for the current browser tab on the specified device.
|
|
971
|
+
*
|
|
972
|
+
* @param {String} deviceName name of the target device.
|
|
973
|
+
* @return {!promise.Thenable<void>} A promise that will be resolved
|
|
974
|
+
* when the mirror command has been issued to the device.
|
|
975
|
+
*/
|
|
976
|
+
startCastTabMirroring(deviceName) {
|
|
977
|
+
return this.schedule(
|
|
978
|
+
new command.Command(Command.START_CAST_TAB_MIRRORING).setParameter(
|
|
979
|
+
'sinkName',
|
|
980
|
+
deviceName
|
|
981
|
+
),
|
|
982
|
+
'Driver.startCastTabMirroring(' + deviceName + ')'
|
|
983
|
+
)
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
/**
|
|
987
|
+
* Returns an error message when there is any issue in a Cast session.
|
|
988
|
+
* @return {!promise.Thenable<void>} A promise that will be resolved
|
|
989
|
+
* when the mirror command has been issued to the device.
|
|
990
|
+
*/
|
|
991
|
+
getCastIssueMessage() {
|
|
992
|
+
return this.schedule(
|
|
993
|
+
new command.Command(Command.GET_CAST_ISSUE_MESSAGE),
|
|
994
|
+
'Driver.getCastIssueMessage()'
|
|
995
|
+
)
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
/**
|
|
999
|
+
* Stops casting from media router to the specified device, if connected.
|
|
1000
|
+
*
|
|
1001
|
+
* @param {String} deviceName name of the target device.
|
|
1002
|
+
* @return {!promise.Thenable<void>} A promise that will be resolved
|
|
1003
|
+
* when the stop command has been issued to the device.
|
|
1004
|
+
*/
|
|
1005
|
+
stopCasting(deviceName) {
|
|
1006
|
+
return this.schedule(
|
|
1007
|
+
new command.Command(Command.STOP_CASTING).setParameter(
|
|
1008
|
+
'sinkName',
|
|
1009
|
+
deviceName
|
|
1010
|
+
),
|
|
1011
|
+
'Driver.stopCasting(' + deviceName + ')'
|
|
1012
|
+
)
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
getRandomNumber(min, max) {
|
|
1016
|
+
return Math.floor(Math.random() * (max - min + 1) + min)
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// PUBLIC API
|
|
1021
|
+
|
|
1022
|
+
exports.Driver = Driver
|
|
1023
|
+
exports.Options = Options
|
|
1024
|
+
exports.ServiceBuilder = ServiceBuilder
|