UNPKG

9.37 kBJavaScriptView Raw
1const path = require('path');
2const fs = require('fs');
3const chalk = require('chalk');
4const {defaultsDeep, WdioPrepareConfig} = require('@aofl/cli-lib');
5const {LocalServer} = require('./modules/local-server');
6const findCacheDir = require('find-cache-dir');
7const Launcher = require('@wdio/cli').default;
8const {configMap} = require('./modules/wdio-config/wdio-presets');
9const spawn = require('cross-spawn');
10const rimraf = require('rimraf');
11const {titleCase} = require('title-case');
12
13class UnitTestingPlugin {
14 static get name() {
15 return 'AoflUnitTestingPlugin';
16 }
17
18 static get cacheDir() {
19 return findCacheDir({name: UnitTestingPlugin.name, create: true});
20 }
21
22 static get nycOutputDir() {
23 return path.join(UnitTestingPlugin.cacheDir, '.nyc_output');
24 }
25
26 constructor(options = {}) {
27 this.options = defaultsDeep(options, {
28 root: process.cwd(),
29 output: '__build_tests',
30 host: 'localhost',
31 port: 3035,
32 config: this.getConfigPath(),
33 watch: false,
34 debug: false,
35 nycArgs: [
36 'report',
37 '--reporter=lcov',
38 '--reporter=text-summary',
39 '--report-dir=./logs/coverage'
40 ]
41 });
42
43 this.server = null;
44 rimraf.sync(UnitTestingPlugin.cacheDir);
45 }
46
47 apply(compiler) {
48 compiler.hooks.done.tapAsync(UnitTestingPlugin.name, async (stats, cb) => {
49 try {
50 const suites = [];
51 for (let i = 0; i < this.options.entries.length; i++) {
52 const entry = this.options.entries[i];
53 if (stats.compilation.entrypoints.has(entry)) {
54 suites.push({
55 html: path.join(entry + '.html'),
56 path: entry
57 });
58 }
59 }
60
61 if (this.server === null) {
62 this.server = new LocalServer(
63 path.resolve(this.options.output), this.options.host, this.options.port, this.options.debug
64 );
65 this.server.listen();
66 }
67
68 const wdioSuites = this.createSuites(suites);
69 const wdioConfig = new WdioPrepareConfig(configMap, this.options.config, UnitTestingPlugin.name + '_config', this.options.debug);
70 const wdioConfigPath = wdioConfig.generateConfig();
71
72 const wdio = new Launcher(wdioConfigPath, {
73 specs: wdioSuites,
74 baseUrl: `http://${this.server.host}:${this.server.port}`
75 });
76
77 let code = await wdio.run();
78 if (!this.options.watch) {
79 this.server.close();
80 if (this.options.nycArgs && code === 0) {
81 code = await this.runNyc();
82 }
83 }
84 if (code !== 0) {
85 return cb(code);
86 }
87 } catch (e) {
88 process.stdout.write(chalk.red('Launcher failed to start the test' + '\n'));
89 return cb(e);
90 }
91 return cb(null);
92 });
93 }
94
95 runNyc() {
96 return new Promise((resolve, reject) => {
97 const nyc = spawn('nyc', [
98 ...this.options.nycArgs,
99 `--temp-dir=${UnitTestingPlugin.nycOutputDir}`,
100 '--clean'
101 ], {
102 stdio: 'pipe',
103 env: {
104 ...process.env,
105 FORCE_COLOR: 1
106 }
107 });
108
109 if (nyc.stdout !== null) {
110 nyc.stdout.on('data', (data) => {
111 process.stdout.write(data);
112 });
113 }
114 nyc.on('close', (code, ...args) => {
115 if (code === 0) {
116 resolve(null);
117 } else {
118 reject(code);
119 }
120 });
121 });
122 }
123 /**
124 * @return {String}
125 */
126 getConfigPath() {
127 const paths = [
128 '.wct.config.js',
129 '.wctrc.json',
130 'wct.conf.json'
131 ];
132 for (let i = 0; i < paths.length; i++) {
133 try {
134 const p = path.join(process.env.PWD, paths[i]);
135 const stat = fs.statSync(p);
136 if (stat.isFile()) {
137 return p;
138 }
139 } catch (e) {
140 // process.stdout.write('caught', e + '\n');
141 }
142 }
143 }
144
145 createSuites(suites) {
146 const wdioSuites = [];
147 for (let i = 0; i < suites.length; i++) {
148 const suite = suites[i];
149 const suiteName = titleCase(suite.path.replace(/\.spec\.js$/, ''));
150 const target = path.join(UnitTestingPlugin.cacheDir, suite.path);
151 const suiteContent = `const {expect} = require('chai');
152const logger = require('@wdio/logger').default;
153const chalk = require('chalk');
154
155const symbols = {
156 ok: '✓',
157 err: '✖',
158 dot: '․',
159 comma: ',',
160 bang: '!'
161};
162
163const log = logger('@aofl/unit-testing-plugin');
164const getEnvironmentCombo = (caps) => {
165 const device = caps.deviceName;
166 const browser = (caps.browserName || caps.browser);
167 const version = caps.version || caps.platformVersion || caps.browser_version;
168 const platform = caps.os ? (caps.os + ' ' + caps.os_version) : (caps.platform || caps.platformName)
169
170 // Mobile capabilities
171 if (device) {
172 const program = (caps.app || '').replace('sauce-storage:', '') || caps.browserName
173 const executing = program ? 'executing ' + program : ''
174
175 return ('device on ' + platform + ' ' + version + ' ' + executing).trim()
176 }
177
178 return browser + (version ? ' (v' + version + ')' : '') + (platform ? ' on ' + platform : '')
179}
180
181describe('${suiteName}', () => {
182 before(function() {
183 if (driver.isMobile === false) {
184 try {
185 browser.maximizeWindow();
186 } catch (e) {}
187 }
188 });
189
190 it('Running Tests /${suite.html}', function() {
191 browser.url('/${suite.html}');
192 browser.waitUntil(() => {
193 const ready = browser.execute('return !(typeof window.aofljsConfig === "undefined" || typeof window.aofljsConfig.unitTesting === "undefined" || window.aofljsConfig.unitTesting.ready === false)');
194 return ready === true;
195 }, browser.config.waitforTimeout, 'expected tests to be ready in timeout period', browser.config.waitforInterval);
196
197 let report = null;
198 browser.waitUntil(() => {
199 const r = browser.execute(function() {
200 var report = {
201 status: 'init',
202 pass: true,
203 stream: []
204 };
205
206 if (typeof aofljsConfig !== 'undefined' && typeof aofljsConfig.report !== 'undefined' && typeof aofljsConfig.report.status !== 'undefined' && typeof aofljsConfig.report.pass !== 'undefined' && typeof aofljsConfig.report.stream !== 'undefined') {
207 var report = {
208 status: aofljsConfig.report.status,
209 pass: aofljsConfig.report.pass,
210 stream: aofljsConfig.report.stream
211 };
212 aofljsConfig.report.stream = [];
213 }
214 return report;
215 });
216 if (typeof r === 'undefined') return false;
217 if (report === null) {
218 report = r;
219 } else {
220 report = {
221 ...r,
222 stream: report.stream.concat(r.stream)
223 }
224 }
225
226 for (let i = 0; i < r.stream.length; i++) {
227 const test = r.stream[i];
228 const result = test[0];
229 const testInfo = test[1];
230 if (result === 'pass') {
231 if (['warn', 'error', 'silent'].indexOf(browser.config.logLevel) === -1) {
232 let out = '\\t' + chalk.green(symbols.ok) + ' ' + getEnvironmentCombo(browser.capabilities) + ' >> ' + testInfo.fullTitle + ' (' + testInfo.duration + 'ms)';
233 if (testInfo.currentRetry > 0) {
234 out += ' (Retry: ' + testInfo.currentRetry + ')';
235 }
236 process.stdout.write(out + '\\n');
237 }
238 log.info('pass - ' + testInfo.fullTitle);
239 } else {
240 if (browser.config.logLevel !== 'silent') {
241 let out = '\t' + chalk.red(symbols.err) + ' ' + getEnvironmentCombo(browser.capabilities) + ' >> ' + testInfo.fullTitle + ' (' + testInfo.duration + 'ms)';
242 if (testInfo.currentRetry > 0) {
243 out += ' (Retry: ' + testInfo.currentRetry + ')';
244 }
245 process.stdout.write(out + '\\n');
246 process.stdout.write('\\n\\t\\t' + chalk.red(testInfo.stack) + '\\n\\n');
247 }
248 log.error('fail - ' + testInfo.fullTitle + ' - error: ' + testInfo.err);
249 log.error(testInfo.stack);
250 }
251 }
252
253 return report.status === 'done';
254 }, browser.config.waitforTimeout, 'expected tests to run within timeout period.', browser.config.waitforInterval);
255
256 let coverage = {};
257 let coverageIndex = 0;
258 browser.waitUntil(() => {
259 const c = browser.execute(function(index) {
260 var min = index * 5;
261 var max = (index + 1) * 5;
262 var curr = 0;
263 var data = [];
264 for (var key in aofljsConfig.report.coverage) {
265 if (curr >= min && curr < max) {
266 data.push({
267 key: key,
268 value: aofljsConfig.report.coverage[key]
269 });
270 }
271 curr++;
272 }
273 return {
274 data: data,
275 done: max > (Object.keys(aofljsConfig.report.coverage).length - 1)
276 }
277 }, coverageIndex++);
278
279 for (let i = 0; i < c.data.length; i++) {
280 coverage[c.data[i].key] = c.data[i].value;
281 }
282
283 return c.done;
284 }, browser.config.waitforTimeout, 'Failed te retrieve coverage data.', 100);
285 global.aoflUnitTesting.coverage.push(coverage);
286 expect(report.pass).to.be.true;
287 });
288});
289`;
290 fs.mkdirSync(path.dirname(target), {recursive: true});
291 fs.writeFileSync(target, suiteContent, {encoding: 'utf-8'});
292 wdioSuites.push(target);
293 }
294 return wdioSuites;
295 }
296}
297
298module.exports = UnitTestingPlugin;