# 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 |
28 | var fs = require('fs'),
29 | util = require('util');
30 |
31 | var 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 | */
42 | var IEDRIVER_EXE = 'IEDriverServer.exe';
43 |
44 |
45 |
46 | /**
47 | * IEDriverServer logging levels.
48 | * @enum {string}
49 | */
50 | var 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 | */
66 | var 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 | */
90 | var 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 | */
106 | Options.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 | */
137 | Options.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 | */
151 | Options.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 | */
167 | Options.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 | */
181 | Options.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 | */
196 | Options.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 | */
211 | Options.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 | */
225 | Options.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 | */
240 | Options.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 | */
253 | Options.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 | */
269 | Options.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 | */
284 | Options.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 | */
295 | Options.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 | */
306 | Options.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 | */
317 | Options.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 | */
328 | Options.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 | */
339 | Options.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 | */
350 | Options.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 | */
362 | Options.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 |
374 | function 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 | */
426 | var 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 | };
446 | util.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 | */
454 | Driver.prototype.setFileDetector = function() {
455 | };
456 |
457 |
458 | // PUBLIC API
459 |
460 |
461 | exports.Driver = Driver;
462 | exports.Options = Options;
463 | exports.Level = Level;