1 | // Copyright 2013 Selenium committers |
2 | // Copyright 2013 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 | 'use strict'; |
17 | |
18 | var fs = require('fs'), |
19 | util = require('util'); |
20 | |
21 | var webdriver = require('./index'), |
22 | executors = require('./executors'), |
23 | io = require('./io'), |
24 | portprober = require('./net/portprober'), |
25 | remote = require('./remote'); |
26 | |
27 | |
28 | /** |
29 | * Name of the PhantomJS executable. |
30 | * @type {string} |
31 | * @const |
32 | */ |
33 | var PHANTOMJS_EXE = |
34 | process.platform === 'win32' ? 'phantomjs.exe' : 'phantomjs'; |
35 | |
36 | |
37 | /** |
38 | * Capability that designates the location of the PhantomJS executable to use. |
39 | * @type {string} |
40 | * @const |
41 | */ |
42 | var BINARY_PATH_CAPABILITY = 'phantomjs.binary.path'; |
43 | |
44 | |
45 | /** |
46 | * Capability that designates the CLI arguments to pass to PhantomJS. |
47 | * @type {string} |
48 | * @const |
49 | */ |
50 | var CLI_ARGS_CAPABILITY = 'phantomjs.cli.args'; |
51 | |
52 | |
53 | /** |
54 | * Default log file to use if one is not specified through CLI args. |
55 | * @type {string} |
56 | * @const |
57 | */ |
58 | var DEFAULT_LOG_FILE = 'phantomjsdriver.log'; |
59 | |
60 | |
61 | /** |
62 | * Finds the PhantomJS executable. |
63 | * @param {string=} opt_exe Path to the executable to use. |
64 | * @return {string} The located executable. |
65 | * @throws {Error} If the executable cannot be found on the PATH, or if the |
66 | * provided executable path does not exist. |
67 | */ |
68 | function findExecutable(opt_exe) { |
69 | var exe = opt_exe || io.findInPath(PHANTOMJS_EXE, true); |
70 | if (!exe) { |
71 | throw Error( |
72 | 'The PhantomJS executable could not be found on the current PATH. ' + |
73 | 'Please download the latest version from ' + |
74 | 'http://phantomjs.org/download.html and ensure it can be found on ' + |
75 | 'your PATH. For more information, see ' + |
76 | 'https://github.com/ariya/phantomjs/wiki'); |
77 | } |
78 | if (!fs.existsSync(exe)) { |
79 | throw Error('File does not exist: ' + exe); |
80 | } |
81 | return exe; |
82 | } |
83 | |
84 | |
85 | /** |
86 | * Maps WebDriver logging level name to those recognised by PhantomJS. |
87 | * @type {!Object.<string, string>} |
88 | * @const |
89 | */ |
90 | var WEBDRIVER_TO_PHANTOMJS_LEVEL = (function() { |
91 | var map = {}; |
92 | map[webdriver.logging.Level.ALL.name] = 'DEBUG'; |
93 | map[webdriver.logging.Level.DEBUG.name] = 'DEBUG'; |
94 | map[webdriver.logging.Level.INFO.name] = 'INFO'; |
95 | map[webdriver.logging.Level.WARNING.name] = 'WARN'; |
96 | map[webdriver.logging.Level.SEVERE.name] = 'ERROR'; |
97 | return map; |
98 | })(); |
99 | |
100 | |
101 | /** |
102 | * Creates a new PhantomJS WebDriver client. |
103 | * @param {webdriver.Capabilities=} opt_capabilities The desired capabilities. |
104 | * @param {webdriver.promise.ControlFlow=} opt_flow The control flow to use, or |
105 | * {@code null} to use the currently active flow. |
106 | * @return {!webdriver.WebDriver} A new WebDriver instance. |
107 | * @deprecated Use {@link Driver}. |
108 | */ |
109 | function createDriver(opt_capabilities, opt_flow) { |
110 | return new Driver(opt_capabilities, opt_flow); |
111 | } |
112 | |
113 | |
114 | /** |
115 | * Creates a new WebDriver client for PhantomJS. |
116 | * |
117 | * @param {webdriver.Capabilities=} opt_capabilities The desired capabilities. |
118 | * @param {webdriver.promise.ControlFlow=} opt_flow The control flow to use, or |
119 | * {@code null} to use the currently active flow. |
120 | * @constructor |
121 | * @extends {webdriver.WebDriver} |
122 | */ |
123 | var Driver = function(opt_capabilities, opt_flow) { |
124 | var capabilities = opt_capabilities || webdriver.Capabilities.phantomjs(); |
125 | var exe = findExecutable(capabilities.get(BINARY_PATH_CAPABILITY)); |
126 | var args = ['--webdriver-logfile=' + DEFAULT_LOG_FILE]; |
127 | |
128 | var logPrefs = capabilities.get(webdriver.Capability.LOGGING_PREFS); |
129 | if (logPrefs instanceof webdriver.logging.Preferences) { |
130 | logPrefs = logPrefs.toJSON(); |
131 | } |
132 | |
133 | if (logPrefs && logPrefs[webdriver.logging.Type.DRIVER]) { |
134 | var level = WEBDRIVER_TO_PHANTOMJS_LEVEL[ |
135 | logPrefs[webdriver.logging.Type.DRIVER]]; |
136 | if (level) { |
137 | args.push('--webdriver-loglevel=' + level); |
138 | } |
139 | } |
140 | |
141 | var proxy = capabilities.get(webdriver.Capability.PROXY); |
142 | if (proxy) { |
143 | switch (proxy.proxyType) { |
144 | case 'manual': |
145 | if (proxy.httpProxy) { |
146 | args.push( |
147 | '--proxy-type=http', |
148 | '--proxy=http://' + proxy.httpProxy); |
149 | } |
150 | break; |
151 | case 'pac': |
152 | throw Error('PhantomJS does not support Proxy PAC files'); |
153 | case 'system': |
154 | args.push('--proxy-type=system'); |
155 | break; |
156 | case 'direct': |
157 | args.push('--proxy-type=none'); |
158 | break; |
159 | } |
160 | } |
161 | args = args.concat(capabilities.get(CLI_ARGS_CAPABILITY) || []); |
162 | |
163 | var port = portprober.findFreePort(); |
164 | var service = new remote.DriverService(exe, { |
165 | port: port, |
166 | args: webdriver.promise.when(port, function(port) { |
167 | args.push('--webdriver=' + port); |
168 | return args; |
169 | }) |
170 | }); |
171 | |
172 | var executor = executors.createExecutor(service.start()); |
173 | var driver = webdriver.WebDriver.createSession( |
174 | executor, capabilities, opt_flow); |
175 | |
176 | webdriver.WebDriver.call( |
177 | this, driver.getSession(), executor, driver.controlFlow()); |
178 | |
179 | var boundQuit = this.quit.bind(this); |
180 | |
181 | /** @override */ |
182 | this.quit = function() { |
183 | return boundQuit().thenFinally(service.kill.bind(service)); |
184 | }; |
185 | return driver; |
186 | }; |
187 | util.inherits(Driver, webdriver.WebDriver); |
188 | |
189 | |
190 | // PUBLIC API |
191 | |
192 | exports.Driver = Driver; |
193 | exports.createDriver = createDriver; |