1 |
|
2 |
|
3 |
|
4 | const Promise = require('bluebird');
|
5 | const { ArgError } = require('../errors');
|
6 | const path = require("path");
|
7 | const util = require('../utils');
|
8 | const httpRequest = require('./httpRequest');
|
9 | const fs = Promise.promisifyAll(require('fs'));
|
10 | const localRunnerCache = require('./runnerFileCache');
|
11 | const {
|
12 | getCliLocation, isURL, downloadAndSave, getSource, getLocalFileSizeInMB, download, unzipFile
|
13 | } = require('../utils');
|
14 | const ms = require("ms");
|
15 | const config = require('../commons/config');
|
16 | const { requireWithFallback } = require("./requireWithFallback");
|
17 |
|
18 | const logger = require('./logger').getLogger('prepare runner and testim start');
|
19 |
|
20 | const MSEC_IN_HALF_DAY = ms("0.5 day");
|
21 | const MAX_CUSTOM_EXT_SIZE_MB = 16;
|
22 | const MAX_CUSTOM_SIZE_ERROR_MSG = `The size of the custom extension is more than ${MAX_CUSTOM_EXT_SIZE_MB}MB`;
|
23 |
|
24 | module.exports = {
|
25 | prepareChromeDriver,
|
26 | prepareCustomExtension,
|
27 | prepareExtension,
|
28 | getSessionPlayerFolder,
|
29 | preparePlayer
|
30 | };
|
31 |
|
32 |
|
33 |
|
34 |
|
35 | function 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 |
|
66 |
|
67 | function 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 |
|
84 |
|
85 |
|
86 | function prepareExtension(locations) {
|
87 | logger.info("prepare extension", { locations: locations });
|
88 | return Promise.map(locations, location => getSource(location));
|
89 | }
|
90 |
|
91 |
|
92 | async 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.
|
101 | 1. If Chrome is installed, please add the Chrome binary directory to your PATH environment variables.
|
102 | 2. If you do not have Chrome, please install it from https://www.google.com/chrome`);
|
103 |
|
104 |
|
105 | process.exit(1);
|
106 | }
|
107 |
|
108 | const chromeMajorVersion = chromeVersion.substr(0, chromeVersion.indexOf('.'));
|
109 |
|
110 | process.env.NODE_OPTIONS = '';
|
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',
|
143 |
|
144 | '--disable-features=TranslateUI',
|
145 |
|
146 |
|
147 | '--disable-background-networking',
|
148 |
|
149 | '--disable-sync',
|
150 |
|
151 | '--metrics-recording-only',
|
152 |
|
153 | '--disable-default-apps',
|
154 |
|
155 | '--mute-audio',
|
156 |
|
157 | '--no-first-run',
|
158 |
|
159 | '--disable-build-check'
|
160 | ]);
|
161 |
|
162 | }
|
163 |
|
164 |
|
165 |
|
166 |
|
167 | async function startChromeDriver(args) {
|
168 |
|
169 |
|
170 |
|
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 |
|
191 |
|
192 | spinner.succeed('Driver already running');
|
193 | return;
|
194 | }
|
195 |
|
196 | chromeDriver.defaultInstance = require('child_process').spawn(command, args);
|
197 |
|
198 |
|
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 |
|
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'),
|
221 | httpRequest.getText('http://localhost:9515/shutdown')
|
222 | ]).catch(() => {});
|
223 |
|
224 | await Promise.delay(10);
|
225 | await startChromeDriver(args);
|
226 | }
|
227 |
|
228 |
|
229 | function 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 |
|
237 |
|
238 |
|
239 |
|
240 |
|
241 | function 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 |
|
250 | function getSessionPlayerFolder() {
|
251 |
|
252 | const cliLocation = getCliLocation();
|
253 |
|
254 | return path.resolve(cliLocation, 'testim-bin')
|
255 | }
|
256 |
|
257 | function getPlayerDestination() {
|
258 |
|
259 | const testimAppData = getSessionPlayerFolder();
|
260 |
|
261 | const playerDestination = path.resolve(testimAppData, 'sessionPlayer.zip');
|
262 |
|
263 | return playerDestination;
|
264 | }
|
265 |
|
266 | function 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 | }
|