@testomatio/reporter 0.5.9 → 0.6.0
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/.eslintrc.js +2 -1
- package/.github/workflows/node.js.yml +1 -1
- package/Changelog.md +5 -0
- package/README.md +103 -0
- package/lib/adapter/cypress-plugin/index.js +16 -4
- package/lib/bin/reportXml.js +51 -0
- package/lib/client.js +1 -1
- package/lib/junit-adapter/adapter.js +25 -0
- package/lib/junit-adapter/java.js +40 -0
- package/lib/junit-adapter/javascript.js +31 -0
- package/lib/junit-adapter/python.js +42 -0
- package/lib/junit-adapter/ruby.js +11 -0
- package/lib/util.js +93 -0
- package/lib/xmlReader.js +350 -0
- package/package.json +8 -3
- package/tests/adapter/config/index.js +1 -1
- package/tests/data/artifacts/failed_test.png +0 -0
- package/tests/data/artifacts/screenshot1.png +0 -0
- package/tests/data/cli/RunCest.php +1024 -0
- package/tests/data/cli/SnapshotCest.php +97 -0
- package/tests/data/codecept.xml +150 -0
- package/tests/data/codecept2.xml +25 -0
- package/tests/data/java.xml +25 -0
- package/tests/data/junit1.xml +70 -0
- package/tests/data/minitest.xml +25 -0
- package/tests/data/phpunit.xml +65 -0
- package/tests/data/pytest.xml +13 -0
- package/tests/data/src/services/search_service_test.rb +146 -0
- package/tests/util_test.js +51 -0
- package/tests/xmlReader_test.js +232 -0
package/.eslintrc.js
CHANGED
|
@@ -11,7 +11,7 @@ module.exports = {
|
|
|
11
11
|
SharedArrayBuffer: 'readonly',
|
|
12
12
|
},
|
|
13
13
|
parserOptions: {
|
|
14
|
-
ecmaVersion:
|
|
14
|
+
ecmaVersion: 2020,
|
|
15
15
|
},
|
|
16
16
|
rules: {
|
|
17
17
|
'max-len': ['error', { code: 120 }],
|
|
@@ -25,6 +25,7 @@ module.exports = {
|
|
|
25
25
|
'no-use-before-define': 0,
|
|
26
26
|
'object-curly-newline': 0,
|
|
27
27
|
'consistent-return': 0,
|
|
28
|
+
'no-await-in-loop': 0,
|
|
28
29
|
'no-param-reassign': 0,
|
|
29
30
|
'operator-linebreak': 0,
|
|
30
31
|
'prefer-destructuring': 0,
|
package/Changelog.md
CHANGED
package/README.md
CHANGED
|
@@ -205,6 +205,109 @@ Run the following command from you project folder:
|
|
|
205
205
|
TESTOMATIO={API_KEY} npx @testomatio/reporter -c 'npx wdio wdio.conf.js'
|
|
206
206
|
```
|
|
207
207
|
|
|
208
|
+
## JUnit Reports
|
|
209
|
+
|
|
210
|
+
> **Notice** JUnit reports are supported since 0.6.0
|
|
211
|
+
|
|
212
|
+
Other frameworks and languages are supported via JUnit reports.
|
|
213
|
+
|
|
214
|
+
JUnit XML format is standard among test runners on various platforms. Testomat.io can load XML reports from test runners and create tests in a project from it. If your framework is not supported yet, generate JUnit report and upload it into Testomat.io
|
|
215
|
+
|
|
216
|
+
Testomat.io will not only create a run report but also collect all information about tests, including source code, and create tests in a system as regular importer do. The only difference that normal process of Testomat.io import does not require executing tests on import, while importing via repoter requires to have tests executed and XML report provided.
|
|
217
|
+
|
|
218
|
+
Tested Frameworks:
|
|
219
|
+
|
|
220
|
+
* JUnit (JUnit)
|
|
221
|
+
* Python (Pytest)
|
|
222
|
+
* Minitest (Ruby)
|
|
223
|
+
* PHPUnit (PHP)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
To import JUnit reports into Testomat.io **NodeJS >=14 is required**.
|
|
227
|
+
|
|
228
|
+
Package `@testomatio/reporter` should be installed:
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
npm i @testomatio/reporter@latest
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
For local development it is recommended to install @testomatio/reporter globally:
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
npm i -g @testomatio/reporter@latest
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Run your test framework and generate a JUnit report.
|
|
241
|
+
|
|
242
|
+
Then import XML report into Testomat.io
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
TESTOMATIO={API_KEY} npx report-xml "{pattern}" --lang={lang}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
* `pattern` - is a glob pattern to match all XML files from report. For instance, `"test/report/**.xml"` or just `report.xml`
|
|
249
|
+
* `--lang` option should be specified to identify source code of the project. Example: `--lang=Ruby` or `--lang=Java` or `--lang=Python`.
|
|
250
|
+
* `--java-tests` option is avaiable for Java projects, and can be set if path to tests is different then `src/test/java`. When this option is enable, `lang` option is automatically set to `java`
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
> *Notice:* All options from [Advanced Usage](#advanced-usage) are also available for JUnit reporter
|
|
254
|
+
|
|
255
|
+
Check the list of examples:
|
|
256
|
+
|
|
257
|
+
#### Example: Pytest
|
|
258
|
+
|
|
259
|
+
Run pytest tests and generate a report to `report.xml`:
|
|
260
|
+
|
|
261
|
+
```
|
|
262
|
+
pytest --junit-xml report.xml
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Import report with this command
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
TESTOMATIO={API_KEY} npx report-xml report.xml --lang=python
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
#### Example: JUnit with Maven
|
|
272
|
+
|
|
273
|
+
Run tests via Maven, make sure JUnit report was configured in `pom.xml`.
|
|
274
|
+
|
|
275
|
+
```
|
|
276
|
+
maven clean test
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Import report with this command:
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
TESTOMATIO={API_KEY} npx report-xml "target/surefire-reports/*.xml" --java-tests
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
> You can specify `--java-test` option to set a path to tests if they are located in path other than `src/test/java`
|
|
286
|
+
|
|
287
|
+
#### Example: Ruby on Rails with Minitest
|
|
288
|
+
|
|
289
|
+
```ruby
|
|
290
|
+
# test_helper.rb:
|
|
291
|
+
|
|
292
|
+
reporters = [Minitest::Reporters::DefaultReporter.new(color: true)]
|
|
293
|
+
# enable JUnit reporter
|
|
294
|
+
reporters << Minitest::Reporters::JUnitReporter.new
|
|
295
|
+
|
|
296
|
+
Minitest::Reporters.use! reporters
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Launch tests:
|
|
300
|
+
|
|
301
|
+
```
|
|
302
|
+
rails test
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Import reports from `test/reports` directory:
|
|
306
|
+
|
|
307
|
+
```
|
|
308
|
+
TESTOMATIO={API_KEY} npx report-xml "test/reports/*.xml" --lang ruby
|
|
309
|
+
```
|
|
310
|
+
|
|
208
311
|
## Advanced Usage
|
|
209
312
|
|
|
210
313
|
### Create Unmatched Tests
|
|
@@ -3,6 +3,10 @@ const { parseTest, parseSuite } = require('../../util');
|
|
|
3
3
|
const TestomatClient = require('../../client');
|
|
4
4
|
|
|
5
5
|
const testomatioReporter = on => {
|
|
6
|
+
if (!process.env.TESTOMATIO) {
|
|
7
|
+
console.log('TESTOMATIO key is empty, ignoring reports');
|
|
8
|
+
return
|
|
9
|
+
}
|
|
6
10
|
const client = new TestomatClient({ apiKey: process.env.TESTOMATIO });
|
|
7
11
|
|
|
8
12
|
on('before:run', async (run) => {
|
|
@@ -24,15 +28,23 @@ const testomatioReporter = on => {
|
|
|
24
28
|
const time = latestAttempt.duration;
|
|
25
29
|
const error = latestAttempt.error;
|
|
26
30
|
|
|
27
|
-
|
|
31
|
+
let title = test.title.pop();
|
|
32
|
+
const examples = title.match(/\(example (#\d+)\)/);
|
|
33
|
+
let example = null;
|
|
34
|
+
if (examples && examples[1]) example = { example: examples[1] };
|
|
35
|
+
title = title.replace(/\(example #\d+\)/, '').trim();
|
|
36
|
+
|
|
28
37
|
const suiteTitle = test.title.pop();
|
|
29
38
|
|
|
30
39
|
const testId = parseTest(title);
|
|
31
40
|
const suiteId = parseSuite(suiteTitle);
|
|
32
41
|
|
|
33
42
|
if (error) {
|
|
34
|
-
error.inspect = function() {
|
|
35
|
-
|
|
43
|
+
error.inspect = function() { // eslint-disable-line func-names
|
|
44
|
+
if (this && this.codeFrame) {
|
|
45
|
+
return this.codeFrame.frame;
|
|
46
|
+
}
|
|
47
|
+
return '';
|
|
36
48
|
}
|
|
37
49
|
}
|
|
38
50
|
|
|
@@ -46,7 +58,7 @@ const testomatioReporter = on => {
|
|
|
46
58
|
const state = test.state === 'passed' ? TRConstants.PASSED : TRConstants.FAILED;
|
|
47
59
|
|
|
48
60
|
addSpecTestsPromises.push(client.addTestRun(testId, state, {
|
|
49
|
-
title, time, error, files, suite_title: suiteTitle, suite_id: suiteId
|
|
61
|
+
title, time, example, error, files, suite_title: suiteTitle, suite_id: suiteId
|
|
50
62
|
}));
|
|
51
63
|
}
|
|
52
64
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const program = require("commander");
|
|
3
|
+
// const chalk = require("chalk");
|
|
4
|
+
const glob = require('glob');
|
|
5
|
+
|
|
6
|
+
const { APP_PREFIX } = require('../constants');
|
|
7
|
+
const XmlReader = require("../xmlReader");
|
|
8
|
+
|
|
9
|
+
// const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || process.env.TESTOMATIO;
|
|
10
|
+
// const title = process.env.TESTOMATIO_TITLE;
|
|
11
|
+
|
|
12
|
+
// console.log(chalk.cyan.bold(` 🤩 xmlReader by Testomat.io v${version}`));
|
|
13
|
+
|
|
14
|
+
program
|
|
15
|
+
.arguments("<pattern>")
|
|
16
|
+
.option("-d, --dir <dir>", "Project directory")
|
|
17
|
+
.option("--java-tests [java-path]", "Load Java tests from path, by default: src/test/java")
|
|
18
|
+
.option("--lang <lang>", "Language used (python, ruby, java)")
|
|
19
|
+
.action(async (pattern, opts) => {
|
|
20
|
+
if (!pattern.endsWith('.xml')) {
|
|
21
|
+
pattern += '.xml';
|
|
22
|
+
}
|
|
23
|
+
let { javaTests, lang } = opts;
|
|
24
|
+
if (javaTests === true) javaTests = 'src/test/java';
|
|
25
|
+
lang = lang?.toLowerCase();
|
|
26
|
+
const runReader = new XmlReader({ javaTests, lang });
|
|
27
|
+
const files = glob.sync(pattern, { cwd: opts.dir || process.cwd() });
|
|
28
|
+
if (!files.length) {
|
|
29
|
+
console.log(APP_PREFIX,`Report can't be created. No XML files found 😥`);
|
|
30
|
+
process.exitCode = 1;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (const file of files) {
|
|
35
|
+
console.log(APP_PREFIX,`Parsed ${file}`);
|
|
36
|
+
runReader.parse(file);
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
await runReader.createRun();
|
|
40
|
+
await runReader.uploadData();
|
|
41
|
+
} catch (err) {
|
|
42
|
+
console.log(APP_PREFIX, 'Error updating status, skipping...', err);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
if (process.argv.length < 1) {
|
|
48
|
+
program.outputHelp();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
program.parse(process.argv);
|
package/lib/client.js
CHANGED
|
@@ -234,7 +234,7 @@ class TestomatClient {
|
|
|
234
234
|
|
|
235
235
|
formatError(error, message) {
|
|
236
236
|
if (!message) message = error.message;
|
|
237
|
-
if (error.inspect) message = error.inspect();
|
|
237
|
+
if (error.inspect) message = error.inspect() || '';
|
|
238
238
|
|
|
239
239
|
let stack = `\n${chalk.bold(message)}\n`;
|
|
240
240
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
class Adapter {
|
|
2
|
+
|
|
3
|
+
constructor(opts) {
|
|
4
|
+
this.opts = opts;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
getFilePath(t) {
|
|
8
|
+
return t.file;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
formatTest(t) {
|
|
12
|
+
return t;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
formatStack(t) {
|
|
16
|
+
return t.stack || '';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
formatMessage(t) {
|
|
20
|
+
return t.message;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = Adapter;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const Adapter = require('./adapter');
|
|
3
|
+
|
|
4
|
+
class JavaAdapter extends Adapter {
|
|
5
|
+
|
|
6
|
+
getFilePath(t) {
|
|
7
|
+
const fileName = namespaceToFileName(t.suite_title)
|
|
8
|
+
return this.opts.javaTests + path.sep + fileName;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
formatTest(t) {
|
|
12
|
+
const fileParts = t.suite_title.split('.')
|
|
13
|
+
const example = t.title.match(/\[(.*)\]/)?.[1];
|
|
14
|
+
if (example) t.example = { "#": example }
|
|
15
|
+
|
|
16
|
+
t.file = namespaceToFileName(t.suite_title);
|
|
17
|
+
t.title = t.title.split('(')[0];
|
|
18
|
+
t.suite_title = fileParts[fileParts.length - 1].replace(/\$/g, ' | ')
|
|
19
|
+
return t;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
formatStack(t) {
|
|
23
|
+
const stack = super.formatStack(t);
|
|
24
|
+
|
|
25
|
+
const file = t.suite_title.split('.');
|
|
26
|
+
|
|
27
|
+
const fileLine = `at .*${file[file.length - 1]}\.java:(\\d+)` // eslint-disable-line no-useless-escape
|
|
28
|
+
const regexp = new RegExp(fileLine,"g")
|
|
29
|
+
return stack.replace(regexp, `${this.opts.javaTests}${path.sep}${namespaceToFileName(t.suite_title)}:$1:`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function namespaceToFileName(fileName) {
|
|
34
|
+
const fileParts = fileName.split('.')
|
|
35
|
+
fileParts[fileParts.length - 1] = fileParts[fileParts.length - 1]?.replace(/\$.*/, '')
|
|
36
|
+
return `${fileParts.join(path.sep) }.java`;
|
|
37
|
+
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = JavaAdapter;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const createCallsiteRecord = require('callsite-record');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const Adapter = require('./adapter');
|
|
4
|
+
|
|
5
|
+
class JavaScriptAdapter extends Adapter {
|
|
6
|
+
|
|
7
|
+
formatStack(t) {
|
|
8
|
+
let stack = super.formatStack(t);
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const error = new Error(stack.split('\n')[0])
|
|
12
|
+
error.stack = stack;
|
|
13
|
+
const record = createCallsiteRecord({
|
|
14
|
+
forError: error,
|
|
15
|
+
});
|
|
16
|
+
if (record && !record.filename.startsWith('http')) {
|
|
17
|
+
stack += record.renderSync({
|
|
18
|
+
stackFilter: frame =>
|
|
19
|
+
frame.getFileName().indexOf(path.sep) > -1 &&
|
|
20
|
+
frame.getFileName().indexOf('node_modules') < 0 &&
|
|
21
|
+
frame.getFileName().indexOf('internal') < 0,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return stack;
|
|
25
|
+
} catch (err) {
|
|
26
|
+
return stack;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = JavaScriptAdapter;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const Adapter = require('./adapter');
|
|
4
|
+
|
|
5
|
+
class PythonAdapter extends Adapter {
|
|
6
|
+
|
|
7
|
+
getFilePath(t) {
|
|
8
|
+
const fileName = namespaceToFileName(t.suite_title)
|
|
9
|
+
return fileName;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
formatTest(t) {
|
|
13
|
+
const fileParts = t.suite_title.split('.')
|
|
14
|
+
const example = t.title.match(/\[(.*)\]/)?.[1];
|
|
15
|
+
if (example) t.example = { "#": example }
|
|
16
|
+
|
|
17
|
+
t.file = namespaceToFileName(t.suite_title);
|
|
18
|
+
t.title = t.title.split('[')[0];
|
|
19
|
+
t.suite_title = fileParts[fileParts.length - 1].replace(/\$/g, ' | ')
|
|
20
|
+
return t;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
formatMessage(t) {
|
|
24
|
+
return t.message.split(' ')[0];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function namespaceToFileName(fileName) {
|
|
30
|
+
const fileParts = fileName.split('.')
|
|
31
|
+
|
|
32
|
+
while (fileParts.length > 0) {
|
|
33
|
+
const file = `${fileParts.join(path.sep) }.py`;
|
|
34
|
+
if (fs.existsSync(`${fileParts.join(path.sep) }.py`)) {
|
|
35
|
+
return file;
|
|
36
|
+
}
|
|
37
|
+
fileParts.pop();
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = PythonAdapter;
|
package/lib/util.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
const { URL } = require('url');
|
|
2
|
+
const { sep } = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const isValid = require('is-valid-path');
|
|
2
6
|
|
|
3
7
|
/**
|
|
4
8
|
* @param {String} testTitle - Test title
|
|
@@ -48,9 +52,98 @@ const isValidUrl = s => {
|
|
|
48
52
|
}
|
|
49
53
|
};
|
|
50
54
|
|
|
55
|
+
const fetchFilesFromStackTrace = (stack = '') => {
|
|
56
|
+
const files = stack.matchAll(/file:?\/(\/.*?\.(png|avi|webm|jpg|html|txt))/g);
|
|
57
|
+
return Array.from(files).map(f => f[1]).filter(f => fs.existsSync(f));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const fetchSourceCodeFromStackTrace = (stack = '') => {
|
|
61
|
+
const stackLines = stack.split('\n')
|
|
62
|
+
.filter(l => l.includes(':'))
|
|
63
|
+
// .map(l => l.match(/\[(.*?)\]/)?.[1] || l) // minitest format
|
|
64
|
+
// .map(l => l.split(':')[0])
|
|
65
|
+
.map(l => l.trim())
|
|
66
|
+
.map(l => l.split(' ').find(p => p.includes(':')))
|
|
67
|
+
.filter(l => isValid(l.split(':')[0]))
|
|
68
|
+
|
|
69
|
+
// // filter out 3rd party libs
|
|
70
|
+
.filter(l => !l.includes(`vendor${ sep}`))
|
|
71
|
+
.filter(l => !l.includes(`node_modules${ sep}`))
|
|
72
|
+
.filter(l => fs.existsSync(l.split(':')[0]))
|
|
73
|
+
.filter(l => fs.lstatSync(l.split(':')[0]).isFile())
|
|
74
|
+
|
|
75
|
+
if (!stackLines.length) return '';
|
|
76
|
+
|
|
77
|
+
const [file, line] = stackLines[0].split(':');
|
|
78
|
+
|
|
79
|
+
const prepend = 3;
|
|
80
|
+
const source = fetchSourceCode(fs.readFileSync(file).toString(), { line, prepend, limit: 7 })
|
|
81
|
+
|
|
82
|
+
return source.split('\n')
|
|
83
|
+
.map((l, i) => {
|
|
84
|
+
if (i === prepend) return `${line} > ${chalk.bold(l)}`;
|
|
85
|
+
return `${line - prepend + i} | ${l}`
|
|
86
|
+
}).join('\n')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const fetchSourceCode = (contents, opts = {}) => {
|
|
90
|
+
if (!opts.title && !opts.line) return '';
|
|
91
|
+
|
|
92
|
+
// code fragment is 20 lines
|
|
93
|
+
const limit = opts.limit || 50;
|
|
94
|
+
let lineIndex;
|
|
95
|
+
if (opts.line) lineIndex = opts.line - 1;
|
|
96
|
+
const lines = contents.split('\n')
|
|
97
|
+
|
|
98
|
+
// remove special chars from title
|
|
99
|
+
if (!lineIndex && opts.title) {
|
|
100
|
+
const title = opts.title.replace(/[([@].*/g, '')
|
|
101
|
+
lineIndex = lines.findIndex(l => l.includes(title))
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (opts.prepend) {
|
|
105
|
+
lineIndex -= opts.prepend;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (lineIndex) {
|
|
109
|
+
const result = [];
|
|
110
|
+
for (let i = lineIndex; i < (lineIndex + limit); i++) {
|
|
111
|
+
if (lines[i] === undefined) continue;
|
|
112
|
+
|
|
113
|
+
if (i > lineIndex + 2 && !opts.prepend) {
|
|
114
|
+
// annotation
|
|
115
|
+
if (opts.lang === 'php' && lines[i].trim().startsWith('#[')) break;
|
|
116
|
+
if (opts.lang === 'php' && lines[i].includes(' private function ')) break;
|
|
117
|
+
if (opts.lang === 'php' && lines[i].includes(' protected function ')) break;
|
|
118
|
+
if (opts.lang === 'php' && lines[i].includes(' public function ')) break;
|
|
119
|
+
if (opts.lang === 'python' && lines[i].trim().match(/^@\w+/)) break;
|
|
120
|
+
if (opts.lang === 'python' && lines[i].includes(' def ')) break;
|
|
121
|
+
if (opts.lang === 'ruby' && lines[i].includes(' def ')) break;
|
|
122
|
+
if (opts.lang === 'ruby' && lines[i].includes(' test ')) break;
|
|
123
|
+
if (opts.lang === 'ruby' && lines[i].includes(' it ')) break;
|
|
124
|
+
if (opts.lang === 'ruby' && lines[i].includes(' specify ')) break;
|
|
125
|
+
if (opts.lang === 'ruby' && lines[i].includes(' context ')) break;
|
|
126
|
+
if (opts.lang === 'ts' && lines[i].includes(' it(')) break;
|
|
127
|
+
if (opts.lang === 'ts' && lines[i].includes(' test(')) break;
|
|
128
|
+
if (opts.lang === 'js' && lines[i].includes(' it(')) break;
|
|
129
|
+
if (opts.lang === 'js' && lines[i].includes(' test(')) break;
|
|
130
|
+
if (opts.lang === 'java' && lines[i].trim().match(/^@\w+/)) break;
|
|
131
|
+
if (opts.lang === 'java' && lines[i].includes(' public void ')) break;
|
|
132
|
+
if (opts.lang === 'java' && lines[i].includes(' class ')) break;
|
|
133
|
+
}
|
|
134
|
+
result.push(lines[i])
|
|
135
|
+
}
|
|
136
|
+
return result.join('\n');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
}
|
|
140
|
+
|
|
51
141
|
module.exports = {
|
|
52
142
|
parseTest,
|
|
53
143
|
parseSuite,
|
|
54
144
|
ansiRegExp,
|
|
55
145
|
isValidUrl,
|
|
146
|
+
fetchSourceCode,
|
|
147
|
+
fetchSourceCodeFromStackTrace,
|
|
148
|
+
fetchFilesFromStackTrace,
|
|
56
149
|
};
|