@testomatio/reporter 1.2.2-beta-html-pagination-feature-v2 → 1.2.2-beta-cancel-reporting-after-multiple-fails
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/constants.js +15 -2
- package/lib/pipe/html.js +38 -84
- package/lib/pipe/testomatio.js +61 -15
- package/lib/template/template-draft.hbs +249 -0
- package/lib/template/testomatio.hbs +337 -1182
- package/package.json +1 -1
package/lib/constants.js
CHANGED
|
@@ -4,7 +4,6 @@ const path = require('path');
|
|
|
4
4
|
|
|
5
5
|
const APP_PREFIX = chalk.gray('[TESTOMATIO]');
|
|
6
6
|
const AXIOS_TIMEOUT = 20 * 1000; // sum = 20sec
|
|
7
|
-
const AXIOS_RETRY_TIMEOUT = 5 * 1000; // sum = 5sec
|
|
8
7
|
|
|
9
8
|
const TESTOMAT_TMP_STORAGE_DIR = path.join(os.tmpdir(), 'testomatio_tmp');
|
|
10
9
|
|
|
@@ -31,6 +30,19 @@ const HTML_REPORT = {
|
|
|
31
30
|
|
|
32
31
|
const testomatLogoURL = 'https://avatars.githubusercontent.com/u/59105116?s=36&v=4';
|
|
33
32
|
|
|
33
|
+
const REPORTER_REQUEST_RETRIES = {
|
|
34
|
+
retryTimeout: 5 * 1000, // sum = 5sec
|
|
35
|
+
retriesPerRequest: 2,
|
|
36
|
+
maxTotalRetries: Number(process.env.TESTOMATIO_MAX_REQUEST_FAILURES_COUNT) || 10,
|
|
37
|
+
withinTimeSeconds: Number(process.env.TESTOMATIO_MAX_REQUEST_RETRIES_WITHIN_TIME_SECONDS) || 60,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const RunStatus = {
|
|
41
|
+
Passed: 'passed',
|
|
42
|
+
Failed: 'failed',
|
|
43
|
+
Finished: 'finished',
|
|
44
|
+
};
|
|
45
|
+
|
|
34
46
|
module.exports = {
|
|
35
47
|
APP_PREFIX,
|
|
36
48
|
TESTOMAT_TMP_STORAGE_DIR,
|
|
@@ -38,6 +50,7 @@ module.exports = {
|
|
|
38
50
|
STATUS,
|
|
39
51
|
HTML_REPORT,
|
|
40
52
|
AXIOS_TIMEOUT,
|
|
41
|
-
AXIOS_RETRY_TIMEOUT,
|
|
42
53
|
testomatLogoURL,
|
|
54
|
+
REPORTER_REQUEST_RETRIES,
|
|
55
|
+
RunStatus,
|
|
43
56
|
};
|
package/lib/pipe/html.js
CHANGED
|
@@ -91,7 +91,7 @@ class HtmlPipe {
|
|
|
91
91
|
// GENERATE HTML reports based on the results data
|
|
92
92
|
this.buildReport({
|
|
93
93
|
runParams,
|
|
94
|
-
// TODO: this.tests=[] in case of Mocha
|
|
94
|
+
// TODO: this.tests=[] in case of Mocha
|
|
95
95
|
tests: this.tests,
|
|
96
96
|
outputPath: this.htmlOutputPath,
|
|
97
97
|
templatePath: this.templateHtmlPath,
|
|
@@ -135,13 +135,11 @@ class HtmlPipe {
|
|
|
135
135
|
test.title = 'Unknown test title';
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
test.files = 'This test has no files';
|
|
141
|
-
}
|
|
138
|
+
if (!test.files || test.files.length === 0) {
|
|
139
|
+
test.files = 'This test has no files';
|
|
142
140
|
}
|
|
143
141
|
|
|
144
|
-
if (
|
|
142
|
+
if (test.steps) {
|
|
145
143
|
if (!test.steps || test.steps.trim() === '') {
|
|
146
144
|
test.steps = "This test has no 'steps' code";
|
|
147
145
|
} else {
|
|
@@ -149,15 +147,6 @@ class HtmlPipe {
|
|
|
149
147
|
}
|
|
150
148
|
}
|
|
151
149
|
|
|
152
|
-
// TODO: future-proof: currently there is no need to display Artifacts and Metadata in HTML
|
|
153
|
-
if ('artifacts' in test) {
|
|
154
|
-
test.artifacts = [];
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if ('meta' in test) {
|
|
158
|
-
test.meta = {};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
150
|
// TODO: u can added an additional test values to this checks in the future
|
|
162
151
|
});
|
|
163
152
|
|
|
@@ -210,8 +199,7 @@ class HtmlPipe {
|
|
|
210
199
|
|
|
211
200
|
return template(data);
|
|
212
201
|
} catch (e) {
|
|
213
|
-
console.log(
|
|
214
|
-
console.log(chalk.red(e));
|
|
202
|
+
console.log('Unknown HTML report generation error: ', e);
|
|
215
203
|
}
|
|
216
204
|
}
|
|
217
205
|
|
|
@@ -221,76 +209,42 @@ class HtmlPipe {
|
|
|
221
209
|
(tests, status) => tests.filter(test => test.status.toLowerCase() === status.toLowerCase()).length,
|
|
222
210
|
);
|
|
223
211
|
|
|
224
|
-
handlebars.registerHelper(
|
|
225
|
-
|
|
226
|
-
()
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
.replace(/<\/script>/g, '</script>'), // eslint-disable-line
|
|
252
|
-
);
|
|
253
|
-
|
|
254
|
-
const paginationOptions = {
|
|
255
|
-
0: 10,
|
|
256
|
-
1: 25,
|
|
257
|
-
2: 50,
|
|
258
|
-
};
|
|
259
|
-
const statuses = ['all', 'passed', 'failed', 'skipped'];
|
|
260
|
-
const pageItemGroups = {
|
|
261
|
-
all: {},
|
|
262
|
-
passed: {},
|
|
263
|
-
failed: {},
|
|
264
|
-
skipped: {},
|
|
265
|
-
totalTests,
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
function paginateItems(items, pageSize) {
|
|
269
|
-
const paginatedItems = [];
|
|
270
|
-
for (let i = 0; i < items.length; i += pageSize) {
|
|
271
|
-
paginatedItems.push(items.slice(i, i + pageSize));
|
|
272
|
-
}
|
|
273
|
-
return paginatedItems;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
statuses.forEach(status => {
|
|
277
|
-
for (const option in paginationOptions) {
|
|
278
|
-
if (Object.hasOwnProperty.call(paginationOptions, option)) {
|
|
279
|
-
const pageSize = paginationOptions[option];
|
|
280
|
-
let filteredItems = totalTests;
|
|
281
|
-
|
|
282
|
-
if (status !== 'all') {
|
|
283
|
-
filteredItems = totalTests.filter(item => item.status === status);
|
|
212
|
+
handlebars.registerHelper('json', tests => {
|
|
213
|
+
// TODO: please refactor the code below, add meaningful variable names, separate into functions
|
|
214
|
+
function replaceScriptTagsInArray(array) {
|
|
215
|
+
return array.map(obj => {
|
|
216
|
+
const keysToCheck = ['steps', 'stack', 'title', 'suite_title', 'message', 'code'];
|
|
217
|
+
const newObj = {};
|
|
218
|
+
|
|
219
|
+
for (const key in obj) {
|
|
220
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
221
|
+
if (key === 'example') {
|
|
222
|
+
newObj[key] = {};
|
|
223
|
+
for (const subKey in obj[key]) {
|
|
224
|
+
if (Object.prototype.hasOwnProperty.call(obj[key], subKey)) {
|
|
225
|
+
newObj[key][subKey] =
|
|
226
|
+
typeof obj[key][subKey] === 'string'
|
|
227
|
+
? obj[key][subKey].replace(/<script>/g, '<$cript>').replace(/<\/script>/g, '</$cript>')
|
|
228
|
+
: obj[key][subKey];
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
} else if (keysToCheck.includes(key)) {
|
|
232
|
+
newObj[key] =
|
|
233
|
+
typeof obj[key] === 'string'
|
|
234
|
+
? obj[key].replace(/<script>/g, '<$cript>').replace(/<\/script>/g, '</$cript>')
|
|
235
|
+
: obj[key];
|
|
236
|
+
} else {
|
|
237
|
+
newObj[key] = obj[key];
|
|
238
|
+
}
|
|
284
239
|
}
|
|
285
|
-
|
|
286
|
-
pageItemGroups[status][option] = paginateItems(filteredItems, pageSize);
|
|
287
240
|
}
|
|
288
|
-
}
|
|
289
|
-
});
|
|
290
241
|
|
|
291
|
-
|
|
242
|
+
return newObj;
|
|
243
|
+
});
|
|
244
|
+
}
|
|
292
245
|
|
|
293
|
-
|
|
246
|
+
// Remove ANSI escape codes
|
|
247
|
+
return JSON.stringify(replaceScriptTagsInArray(tests));
|
|
294
248
|
});
|
|
295
249
|
}
|
|
296
250
|
|
|
@@ -307,7 +261,7 @@ class HtmlPipe {
|
|
|
307
261
|
*/
|
|
308
262
|
function testExecutionSumTime(tests) {
|
|
309
263
|
const totalMilliseconds = tests.reduce((sum, test) => {
|
|
310
|
-
if (typeof test.run_time === 'number'
|
|
264
|
+
if (typeof test.run_time === 'number') {
|
|
311
265
|
return sum + test.run_time;
|
|
312
266
|
}
|
|
313
267
|
return sum;
|
package/lib/pipe/testomatio.js
CHANGED
|
@@ -6,7 +6,7 @@ const axiosRetry = require('axios-retry');
|
|
|
6
6
|
const axios = require('axios');
|
|
7
7
|
const JsonCycle = require('json-cycle');
|
|
8
8
|
|
|
9
|
-
const { APP_PREFIX, STATUS, AXIOS_TIMEOUT,
|
|
9
|
+
const { APP_PREFIX, STATUS, AXIOS_TIMEOUT, REPORTER_REQUEST_RETRIES } = require('../constants');
|
|
10
10
|
const { isValidUrl, foundedTestLog } = require('../utils/utils');
|
|
11
11
|
const { parseFilterParams, generateFilterRequestParams, setS3Credentials } = require('../utils/pipe_utils');
|
|
12
12
|
const config = require('../config');
|
|
@@ -23,6 +23,10 @@ if (process.env.TESTOMATIO_RUN) {
|
|
|
23
23
|
*/
|
|
24
24
|
class TestomatioPipe {
|
|
25
25
|
constructor(params, store) {
|
|
26
|
+
this.retriesTimestamps = [];
|
|
27
|
+
this.reportingCanceledDueToReqFailures = false;
|
|
28
|
+
this.notReportedTestsCount = 0;
|
|
29
|
+
|
|
26
30
|
this.isEnabled = false;
|
|
27
31
|
this.url = params.testomatioUrl || process.env.TESTOMATIO_URL || 'https://app.testomat.io';
|
|
28
32
|
this.apiKey = params.apiKey || config.TESTOMATIO;
|
|
@@ -43,25 +47,29 @@ class TestomatioPipe {
|
|
|
43
47
|
baseURL: `${this.url.trim()}`,
|
|
44
48
|
timeout: AXIOS_TIMEOUT,
|
|
45
49
|
});
|
|
50
|
+
|
|
46
51
|
// Pass the axios instance to the retry function
|
|
47
52
|
axiosRetry(this.axios, {
|
|
48
|
-
|
|
53
|
+
// do not use retries for unit tests
|
|
54
|
+
retries: REPORTER_REQUEST_RETRIES.retriesPerRequest, // Number of retries
|
|
49
55
|
shouldResetTimeout: true,
|
|
50
56
|
retryCondition: error => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
case
|
|
54
|
-
case 429:
|
|
55
|
-
case
|
|
56
|
-
|
|
57
|
-
return true; // Retry request with response status code 409, 429, 502, 503
|
|
57
|
+
switch (error.response?.status) {
|
|
58
|
+
case 400: // Bad request (probably wrong API key)
|
|
59
|
+
case 404: // Test not matched
|
|
60
|
+
case 429: // Rate limit exceeded
|
|
61
|
+
case 500: // Internal server error
|
|
62
|
+
return false;
|
|
58
63
|
default:
|
|
59
|
-
|
|
64
|
+
break;
|
|
60
65
|
}
|
|
66
|
+
return error.response?.status >= 401; // Retry on 401+ and 5xx
|
|
61
67
|
},
|
|
62
|
-
retryDelay:
|
|
63
|
-
onRetry: retryCount => {
|
|
64
|
-
|
|
68
|
+
retryDelay: () => REPORTER_REQUEST_RETRIES.retryTimeout, // sum = 15sec
|
|
69
|
+
onRetry: async (retryCount, error) => {
|
|
70
|
+
this.retriesTimestamps.push(Date.now());
|
|
71
|
+
|
|
72
|
+
debug(`${error.message || `Request failed ${error.status}`}. Retry #${retryCount} ...`);
|
|
65
73
|
},
|
|
66
74
|
});
|
|
67
75
|
|
|
@@ -194,9 +202,40 @@ class TestomatioPipe {
|
|
|
194
202
|
debug('"createRun" function finished');
|
|
195
203
|
}
|
|
196
204
|
|
|
205
|
+
/**
|
|
206
|
+
* Decides whether to skip test reporting in case of too many request failures
|
|
207
|
+
* @param {TestData} testData
|
|
208
|
+
* @returns {boolean}
|
|
209
|
+
*/
|
|
210
|
+
#cancelTestReportingInCaseOfTooManyReqFailures(testData) {
|
|
211
|
+
if (this.reportingCanceledDueToReqFailures) return true;
|
|
212
|
+
|
|
213
|
+
const retriesCountWithinTime = this.retriesTimestamps.filter(
|
|
214
|
+
timestamp => Date.now() - timestamp < REPORTER_REQUEST_RETRIES.withinTimeSeconds * 1000,
|
|
215
|
+
).length;
|
|
216
|
+
debug(`${retriesCountWithinTime} failed requests within ${REPORTER_REQUEST_RETRIES.withinTimeSeconds}s`);
|
|
217
|
+
|
|
218
|
+
if (retriesCountWithinTime > REPORTER_REQUEST_RETRIES.maxTotalRetries) {
|
|
219
|
+
const errorMessage = chalk.yellow(
|
|
220
|
+
`${retriesCountWithinTime} requests were failed within ${REPORTER_REQUEST_RETRIES.withinTimeSeconds}s,\
|
|
221
|
+
reporting for test "${testData.title}" to Testomat is skipped`,
|
|
222
|
+
);
|
|
223
|
+
console.warn(`${APP_PREFIX} ${errorMessage}`);
|
|
224
|
+
|
|
225
|
+
this.reportingCanceledDueToReqFailures = true;
|
|
226
|
+
this.notReportedTestsCount++;
|
|
227
|
+
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
|
|
197
234
|
addTest(data) {
|
|
198
235
|
if (!this.isEnabled) return;
|
|
199
236
|
if (!this.runId) return;
|
|
237
|
+
if (this.#cancelTestReportingInCaseOfTooManyReqFailures(data)) return;
|
|
238
|
+
|
|
200
239
|
data.api_key = this.apiKey;
|
|
201
240
|
data.create = this.createNewTests;
|
|
202
241
|
const json = JsonCycle.stringify(data);
|
|
@@ -221,7 +260,7 @@ class TestomatioPipe {
|
|
|
221
260
|
chalk.yellow(`Warning: ${responseData.message} (${err.response.status})`),
|
|
222
261
|
chalk.grey(data?.title || ''),
|
|
223
262
|
);
|
|
224
|
-
if (err.response.data
|
|
263
|
+
if (err.response.data?.message?.includes('could not be matched')) {
|
|
225
264
|
this.hasUnmatchedTests = true;
|
|
226
265
|
}
|
|
227
266
|
return;
|
|
@@ -242,8 +281,15 @@ class TestomatioPipe {
|
|
|
242
281
|
* @returns
|
|
243
282
|
*/
|
|
244
283
|
async finishRun(params) {
|
|
245
|
-
debug('Finishing run...');
|
|
246
284
|
if (!this.isEnabled) return;
|
|
285
|
+
debug('Finishing run...');
|
|
286
|
+
|
|
287
|
+
if (this.reportingCanceledDueToReqFailures) {
|
|
288
|
+
const errorMessage = chalk.red(
|
|
289
|
+
`⚠️ Due to request failures, ${this.notReportedTestsCount} test(s) were not reported to Testomat.io`,
|
|
290
|
+
);
|
|
291
|
+
console.warn(`${APP_PREFIX} ${errorMessage}`);
|
|
292
|
+
}
|
|
247
293
|
|
|
248
294
|
const { status, parallel } = params;
|
|
249
295
|
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<link href="https://fonts.googleapis.com/css2?family=Fira+Mono:wght@400;500;700&display=swap" rel="stylesheet">
|
|
7
|
+
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">
|
|
8
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/accordionjs@2.1.2/accordion.min.css">
|
|
9
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
|
|
10
|
+
<title>Report {{runId}} - Testomat.io</title>
|
|
11
|
+
<link rel="stylesheet" href="./style.css">
|
|
12
|
+
{{!-- TODO: styling create as a new additional file instead of template => copy default one --}}
|
|
13
|
+
<style>
|
|
14
|
+
body {
|
|
15
|
+
font-family: "Fira Sans", sans-serif;
|
|
16
|
+
}
|
|
17
|
+
</style>
|
|
18
|
+
</head>
|
|
19
|
+
|
|
20
|
+
<body class="bg-gray-50 pt-6 px-4 lg:pt-4 lg:px-40 h-full;">
|
|
21
|
+
<div id="testomatio_report">
|
|
22
|
+
<div class="flex items-center flex-col space-y-4 md:flex-row md:justify-between md:h-14 mb-6">
|
|
23
|
+
<div class="flex flex-col md:flex-row space-x-3 items-center">
|
|
24
|
+
<h2 class="text-gray-900 leading-none text-3xl font-semibold mb-0">{{runId}}
|
|
25
|
+
</h2>
|
|
26
|
+
{{!-- <span class="text-sm text-gray-500">
|
|
27
|
+
{{executionDate}}
|
|
28
|
+
</span> --}}
|
|
29
|
+
</div>
|
|
30
|
+
<div class="flex space-x-3 items-center">
|
|
31
|
+
<a class="inline-flex items-center border border-transparent leading-none text-white bg-indigo-500 hover:bg-indigo-700 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 cursor-pointer px-4 py-2 text-base leading-none"
|
|
32
|
+
style="min-height: 38px"
|
|
33
|
+
href="{{runUrl}}"
|
|
34
|
+
target="_blank">
|
|
35
|
+
Full Report
|
|
36
|
+
</a>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="bg-white border overflow-hidden rounded-lg divide-y divide-gray-200 mb-12">
|
|
40
|
+
<div class="flex flex-col lg:divide-x divide-gray-200 sm:flex-col md:flex-col lg:flex-row">
|
|
41
|
+
<div class="w-full lg:w-1/2">
|
|
42
|
+
<!-- Left chart section -->
|
|
43
|
+
<div class="p-4">
|
|
44
|
+
<h2 class="text-xl font-semibold mb-4">Testomatio Test Results</h2>
|
|
45
|
+
<span class="text-sm text-gray-500 items-center">{{executionDate}}</span>
|
|
46
|
+
<div class="hart-container" style="height: 200px;">
|
|
47
|
+
<canvas id="testomatio_chart"></canvas>
|
|
48
|
+
<div for="testomatio_chart"></div>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="w-full lg:w-1/2">
|
|
53
|
+
<!-- Right chart section -->
|
|
54
|
+
<div class="p-4">
|
|
55
|
+
<h2 class="text-xl font-semibold mb-4">Testomatio Run Info</h2>
|
|
56
|
+
<dl>
|
|
57
|
+
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
|
58
|
+
<dt class="text-sm font-medium text-gray-500">
|
|
59
|
+
RUN Status
|
|
60
|
+
</dt>
|
|
61
|
+
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
|
62
|
+
<span class="inline-flex items-center justify-center px-2 py-0.5 text-xs font-bold leading-none bg-green-200 text-green-600 rounded-full">
|
|
63
|
+
{{status}}
|
|
64
|
+
</span>
|
|
65
|
+
</dd>
|
|
66
|
+
</div>
|
|
67
|
+
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
|
68
|
+
<dt class="text-sm font-medium text-gray-500">
|
|
69
|
+
Duration
|
|
70
|
+
</dt>
|
|
71
|
+
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
|
72
|
+
<span>
|
|
73
|
+
<i class="fa fa-clock-o"></i>
|
|
74
|
+
</span>
|
|
75
|
+
{{executionTime}}
|
|
76
|
+
</dd>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</dl>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<div class="px-4 py-5 lg:px-20 lg:py-5">
|
|
86
|
+
<!-- TODO: maybe add a search section by test NAME!! -->
|
|
87
|
+
<div class="px-4 py-5 lg:px-20 lg:py-5 border rounded-md mb-4">
|
|
88
|
+
<div class="mb-2">
|
|
89
|
+
<h3 class="text-2xl leading-10 font-medium text-gray-900">
|
|
90
|
+
Search and Summary
|
|
91
|
+
</h3>
|
|
92
|
+
</div>
|
|
93
|
+
<div class="flex items-center space-x-4">
|
|
94
|
+
<div class="relative">
|
|
95
|
+
<input type="text" id="search-input" placeholder="Search by test title..." class="border rounded-md py-1 px-2 w-48 focus:outline-none focus:ring focus:border-blue-300">
|
|
96
|
+
|
|
97
|
+
</div>
|
|
98
|
+
<div class="test-status">
|
|
99
|
+
<div class="flex items-center">
|
|
100
|
+
<span class="font-medium mr-2">All:</span>
|
|
101
|
+
<span class="text-blue-600 font-semibold">{{tests.length}}</span>
|
|
102
|
+
<span class="font-medium ml-4 mr-2">Passed:</span>
|
|
103
|
+
<span class="text-green-600 font-semibold">{{getTestsByStatus tests "PASSED"}}</span>
|
|
104
|
+
<span class="font-medium ml-4 mr-2">Failed:</span>
|
|
105
|
+
<span class="text-red-600 font-semibold">{{getTestsByStatus tests "FAILED"}}</span>
|
|
106
|
+
<span class="font-medium ml-4 mr-2">Skipped:</span>
|
|
107
|
+
<span class="text-yellow-600 font-semibold">{{getTestsByStatus tests "SKIPPED"}}</span>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<ul class="accordionjs bg-gray-50 rounded-md divide-y divide-gray-200 text-gray-900">
|
|
115
|
+
<li class="pl-3 pr-4 py-3 text-sm cursor-pointer">
|
|
116
|
+
<h4 class="text-lg flex items-center space-x-1">
|
|
117
|
+
<span class="font-medium">Passed</span>
|
|
118
|
+
<span class="inline-flex items-center justify-center px-2 py-0.5 text-xs font-bold leading-none bg-green-200 text-green-600 rounded-full">
|
|
119
|
+
{{getTestsByStatus tests "PASSED"}}
|
|
120
|
+
</span>
|
|
121
|
+
</h4>
|
|
122
|
+
<ul role="list" class="pl-2 divide-y divide-gray-200">
|
|
123
|
+
{{#each tests}}
|
|
124
|
+
<ul role="list" class="pl-2 divide-y divide-gray-200">
|
|
125
|
+
{{#eq status "passed"}}
|
|
126
|
+
<li class="py-3 flex justify-between items-center">
|
|
127
|
+
<div>
|
|
128
|
+
<div class="success"></div>
|
|
129
|
+
<a class="testrun hover:underline" data-toggle="test-{{test_id}}" data-target="test-code-{{test_id}}">
|
|
130
|
+
{{title}} title
|
|
131
|
+
</a>
|
|
132
|
+
</div>
|
|
133
|
+
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
|
134
|
+
<span>
|
|
135
|
+
<i class="fa fa-clock-o"></i>
|
|
136
|
+
</span>
|
|
137
|
+
{{#if run_time}}
|
|
138
|
+
{{run_time}}ms
|
|
139
|
+
{{/if}}
|
|
140
|
+
</dd>
|
|
141
|
+
</li>
|
|
142
|
+
<li class="py-3 test-code-container test-code" id="test-code-{{test_id}}">
|
|
143
|
+
<pre>
|
|
144
|
+
<code>
|
|
145
|
+
{{steps}}
|
|
146
|
+
</code>
|
|
147
|
+
</pre>
|
|
148
|
+
</li>
|
|
149
|
+
{{/eq}}
|
|
150
|
+
</ul>
|
|
151
|
+
{{/each}}
|
|
152
|
+
</ul>
|
|
153
|
+
</li>
|
|
154
|
+
|
|
155
|
+
<li class="pl-3 pr-4 py-3 text-sm cursor-pointer">
|
|
156
|
+
<h4 class="text-lg flex items-center space-x-1">
|
|
157
|
+
<span class="font-medium">Failed</span>
|
|
158
|
+
<span class="inline-flex items-center justify-center px-2 py-0.5 text-xs font-bold leading-none bg-red-200 text-red-500 rounded-full">
|
|
159
|
+
{{getTestsByStatus tests "FAILED"}}
|
|
160
|
+
</span>
|
|
161
|
+
</h4>
|
|
162
|
+
|
|
163
|
+
<ul role="list" class="pl-2 divide-y divide-gray-200">
|
|
164
|
+
{{#each tests}}
|
|
165
|
+
{{#eq status "failed"}}
|
|
166
|
+
<li class="py-3 flex justify-between items-center">
|
|
167
|
+
<div>
|
|
168
|
+
<div class="fail"></div>
|
|
169
|
+
<a class="testrun hover:underline" data-toggle="test-{{test_id}}" data-target="test-code-{{test_id}}">
|
|
170
|
+
{{title}} title
|
|
171
|
+
</a>
|
|
172
|
+
</div>
|
|
173
|
+
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
|
174
|
+
<span>
|
|
175
|
+
<i class="fa fa-clock-o"></i>
|
|
176
|
+
</span>
|
|
177
|
+
{{#if run_time}}
|
|
178
|
+
{{run_time}}ms
|
|
179
|
+
{{/if}}
|
|
180
|
+
</dd>
|
|
181
|
+
</li>
|
|
182
|
+
<li class="py-3 test-code-container test-code" id="test-code-{{test_id}}">
|
|
183
|
+
<pre>
|
|
184
|
+
<code>
|
|
185
|
+
{{steps}}
|
|
186
|
+
</code>
|
|
187
|
+
</pre>
|
|
188
|
+
</li>
|
|
189
|
+
{{/eq}}
|
|
190
|
+
{{/each}}
|
|
191
|
+
</ul>
|
|
192
|
+
</li>
|
|
193
|
+
</ul>
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
<div
|
|
197
|
+
class="fixed bg-white border-t inset-x-0 bottom-0 mx-auto py-1 px-3 sm:px-6 lg:px-8 flex items-center justify-between flex-wrap">
|
|
198
|
+
<p class="ml-3 text-gray-500 text-xs lg:truncate">
|
|
199
|
+
This is a public report generated by <a class="hover:underline text-gray-700 font-semibold"
|
|
200
|
+
href="https://testomat.io" target="_blank">Testomat.io</a>. Everyone who has accesse to this link can
|
|
201
|
+
see this report.<br>
|
|
202
|
+
</p>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
</body>
|
|
206
|
+
<!-- Jquery from a CDN -->
|
|
207
|
+
<script type="module" src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.3.2/chart.min.js"></script>
|
|
208
|
+
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
|
|
209
|
+
<script src="https://cdn.jsdelivr.net/npm/accordionjs@2.1.2/accordion.min.js"></script>
|
|
210
|
+
<!-- Chart.js from a CDN -->
|
|
211
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
212
|
+
|
|
213
|
+
<!-- Include Handlebars from a CDN -->
|
|
214
|
+
<script src="https://cdn.jsdelivr.net/npm/handlebars@latest/dist/handlebars.js"></script>
|
|
215
|
+
<!-- Handlebars tamplate actions -->
|
|
216
|
+
<script>
|
|
217
|
+
$(document).ready(function ($) {
|
|
218
|
+
$(".accordionjs").accordionjs();
|
|
219
|
+
|
|
220
|
+
$('#copy-url').on('click', () => {
|
|
221
|
+
navigator.clipboard.writeText(window.location.href);
|
|
222
|
+
})
|
|
223
|
+
});
|
|
224
|
+
//Open code section handler
|
|
225
|
+
document.querySelectorAll('.test-code').forEach(element => {
|
|
226
|
+
element.style.display = 'none';
|
|
227
|
+
});
|
|
228
|
+
const testrunLinks = document.querySelectorAll('.testrun');
|
|
229
|
+
|
|
230
|
+
testrunLinks.forEach(link => {
|
|
231
|
+
link.addEventListener('click', function () {
|
|
232
|
+
// get "data-toggle" & "data-target" from link env
|
|
233
|
+
const toggleId = this.getAttribute('data-toggle');
|
|
234
|
+
const targetId = this.getAttribute('data-target');
|
|
235
|
+
|
|
236
|
+
// get "tests.code" elements and change view
|
|
237
|
+
const targetElement = document.querySelector(`#${targetId}`);
|
|
238
|
+
|
|
239
|
+
// SET "tests.code" viewing after each click
|
|
240
|
+
if (targetElement.style.display === 'none') {
|
|
241
|
+
targetElement.style.display = 'block';
|
|
242
|
+
} else {
|
|
243
|
+
targetElement.style.display = 'none';
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
</script>
|
|
248
|
+
<script type="text/javascript" src="./chart-my.js"></script>
|
|
249
|
+
</html>
|