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 the {@linkplain Driver WebDriver} client for Firefox.
|
20 | * Before using this module, you must download the latest
|
21 | * [geckodriver release] and ensure it can be found on your system [PATH].
|
22 | *
|
23 | * Each FirefoxDriver instance will be created with an anonymous profile,
|
24 | * ensuring browser historys do not share session data (cookies, history, cache,
|
25 | * offline storage, etc.)
|
26 | *
|
27 | * __Customizing the Firefox Profile__
|
28 | *
|
29 | * The {@link Profile} class may be used to configure the browser profile used
|
30 | * with WebDriver, with functions to install additional
|
31 | * {@linkplain Profile#addExtension extensions}, configure browser
|
32 | * {@linkplain Profile#setPreference preferences}, and more. For example, you
|
33 | * may wish to include Firebug:
|
34 | *
|
35 | * var firefox = require('selenium-webdriver/firefox');
|
36 | *
|
37 | * var profile = new firefox.Profile();
|
38 | * profile.addExtension('/path/to/firebug.xpi');
|
39 | * profile.setPreference('extensions.firebug.showChromeErrors', true);
|
40 | *
|
41 | * var options = new firefox.Options().setProfile(profile);
|
42 | * var driver = new firefox.Driver(options);
|
43 | *
|
44 | * The {@link Profile} class may also be used to configure WebDriver based on a
|
45 | * pre-existing browser profile:
|
46 | *
|
47 | * var profile = new firefox.Profile(
|
48 | * '/usr/local/home/bob/.mozilla/firefox/3fgog75h.testing');
|
49 | * var options = new firefox.Options().setProfile(profile);
|
50 | * var driver = new firefox.Driver(options);
|
51 | *
|
52 | * The FirefoxDriver will _never_ modify a pre-existing profile; instead it will
|
53 | * create a copy for it to modify. By extension, there are certain browser
|
54 | * preferences that are required for WebDriver to function properly and they
|
55 | * will always be overwritten.
|
56 | *
|
57 | * __Using a Custom Firefox Binary__
|
58 | *
|
59 | * On Windows and OSX, the FirefoxDriver will search for Firefox in its
|
60 | * default installation location:
|
61 | *
|
62 | * * Windows: C:\Program Files and C:\Program Files (x86).
|
63 | * * Mac OS X: /Applications/Firefox.app
|
64 | *
|
65 | * For Linux, Firefox will be located on the PATH: `$(where firefox)`.
|
66 | *
|
67 | * You can configure WebDriver to start use a custom Firefox installation with
|
68 | * the {@link Binary} class:
|
69 | *
|
70 | * var firefox = require('selenium-webdriver/firefox');
|
71 | * var binary = new firefox.Binary('/my/firefox/install/dir/firefox-bin');
|
72 | * var options = new firefox.Options().setBinary(binary);
|
73 | * var driver = new firefox.Driver(options);
|
74 | *
|
75 | * __Remote Testing__
|
76 | *
|
77 | * You may customize the Firefox binary and profile when running against a
|
78 | * remote Selenium server. Your custom profile will be packaged as a zip and
|
79 | * transfered to the remote host for use. The profile will be transferred
|
80 | * _once for each new session_. The performance impact should be minimal if
|
81 | * you've only configured a few extra browser preferences. If you have a large
|
82 | * profile with several extensions, you should consider installing it on the
|
83 | * remote host and defining its path via the {@link Options} class. Custom
|
84 | * binaries are never copied to remote machines and must be referenced by
|
85 | * installation path.
|
86 | *
|
87 | * var options = new firefox.Options()
|
88 | * .setProfile('/profile/path/on/remote/host')
|
89 | * .setBinary('/install/dir/on/remote/host/firefox-bin');
|
90 | *
|
91 | * var driver = new (require('selenium-webdriver')).Builder()
|
92 | * .forBrowser('firefox')
|
93 | * .usingServer('http://127.0.0.1:4444/wd/hub')
|
94 | * .setFirefoxOptions(options)
|
95 | * .build();
|
96 | *
|
97 | * __Testing Older Versions of Firefox__
|
98 | *
|
99 | * To test versions of Firefox prior to Firefox 47, you must disable the use of
|
100 | * the geckodriver using the {@link Options} class.
|
101 | *
|
102 | * var options = new firefox.Options().useGeckoDriver(false);
|
103 | * var driver = new firefox.Driver(options);
|
104 | *
|
105 | * Alternatively, you may disable the geckodriver at runtime by setting the
|
106 | * environment variable `SELENIUM_MARIONETTE=false`.
|
107 | *
|
108 | * [geckodriver release]: https://github.com/mozilla/geckodriver/releases/
|
109 | * [PATH]: http://en.wikipedia.org/wiki/PATH_%28variable%29
|
110 | */
|
111 |
|
112 | ;
|
113 |
|
114 | const url = require('url');
|
115 |
|
116 | const Binary = require('./binary').Binary,
|
117 | Profile = require('./profile').Profile,
|
118 | decodeProfile = require('./profile').decode,
|
119 | http = require('../http'),
|
120 | httpUtil = require('../http/util'),
|
121 | io = require('../io'),
|
122 | capabilities = require('../lib/capabilities'),
|
123 | command = require('../lib/command'),
|
124 | logging = require('../lib/logging'),
|
125 | promise = require('../lib/promise'),
|
126 | webdriver = require('../lib/webdriver'),
|
127 | net = require('../net'),
|
128 | portprober = require('../net/portprober'),
|
129 | remote = require('../remote');
|
130 |
|
131 |
|
132 | /**
|
133 | * Firefox-specific capability keys. Users should use the {@linkplain Options}
|
134 | * class instead of referencing these keys directly. _These keys are considered
|
135 | * implementation details and may be removed or changed at any time._
|
136 | *
|
137 | * @enum {string}
|
138 | */
|
139 | const Capability = {
|
140 | /**
|
141 | * Defines the Firefox binary to use. May be set to either a
|
142 | * {@linkplain Binary} instance, or a string path to the Firefox executable.
|
143 | */
|
144 | BINARY: 'firefox_binary',
|
145 |
|
146 | /**
|
147 | * Specifies whether to use Mozilla's Marionette, or the legacy FirefoxDriver
|
148 | * from the Selenium project. Defaults to false.
|
149 | */
|
150 | MARIONETTE: 'marionette',
|
151 |
|
152 | /**
|
153 | * Defines the Firefox profile to use. May be set to either a
|
154 | * {@linkplain Profile} instance, or to a base-64 encoded zip of a profile
|
155 | * directory.
|
156 | */
|
157 | PROFILE: 'firefox_profile'
|
158 | };
|
159 |
|
160 |
|
161 | /**
|
162 | * Configuration options for the FirefoxDriver.
|
163 | */
|
164 | class Options {
|
165 | constructor() {
|
166 | /** @private {Profile} */
|
167 | this.profile_ = null;
|
168 |
|
169 | /** @private {Binary} */
|
170 | this.binary_ = null;
|
171 |
|
172 | /** @private {logging.Preferences} */
|
173 | this.logPrefs_ = null;
|
174 |
|
175 | /** @private {?capabilities.ProxyConfig} */
|
176 | this.proxy_ = null;
|
177 |
|
178 | /** @private {boolean} */
|
179 | this.marionette_ = true;
|
180 | }
|
181 |
|
182 | /**
|
183 | * Sets the profile to use. The profile may be specified as a
|
184 | * {@link Profile} object or as the path to an existing Firefox profile to use
|
185 | * as a template.
|
186 | *
|
187 | * @param {(string|!Profile)} profile The profile to use.
|
188 | * @return {!Options} A self reference.
|
189 | */
|
190 | setProfile(profile) {
|
191 | if (typeof profile === 'string') {
|
192 | profile = new Profile(profile);
|
193 | }
|
194 | this.profile_ = profile;
|
195 | return this;
|
196 | }
|
197 |
|
198 | /**
|
199 | * Sets the binary to use. The binary may be specified as the path to a Firefox
|
200 | * executable, or as a {@link Binary} object.
|
201 | *
|
202 | * @param {(string|!Binary)} binary The binary to use.
|
203 | * @return {!Options} A self reference.
|
204 | */
|
205 | setBinary(binary) {
|
206 | if (typeof binary === 'string') {
|
207 | binary = new Binary(binary);
|
208 | }
|
209 | this.binary_ = binary;
|
210 | return this;
|
211 | }
|
212 |
|
213 | /**
|
214 | * Sets the logging preferences for the new session.
|
215 | * @param {logging.Preferences} prefs The logging preferences.
|
216 | * @return {!Options} A self reference.
|
217 | */
|
218 | setLoggingPreferences(prefs) {
|
219 | this.logPrefs_ = prefs;
|
220 | return this;
|
221 | }
|
222 |
|
223 | /**
|
224 | * Sets the proxy to use.
|
225 | *
|
226 | * @param {capabilities.ProxyConfig} proxy The proxy configuration to use.
|
227 | * @return {!Options} A self reference.
|
228 | */
|
229 | setProxy(proxy) {
|
230 | this.proxy_ = proxy;
|
231 | return this;
|
232 | }
|
233 |
|
234 | /**
|
235 | * Sets whether to use Mozilla's geckodriver to drive the browser. This option
|
236 | * is enabled by default and required for Firefox 47+.
|
237 | *
|
238 | * @param {boolean} enable Whether to enable the geckodriver.
|
239 | * @see https://github.com/mozilla/geckodriver
|
240 | */
|
241 | useGeckoDriver(enable) {
|
242 | this.marionette_ = enable;
|
243 | return this;
|
244 | }
|
245 |
|
246 | /**
|
247 | * Converts these options to a {@link capabilities.Capabilities} instance.
|
248 | *
|
249 | * @return {!capabilities.Capabilities} A new capabilities object.
|
250 | */
|
251 | toCapabilities() {
|
252 | var caps = capabilities.Capabilities.firefox();
|
253 | if (this.logPrefs_) {
|
254 | caps.set(capabilities.Capability.LOGGING_PREFS, this.logPrefs_);
|
255 | }
|
256 | if (this.proxy_) {
|
257 | caps.set(capabilities.Capability.PROXY, this.proxy_);
|
258 | }
|
259 | if (this.binary_) {
|
260 | caps.set(Capability.BINARY, this.binary_);
|
261 | }
|
262 | if (this.profile_) {
|
263 | caps.set(Capability.PROFILE, this.profile_);
|
264 | }
|
265 | caps.set(Capability.MARIONETTE, this.marionette_);
|
266 | return caps;
|
267 | }
|
268 | }
|
269 |
|
270 |
|
271 | /**
|
272 | * Enum of available command contexts.
|
273 | *
|
274 | * Command contexts are specific to Marionette, and may be used with the
|
275 | * {@link #context=} method. Contexts allow you to direct all subsequent
|
276 | * commands to either "content" (default) or "chrome". The latter gives
|
277 | * you elevated security permissions.
|
278 | *
|
279 | * @enum {string}
|
280 | */
|
281 | const Context = {
|
282 | CONTENT: "content",
|
283 | CHROME: "chrome",
|
284 | };
|
285 |
|
286 |
|
287 | const GECKO_DRIVER_EXE =
|
288 | process.platform === 'win32' ? 'geckodriver.exe' : 'geckodriver';
|
289 |
|
290 |
|
291 | /**
|
292 | * @return {string} .
|
293 | * @throws {Error}
|
294 | */
|
295 | function findGeckoDriver() {
|
296 | let exe = io.findInPath(GECKO_DRIVER_EXE, true);
|
297 | if (!exe) {
|
298 | throw Error(
|
299 | 'The ' + GECKO_DRIVER_EXE + ' executable could not be found on the current ' +
|
300 | 'PATH. Please download the latest version from ' +
|
301 | 'https://github.com/mozilla/geckodriver/releases/' +
|
302 | 'WebDriver and ensure it can be found on your PATH.');
|
303 | }
|
304 | return exe;
|
305 | }
|
306 |
|
307 |
|
308 | /**
|
309 | * @param {(Profile|string)} profile The profile to prepare.
|
310 | * @param {number} port The port the FirefoxDriver should listen on.
|
311 | * @return {!Promise<string>} a promise for the path to the profile directory.
|
312 | */
|
313 | function prepareProfile(profile, port) {
|
314 | if (typeof profile === 'string') {
|
315 | return decodeProfile(/** @type {string} */(profile)).then(dir => {
|
316 | profile = new Profile(dir);
|
317 | profile.setPreference('webdriver_firefox_port', port);
|
318 | return profile.writeToDisk();
|
319 | });
|
320 | }
|
321 |
|
322 | profile = profile || new Profile;
|
323 | profile.setPreference('webdriver_firefox_port', port);
|
324 | return profile.writeToDisk();
|
325 | }
|
326 |
|
327 |
|
328 | function normalizeProxyConfiguration(config) {
|
329 | if ('manual' === config.proxyType) {
|
330 | if (config.ftpProxy && !config.ftpProxyPort) {
|
331 | let hostAndPort = net.splitHostAndPort(config.ftpProxy);
|
332 | config.ftpProxy = hostAndPort.host;
|
333 | config.ftpProxyPort = hostAndPort.port;
|
334 | }
|
335 |
|
336 | if (config.httpProxy && !config.httpProxyPort) {
|
337 | let hostAndPort = net.splitHostAndPort(config.httpProxy);
|
338 | config.httpProxy = hostAndPort.host;
|
339 | config.httpProxyPort = hostAndPort.port;
|
340 | }
|
341 |
|
342 | if (config.sslProxy && !config.sslProxyPort) {
|
343 | let hostAndPort = net.splitHostAndPort(config.sslProxy);
|
344 | config.sslProxy = hostAndPort.host;
|
345 | config.sslProxyPort = hostAndPort.port;
|
346 | }
|
347 |
|
348 | if (config.socksProxy && !config.socksProxyPort) {
|
349 | let hostAndPort = net.splitHostAndPort(config.socksProxy);
|
350 | config.socksProxy = hostAndPort.host;
|
351 | config.socksProxyPort = hostAndPort.port;
|
352 | }
|
353 | } else if ('pac' === config.proxyType) {
|
354 | if (config.proxyAutoconfigUrl && !config.pacUrl) {
|
355 | config.pacUrl = config.proxyAutoconfigUrl;
|
356 | }
|
357 | }
|
358 | return config;
|
359 | }
|
360 |
|
361 |
|
362 | /** @enum {string} */
|
363 | const ExtensionCommand = {
|
364 | GET_CONTEXT: 'getContext',
|
365 | SET_CONTEXT: 'setContext',
|
366 | };
|
367 |
|
368 |
|
369 | /**
|
370 | * Creates a command executor with support for Marionette's custom commands.
|
371 | * @param {!Promise<string>} serverUrl The server's URL.
|
372 | * @return {!command.Executor} The new command executor.
|
373 | */
|
374 | function createExecutor(serverUrl) {
|
375 | let client = serverUrl.then(url => new http.HttpClient(url));
|
376 | let executor = new http.Executor(client);
|
377 | configureExecutor(executor);
|
378 | return executor;
|
379 | }
|
380 |
|
381 |
|
382 | /**
|
383 | * Configures the given executor with Firefox-specific commands.
|
384 | * @param {!http.Executor} executor the executor to configure.
|
385 | */
|
386 | function configureExecutor(executor) {
|
387 | executor.defineCommand(
|
388 | ExtensionCommand.GET_CONTEXT,
|
389 | 'GET',
|
390 | '/session/:sessionId/moz/context');
|
391 |
|
392 | executor.defineCommand(
|
393 | ExtensionCommand.SET_CONTEXT,
|
394 | 'POST',
|
395 | '/session/:sessionId/moz/context');
|
396 | }
|
397 |
|
398 |
|
399 | /**
|
400 | * Creates {@link selenium-webdriver/remote.DriverService} instances that manage
|
401 | * a [geckodriver](https://github.com/mozilla/geckodriver) server in a child
|
402 | * process.
|
403 | */
|
404 | class ServiceBuilder extends remote.DriverService.Builder {
|
405 | /**
|
406 | * @param {string=} opt_exe Path to the server executable to use. If omitted,
|
407 | * the builder will attempt to locate the geckodriver on the system PATH.
|
408 | */
|
409 | constructor(opt_exe) {
|
410 | super(opt_exe || findGeckoDriver());
|
411 | this.setLoopback(true); // Required.
|
412 | }
|
413 |
|
414 | /**
|
415 | * Enables verbose logging.
|
416 | *
|
417 | * @param {boolean=} opt_trace Whether to enable trace-level logging. By
|
418 | * default, only debug logging is enabled.
|
419 | * @return {!ServiceBuilder} A self reference.
|
420 | */
|
421 | enableVerboseLogging(opt_trace) {
|
422 | return this.addArguments(opt_trace ? '-vv' : '-v');
|
423 | }
|
424 |
|
425 | /**
|
426 | * Sets the path to the executable Firefox binary that the geckodriver should
|
427 | * use. If this method is not called, this builder will attempt to locate
|
428 | * Firefox in the default installation location for the current platform.
|
429 | *
|
430 | * @param {(string|!Binary)} binary Path to the executable Firefox binary to use.
|
431 | * @return {!ServiceBuilder} A self reference.
|
432 | * @see Binary#locate()
|
433 | */
|
434 | setFirefoxBinary(binary) {
|
435 | let exe = typeof binary === 'string'
|
436 | ? Promise.resolve(binary) : binary.locate();
|
437 | return this.addArguments('-b', exe);
|
438 | }
|
439 | }
|
440 |
|
441 |
|
442 | /**
|
443 | * @typedef {{executor: !command.Executor,
|
444 | * capabilities: (!capabilities.Capabilities|
|
445 | * {desired: (capabilities.Capabilities|undefined),
|
446 | * required: (capabilities.Capabilities|undefined)}),
|
447 | * onQuit: function(this: void): ?}}
|
448 | */
|
449 | var DriverSpec;
|
450 |
|
451 |
|
452 | /**
|
453 | * @param {(http.Executor|remote.DriverService|undefined)} executor
|
454 | * @param {!capabilities.Capabilities} caps
|
455 | * @param {Profile} profile
|
456 | * @param {Binary} binary
|
457 | * @return {DriverSpec}
|
458 | */
|
459 | function createGeckoDriver(executor, caps, profile, binary) {
|
460 | let firefoxOptions = {};
|
461 | caps.set('moz:firefoxOptions', firefoxOptions);
|
462 |
|
463 | if (binary) {
|
464 | if (binary.getExe()) {
|
465 | firefoxOptions['binary'] = binary.getExe();
|
466 | }
|
467 |
|
468 | let args = binary.getArguments();
|
469 | if (args.length) {
|
470 | firefoxOptions['args'] = args;
|
471 | }
|
472 | }
|
473 |
|
474 | if (profile) {
|
475 | // If the user specified a template directory or any extensions to install,
|
476 | // we need to encode the profile as a base64 string (which requires writing
|
477 | // it to disk first). Otherwise, if the user just specified some custom
|
478 | // preferences, we can send those directly.
|
479 | if (profile.getTemplateDir() || profile.getExtensions().length) {
|
480 | firefoxOptions['profile'] = profile.encode();
|
481 |
|
482 | } else {
|
483 | let prefs = profile.getPreferences();
|
484 | if (Object.keys(prefs).length) {
|
485 | firefoxOptions['prefs'] = prefs;
|
486 | }
|
487 | }
|
488 | }
|
489 |
|
490 | let sessionCaps = caps;
|
491 | if (caps.has(capabilities.Capability.PROXY)) {
|
492 | let proxy = normalizeProxyConfiguration(
|
493 | caps.get(capabilities.Capability.PROXY));
|
494 |
|
495 | // Marionette requires proxy settings to be specified as required
|
496 | // capabilities. See mozilla/geckodriver#97
|
497 | let required = new capabilities.Capabilities()
|
498 | .set(capabilities.Capability.PROXY, proxy);
|
499 |
|
500 | caps.delete(capabilities.Capability.PROXY);
|
501 | sessionCaps = {required, desired: caps};
|
502 | }
|
503 |
|
504 | /** @type {!command.Executor} */
|
505 | let cmdExecutor;
|
506 | let onQuit = function() {};
|
507 |
|
508 | if (executor instanceof http.Executor) {
|
509 | configureExecutor(executor);
|
510 | cmdExecutor = executor;
|
511 | } else if (executor instanceof remote.DriverService) {
|
512 | cmdExecutor = createExecutor(executor.start());
|
513 | onQuit = () => executor.kill();
|
514 | } else {
|
515 | let builder = new ServiceBuilder();
|
516 | if (binary) {
|
517 | builder.setFirefoxBinary(binary);
|
518 | }
|
519 | let service = builder.build();
|
520 | cmdExecutor = createExecutor(service.start());
|
521 | onQuit = () => service.kill();
|
522 | }
|
523 |
|
524 | return {
|
525 | executor: cmdExecutor,
|
526 | capabilities: sessionCaps,
|
527 | onQuit
|
528 | };
|
529 | }
|
530 |
|
531 |
|
532 | /**
|
533 | * @param {!capabilities.Capabilities} caps
|
534 | * @param {Profile} profile
|
535 | * @param {!Binary} binary
|
536 | * @return {DriverSpec}
|
537 | */
|
538 | function createLegacyDriver(caps, profile, binary, flow) {
|
539 | profile = profile || new Profile;
|
540 |
|
541 | let freePort = portprober.findFreePort();
|
542 | let preparedProfile =
|
543 | freePort.then(port => prepareProfile(profile, port));
|
544 | let command = preparedProfile.then(dir => binary.launch(dir));
|
545 |
|
546 | let serverUrl = command.then(() => freePort)
|
547 | .then(function(/** number */port) {
|
548 | let serverUrl = url.format({
|
549 | protocol: 'http',
|
550 | hostname: net.getLoopbackAddress(),
|
551 | port: port + '',
|
552 | pathname: '/hub'
|
553 | });
|
554 | let ready = httpUtil.waitForServer(serverUrl, 45 * 1000);
|
555 | return ready.then(() => serverUrl);
|
556 | });
|
557 |
|
558 | return {
|
559 | executor: createExecutor(serverUrl),
|
560 | capabilities: caps,
|
561 | onQuit: function() {
|
562 | return command.then(command => {
|
563 | command.kill();
|
564 | return preparedProfile.then(io.rmDir)
|
565 | .then(() => command.result(),
|
566 | () => command.result());
|
567 | });
|
568 | }
|
569 | };
|
570 | }
|
571 |
|
572 |
|
573 | /**
|
574 | * A WebDriver client for Firefox.
|
575 | */
|
576 | class Driver extends webdriver.WebDriver {
|
577 | /**
|
578 | * Creates a new Firefox session.
|
579 | *
|
580 | * @param {(Options|capabilities.Capabilities|Object)=} opt_config The
|
581 | * configuration options for this driver, specified as either an
|
582 | * {@link Options} or {@link capabilities.Capabilities}, or as a raw hash
|
583 | * object.
|
584 | * @param {(http.Executor|remote.DriverService)=} opt_executor Either a
|
585 | * pre-configured command executor to use for communicating with an
|
586 | * externally managed remote end (which is assumed to already be running),
|
587 | * or the `DriverService` to use to start the geckodriver in a child
|
588 | * process.
|
589 | *
|
590 | * If an executor is provided, care should e taken not to use reuse it with
|
591 | * other clients as its internal command mappings will be updated to support
|
592 | * Firefox-specific commands.
|
593 | *
|
594 | * _This parameter may only be used with Mozilla's GeckoDriver._
|
595 | *
|
596 | * @param {promise.ControlFlow=} opt_flow The flow to
|
597 | * schedule commands through. Defaults to the active flow object.
|
598 | * @throws {Error} If a custom command executor is provided and the driver is
|
599 | * configured to use the legacy FirefoxDriver from the Selenium project.
|
600 | * @return {!Driver} A new driver instance.
|
601 | */
|
602 | static createSession(opt_config, opt_executor, opt_flow) {
|
603 | let caps;
|
604 | if (opt_config instanceof Options) {
|
605 | caps = opt_config.toCapabilities();
|
606 | } else {
|
607 | caps = new capabilities.Capabilities(opt_config);
|
608 | }
|
609 |
|
610 | let binary = caps.get(Capability.BINARY) || new Binary();
|
611 | caps.delete(Capability.BINARY);
|
612 | if (typeof binary === 'string') {
|
613 | binary = new Binary(binary);
|
614 | }
|
615 |
|
616 | let profile;
|
617 | if (caps.has(Capability.PROFILE)) {
|
618 | profile = caps.get(Capability.PROFILE);
|
619 | caps.delete(Capability.PROFILE);
|
620 | }
|
621 |
|
622 | // Users must now explicitly disable marionette to use the legacy
|
623 | // FirefoxDriver.
|
624 | let noMarionette =
|
625 | caps.get(Capability.MARIONETTE) === false
|
626 | || /^0|false$/i.test(process.env['SELENIUM_MARIONETTE']);
|
627 | let useMarionette = !noMarionette;
|
628 |
|
629 | let spec;
|
630 | if (useMarionette) {
|
631 | spec = createGeckoDriver(opt_executor, caps, profile, binary);
|
632 | } else {
|
633 | if (opt_executor) {
|
634 | throw Error('You may not use a custom command executor with the legacy'
|
635 | + ' FirefoxDriver');
|
636 | }
|
637 | spec = createLegacyDriver(caps, profile, binary, opt_flow);
|
638 | }
|
639 |
|
640 | return /** @type {!Driver} */(webdriver.WebDriver.createSession(
|
641 | spec.executor, spec.capabilities, opt_flow, this, spec.onQuit));
|
642 | }
|
643 |
|
644 | /**
|
645 | * This function is a no-op as file detectors are not supported by this
|
646 | * implementation.
|
647 | * @override
|
648 | */
|
649 | setFileDetector() {
|
650 | }
|
651 |
|
652 | /**
|
653 | * Get the context that is currently in effect.
|
654 | *
|
655 | * @return {!promise.Thenable<Context>} Current context.
|
656 | */
|
657 | getContext() {
|
658 | return this.schedule(
|
659 | new command.Command(ExtensionCommand.GET_CONTEXT),
|
660 | 'get WebDriver.context');
|
661 | }
|
662 |
|
663 | /**
|
664 | * Changes target context for commands between chrome- and content.
|
665 | *
|
666 | * Changing the current context has a stateful impact on all subsequent
|
667 | * commands. The {@link Context.CONTENT} context has normal web
|
668 | * platform document permissions, as if you would evaluate arbitrary
|
669 | * JavaScript. The {@link Context.CHROME} context gets elevated
|
670 | * permissions that lets you manipulate the browser chrome itself,
|
671 | * with full access to the XUL toolkit.
|
672 | *
|
673 | * Use your powers wisely.
|
674 | *
|
675 | * @param {!promise.Thenable<void>} ctx The context to switch to.
|
676 | */
|
677 | setContext(ctx) {
|
678 | return this.schedule(
|
679 | new command.Command(ExtensionCommand.SET_CONTEXT)
|
680 | .setParameter("context", ctx),
|
681 | 'set WebDriver.context');
|
682 | }
|
683 | }
|
684 |
|
685 |
|
686 | // PUBLIC API
|
687 |
|
688 |
|
689 | exports.Binary = Binary;
|
690 | exports.Context = Context;
|
691 | exports.Driver = Driver;
|
692 | exports.Options = Options;
|
693 | exports.Profile = Profile;
|
694 | exports.ServiceBuilder = ServiceBuilder;
|