testomatio-reporter-cli 2.8.4 → 2.8.5-beta.2-yarn
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/README.md +3 -3
- package/bin/cli.js +6 -26
- package/package.json +39 -4
- package/src/adapter/codecept.js +626 -0
- package/src/adapter/cucumber/current.js +230 -0
- package/src/adapter/cucumber/legacy.js +158 -0
- package/src/adapter/cucumber.js +4 -0
- package/src/adapter/cypress-plugin/index.js +110 -0
- package/src/adapter/jasmine.js +60 -0
- package/src/adapter/jest.js +108 -0
- package/src/adapter/mocha.cjs +2 -0
- package/src/adapter/mocha.js +211 -0
- package/src/adapter/nightwatch.js +88 -0
- package/src/adapter/playwright.js +343 -0
- package/src/adapter/utils/playwright.js +121 -0
- package/src/adapter/utils/step-formatter.js +232 -0
- package/src/adapter/vitest.js +455 -0
- package/src/adapter/webdriver.js +201 -0
- package/src/bin/cli.js +507 -0
- package/src/bin/reportXml.js +79 -0
- package/src/bin/startTest.js +54 -0
- package/src/bin/uploadArtifacts.js +91 -0
- package/src/client.js +524 -0
- package/src/config.js +30 -0
- package/src/constants.js +72 -0
- package/src/data-storage.js +204 -0
- package/src/helpers.js +1 -0
- package/src/junit-adapter/adapter.js +23 -0
- package/src/junit-adapter/csharp.js +70 -0
- package/src/junit-adapter/index.js +28 -0
- package/src/junit-adapter/java.js +58 -0
- package/src/junit-adapter/javascript.js +31 -0
- package/src/junit-adapter/nunit-parser.js +474 -0
- package/src/junit-adapter/python.js +42 -0
- package/src/junit-adapter/ruby.js +10 -0
- package/src/output.js +57 -0
- package/src/pipe/bitbucket.js +285 -0
- package/src/pipe/coverage.js +500 -0
- package/src/pipe/csv.js +161 -0
- package/src/pipe/debug.js +143 -0
- package/src/pipe/github.js +256 -0
- package/src/pipe/gitlab.js +258 -0
- package/src/pipe/html.js +1153 -0
- package/src/pipe/index.js +73 -0
- package/src/pipe/markdown.js +753 -0
- package/src/pipe/testomatio.js +707 -0
- package/src/replay.js +274 -0
- package/src/reporter-functions.js +155 -0
- package/src/reporter.js +42 -0
- package/src/services/artifacts.js +59 -0
- package/src/services/index.js +15 -0
- package/src/services/key-values.js +59 -0
- package/src/services/links.js +69 -0
- package/src/services/logger.js +320 -0
- package/src/template/emptyData.svg +23 -0
- package/src/template/testomatio-old.hbs +1421 -0
- package/src/template/testomatio.hbs +3726 -0
- package/src/uploader.js +382 -0
- package/src/utils/constants.js +12 -0
- package/src/utils/debug.js +20 -0
- package/src/utils/log-formatter.js +118 -0
- package/src/utils/log.js +88 -0
- package/src/utils/pipe_utils.js +193 -0
- package/src/utils/utils.js +732 -0
- package/src/xmlReader.js +834 -0
- package/types/types.d.ts +425 -0
- package/types/vitest.types.d.ts +93 -0
package/src/constants.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { transformEnvVarToBoolean } from './utils/utils.js';
|
|
5
|
+
|
|
6
|
+
const APP_PREFIX = pc.gray('[TESTOMATIO]');
|
|
7
|
+
const TESTOMATIO_REQUEST_TIMEOUT = parseInt(process.env.TESTOMATIO_REQUEST_TIMEOUT, 10);
|
|
8
|
+
if (TESTOMATIO_REQUEST_TIMEOUT) {
|
|
9
|
+
console.log(`${APP_PREFIX} Request timeout is set to ${TESTOMATIO_REQUEST_TIMEOUT / 1000}s`);
|
|
10
|
+
}
|
|
11
|
+
const REQUEST_TIMEOUT = TESTOMATIO_REQUEST_TIMEOUT || 20 * 1000;
|
|
12
|
+
const SCREENSHOTS_ON_STEPS = process.env.TESTOMATIO_SCREENSHOTS_ON_STEPS == null
|
|
13
|
+
|| transformEnvVarToBoolean(process.env.TESTOMATIO_SCREENSHOTS_ON_STEPS);
|
|
14
|
+
|
|
15
|
+
const TESTOMAT_TMP_STORAGE_DIR = path.join(os.tmpdir(), 'testomatio_tmp');
|
|
16
|
+
|
|
17
|
+
const CSV_HEADERS = [
|
|
18
|
+
{ id: 'suite_title', title: 'Suite_title' },
|
|
19
|
+
{ id: 'title', title: 'Title' },
|
|
20
|
+
{ id: 'status', title: 'Status' },
|
|
21
|
+
{ id: 'message', title: 'Message' },
|
|
22
|
+
{ id: 'stack', title: 'Stack' },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const STATUS = {
|
|
26
|
+
PASSED: 'passed',
|
|
27
|
+
FAILED: 'failed',
|
|
28
|
+
SKIPPED: 'skipped',
|
|
29
|
+
FINISHED: 'finished',
|
|
30
|
+
};
|
|
31
|
+
// html pipe var
|
|
32
|
+
const HTML_REPORT = {
|
|
33
|
+
FOLDER: 'html-report',
|
|
34
|
+
REPORT_DEFAULT_NAME: 'testomatio-report.html',
|
|
35
|
+
TEMPLATE_NAME: 'testomatio.hbs',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// markdown pipe var
|
|
39
|
+
const MARKDOWN_REPORT = {
|
|
40
|
+
FOLDER: 'md-report',
|
|
41
|
+
REPORT_DEFAULT_NAME: 'testomatio-report.md',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const testomatLogoURL = 'https://avatars.githubusercontent.com/u/59105116?s=36&v=4';
|
|
45
|
+
|
|
46
|
+
const REPORTER_REQUEST_RETRIES = {
|
|
47
|
+
retryTimeout: 5 * 1000, // sum = 5sec
|
|
48
|
+
retriesPerRequest: 2,
|
|
49
|
+
maxTotalRetries: Number(process.env.TESTOMATIO_MAX_REQUEST_FAILURES_COUNT) || 10,
|
|
50
|
+
withinTimeSeconds: Number(process.env.TESTOMATIO_MAX_REQUEST_RETRIES_WITHIN_TIME_SECONDS) || 60,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const DEBUG_FILE = 'testomatio.debug';
|
|
54
|
+
|
|
55
|
+
function getCreateRunRequestTimeout() {
|
|
56
|
+
return Math.max(REQUEST_TIMEOUT, 80 * 1000);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export {
|
|
60
|
+
APP_PREFIX,
|
|
61
|
+
TESTOMAT_TMP_STORAGE_DIR,
|
|
62
|
+
CSV_HEADERS,
|
|
63
|
+
STATUS,
|
|
64
|
+
HTML_REPORT,
|
|
65
|
+
MARKDOWN_REPORT,
|
|
66
|
+
REQUEST_TIMEOUT,
|
|
67
|
+
getCreateRunRequestTimeout,
|
|
68
|
+
testomatLogoURL,
|
|
69
|
+
REPORTER_REQUEST_RETRIES,
|
|
70
|
+
SCREENSHOTS_ON_STEPS,
|
|
71
|
+
DEBUG_FILE,
|
|
72
|
+
};
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import createDebugMessages from 'debug';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path, { join } from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import { TESTOMAT_TMP_STORAGE_DIR } from './constants.js';
|
|
6
|
+
import { fileSystem, testRunnerHelper } from './utils/utils.js';
|
|
7
|
+
import crypto from 'crypto';
|
|
8
|
+
|
|
9
|
+
const debug = createDebugMessages('@testomatio/reporter:storage');
|
|
10
|
+
class DataStorage {
|
|
11
|
+
static #instance;
|
|
12
|
+
|
|
13
|
+
context;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
*
|
|
17
|
+
* @returns {DataStorage}
|
|
18
|
+
*/
|
|
19
|
+
static getInstance() {
|
|
20
|
+
if (!this.#instance) {
|
|
21
|
+
this.#instance = new DataStorage();
|
|
22
|
+
}
|
|
23
|
+
return this.#instance;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
setContext(context) {
|
|
27
|
+
this.context = context;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Creates data storage instance as singleton
|
|
32
|
+
* Stores data to global variable or to file depending on what is applicable for current test runner (adapter)
|
|
33
|
+
* Recommend to use composition while using this class (instead of inheritance).
|
|
34
|
+
* ! Also the class which will use data storage should be singleton (to avoid data loss).
|
|
35
|
+
*/
|
|
36
|
+
constructor() {
|
|
37
|
+
// some frameworks use global variable to store data, some use file storage
|
|
38
|
+
this.isFileStorage = true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Puts any data to storage (file or global variable).
|
|
43
|
+
* If file: stores data as text, if global variable – stores as array of data.
|
|
44
|
+
* @param {'log' | 'artifact' | 'keyvalue' | 'links'} dataType
|
|
45
|
+
* @param {*} data anything you want to store (string, object, array, etc)
|
|
46
|
+
* @param {*} context could be testId or any context (test name, suite name, including their IDs etc)
|
|
47
|
+
* suite name + test name is used by default
|
|
48
|
+
* @returns
|
|
49
|
+
*/
|
|
50
|
+
putData(dataType, data, context = null) {
|
|
51
|
+
if (!dataType || !data) return;
|
|
52
|
+
|
|
53
|
+
context = context || this.context || testRunnerHelper.getNameOfCurrentlyRunningTest();
|
|
54
|
+
if (!context) {
|
|
55
|
+
// debug(`No context provided for "${dataType}" data:`, data);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const contextHash = stringToMD5Hash(context);
|
|
59
|
+
|
|
60
|
+
if (this.isFileStorage) {
|
|
61
|
+
const dataDirPath = path.join(TESTOMAT_TMP_STORAGE_DIR, dataType);
|
|
62
|
+
fileSystem.createDir(dataDirPath);
|
|
63
|
+
this.#putDataToFile(dataType, data, contextHash);
|
|
64
|
+
} else {
|
|
65
|
+
this.#putDataToGlobalVar(dataType, data, contextHash);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Returns data, stored for specific test/context (or data which was stored without test id specified).
|
|
71
|
+
* This method will get data from global variable and/or from from file (previosly saved with put method).
|
|
72
|
+
*
|
|
73
|
+
* @param {'log' | 'artifact' | 'keyvalue' | 'links'} dataType
|
|
74
|
+
* @param {string} context
|
|
75
|
+
* @returns {any []} array of data (any type), null (if no data found for context) or string (if data type is log)
|
|
76
|
+
*/
|
|
77
|
+
getData(dataType, context) {
|
|
78
|
+
// TODO: think if it could be useful
|
|
79
|
+
// context = context || this.context || testRunnerHelper.getNameOfCurrentlyRunningTest();
|
|
80
|
+
|
|
81
|
+
if (!context) {
|
|
82
|
+
debug(`Trying to get "${dataType}" data without context`);
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const contextHash = stringToMD5Hash(context);
|
|
87
|
+
|
|
88
|
+
let testDataFromFile = [];
|
|
89
|
+
let testDataFromGlobalVar = [];
|
|
90
|
+
|
|
91
|
+
if (global?.testomatioDataStore) {
|
|
92
|
+
testDataFromGlobalVar = this.#getDataFromGlobalVar(dataType, contextHash);
|
|
93
|
+
if (testDataFromGlobalVar) {
|
|
94
|
+
if (testDataFromGlobalVar.length) return testDataFromGlobalVar;
|
|
95
|
+
}
|
|
96
|
+
// don't return nothing if no data in global variable
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
testDataFromFile = this.#getDataFromFile(dataType, contextHash);
|
|
100
|
+
|
|
101
|
+
if (testDataFromFile.length) {
|
|
102
|
+
return testDataFromFile;
|
|
103
|
+
}
|
|
104
|
+
// debug(`No "${dataType}" data for context "${contextHash}" in both file and global variable`);
|
|
105
|
+
|
|
106
|
+
// in case no data found for context
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @param {'log' | 'artifact' | 'keyvalue' | 'links'} dataType
|
|
112
|
+
* @param {string} context
|
|
113
|
+
* @returns aray of data (any type)
|
|
114
|
+
*/
|
|
115
|
+
#getDataFromGlobalVar(dataType, context) {
|
|
116
|
+
try {
|
|
117
|
+
if (global?.testomatioDataStore[dataType]) {
|
|
118
|
+
const testData = global.testomatioDataStore[dataType][context];
|
|
119
|
+
if (testData) debug('<=', dataType, 'global', context, testData);
|
|
120
|
+
return testData || [];
|
|
121
|
+
}
|
|
122
|
+
// debug(`No ${this.dataType} data for context ${context} in <global> storage`);
|
|
123
|
+
return [];
|
|
124
|
+
} catch (e) {
|
|
125
|
+
// there could be no data, ignore
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* @param {'log' | 'artifact' | 'keyvalue' | 'links'} dataType
|
|
131
|
+
* @param {*} context
|
|
132
|
+
* @returns array of data (any type)
|
|
133
|
+
*/
|
|
134
|
+
#getDataFromFile(dataType, context) {
|
|
135
|
+
const dataDirPath = path.join(TESTOMAT_TMP_STORAGE_DIR, dataType);
|
|
136
|
+
try {
|
|
137
|
+
const filepath = join(dataDirPath, `${dataType}_${context}`);
|
|
138
|
+
if (fs.existsSync(filepath)) {
|
|
139
|
+
const testDataAsText = fs.readFileSync(filepath, 'utf-8');
|
|
140
|
+
if (testDataAsText) debug('<=', dataType, 'file', context, testDataAsText);
|
|
141
|
+
const testDataArr = testDataAsText?.split(os.EOL) || [];
|
|
142
|
+
debug('<=', dataType, 'file', context, testDataArr);
|
|
143
|
+
return testDataArr;
|
|
144
|
+
}
|
|
145
|
+
// debug(`No ${this.dataType} data for ${context} in <file> storage`);
|
|
146
|
+
return [];
|
|
147
|
+
} catch (e) {
|
|
148
|
+
// there could be no data, ignore
|
|
149
|
+
}
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Puts data to global variable. Unlike the file storage, stores data in array (file storage just append as string).
|
|
155
|
+
* @param {'log' | 'artifact' | 'keyvalue' | 'links'} dataType
|
|
156
|
+
* @param {*} data
|
|
157
|
+
* @param {*} context
|
|
158
|
+
*/
|
|
159
|
+
#putDataToGlobalVar(dataType, data, context) {
|
|
160
|
+
debug('=>', dataType, 'global', context, data);
|
|
161
|
+
if (!global.testomatioDataStore) global.testomatioDataStore = {};
|
|
162
|
+
if (!global.testomatioDataStore?.[dataType]) global.testomatioDataStore[dataType] = {};
|
|
163
|
+
|
|
164
|
+
if (!global.testomatioDataStore?.[dataType][context]) global.testomatioDataStore[dataType][context] = [];
|
|
165
|
+
global.testomatioDataStore[dataType][context].push(data);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Puts data to file. Unlike the global variable storage, stores data as string
|
|
170
|
+
* @param {'log' | 'artifact' | 'keyvalue' | 'links'} dataType
|
|
171
|
+
* @param {*} data
|
|
172
|
+
* @param {string} context
|
|
173
|
+
* @returns
|
|
174
|
+
*/
|
|
175
|
+
#putDataToFile(dataType, data, context) {
|
|
176
|
+
const dataDirPath = path.join(TESTOMAT_TMP_STORAGE_DIR, dataType);
|
|
177
|
+
if (typeof data !== 'string') data = JSON.stringify(data);
|
|
178
|
+
const filename = `${dataType}_${context}`;
|
|
179
|
+
const filepath = join(dataDirPath, filename);
|
|
180
|
+
if (!fs.existsSync(dataDirPath)) fileSystem.createDir(dataDirPath);
|
|
181
|
+
debug('=>', dataType, 'file', context, data);
|
|
182
|
+
|
|
183
|
+
// append new line if file already exists (in this case its definitely includes some data)
|
|
184
|
+
if (fs.existsSync(filepath)) {
|
|
185
|
+
fs.appendFileSync(filepath, os.EOL + data, 'utf-8');
|
|
186
|
+
} else {
|
|
187
|
+
fs.writeFileSync(filepath, data, 'utf-8');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function stringToMD5Hash(str) {
|
|
193
|
+
const md5 = crypto.createHash('md5');
|
|
194
|
+
md5.update(str);
|
|
195
|
+
const hash = md5.digest('hex');
|
|
196
|
+
return `${process.env.runId || 'run'}_${hash}`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export const dataStorage = DataStorage.getInstance();
|
|
200
|
+
|
|
201
|
+
export { stringToMD5Hash };
|
|
202
|
+
|
|
203
|
+
// TODO: consider using fs promises instead of writeSync/appendFileSync to
|
|
204
|
+
// prevent blocking and improve performance (probably queue usage will be required)
|
package/src/helpers.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const isPlaywright = Boolean(process.env.PLAYWRIGHT_TEST || process.env.PLAYWRIGHT);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
class Adapter {
|
|
2
|
+
constructor(opts) {
|
|
3
|
+
this.opts = opts;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
getFilePath(t) {
|
|
7
|
+
return t.file;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
formatTest(t) {
|
|
11
|
+
return t;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
formatStack(t) {
|
|
15
|
+
return t.stack || '';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
formatMessage(t) {
|
|
19
|
+
return t.message;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default Adapter;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import Adapter from './adapter.js';
|
|
3
|
+
|
|
4
|
+
class CSharpAdapter extends Adapter {
|
|
5
|
+
formatTest(t) {
|
|
6
|
+
// Extract example from title if not already present
|
|
7
|
+
if (!t.example) {
|
|
8
|
+
const exampleMatch = t.title.match(/\((.*?)\)/);
|
|
9
|
+
if (exampleMatch) {
|
|
10
|
+
// Extract parameters as object with numeric keys for API
|
|
11
|
+
const params = exampleMatch[1]
|
|
12
|
+
.split(',')
|
|
13
|
+
.map(param => param.trim())
|
|
14
|
+
.filter(param => param !== '');
|
|
15
|
+
t.example = {};
|
|
16
|
+
params.forEach((param, index) => {
|
|
17
|
+
t.example[index] = param;
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Remove parameters from title to avoid duplicates in Test Suite
|
|
23
|
+
// The example field will be used for grouping on import
|
|
24
|
+
t.title = t.title.replace(/\(.*?\)/, '').trim();
|
|
25
|
+
|
|
26
|
+
const suite = t.suite_title.split('.');
|
|
27
|
+
t.suite_title = suite.pop();
|
|
28
|
+
t.file = namespaceToFileName(t.file);
|
|
29
|
+
return t;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getFilePath(t) {
|
|
33
|
+
if (!t.file) return null;
|
|
34
|
+
|
|
35
|
+
// Normalize path separators for cross-platform compatibility
|
|
36
|
+
let filePath = t.file.replace(/\\/g, '/');
|
|
37
|
+
|
|
38
|
+
// If file already has .cs extension, use it directly
|
|
39
|
+
if (filePath.endsWith('.cs')) {
|
|
40
|
+
// Make relative path if it's absolute
|
|
41
|
+
if (path.isAbsolute(filePath)) {
|
|
42
|
+
// Try to find project-relative path
|
|
43
|
+
const cwd = process.cwd().replace(/\\/g, '/');
|
|
44
|
+
if (filePath.startsWith(cwd)) {
|
|
45
|
+
filePath = path.relative(cwd, filePath).replace(/\\/g, '/');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return filePath;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Convert namespace path to file path
|
|
52
|
+
const fileName = namespaceToFileName(filePath);
|
|
53
|
+
return fileName;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default CSharpAdapter;
|
|
58
|
+
|
|
59
|
+
function namespaceToFileName(fileName) {
|
|
60
|
+
if (!fileName) return '';
|
|
61
|
+
|
|
62
|
+
// If already a .cs file path, clean it up
|
|
63
|
+
if (fileName.endsWith('.cs')) {
|
|
64
|
+
return fileName.replace(/\\/g, '/');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const fileParts = fileName.split('.');
|
|
68
|
+
fileParts[fileParts.length - 1] = fileParts[fileParts.length - 1]?.replace(/\$.*/, '');
|
|
69
|
+
return `${fileParts.join('/')}.cs`;
|
|
70
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import Adapter from './adapter.js';
|
|
2
|
+
import JavaScriptAdapter from './javascript.js';
|
|
3
|
+
import JavaAdapter from './java.js';
|
|
4
|
+
import PythonAdapter from './python.js';
|
|
5
|
+
import RubyAdapter from './ruby.js';
|
|
6
|
+
import CSharpAdapter from './csharp.js';
|
|
7
|
+
|
|
8
|
+
function AdapterFactory(lang, opts) {
|
|
9
|
+
if (lang === 'java') {
|
|
10
|
+
return new JavaAdapter(opts);
|
|
11
|
+
}
|
|
12
|
+
if (lang === 'js') {
|
|
13
|
+
return new JavaScriptAdapter(opts);
|
|
14
|
+
}
|
|
15
|
+
if (lang === 'python') {
|
|
16
|
+
return new PythonAdapter(opts);
|
|
17
|
+
}
|
|
18
|
+
if (lang === 'ruby') {
|
|
19
|
+
return new RubyAdapter(opts);
|
|
20
|
+
}
|
|
21
|
+
if (lang === 'c#' || lang === 'csharp') {
|
|
22
|
+
return new CSharpAdapter(opts);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return new Adapter(opts);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default AdapterFactory;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import Adapter from './adapter.js';
|
|
3
|
+
|
|
4
|
+
class JavaAdapter extends Adapter {
|
|
5
|
+
getFilePath(t) {
|
|
6
|
+
const fileName = namespaceToFileName(t.suite_title);
|
|
7
|
+
return this.opts.javaTests + path.sep + fileName;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
formatTest(t) {
|
|
11
|
+
const fileParts = t.suite_title.split('.');
|
|
12
|
+
|
|
13
|
+
t.file = namespaceToFileName(t.suite_title);
|
|
14
|
+
t.title = t.title.split('(')[0];
|
|
15
|
+
|
|
16
|
+
// detect params
|
|
17
|
+
const paramMatches = t.title.match(/\[(.*?)\]/g);
|
|
18
|
+
|
|
19
|
+
if (paramMatches) {
|
|
20
|
+
const params = paramMatches.map((_match, index) => `param${index + 1}`);
|
|
21
|
+
if (params.length === 1) params[0] = 'param';
|
|
22
|
+
let paramIndex = 0;
|
|
23
|
+
|
|
24
|
+
t.title = t.title.replace(/: \[(.*?)\]/g, () => {
|
|
25
|
+
if (params.length < 2) return `\${param}`;
|
|
26
|
+
const paramName = params[paramIndex] || `param${paramIndex + 1}`;
|
|
27
|
+
paramIndex++;
|
|
28
|
+
return `\${${paramName}}`;
|
|
29
|
+
});
|
|
30
|
+
const example = {};
|
|
31
|
+
paramMatches.forEach((match, index) => {
|
|
32
|
+
example[params[index]] = match.replace(/[[\]]/g, '');
|
|
33
|
+
});
|
|
34
|
+
t.example = example;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
t.suite_title = fileParts[fileParts.length - 1].replace(/\$/g, ' | ');
|
|
38
|
+
return t;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// formatStack(t) {
|
|
42
|
+
// const stack = super.formatStack(t);
|
|
43
|
+
|
|
44
|
+
// const file = t.suite_title.split('.');
|
|
45
|
+
|
|
46
|
+
// const fileLine = `at .*${file[file.length - 1]}\.java:(\\d+)` // eslint-disable-line no-useless-escape
|
|
47
|
+
// const regexp = new RegExp(fileLine,"g")
|
|
48
|
+
// return stack.replace(regexp, `${this.opts.javaTests}${path.sep}${namespaceToFileName(t.suite_title)}:$1:`);
|
|
49
|
+
// }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function namespaceToFileName(fileName) {
|
|
53
|
+
const fileParts = fileName.split('.');
|
|
54
|
+
fileParts[fileParts.length - 1] = fileParts[fileParts.length - 1]?.replace(/\$.*/, '');
|
|
55
|
+
return `${fileParts.join(path.sep)}.java`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default JavaAdapter;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import createCallsiteRecord from 'callsite-record';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import Adapter from './adapter.js';
|
|
4
|
+
|
|
5
|
+
class JavaScriptAdapter extends Adapter {
|
|
6
|
+
formatStack(t) {
|
|
7
|
+
let stack = super.formatStack(t);
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
const error = new Error(stack.split('\n')[0]);
|
|
11
|
+
error.stack = stack;
|
|
12
|
+
const record = createCallsiteRecord({
|
|
13
|
+
forError: error,
|
|
14
|
+
});
|
|
15
|
+
// @ts-ignore
|
|
16
|
+
if (record && !record.filename.startsWith('http')) {
|
|
17
|
+
stack += record.renderSync({
|
|
18
|
+
stackFilter: frame =>
|
|
19
|
+
frame.fileName?.indexOf(path.sep) > -1 &&
|
|
20
|
+
frame.fileName?.indexOf('node_modules') < 0 &&
|
|
21
|
+
frame.fileName?.indexOf('internal') < 0,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return stack;
|
|
25
|
+
} catch (err) {
|
|
26
|
+
return stack;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default JavaScriptAdapter;
|