UNPKG

5.89 kBJavaScriptView Raw
1/*
2 * Copyright (c) 2015, Groupon, Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer.
11 *
12 * Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * Neither the name of GROUPON nor the names of its contributors may be
17 * used to endorse or promote products derived from this software without
18 * specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
21 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
23 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
26 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33'use strict';
34
35const urlLib = require('url');
36const http = require('http');
37const path = require('path');
38const fs = require('fs');
39
40const _ = require('lodash');
41const debug = require('debug')('testium-core:init');
42const qs = require('qs');
43const Bluebird = require('bluebird');
44
45const Config = require('./config');
46const launchAll = require('./processes');
47
48// Retains the url fragment and query args from url, not overridden via queryArgs
49function extendUrlWithQuery(url, queryArgs) {
50 if (_.isEmpty(queryArgs)) {
51 return url;
52 }
53
54 const parts = urlLib.parse(url);
55 const query = _.extend(qs.parse(parts.query), queryArgs);
56 parts.search = `?${qs.stringify(query)}`;
57 return urlLib.format(
58 _.pick(
59 parts,
60 'protocol',
61 'slashes',
62 'host',
63 'auth',
64 'pathname',
65 'search',
66 'hash'
67 )
68 );
69}
70
71function getNewPageUrl(targetUrl, url, options) {
72 options = options || {};
73 const query = options.query;
74 if (query) {
75 if (typeof query !== 'object') {
76 throw new Error('options.query has to be an Object if provided');
77 }
78 url = extendUrlWithQuery(url, query);
79 }
80
81 // We don't support absolute urls in proxy (~= starting with a protocol)
82 if (/^[\w]+:\/\//.test(url)) {
83 return url;
84 }
85 options = _.defaults({ url, redirect: true }, _.omit(options, 'query'));
86 return `${targetUrl}/__testium_command__/new-page?${qs.stringify(options)}`;
87}
88
89function isTruthyConfig(setting) {
90 return (
91 setting && setting !== '0' && setting !== 'null' && setting !== 'false'
92 );
93}
94
95function initTestium(config) {
96 config = config || Config.load();
97
98 const appConfig = config.get('app', {});
99 if (!isTruthyConfig(appConfig)) {
100 debug('Disabling launch via app config', appConfig);
101 config.set('launch', false);
102 }
103
104 function createFromProcesses(procs) {
105 let testium;
106
107 function closeSeleniumSession() {
108 const browser = testium.browser;
109 if (browser && typeof browser.quit === 'function') {
110 return browser.quit();
111 }
112 return browser && browser.close();
113 }
114
115 let devtoolsPort = null;
116 function getChromeDevtoolsPort() {
117 const capabilities = testium.browser.capabilities;
118 if (!capabilities.chrome) {
119 throw new Error('Can only get devtools port for chrome');
120 }
121 if (devtoolsPort === null) {
122 const userDataDir = capabilities.chrome.userDataDir;
123 const devToolsPortPath = path.join(userDataDir, 'DevToolsActivePort');
124 const devToolsPortFile = fs.readFileSync(devToolsPortPath, 'utf8');
125 devtoolsPort = +devToolsPortFile.split('\n')[0];
126 debug('Found DevTools port %j in', devtoolsPort, devToolsPortFile);
127 }
128 return devtoolsPort;
129 }
130
131 function killAllProcesses() {
132 _.each(procs, (proc, name) => {
133 try {
134 proc.rawProcess.kill();
135 } catch (e) {
136 debug('Error killing process %s', name, e);
137 }
138 });
139 }
140
141 function close() {
142 return Bluebird.try(closeSeleniumSession)
143 .catch(e => {
144 debug('Could not close session', e);
145 })
146 .then(killAllProcesses);
147 }
148
149 function getInitialUrl() {
150 return `${config.get('proxy.targetUrl')}/testium-priming-load`;
151 }
152
153 testium = {
154 close,
155 config,
156 getChromeDevtoolsPort,
157 getInitialUrl,
158 getNewPageUrl: _.partial(getNewPageUrl, config.get('proxy.targetUrl')),
159 };
160 return testium;
161 }
162
163 function verifySelenium(procs) {
164 return new Bluebird((resolve, reject) => {
165 const seleniumUrl = config.get('selenium.serverUrl');
166 debug('Verify selenium: ', seleniumUrl);
167 const req = http.get(`${seleniumUrl}/status`, res => {
168 debug('Selenium /status: ', res.statusCode);
169 resolve(procs);
170 });
171 req.on('error', error => {
172 let oldStack = error.stack;
173 oldStack = oldStack.substr(oldStack.indexOf('\n') + 1);
174 error.message = [
175 'Error: Failed to connect to existing selenium server',
176 ` - url: ${seleniumUrl}`,
177 ` - message: ${error.message}`,
178 ].join('\n');
179 error.stack = `${error.message}\n${oldStack}`;
180 reject(error);
181 });
182 });
183 }
184
185 return launchAll(config).then(verifySelenium).then(createFromProcesses);
186}
187module.exports = initTestium;