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 a {@linkplain Driver WebDriver} client for Microsoft's
|
20 | * Internet Explorer. Before using the IEDriver, you must download the latest
|
21 | * [IEDriverServer](http://selenium-release.storage.googleapis.com/index.html)
|
22 | * and place it on your
|
23 | * [PATH](http://en.wikipedia.org/wiki/PATH_%28variable%29). You must also apply
|
24 | * the system configuration outlined on the Selenium project
|
25 | * [wiki](https://github.com/SeleniumHQ/selenium/wiki/InternetExplorerDriver)
|
26 | */
|
27 |
|
28 | ;
|
29 |
|
30 | const fs = require('fs');
|
31 | const util = require('util');
|
32 |
|
33 | const http = require('./http');
|
34 | const io = require('./io');
|
35 | const portprober = require('./net/portprober');
|
36 | const promise = require('./lib/promise');
|
37 | const remote = require('./remote');
|
38 | const webdriver = require('./lib/webdriver');
|
39 | const {Browser, Capabilities, Capability} = require('./lib/capabilities');
|
40 |
|
41 |
|
42 | const IEDRIVER_EXE = 'IEDriverServer.exe';
|
43 |
|
44 |
|
45 |
|
46 | /**
|
47 | * IEDriverServer logging levels.
|
48 | * @enum {string}
|
49 | */
|
50 | const Level = {
|
51 | FATAL: 'FATAL',
|
52 | ERROR: 'ERROR',
|
53 | WARN: 'WARN',
|
54 | INFO: 'INFO',
|
55 | DEBUG: 'DEBUG',
|
56 | TRACE: 'TRACE'
|
57 | };
|
58 |
|
59 |
|
60 |
|
61 | /**
|
62 | * Option keys:
|
63 | * https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities#ie-specific
|
64 | * @enum {string}
|
65 | */
|
66 | const Key = {
|
67 | IGNORE_PROTECTED_MODE_SETTINGS: 'ignoreProtectedModeSettings',
|
68 | IGNORE_ZOOM_SETTING: 'ignoreZoomSetting',
|
69 | INITIAL_BROWSER_URL: 'initialBrowserUrl',
|
70 | ENABLE_PERSISTENT_HOVER: 'enablePersistentHover',
|
71 | ENABLE_ELEMENT_CACHE_CLEANUP: 'enableElementCacheCleanup',
|
72 | REQUIRE_WINDOW_FOCUS: 'requireWindowFocus',
|
73 | BROWSER_ATTACH_TIMEOUT: 'browserAttachTimeout',
|
74 | FORCE_CREATE_PROCESS: 'ie.forceCreateProcessApi',
|
75 | BROWSER_COMMAND_LINE_SWITCHES: 'ie.browserCommandLineSwitches',
|
76 | USE_PER_PROCESS_PROXY: 'ie.usePerProcessProxy',
|
77 | ENSURE_CLEAN_SESSION: 'ie.ensureCleanSession',
|
78 | LOG_FILE: 'logFile',
|
79 | LOG_LEVEL: 'logLevel',
|
80 | HOST: 'host',
|
81 | EXTRACT_PATH: 'extractPath',
|
82 | SILENT: 'silent'
|
83 | };
|
84 |
|
85 |
|
86 | /**
|
87 | * Class for managing IEDriver specific options.
|
88 | */
|
89 | class Options extends Capabilities {
|
90 | /**
|
91 | * @param {(Capabilities|Map<string, ?>|Object)=} other Another set of
|
92 | * capabilities to initialize this instance from.
|
93 | */
|
94 | constructor(other = undefined) {
|
95 | super(other);
|
96 | this.setBrowserName(Browser.IE);
|
97 | }
|
98 |
|
99 | /**
|
100 | * Whether to disable the protected mode settings check when the session is
|
101 | * created. Disbling this setting may lead to significant instability as the
|
102 | * browser may become unresponsive/hang. Only "best effort" support is provided
|
103 | * when using this capability.
|
104 | *
|
105 | * For more information, refer to the IEDriver's
|
106 | * [required system configuration](http://goo.gl/eH0Yi3).
|
107 | *
|
108 | * @param {boolean} ignoreSettings Whether to ignore protected mode settings.
|
109 | * @return {!Options} A self reference.
|
110 | */
|
111 | introduceFlakinessByIgnoringProtectedModeSettings(ignoreSettings) {
|
112 | this.set(Key.IGNORE_PROTECTED_MODE_SETTINGS, !!ignoreSettings);
|
113 | return this;
|
114 | }
|
115 |
|
116 | /**
|
117 | * Indicates whether to skip the check that the browser's zoom level is set to
|
118 | * 100%.
|
119 | *
|
120 | * @param {boolean} ignore Whether to ignore the browser's zoom level settings.
|
121 | * @return {!Options} A self reference.
|
122 | */
|
123 | ignoreZoomSetting(ignore) {
|
124 | this.set(Key.IGNORE_ZOOM_SETTING, !!ignore);
|
125 | return this;
|
126 | }
|
127 |
|
128 | /**
|
129 | * Sets the initial URL loaded when IE starts. This is intended to be used with
|
130 | * {@link #ignoreProtectedModeSettings} to allow the user to initialize IE in
|
131 | * the proper Protected Mode zone. Setting this option may cause browser
|
132 | * instability or flaky and unresponsive code. Only "best effort" support is
|
133 | * provided when using this option.
|
134 | *
|
135 | * @param {string} url The initial browser URL.
|
136 | * @return {!Options} A self reference.
|
137 | */
|
138 | initialBrowserUrl(url) {
|
139 | this.set(Key.INITIAL_BROWSER_URL, url);
|
140 | return this;
|
141 | }
|
142 |
|
143 | /**
|
144 | * Configures whether to enable persistent mouse hovering (true by default).
|
145 | * Persistent hovering is achieved by continuously firing mouse over events at
|
146 | * the last location the mouse cursor has been moved to.
|
147 | *
|
148 | * @param {boolean} enable Whether to enable persistent hovering.
|
149 | * @return {!Options} A self reference.
|
150 | */
|
151 | enablePersistentHover(enable) {
|
152 | this.set(Key.ENABLE_PERSISTENT_HOVER, !!enable);
|
153 | return this;
|
154 | }
|
155 |
|
156 | /**
|
157 | * Configures whether the driver should attempt to remove obsolete
|
158 | * {@linkplain webdriver.WebElement WebElements} from its internal cache on
|
159 | * page navigation (true by default). Disabling this option will cause the
|
160 | * driver to run with a larger memory footprint.
|
161 | *
|
162 | * @param {boolean} enable Whether to enable element reference cleanup.
|
163 | * @return {!Options} A self reference.
|
164 | */
|
165 | enableElementCacheCleanup(enable) {
|
166 | this.set(Key.ENABLE_ELEMENT_CACHE_CLEANUP, !!enable);
|
167 | return this;
|
168 | }
|
169 |
|
170 | /**
|
171 | * Configures whether to require the IE window to have input focus before
|
172 | * performing any user interactions (i.e. mouse or keyboard events). This
|
173 | * option is disabled by default, but delivers much more accurate interaction
|
174 | * events when enabled.
|
175 | *
|
176 | * @param {boolean} require Whether to require window focus.
|
177 | * @return {!Options} A self reference.
|
178 | */
|
179 | requireWindowFocus(require) {
|
180 | this.set(Key.REQUIRE_WINDOW_FOCUS, !!require);
|
181 | return this;
|
182 | }
|
183 |
|
184 | /**
|
185 | * Configures the timeout, in milliseconds, that the driver will attempt to
|
186 | * located and attach to a newly opened instance of Internet Explorer. The
|
187 | * default is zero, which indicates waiting indefinitely.
|
188 | *
|
189 | * @param {number} timeout How long to wait for IE.
|
190 | * @return {!Options} A self reference.
|
191 | */
|
192 | browserAttachTimeout(timeout) {
|
193 | this.set(Key.BROWSER_ATTACH_TIMEOUT, Math.max(timeout, 0));
|
194 | return this;
|
195 | }
|
196 |
|
197 | /**
|
198 | * Configures whether to launch Internet Explorer using the CreateProcess API.
|
199 | * If this option is not specified, IE is launched using IELaunchURL, if
|
200 | * available. For IE 8 and above, this option requires the TabProcGrowth
|
201 | * registry value to be set to 0.
|
202 | *
|
203 | * @param {boolean} force Whether to use the CreateProcess API.
|
204 | * @return {!Options} A self reference.
|
205 | */
|
206 | forceCreateProcessApi(force) {
|
207 | this.set(Key.FORCE_CREATE_PROCESS, !!force);
|
208 | return this;
|
209 | }
|
210 |
|
211 | /**
|
212 | * Specifies command-line switches to use when launching Internet Explorer.
|
213 | * This is only valid when used with {@link #forceCreateProcessApi}.
|
214 | *
|
215 | * @param {...(string|!Array.<string>)} args The arguments to add.
|
216 | * @return {!Options} A self reference.
|
217 | */
|
218 | addArguments(...args) {
|
219 | let current = this.get(Key.BROWSER_COMMAND_LINE_SWITCHES) || [];
|
220 | this.set(
|
221 | Key.BROWSER_COMMAND_LINE_SWITCHES,
|
222 | current.concat.apply(current, args));
|
223 | return this;
|
224 | }
|
225 |
|
226 | /**
|
227 | * Configures whether proxies should be configured on a per-process basis. If
|
228 | * not set, setting a {@linkplain #setProxy proxy} will configure the system
|
229 | * proxy. The default behavior is to use the system proxy.
|
230 | *
|
231 | * @param {boolean} enable Whether to enable per-process proxy settings.
|
232 | * @return {!Options} A self reference.
|
233 | */
|
234 | usePerProcessProxy(enable) {
|
235 | this.set(Key.USE_PER_PROCESS_PROXY, !!enable);
|
236 | return this;
|
237 | }
|
238 |
|
239 | /**
|
240 | * Configures whether to clear the cache, cookies, history, and saved form data
|
241 | * before starting the browser. _Using this capability will clear session data
|
242 | * for all running instances of Internet Explorer, including those started
|
243 | * manually._
|
244 | *
|
245 | * @param {boolean} cleanSession Whether to clear all session data on startup.
|
246 | * @return {!Options} A self reference.
|
247 | */
|
248 | ensureCleanSession(cleanSession) {
|
249 | this.set(Key.ENSURE_CLEAN_SESSION, !!cleanSession);
|
250 | return this;
|
251 | }
|
252 |
|
253 | /**
|
254 | * Sets the path to the log file the driver should log to.
|
255 | * @param {string} file The log file path.
|
256 | * @return {!Options} A self reference.
|
257 | */
|
258 | setLogFile(file) {
|
259 | this.set(Key.LOG_FILE, file);
|
260 | return this;
|
261 | }
|
262 |
|
263 | /**
|
264 | * Sets the IEDriverServer's logging {@linkplain Level level}.
|
265 | * @param {Level} level The logging level.
|
266 | * @return {!Options} A self reference.
|
267 | */
|
268 | setLogLevel(level) {
|
269 | this.set(Key.LOG_LEVEL, level);
|
270 | return this;
|
271 | }
|
272 |
|
273 | /**
|
274 | * Sets the IP address of the driver's host adapter.
|
275 | * @param {string} host The IP address to use.
|
276 | * @return {!Options} A self reference.
|
277 | */
|
278 | setHost(host) {
|
279 | this.set(Key.HOST, host);
|
280 | return this;
|
281 | }
|
282 |
|
283 | /**
|
284 | * Sets the path of the temporary data directory to use.
|
285 | * @param {string} path The log file path.
|
286 | * @return {!Options} A self reference.
|
287 | */
|
288 | setExtractPath(path) {
|
289 | this.set(Key.EXTRACT_PATH, path);
|
290 | return this;
|
291 | }
|
292 |
|
293 | /**
|
294 | * Sets whether the driver should start in silent mode.
|
295 | * @param {boolean} silent Whether to run in silent mode.
|
296 | * @return {!Options} A self reference.
|
297 | */
|
298 | silent(silent) {
|
299 | this.set(Key.SILENT, silent);
|
300 | return this;
|
301 | }
|
302 | }
|
303 |
|
304 |
|
305 | /**
|
306 | * _Synchronously_ attempts to locate the IE driver executable on the current
|
307 | * system.
|
308 | *
|
309 | * @return {?string} the located executable, or `null`.
|
310 | */
|
311 | function locateSynchronously() {
|
312 | return process.platform === 'win32'
|
313 | ? io.findInPath(IEDRIVER_EXE, true) : null;
|
314 | }
|
315 |
|
316 |
|
317 | function createServiceFromCapabilities(capabilities) {
|
318 | if (process.platform !== 'win32') {
|
319 | throw Error(
|
320 | 'The IEDriver may only be used on Windows, but you appear to be on ' +
|
321 | process.platform + '. Did you mean to run against a remote ' +
|
322 | 'WebDriver server?');
|
323 | }
|
324 |
|
325 | let exe = locateSynchronously();
|
326 | if (!exe || !fs.existsSync(exe)) {
|
327 | throw Error(
|
328 | `${IEDRIVER_EXE} could not be found on the current PATH. Please ` +
|
329 | `download the latest version of ${IEDRIVER_EXE} from ` +
|
330 | 'http://selenium-release.storage.googleapis.com/index.html and ' +
|
331 | 'ensure it can be found on your system PATH.');
|
332 | }
|
333 |
|
334 | var args = [];
|
335 | if (capabilities.has(Key.HOST)) {
|
336 | args.push('--host=' + capabilities.get(Key.HOST));
|
337 | }
|
338 | if (capabilities.has(Key.LOG_FILE)) {
|
339 | args.push('--log-file=' + capabilities.get(Key.LOG_FILE));
|
340 | }
|
341 | if (capabilities.has(Key.LOG_LEVEL)) {
|
342 | args.push('--log-level=' + capabilities.get(Key.LOG_LEVEL));
|
343 | }
|
344 | if (capabilities.has(Key.EXTRACT_PATH)) {
|
345 | args.push('--extract-path=' + capabilities.get(Key.EXTRACT_PATH));
|
346 | }
|
347 | if (capabilities.get(Key.SILENT)) {
|
348 | args.push('--silent');
|
349 | }
|
350 |
|
351 | var port = portprober.findFreePort();
|
352 | return new remote.DriverService(exe, {
|
353 | loopback: true,
|
354 | port: port,
|
355 | args: port.then(function(port) {
|
356 | return args.concat('--port=' + port);
|
357 | }),
|
358 | stdio: 'ignore'
|
359 | });
|
360 | }
|
361 |
|
362 |
|
363 | /**
|
364 | * Creates {@link selenium-webdriver/remote.DriverService} instances that manage
|
365 | * an [IEDriverServer](https://github.com/SeleniumHQ/selenium/wiki/InternetExplorerDriver)
|
366 | * server in a child process.
|
367 | */
|
368 | class ServiceBuilder extends remote.DriverService.Builder {
|
369 | /**
|
370 | * @param {string=} opt_exe Path to the server executable to use. If omitted,
|
371 | * the builder will attempt to locate the IEDriverServer on the system PATH.
|
372 | */
|
373 | constructor(opt_exe) {
|
374 | super(opt_exe || IEDRIVER_EXE);
|
375 | this.setLoopback(true); // Required.
|
376 | }
|
377 | }
|
378 |
|
379 |
|
380 | /**
|
381 | * A WebDriver client for Microsoft's Internet Explorer.
|
382 | */
|
383 | class Driver extends webdriver.WebDriver {
|
384 | /**
|
385 | * Creates a new session for Microsoft's Internet Explorer.
|
386 | *
|
387 | * @param {(Capabilities|Options)=} options The configuration options.
|
388 | * @param {(remote.DriverService)=} opt_service The `DriverService` to use
|
389 | * to start the IEDriverServer in a child process, optionally.
|
390 | * @return {!Driver} A new driver instance.
|
391 | */
|
392 | static createSession(options, opt_service) {
|
393 | options = options || new Options();
|
394 |
|
395 | let service;
|
396 |
|
397 | if (opt_service instanceof remote.DriverService) {
|
398 | service = opt_service;
|
399 | } else {
|
400 | service = createServiceFromCapabilities(options);
|
401 | }
|
402 |
|
403 | let client = service.start().then(url => new http.HttpClient(url));
|
404 | let executor = new http.Executor(client);
|
405 |
|
406 | return /** @type {!Driver} */(super.createSession(
|
407 | executor, options, () => service.kill()));
|
408 | }
|
409 |
|
410 | /**
|
411 | * This function is a no-op as file detectors are not supported by this
|
412 | * implementation.
|
413 | * @override
|
414 | */
|
415 | setFileDetector() {}
|
416 | }
|
417 |
|
418 |
|
419 | // PUBLIC API
|
420 |
|
421 |
|
422 | exports.Driver = Driver;
|
423 | exports.Options = Options;
|
424 | exports.Level = Level;
|
425 | exports.ServiceBuilder = ServiceBuilder;
|
426 | exports.locateSynchronously = locateSynchronously;
|
427 |
|