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
30var fs = require('fs'),
31 util = require('util');
32
33var 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 */
44var IEDRIVER_EXE = 'IEDriverServer.exe';
45
46
47
48/**
49 * IEDriverServer logging levels.
50 * @enum {string}
51 */
52var 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 */
68var 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 */
92var 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 */
108Options.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 */
139Options.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 */
153Options.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 */
169Options.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 */
183Options.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 */
198Options.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 */
213Options.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 */
227Options.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 */
242Options.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 */
255Options.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 */
271Options.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 */
286Options.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 */
297Options.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 */
308Options.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 */
319Options.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 */
330Options.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 */
341Options.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 */
352Options.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 */
364Options.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
376function 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 */
428var 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};
448util.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 */
456Driver.prototype.setFileDetector = function() {
457};
458
459
460// PUBLIC API
461
462
463exports.Driver = Driver;
464exports.Options = Options;
465exports.Level = Level;