ie.js

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