opera.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  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. * @fileoverview Defines a {@linkplain Driver WebDriver} client for the
  19. * Opera web browser (v26+). Before using this module, you must download the
  20. * latest OperaDriver
  21. * [release](https://github.com/operasoftware/operachromiumdriver/releases) and
  22. * ensure it can be found on your system
  23. * [PATH](http://en.wikipedia.org/wiki/PATH_%28variable%29).
  24. *
  25. * There are three primary classes exported by this module:
  26. *
  27. * 1. {@linkplain ServiceBuilder}: configures the
  28. * {@link selenium-webdriver/remote.DriverService remote.DriverService}
  29. * that manages the
  30. * [OperaDriver](https://github.com/operasoftware/operachromiumdriver)
  31. * child process.
  32. *
  33. * 2. {@linkplain Options}: defines configuration options for each new Opera
  34. * session, such as which {@linkplain Options#setProxy proxy} to use,
  35. * what {@linkplain Options#addExtensions extensions} to install, or
  36. * what {@linkplain Options#addArguments command-line switches} to use when
  37. * starting the browser.
  38. *
  39. * 3. {@linkplain Driver}: the WebDriver client; each new instance will control
  40. * a unique browser session with a clean user profile (unless otherwise
  41. * configured through the {@link Options} class).
  42. *
  43. * By default, every Opera session will use a single driver service, which is
  44. * started the first time a {@link Driver} instance is created and terminated
  45. * when this process exits. The default service will inherit its environment
  46. * from the current process and direct all output to /dev/null. You may obtain
  47. * a handle to this default service using
  48. * {@link #getDefaultService getDefaultService()} and change its configuration
  49. * with {@link #setDefaultService setDefaultService()}.
  50. *
  51. * You may also create a {@link Driver} with its own driver service. This is
  52. * useful if you need to capture the server's log output for a specific session:
  53. *
  54. * var opera = require('selenium-webdriver/opera');
  55. *
  56. * var service = new opera.ServiceBuilder()
  57. * .loggingTo('/my/log/file.txt')
  58. * .enableVerboseLogging()
  59. * .build();
  60. *
  61. * var options = new opera.Options();
  62. * // configure browser options ...
  63. *
  64. * var driver = opera.Driver.createSession(options, service);
  65. *
  66. * Users should only instantiate the {@link Driver} class directly when they
  67. * need a custom driver service configuration (as shown above). For normal
  68. * operation, users should start Opera using the
  69. * {@link selenium-webdriver.Builder}.
  70. */
  71. 'use strict';
  72. const fs = require('fs');
  73. const http = require('./http'),
  74. io = require('./io'),
  75. capabilities = require('./lib/capabilities'),
  76. promise = require('./lib/promise'),
  77. Symbols = require('./lib/symbols'),
  78. webdriver = require('./lib/webdriver'),
  79. portprober = require('./net/portprober'),
  80. remote = require('./remote');
  81. /**
  82. * Name of the OperaDriver executable.
  83. * @type {string}
  84. * @const
  85. */
  86. const OPERADRIVER_EXE =
  87. process.platform === 'win32' ? 'operadriver.exe' : 'operadriver';
  88. /**
  89. * Creates {@link remote.DriverService} instances that manages an
  90. * [OperaDriver](https://github.com/operasoftware/operachromiumdriver)
  91. * server in a child process.
  92. */
  93. class ServiceBuilder extends remote.DriverService.Builder {
  94. /**
  95. * @param {string=} opt_exe Path to the server executable to use. If omitted,
  96. * the builder will attempt to locate the operadriver on the current
  97. * PATH.
  98. * @throws {Error} If provided executable does not exist, or the operadriver
  99. * cannot be found on the PATH.
  100. */
  101. constructor(opt_exe) {
  102. let exe = opt_exe || io.findInPath(OPERADRIVER_EXE, true);
  103. if (!exe) {
  104. throw Error(
  105. 'The OperaDriver could not be found on the current PATH. Please ' +
  106. 'download the latest version of the OperaDriver from ' +
  107. 'https://github.com/operasoftware/operachromiumdriver/releases and ' +
  108. 'ensure it can be found on your PATH.');
  109. }
  110. super(exe);
  111. this.setLoopback(true);
  112. }
  113. /**
  114. * Sets the path of the log file the driver should log to. If a log file is
  115. * not specified, the driver will log to stderr.
  116. * @param {string} path Path of the log file to use.
  117. * @return {!ServiceBuilder} A self reference.
  118. */
  119. loggingTo(path) {
  120. return this.addArguments('--log-path=' + path);
  121. }
  122. /**
  123. * Enables verbose logging.
  124. * @return {!ServiceBuilder} A self reference.
  125. */
  126. enableVerboseLogging() {
  127. return this.addArguments('--verbose');
  128. }
  129. /**
  130. * Silence sthe drivers output.
  131. * @return {!ServiceBuilder} A self reference.
  132. */
  133. silent() {
  134. return this.addArguments('--silent');
  135. }
  136. }
  137. /** @type {remote.DriverService} */
  138. var defaultService = null;
  139. /**
  140. * Sets the default service to use for new OperaDriver instances.
  141. * @param {!remote.DriverService} service The service to use.
  142. * @throws {Error} If the default service is currently running.
  143. */
  144. function setDefaultService(service) {
  145. if (defaultService && defaultService.isRunning()) {
  146. throw Error(
  147. 'The previously configured OperaDriver service is still running. ' +
  148. 'You must shut it down before you may adjust its configuration.');
  149. }
  150. defaultService = service;
  151. }
  152. /**
  153. * Returns the default OperaDriver service. If such a service has not been
  154. * configured, one will be constructed using the default configuration for
  155. * a OperaDriver executable found on the system PATH.
  156. * @return {!remote.DriverService} The default OperaDriver service.
  157. */
  158. function getDefaultService() {
  159. if (!defaultService) {
  160. defaultService = new ServiceBuilder().build();
  161. }
  162. return defaultService;
  163. }
  164. /**
  165. * @type {string}
  166. * @const
  167. */
  168. var OPTIONS_CAPABILITY_KEY = 'chromeOptions';
  169. /**
  170. * Class for managing {@linkplain Driver OperaDriver} specific options.
  171. */
  172. class Options {
  173. constructor() {
  174. /** @private {!Array.<string>} */
  175. this.args_ = [];
  176. /** @private {?string} */
  177. this.binary_ = null;
  178. /** @private {!Array.<(string|!Buffer)>} */
  179. this.extensions_ = [];
  180. /** @private {./lib/logging.Preferences} */
  181. this.logPrefs_ = null;
  182. /** @private {?capabilities.ProxyConfig} */
  183. this.proxy_ = null;
  184. }
  185. /**
  186. * Extracts the OperaDriver specific options from the given capabilities
  187. * object.
  188. * @param {!capabilities.Capabilities} caps The capabilities object.
  189. * @return {!Options} The OperaDriver options.
  190. */
  191. static fromCapabilities(caps) {
  192. var options;
  193. var o = caps.get(OPTIONS_CAPABILITY_KEY);
  194. if (o instanceof Options) {
  195. options = o;
  196. } else if (o) {
  197. options = new Options()
  198. .addArguments(o.args || [])
  199. .addExtensions(o.extensions || [])
  200. .setOperaBinaryPath(o.binary);
  201. } else {
  202. options = new Options;
  203. }
  204. if (caps.has(capabilities.Capability.PROXY)) {
  205. options.setProxy(caps.get(capabilities.Capability.PROXY));
  206. }
  207. if (caps.has(capabilities.Capability.LOGGING_PREFS)) {
  208. options.setLoggingPrefs(
  209. caps.get(capabilities.Capability.LOGGING_PREFS));
  210. }
  211. return options;
  212. }
  213. /**
  214. * Add additional command line arguments to use when launching the Opera
  215. * browser. Each argument may be specified with or without the "--" prefix
  216. * (e.g. "--foo" and "foo"). Arguments with an associated value should be
  217. * delimited by an "=": "foo=bar".
  218. * @param {...(string|!Array.<string>)} var_args The arguments to add.
  219. * @return {!Options} A self reference.
  220. */
  221. addArguments(var_args) {
  222. this.args_ = this.args_.concat.apply(this.args_, arguments);
  223. return this;
  224. }
  225. /**
  226. * Add additional extensions to install when launching Opera. Each extension
  227. * should be specified as the path to the packed CRX file, or a Buffer for an
  228. * extension.
  229. * @param {...(string|!Buffer|!Array.<(string|!Buffer)>)} var_args The
  230. * extensions to add.
  231. * @return {!Options} A self reference.
  232. */
  233. addExtensions(var_args) {
  234. this.extensions_ = this.extensions_.concat.apply(
  235. this.extensions_, arguments);
  236. return this;
  237. }
  238. /**
  239. * Sets the path to the Opera binary to use. On Mac OS X, this path should
  240. * reference the actual Opera executable, not just the application binary. The
  241. * binary path be absolute or relative to the operadriver server executable, but
  242. * it must exist on the machine that will launch Opera.
  243. *
  244. * @param {string} path The path to the Opera binary to use.
  245. * @return {!Options} A self reference.
  246. */
  247. setOperaBinaryPath(path) {
  248. this.binary_ = path;
  249. return this;
  250. }
  251. /**
  252. * Sets the logging preferences for the new session.
  253. * @param {!./lib/logging.Preferences} prefs The logging preferences.
  254. * @return {!Options} A self reference.
  255. */
  256. setLoggingPrefs(prefs) {
  257. this.logPrefs_ = prefs;
  258. return this;
  259. }
  260. /**
  261. * Sets the proxy settings for the new session.
  262. * @param {capabilities.ProxyConfig} proxy The proxy configuration to use.
  263. * @return {!Options} A self reference.
  264. */
  265. setProxy(proxy) {
  266. this.proxy_ = proxy;
  267. return this;
  268. }
  269. /**
  270. * Converts this options instance to a {@link capabilities.Capabilities}
  271. * object.
  272. * @param {capabilities.Capabilities=} opt_capabilities The capabilities to
  273. * merge these options into, if any.
  274. * @return {!capabilities.Capabilities} The capabilities.
  275. */
  276. toCapabilities(opt_capabilities) {
  277. var caps = opt_capabilities || capabilities.Capabilities.opera();
  278. caps.
  279. set(capabilities.Capability.PROXY, this.proxy_).
  280. set(capabilities.Capability.LOGGING_PREFS, this.logPrefs_).
  281. set(OPTIONS_CAPABILITY_KEY, this);
  282. return caps;
  283. }
  284. /**
  285. * Converts this instance to its JSON wire protocol representation. Note this
  286. * function is an implementation not intended for general use.
  287. * @return {!Object} The JSON wire protocol representation of this instance.
  288. */
  289. [Symbols.serialize]() {
  290. var json = {
  291. args: this.args_,
  292. extensions: this.extensions_.map(function(extension) {
  293. if (Buffer.isBuffer(extension)) {
  294. return extension.toString('base64');
  295. }
  296. return io.read(/** @type {string} */(extension))
  297. .then(buffer => buffer.toString('base64'));
  298. })
  299. };
  300. if (this.binary_) {
  301. json.binary = this.binary_;
  302. }
  303. return json;
  304. }
  305. }
  306. /**
  307. * Creates a new WebDriver client for Opera.
  308. */
  309. class Driver extends webdriver.WebDriver {
  310. /**
  311. * Creates a new session for Opera.
  312. *
  313. * @param {(capabilities.Capabilities|Options)=} opt_config The configuration
  314. * options.
  315. * @param {remote.DriverService=} opt_service The session to use; will use
  316. * the {@link getDefaultService default service} by default.
  317. * @param {promise.ControlFlow=} opt_flow The control flow to use,
  318. * or {@code null} to use the currently active flow.
  319. * @return {!Driver} A new driver instance.
  320. */
  321. static createSession(opt_config, opt_service, opt_flow) {
  322. var service = opt_service || getDefaultService();
  323. var client = service.start().then(url => new http.HttpClient(url));
  324. var executor = new http.Executor(client);
  325. var caps =
  326. opt_config instanceof Options ? opt_config.toCapabilities() :
  327. (opt_config || capabilities.Capabilities.opera());
  328. // On Linux, the OperaDriver does not look for Opera on the PATH, so we
  329. // must explicitly find it. See: operachromiumdriver #9.
  330. if (process.platform === 'linux') {
  331. var options = Options.fromCapabilities(caps);
  332. if (!options.binary_) {
  333. let exe = io.findInPath('opera', true);
  334. if (!exe) {
  335. throw Error(
  336. 'The opera executable could not be found on the current PATH');
  337. }
  338. options.setOperaBinaryPath(exe);
  339. }
  340. caps = options.toCapabilities(caps);
  341. }
  342. return /** @type {!Driver} */(
  343. super.createSession(executor, caps, opt_flow));
  344. }
  345. /**
  346. * This function is a no-op as file detectors are not supported by this
  347. * implementation.
  348. * @override
  349. */
  350. setFileDetector() {}
  351. }
  352. // PUBLIC API
  353. exports.Driver = Driver;
  354. exports.Options = Options;
  355. exports.ServiceBuilder = ServiceBuilder;
  356. exports.getDefaultService = getDefaultService;
  357. exports.setDefaultService = setDefaultService;