phantomjs.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  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. * PhantomJS web browser. By default, it is expected that the PhantomJS
  20. * executable can be located on your
  21. * [PATH](https://en.wikipedia.org/wiki/PATH_(variable))
  22. *
  23. * __Using a Custom PhantomJS Binary__
  24. *
  25. * If you have PhantomJS.exe placed somewhere other than the root of your
  26. * working directory, you can build a custom Capability and attach the
  27. * executable's location to the Capability
  28. *
  29. * For example, if you're using the
  30. * [phantomjs-prebuilt](https://www.npmjs.com/package/phantomjs-prebuilt) module
  31. * from npm:
  32. *
  33. * //setup custom phantomJS capability
  34. * var phantomjs_exe = require('phantomjs-prebuilt').path;
  35. * var customPhantom = selenium.Capabilities.phantomjs();
  36. * customPhantom.set("phantomjs.binary.path", phantomjs_exe);
  37. * //build custom phantomJS driver
  38. * var driver = new selenium.Builder().
  39. * withCapabilities(customPhantom).
  40. * build();
  41. *
  42. */
  43. 'use strict';
  44. const fs = require('fs');
  45. const http = require('./http'),
  46. io = require('./io'),
  47. capabilities = require('./lib/capabilities'),
  48. command = require('./lib/command'),
  49. logging = require('./lib/logging'),
  50. promise = require('./lib/promise'),
  51. webdriver = require('./lib/webdriver'),
  52. portprober = require('./net/portprober'),
  53. remote = require('./remote');
  54. /**
  55. * Name of the PhantomJS executable.
  56. * @type {string}
  57. * @const
  58. */
  59. const PHANTOMJS_EXE =
  60. process.platform === 'win32' ? 'phantomjs.exe' : 'phantomjs';
  61. /**
  62. * Capability that designates the location of the PhantomJS executable to use.
  63. * @type {string}
  64. * @const
  65. */
  66. const BINARY_PATH_CAPABILITY = 'phantomjs.binary.path';
  67. /**
  68. * Capability that designates the CLI arguments to pass to PhantomJS.
  69. * @type {string}
  70. * @const
  71. */
  72. const CLI_ARGS_CAPABILITY = 'phantomjs.cli.args';
  73. /**
  74. * Custom command names supported by PhantomJS.
  75. * @enum {string}
  76. */
  77. const Command = {
  78. EXECUTE_PHANTOM_SCRIPT: 'executePhantomScript'
  79. };
  80. /**
  81. * Finds the PhantomJS executable.
  82. * @param {string=} opt_exe Path to the executable to use.
  83. * @return {string} The located executable.
  84. * @throws {Error} If the executable cannot be found on the PATH, or if the
  85. * provided executable path does not exist.
  86. */
  87. function findExecutable(opt_exe) {
  88. var exe = opt_exe || io.findInPath(PHANTOMJS_EXE, true);
  89. if (!exe) {
  90. throw Error(
  91. 'The PhantomJS executable could not be found on the current PATH. ' +
  92. 'Please download the latest version from ' +
  93. 'http://phantomjs.org/download.html and ensure it can be found on ' +
  94. 'your PATH. For more information, see ' +
  95. 'https://github.com/ariya/phantomjs/wiki');
  96. }
  97. if (!fs.existsSync(exe)) {
  98. throw Error('File does not exist: ' + exe);
  99. }
  100. return exe;
  101. }
  102. /**
  103. * Maps WebDriver logging level name to those recognised by PhantomJS.
  104. * @const {!Map<string, string>}
  105. */
  106. const WEBDRIVER_TO_PHANTOMJS_LEVEL = new Map([
  107. [logging.Level.ALL.name, 'DEBUG'],
  108. [logging.Level.DEBUG.name, 'DEBUG'],
  109. [logging.Level.INFO.name, 'INFO'],
  110. [logging.Level.WARNING.name, 'WARN'],
  111. [logging.Level.SEVERE.name, 'ERROR']]);
  112. /**
  113. * Creates a command executor with support for PhantomJS' custom commands.
  114. * @param {!Promise<string>} url The server's URL.
  115. * @return {!command.Executor} The new command executor.
  116. */
  117. function createExecutor(url) {
  118. let client = url.then(url => new http.HttpClient(url));
  119. let executor = new http.Executor(client);
  120. executor.defineCommand(
  121. Command.EXECUTE_PHANTOM_SCRIPT,
  122. 'POST', '/session/:sessionId/phantom/execute');
  123. return executor;
  124. }
  125. /**
  126. * Creates a new WebDriver client for PhantomJS.
  127. */
  128. class Driver extends webdriver.WebDriver {
  129. /**
  130. * Creates a new PhantomJS session.
  131. *
  132. * @param {capabilities.Capabilities=} opt_capabilities The desired
  133. * capabilities.
  134. * @param {promise.ControlFlow=} opt_flow The control flow to use,
  135. * or {@code null} to use the currently active flow.
  136. * @param {string=} opt_logFile Path to the log file for the phantomjs
  137. * executable's output. For convenience, this may be set at runtime with
  138. * the `SELENIUM_PHANTOMJS_LOG` environment variable.
  139. * @return {!Driver} A new driver reference.
  140. */
  141. static createSession(opt_capabilities, opt_flow, opt_logFile) {
  142. // TODO: add an Options class for consistency with the other driver types.
  143. var caps = opt_capabilities || capabilities.Capabilities.phantomjs();
  144. var exe = findExecutable(caps.get(BINARY_PATH_CAPABILITY));
  145. var args = [];
  146. var logPrefs = caps.get(capabilities.Capability.LOGGING_PREFS);
  147. if (logPrefs instanceof logging.Preferences) {
  148. logPrefs = logPrefs.toJSON();
  149. }
  150. if (logPrefs && logPrefs[logging.Type.DRIVER]) {
  151. let level = WEBDRIVER_TO_PHANTOMJS_LEVEL.get(
  152. logPrefs[logging.Type.DRIVER]);
  153. if (level) {
  154. args.push('--webdriver-loglevel=' + level);
  155. }
  156. }
  157. opt_logFile = process.env['SELENIUM_PHANTOMJS_LOG'] || opt_logFile;
  158. if (typeof opt_logFile === 'string') {
  159. args.push('--webdriver-logfile=' + opt_logFile);
  160. }
  161. var proxy = caps.get(capabilities.Capability.PROXY);
  162. if (proxy) {
  163. switch (proxy.proxyType) {
  164. case 'manual':
  165. if (proxy.httpProxy) {
  166. args.push(
  167. '--proxy-type=http',
  168. '--proxy=' + proxy.httpProxy);
  169. console.log(args);
  170. }
  171. break;
  172. case 'pac':
  173. throw Error('PhantomJS does not support Proxy PAC files');
  174. case 'system':
  175. args.push('--proxy-type=system');
  176. break;
  177. case 'direct':
  178. args.push('--proxy-type=none');
  179. break;
  180. }
  181. }
  182. args = args.concat(caps.get(CLI_ARGS_CAPABILITY) || []);
  183. var port = portprober.findFreePort();
  184. var service = new remote.DriverService(exe, {
  185. port: port,
  186. args: Promise.resolve(port).then(function(port) {
  187. args.push('--webdriver=' + port);
  188. return args;
  189. })
  190. });
  191. var executor = createExecutor(service.start());
  192. return /** @type {!Driver} */(super.createSession(
  193. executor, caps, opt_flow, () => service.kill()));
  194. }
  195. /**
  196. * This function is a no-op as file detectors are not supported by this
  197. * implementation.
  198. * @override
  199. */
  200. setFileDetector() {}
  201. /**
  202. * Executes a PhantomJS fragment. This method is similar to
  203. * {@link #executeScript}, except it exposes the
  204. * <a href="http://phantomjs.org/api/">PhantomJS API</a> to the injected
  205. * script.
  206. *
  207. * <p>The injected script will execute in the context of PhantomJS's
  208. * {@code page} variable. If a page has not been loaded before calling this
  209. * method, one will be created.</p>
  210. *
  211. * <p>Be sure to wrap callback definitions in a try/catch block, as failures
  212. * may cause future WebDriver calls to fail.</p>
  213. *
  214. * <p>Certain callbacks are used by GhostDriver (the PhantomJS WebDriver
  215. * implementation) and overriding these may cause the script to fail. It is
  216. * recommended that you check for existing callbacks before defining your own.
  217. * </p>
  218. *
  219. * As with {@link #executeScript}, the injected script may be defined as
  220. * a string for an anonymous function body (e.g. "return 123;"), or as a
  221. * function. If a function is provided, it will be decompiled to its original
  222. * source. Note that injecting functions is provided as a convenience to
  223. * simplify defining complex scripts. Care must be taken that the function
  224. * only references variables that will be defined in the page's scope and
  225. * that the function does not override {@code Function.prototype.toString}
  226. * (overriding toString() will interfere with how the function is
  227. * decompiled.
  228. *
  229. * @param {(string|!Function)} script The script to execute.
  230. * @param {...*} var_args The arguments to pass to the script.
  231. * @return {!promise.Thenable<T>} A promise that resolve to the
  232. * script's return value.
  233. * @template T
  234. */
  235. executePhantomJS(script, var_args) {
  236. if (typeof script === 'function') {
  237. script = 'return (' + script + ').apply(this, arguments);';
  238. }
  239. var args = arguments.length > 1
  240. ? Array.prototype.slice.call(arguments, 1) : [];
  241. return this.schedule(
  242. new command.Command(Command.EXECUTE_PHANTOM_SCRIPT)
  243. .setParameter('script', script)
  244. .setParameter('args', args),
  245. 'Driver.executePhantomJS()');
  246. }
  247. }
  248. // PUBLIC API
  249. exports.Driver = Driver;