# ie.js

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