UNPKG

9.92 kBJavaScriptView Raw
1// @ts-check
2
3// @ts-ignore
4const Promise = require('bluebird');
5const { ArgError } = require('../errors');
6const path = require("path");
7const util = require('../utils');
8const httpRequest = require('./httpRequest');
9const fs = Promise.promisifyAll(require('fs'));
10const localRunnerCache = require('./runnerFileCache');
11const {
12 getCliLocation, isURL, downloadAndSave, getSource, getLocalFileSizeInMB, download, unzipFile
13} = require('../utils');
14const ms = require("ms");
15const config = require('../commons/config');
16const { requireWithFallback } = require("./requireWithFallback");
17
18const logger = require('./logger').getLogger('prepare runner and testim start');
19
20const MSEC_IN_HALF_DAY = ms("0.5 day");
21const MAX_CUSTOM_EXT_SIZE_MB = 16;
22const MAX_CUSTOM_SIZE_ERROR_MSG = `The size of the custom extension is more than ${MAX_CUSTOM_EXT_SIZE_MB}MB`;
23
24module.exports = {
25 prepareChromeDriver,
26 prepareCustomExtension,
27 prepareExtension,
28 getSessionPlayerFolder,
29 preparePlayer
30};
31
32/**
33 * @param {string} location
34 */
35function prepareCustomExtension(location) {
36 if (!location) {
37 return Promise.resolve();
38 }
39
40 if (isURL(location)) {
41 const destFile = path.join(process.cwd(), location.replace(/^.*[\\\/]/, ''));
42 return getRemoteFileSizeInMB(location)
43 .then(contentLength => {
44 if (contentLength > MAX_CUSTOM_EXT_SIZE_MB) {
45 return Promise.reject(new ArgError(MAX_CUSTOM_SIZE_ERROR_MSG));
46 }
47 return downloadAndSave(location, destFile);
48 })
49 .then(() => Promise.resolve(destFile));
50 }
51
52 const destFile = path.resolve(location);
53 if (!fs.existsSync(destFile)) {
54 return Promise.reject(new ArgError(`Failed to find custom extension in location: ${destFile}`));
55 }
56 const fileSize = getLocalFileSizeInMB(destFile);
57 if (fileSize > MAX_CUSTOM_EXT_SIZE_MB) {
58 return Promise.reject(new ArgError(MAX_CUSTOM_SIZE_ERROR_MSG));
59 }
60 return Promise.resolve(destFile);
61}
62
63
64/**
65 * @param {string} url
66 */
67function getRemoteFileSizeInMB(url) {
68 const httpRequest = require('../commons/httpRequest');
69 return httpRequest.head(url)
70 .then(res => {
71 const contentLengthHeader = res.headers["content-length"];
72 const contentLengthBytes = contentLengthHeader ? parseInt(contentLengthHeader) : 0;
73 return Promise.resolve(contentLengthBytes / 1000000);
74 })
75 .catch(err => {
76 logger.warn(`failed to download custom extension`, { err });
77 return Promise.reject(new ArgError(`Failed to download custom extension from location: ${url}`));
78 });
79}
80
81/**
82 *
83 * @param {string[]} locations
84 *
85 */
86function prepareExtension(locations) {
87 logger.info("prepare extension", { locations: locations });
88 return Promise.map(locations, location => getSource(location));
89}
90
91
92async function prepareChromeDriver() {
93 const ora = require('ora');
94 const npmWrapper = require('./npmWrapper');
95 const { getChromeVersion } = require('@testim/chrome-version');
96
97 const chromeVersion = await getChromeVersion();
98
99 if (!chromeVersion) {
100 console.log(`Chrome browser not found and is needed to locally run Testim.
1011. If Chrome is installed, please add the Chrome binary directory to your PATH environment variables.
1022. If you do not have Chrome, please install it from https://www.google.com/chrome`);
103// For more information see: https://help.testim.io/docs/add-a-doc-with-instructions-for-win-and-linux-here
104
105 process.exit(1);
106 }
107
108 const chromeMajorVersion = chromeVersion.substr(0, chromeVersion.indexOf('.'));
109
110 process.env.NODE_OPTIONS = ''; // removes --inspect before starting chromeDriver
111
112 const isChromedriverInstalled = npmWrapper.getPackageIfInstalledLocally('chromedriver');
113 let isChromeDriverInTheRightVersion;
114 if (isChromedriverInstalled) {
115 const currentChromeDriverVersion = npmWrapper.getLocallyInstalledPackageVersion(getCliLocation(), 'chromedriver');
116
117 const chromeDriverMajorVersion = currentChromeDriverVersion.substr(0, currentChromeDriverVersion.indexOf('.'));
118 isChromeDriverInTheRightVersion = chromeDriverMajorVersion === chromeMajorVersion;
119 if (!isChromeDriverInTheRightVersion) {
120 console.log(`Updating ChromeDriver version from ${chromeDriverMajorVersion} to ${chromeMajorVersion}...`);
121 }
122 }
123
124 if (!isChromedriverInstalled || !isChromeDriverInTheRightVersion) {
125 const spinner = ora('No selenium grid detected. Installing ChromeDriver...').start();
126 try {
127 await npmWrapper.installPackageLocally(getCliLocation(), `chromedriver@${chromeMajorVersion}`);
128 spinner.succeed('ChromeDriver installed successfully');
129 } catch (err) {
130 try {
131 await npmWrapper.installPackageLocally(getCliLocation(), `chromedriver@latest`);
132 } catch (e) {
133 console.log('\nLocal ChromeDriver installation failed, please provide selenium grid details with the --host flag');
134 process.exit(1);
135 }
136 }
137 } else {
138 console.log('Using local browser');
139 }
140
141 await startChromeDriver([
142 '--url-base=/wd/hub', // webdriverio expects a wd hub - so we scam it it's facing selenium server
143 // Disable built-in Google Translate service
144 '--disable-features=TranslateUI',
145 // Disable various background network services, including extension updating,
146 // safe browsing service, upgrade detector, translate, UMA
147 '--disable-background-networking',
148 // Disable syncing to a Google account
149 '--disable-sync',
150 // Disable reporting to UMA, but allows for collection
151 '--metrics-recording-only',
152 // Disable installation of default apps on first run
153 '--disable-default-apps',
154 // Mute any audio
155 '--mute-audio',
156 // Skip first run wizards
157 '--no-first-run',
158 // don't complain about driver version
159 '--disable-build-check'
160 ]);
161
162}
163
164/**
165 * @param {string[]} args
166 */
167async function startChromeDriver(args) {
168 // code copied from
169 // https://github.com/giggio/node-chromedriver/blob/master/lib/chromedriver.js#L21-L47
170 // sans piping
171 const chromeDriver = requireWithFallback('chromedriver');
172
173 let { path: command } = chromeDriver;
174 if (!fs.existsSync(command)) {
175 command = process.platform === 'win32' ? 'chromedriver.exe' : 'chromedriver';
176 }
177
178 if (!fs.existsSync(command)) {
179 throw new Error("chromedriver command not found");
180 }
181
182 const ora = require('ora');
183 const spinner = ora('Starting Driver').start();
184
185 const isChromedriverAlreadyRunning = await httpRequest.get('http://localhost:9515/wd/hub/status')
186 .then(({value}) => value.ready)
187 .catch(() => false);
188
189 if (isChromedriverAlreadyRunning) {
190 // this is fine, this means both chromedriver is running and with our prefix of /wd/hub
191 // we don't have to start it again although this is risky since someone else owns its lifetime
192 spinner.succeed('Driver already running');
193 return;
194 }
195
196 chromeDriver.defaultInstance = require('child_process').spawn(command, args);
197
198 // 100 tries, every 30ms
199 const serverUp = await util.runWithRetries(() => httpRequest.get('http://localhost:9515/wd/hub/status'), 100, 30);
200
201 if (serverUp && serverUp.value && serverUp.value.ready) {
202 spinner.succeed('Driver Successfully Started');
203 return;
204 }
205
206 // check for either IPv6 port not available or IPv4 port not available
207 const prompts = require('prompts');
208 const answer = await prompts({
209 type: 'toggle',
210 name: 'killChromedriver',
211 initial: 'yes',
212 active: 'yes',
213 inactive: 'no',
214 message: 'ChromeDriver port already taken - would you like us to try and restart it?'
215 });
216 if (!answer.killChromedriver) {
217 throw new ArgError("Driver port already taken, please close any other Testim instances running");
218 }
219 await Promise.all([
220 httpRequest.getText('http://localhost:9515/wd/hub/shutdown'), // us
221 httpRequest.getText('http://localhost:9515/shutdown') // someone else
222 ]).catch(() => {});
223 // arbitrary long delay since what we're actually waiting for is for the OS to release the port
224 await Promise.delay(10);
225 await startChromeDriver(args);
226}
227
228
229function getPlayerVersion() {
230 const url = 'https://testimstatic.blob.core.windows.net/extension/sessionPlayer_LATEST_RELEASE';
231 return download(url)
232 .then(res => Promise.resolve(res.body.toString('utf8')));
233}
234
235/**
236 * @param {string} location
237 * @param {string | undefined} canary
238 *
239 * @returns {Promise<string>}
240 */
241function getPlayerLocation(location, canary) {
242 if (!isURL(location) || (isURL(location) && canary) || config.IS_ON_PREM) {
243 return Promise.resolve(location);
244 }
245
246 return getPlayerVersion()
247 .then(ver => Promise.resolve(location + "-" + ver));
248}
249
250function getSessionPlayerFolder() {
251
252 const cliLocation = getCliLocation();
253
254 return path.resolve(cliLocation, 'testim-bin')
255}
256
257function getPlayerDestination() {
258
259 const testimAppData = getSessionPlayerFolder();
260
261 const playerDestination = path.resolve(testimAppData, 'sessionPlayer.zip');
262
263 return playerDestination;
264}
265
266function preparePlayer(location, canary) {
267 logger.info("prepare player", { location: location, canary: canary });
268 const playerFileName = getPlayerDestination();
269 return localRunnerCache.memoize(
270 () => getPlayerLocation(location, canary)
271 .then(loc => getSource(loc, playerFileName))
272 .then(() => unzipFile(playerFileName, getSessionPlayerFolder()))
273 .then(() => ({})), 'preparePlayer', MSEC_IN_HALF_DAY, [location, canary]
274 )();
275}