@toptal/davinci-qa 6.0.2 → 7.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Change Log
2
2
 
3
+ ## 7.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - [#1224](https://github.com/toptal/davinci/pull/1224) [`caf0ee02`](https://github.com/toptal/davinci/commit/caf0ee027107be2acf0315deed33ef89d34f6b69) Thanks [@rafael-anachoreta](https://github.com/rafael-anachoreta)! - ---
8
+
9
+ ### Anvil reporter
10
+
11
+ - Add Anvil test mapper
12
+ - Add Anvil reporter to Cypress
13
+ - Update Jest Anvil reporter to use new mapper
14
+
3
15
  ## 6.0.2
4
16
 
5
17
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toptal/davinci-qa",
3
- "version": "6.0.2",
3
+ "version": "7.0.0",
4
4
  "description": "QA package to test your application",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -41,6 +41,7 @@
41
41
  "@types/jest": "^27.4.1",
42
42
  "babel-jest": "^27.5.1",
43
43
  "cypress": "^9.4.1",
44
+ "enhanced-resolve": "^5.9.2",
44
45
  "fs-extra": "^10.0.0",
45
46
  "glob": "^7.2.0",
46
47
  "jest": "^27.5.1",
@@ -50,7 +51,9 @@
50
51
  "jest-styled-components": "^7.0.8",
51
52
  "jsdom": "^19.0.0",
52
53
  "matchmedia-polyfill": "^0.3.2",
53
- "semver": "^7.3.2",
54
- "enhanced-resolve": "^5.9.2"
54
+ "semver": "^7.3.2"
55
+ },
56
+ "devDependencies": {
57
+ "mocha": "^9.2.2"
55
58
  }
56
59
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toptal/davinci-qa",
3
- "version": "6.0.2",
3
+ "version": "7.0.0",
4
4
  "description": "QA package to test your application",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -41,6 +41,7 @@
41
41
  "@types/jest": "^27.4.1",
42
42
  "babel-jest": "^27.5.1",
43
43
  "cypress": "^9.4.1",
44
+ "enhanced-resolve": "^5.9.2",
44
45
  "fs-extra": "^10.0.0",
45
46
  "glob": "^7.2.0",
46
47
  "jest": "^27.5.1",
@@ -50,7 +51,9 @@
50
51
  "jest-styled-components": "^7.0.8",
51
52
  "jsdom": "^19.0.0",
52
53
  "matchmedia-polyfill": "^0.3.2",
53
- "semver": "^7.3.2",
54
- "enhanced-resolve": "^5.9.2"
54
+ "semver": "^7.3.2"
55
+ },
56
+ "devDependencies": {
57
+ "mocha": "^9.2.2"
55
58
  }
56
59
  }
@@ -6,11 +6,7 @@ const {
6
6
  } = require('@toptal/davinci-cli-shared')
7
7
 
8
8
  const endToEndTestCommand = ({
9
- options: {
10
- open = false,
11
- baseUrl,
12
- ...cypressOptions
13
- } = {},
9
+ options: { open = false, baseUrl, anvilTag, ...cypressOptions } = {},
14
10
  files: testFiles = []
15
11
  }) => {
16
12
  print.green('Running end to end tests...')
@@ -19,6 +15,10 @@ const endToEndTestCommand = ({
19
15
  '@toptal/davinci-qa',
20
16
  'src/configs/cypress.config.json'
21
17
  )
18
+ const cypressReporterPath = files.getPackageFilePath(
19
+ '@toptal/davinci-qa',
20
+ 'src/reporters/cypress-anvil-reporter.js'
21
+ )
22
22
 
23
23
  const mode = open ? 'open' : 'run'
24
24
 
@@ -29,6 +29,14 @@ const endToEndTestCommand = ({
29
29
  configPath,
30
30
  ...testFiles,
31
31
  ...(baseUrl ? ['--config', `baseUrl=${baseUrl}`] : []),
32
+ ...(!open && anvilTag
33
+ ? [
34
+ '--reporter',
35
+ cypressReporterPath,
36
+ '--reporter-options',
37
+ `anvilTag=${anvilTag}`
38
+ ]
39
+ : []),
32
40
  ...convertToCLIParameters(cypressOptions)
33
41
  ])
34
42
  }
@@ -45,8 +53,13 @@ const endToEndCommandCreator = {
45
53
  name: '--open'
46
54
  },
47
55
  {
48
- label: 'opens the Cypress Test Runner',
56
+ label: "URL used as prefix for cy.visit() or cy.request() command's URL",
49
57
  name: '--baseUrl <baseUrl>'
58
+ },
59
+ {
60
+ label:
61
+ "the project name matching Anvil's expected tag to generate Anvil Reports",
62
+ name: '--anvilTag <anvilTag>'
50
63
  }
51
64
  ]
52
65
  }
@@ -0,0 +1,46 @@
1
+ const { print } = require('@toptal/davinci-cli-shared')
2
+ const fs = require('fs-extra')
3
+
4
+ const anvilMapper = ({
5
+ fileName,
6
+ lineNumber,
7
+ status,
8
+ durationInSeconds,
9
+ scenarioName,
10
+ description,
11
+ error,
12
+ backtrace,
13
+ testType,
14
+ pendingMessage,
15
+ tag
16
+ }) => ({
17
+ file_name: fileName,
18
+ line_number: lineNumber,
19
+ status: status === 'todo' ? 'pending' : status,
20
+ duration: durationInSeconds,
21
+ scenario_name: scenarioName,
22
+ description,
23
+ error,
24
+ backtrace,
25
+ test_type: testType,
26
+ pending_message: pendingMessage,
27
+ tag
28
+ })
29
+
30
+ const generateAnvilResults = (fileName, testResults) => {
31
+ const reportPath = './reports'
32
+
33
+ try {
34
+ fs.ensureDirSync(reportPath)
35
+ fs.writeJsonSync(`${reportPath}/${fileName}`, testResults)
36
+ print.green('[anvil-reporter] Anvil reports generated successfully!')
37
+ } catch (err) {
38
+ print.red('[anvil-reporter] Failed to create Anvil test results!', err)
39
+ throw err
40
+ }
41
+ }
42
+
43
+ module.exports = {
44
+ anvilMapper,
45
+ generateAnvilResults
46
+ }
@@ -0,0 +1,87 @@
1
+ const fs = require('fs-extra')
2
+ const { print } = require('@toptal/davinci-cli-shared')
3
+
4
+ const { anvilMapper, generateAnvilResults } = require('./anvil-results-helper')
5
+
6
+ describe('Anvil results helper', () => {
7
+ beforeEach(() => {
8
+ jest.resetModules()
9
+ jest.resetAllMocks()
10
+ })
11
+
12
+ describe('anvilMapper', () => {
13
+ it('outputs an Anvil-compatible object', () => {
14
+ const mappedValue = anvilMapper({
15
+ fileName:
16
+ '/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js',
17
+ backtrace: null,
18
+ description: '1 + 1 returns 2',
19
+ durationInSeconds: 0.003,
20
+ error: null,
21
+ lineNumber: 2,
22
+ pendingMessage: null,
23
+ scenarioName: 'Example test suite/1 + 1 returns 2',
24
+ status: 'passed',
25
+ tag: 'platform',
26
+ testType: 'unit'
27
+ })
28
+
29
+ expect(mappedValue).toEqual({
30
+ file_name:
31
+ '/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js',
32
+ line_number: 2,
33
+ status: 'passed',
34
+ duration: 0.003,
35
+ scenario_name: 'Example test suite/1 + 1 returns 2',
36
+ description: '1 + 1 returns 2',
37
+ error: null,
38
+ backtrace: null,
39
+ test_type: 'unit',
40
+ pending_message: null,
41
+ tag: 'platform'
42
+ })
43
+ })
44
+
45
+ it('converts "todo" status to pending', () => {
46
+ const mappedValue = anvilMapper({ status: 'todo' })
47
+
48
+ expect(mappedValue.status).toBe('pending')
49
+ })
50
+ })
51
+
52
+ describe('generateAnvilResults', () => {
53
+ it('creates a report with the data passed in', () => {
54
+ const writeJsonMock = jest.spyOn(fs, 'writeJsonSync')
55
+ const printMock = jest.spyOn(print, 'green')
56
+
57
+ generateAnvilResults('anvil_cypress_test_results.json', { test: 123 })
58
+
59
+ expect(writeJsonMock).toHaveBeenCalledWith(
60
+ './reports/anvil_cypress_test_results.json',
61
+ { test: 123 }
62
+ )
63
+ expect(printMock).toHaveBeenCalledWith(
64
+ '[anvil-reporter] Anvil reports generated successfully!'
65
+ )
66
+ })
67
+
68
+ it('throws an error when unable to create report', () => {
69
+ const mockError = new Error('My error!')
70
+
71
+ jest.spyOn(fs, 'ensureDirSync').mockImplementation(() => {
72
+ throw mockError
73
+ })
74
+ const printMock = jest.spyOn(print, 'red')
75
+
76
+ expect(() =>
77
+ generateAnvilResults('anvil_cypress_test_results.json', { test: 123 })
78
+ ).toThrow()
79
+
80
+ expect(printMock).toHaveBeenCalledTimes(1)
81
+ expect(printMock).toHaveBeenCalledWith(
82
+ '[anvil-reporter] Failed to create Anvil test results!',
83
+ mockError
84
+ )
85
+ })
86
+ })
87
+ })
@@ -0,0 +1,121 @@
1
+ const { print } = require('@toptal/davinci-cli-shared')
2
+ const { Runner, reporters } = require('mocha')
3
+
4
+ const { anvilMapper, generateAnvilResults } = require('./anvil-results-helper')
5
+
6
+ const { Spec, Base } = reporters
7
+ const {
8
+ EVENT_RUN_END,
9
+ EVENT_TEST_FAIL,
10
+ EVENT_TEST_PASS,
11
+ EVENT_TEST_PENDING,
12
+ EVENT_TEST_RETRY
13
+ } = Runner.constants
14
+
15
+ class CypressAnvilReporter extends Base {
16
+ constructor(runner, options) {
17
+ super(runner, options)
18
+ const tests = []
19
+ const joinParentTitles = obj =>
20
+ obj?.parent?.title
21
+ ? `${joinParentTitles(obj.parent)}/${obj.title}`
22
+ : obj.title
23
+
24
+ // Spec is Cypress's default reporter, but it misses retries
25
+ this._specReporter = new Spec(
26
+ runner.on(EVENT_TEST_RETRY, test => {
27
+ print.yellow(
28
+ `(Attempt ${test?.currentRetry + 1} of ${test?.retries}) ${
29
+ test?.title
30
+ }`
31
+ )
32
+ }),
33
+ options
34
+ )
35
+
36
+ const anvilTag = options?.reporterOptions?.anvilTag
37
+
38
+ if (!anvilTag) {
39
+ print.yellow(
40
+ "[anvil-reporter] --anvilTag option is missing! anvil_cypress_test_results.json won't be generated."
41
+ )
42
+ } else {
43
+ runner
44
+ .on(EVENT_TEST_PASS, test => {
45
+ tests.push(
46
+ anvilMapper({
47
+ fileName: test?.invocationDetails?.absoluteFile,
48
+ lineNumber: test?.invocationDetails?.line,
49
+ status: test?.state,
50
+ durationInSeconds: test?.duration / 1000,
51
+ scenarioName: joinParentTitles(test),
52
+ description: test?.title,
53
+ error: test?.err?.message || null,
54
+ backtrace: test?.err?.stack || null,
55
+ testType: 'integration',
56
+ pendingMessage: null,
57
+ tag: anvilTag
58
+ })
59
+ )
60
+ })
61
+ .on(EVENT_TEST_FAIL, test => {
62
+ tests.push(
63
+ anvilMapper({
64
+ fileName: test?.invocationDetails?.absoluteFile,
65
+ lineNumber: test?.invocationDetails?.line,
66
+ status: test?.state,
67
+ durationInSeconds: test?.duration / 1000,
68
+ scenarioName: joinParentTitles(test),
69
+ description: test?.title,
70
+ error: test?.err?.message || null,
71
+ backtrace: test?.err?.stack || null,
72
+ testType: 'integration',
73
+ pendingMessage: null,
74
+ tag: anvilTag
75
+ })
76
+ )
77
+ })
78
+ .on(EVENT_TEST_PENDING, test => {
79
+ tests.push(
80
+ anvilMapper({
81
+ fileName: test?.invocationDetails?.absoluteFile,
82
+ lineNumber: test?.invocationDetails?.line,
83
+ status: test?.state,
84
+ durationInSeconds: 0,
85
+ scenarioName: joinParentTitles(test),
86
+ description: test?.title,
87
+ error: test?.err?.message || null,
88
+ backtrace: test?.err?.stack || null,
89
+ testType: 'integration',
90
+ pendingMessage: null,
91
+ tag: anvilTag
92
+ })
93
+ )
94
+ })
95
+ .on(EVENT_TEST_RETRY, test => {
96
+ tests.push(
97
+ anvilMapper({
98
+ fileName: test?.invocationDetails?.absoluteFile,
99
+ lineNumber: test?.invocationDetails?.line,
100
+ status: 'retried',
101
+ durationInSeconds: test?.duration / 1000,
102
+ scenarioName: joinParentTitles(test),
103
+ description: test?.title,
104
+ error: test?.err?.message || null,
105
+ backtrace: test?.err?.stack || null,
106
+ testType: 'integration',
107
+ pendingMessage: null,
108
+ tag: anvilTag
109
+ })
110
+ )
111
+ })
112
+ .once(EVENT_RUN_END, () => {
113
+ generateAnvilResults('anvil_cypress_test_results.json', {
114
+ test_results: tests
115
+ })
116
+ })
117
+ }
118
+ }
119
+ }
120
+
121
+ module.exports = CypressAnvilReporter
@@ -0,0 +1,269 @@
1
+ /* eslint-disable jest/no-done-callback */
2
+ // If you need to extend these tests, have a look at these other reporters for inspiration:
3
+ // - https://github.com/adamgruber/mochawesome/blob/963f073fe3f4522070788a702329dd6b61014df3/test/reporter.test.js
4
+ // - https://github.com/michaelleeallen/mocha-junit-reporter/blob/3b65764449f6f3a0ec6f54da5842a2a21d788382/test/mocha-junit-reporter-spec.js#L176
5
+ const generateAnvilResultsMock = jest.fn()
6
+
7
+ jest.mock('./anvil-results-helper', () => ({
8
+ ...jest.requireActual('./anvil-results-helper'),
9
+ generateAnvilResults: generateAnvilResultsMock
10
+ }))
11
+ const { print } = require('@toptal/davinci-cli-shared')
12
+ const Mocha = require('mocha')
13
+ const { AssertionError } = require('assert')
14
+
15
+ const CypressAnvilReporter = require('./cypress-anvil-reporter')
16
+
17
+ const { Runner, Suite, Test } = Mocha
18
+
19
+ const createRunner = suite => {
20
+ const runner = new Runner(suite)
21
+
22
+ runner.stats = {
23
+ passes: 0,
24
+ duration: 0
25
+ }
26
+
27
+ return runner
28
+ }
29
+
30
+ const configureReporter = (runner, reporter, options = {}) => {
31
+ const mocha = new Mocha({
32
+ reporter: reporter,
33
+ allowUncaught: true
34
+ })
35
+
36
+ new mocha._reporter(runner, {
37
+ reporterOptions: options
38
+ })
39
+ }
40
+
41
+ describe('Cypress Anvil reporter', () => {
42
+ const rootSuiteName = 'my root suite'
43
+ const testName = 'my test'
44
+ const anvilTag = 'talent_portal'
45
+
46
+ beforeEach(() => {
47
+ jest.resetModules()
48
+ jest.resetAllMocks()
49
+ })
50
+
51
+ it('generates an Anvil-compatible report when anvilTag is set', done => {
52
+ const suite = new Suite(rootSuiteName, 'root').addTest(
53
+ new Test(testName, () => {})
54
+ )
55
+ const runner = createRunner(suite)
56
+
57
+ configureReporter(runner, CypressAnvilReporter, { anvilTag })
58
+
59
+ runner.run(failures => {
60
+ expect(failures).toBe(0)
61
+ expect(generateAnvilResultsMock).toHaveBeenCalledWith(
62
+ 'anvil_cypress_test_results.json',
63
+ {
64
+ test_results: [
65
+ {
66
+ status: 'passed',
67
+ duration: expect.any(Number),
68
+ scenario_name: `${rootSuiteName}/${testName}`,
69
+ description: testName,
70
+ error: null,
71
+ backtrace: null,
72
+ test_type: 'integration',
73
+ pending_message: null,
74
+ tag: anvilTag
75
+ }
76
+ ]
77
+ }
78
+ )
79
+
80
+ done()
81
+ })
82
+ })
83
+
84
+ it('does not generate a report if anvilTag is not specified', done => {
85
+ const printSpy = jest.spyOn(print, 'yellow')
86
+ const suite = new Suite(rootSuiteName, 'root').addTest(
87
+ new Test(testName, () => {})
88
+ )
89
+ const runner = createRunner(suite)
90
+
91
+ configureReporter(runner, CypressAnvilReporter)
92
+
93
+ runner.run(() => {
94
+ expect(printSpy).toHaveBeenCalledWith(
95
+ expect.stringContaining(
96
+ "[anvil-reporter] --anvilTag option is missing! anvil_cypress_test_results.json won't be generated."
97
+ )
98
+ )
99
+ expect(generateAnvilResultsMock).not.toHaveBeenCalled()
100
+
101
+ done()
102
+ })
103
+ })
104
+
105
+ it('returns the scenario name with all parents listed', done => {
106
+ const grandchildSuiteName = 'my grandchild suite'
107
+ const childSuiteName = 'my child suite'
108
+
109
+ const grandchildSuite = new Suite(grandchildSuiteName, 'root').addTest(
110
+ new Test(testName, () => {})
111
+ )
112
+ const childSuite = new Suite(childSuiteName, 'root').addSuite(
113
+ grandchildSuite
114
+ )
115
+
116
+ new Suite(rootSuiteName, 'root').addSuite(childSuite)
117
+
118
+ const runner = createRunner(grandchildSuite)
119
+
120
+ configureReporter(runner, CypressAnvilReporter, { anvilTag })
121
+
122
+ runner.run(failures => {
123
+ expect(failures).toBe(0)
124
+ expect(generateAnvilResultsMock).toHaveBeenCalledWith(
125
+ 'anvil_cypress_test_results.json',
126
+ {
127
+ test_results: [
128
+ {
129
+ status: 'passed',
130
+ duration: expect.any(Number),
131
+ scenario_name: `${rootSuiteName}/${childSuiteName}/${grandchildSuiteName}/${testName}`,
132
+ description: testName,
133
+ error: null,
134
+ backtrace: null,
135
+ test_type: 'integration',
136
+ pending_message: null,
137
+ tag: anvilTag
138
+ }
139
+ ]
140
+ }
141
+ )
142
+
143
+ done()
144
+ })
145
+ })
146
+
147
+ it('returns errors and backtrace when tests fail', done => {
148
+ const errorMessage = 'Expected foo, got bar'
149
+ const suite = new Suite(rootSuiteName, 'root').addTest(
150
+ new Test(testName, callback => {
151
+ callback(new AssertionError({ message: errorMessage }))
152
+ })
153
+ )
154
+ const runner = createRunner(suite)
155
+
156
+ configureReporter(runner, CypressAnvilReporter, { anvilTag })
157
+
158
+ runner.run(failures => {
159
+ expect(failures).toBe(1)
160
+ expect(generateAnvilResultsMock).toHaveBeenCalledWith(
161
+ 'anvil_cypress_test_results.json',
162
+ {
163
+ test_results: [
164
+ {
165
+ status: 'failed',
166
+ duration: expect.any(Number),
167
+ scenario_name: `${rootSuiteName}/${testName}`,
168
+ description: testName,
169
+ error: errorMessage,
170
+ backtrace: expect.stringMatching(
171
+ new RegExp(
172
+ `AssertionError.*${errorMessage}.*at new AssertionError`,
173
+ 'gms'
174
+ )
175
+ ),
176
+ test_type: 'integration',
177
+ pending_message: null,
178
+ tag: anvilTag
179
+ }
180
+ ]
181
+ }
182
+ )
183
+
184
+ done()
185
+ })
186
+ })
187
+
188
+ it('supports retried tests', done => {
189
+ const suite = new Suite(rootSuiteName, 'root').retries(2).addTest(
190
+ new Test(testName, callback => {
191
+ expect(1).toBe(2)
192
+
193
+ callback()
194
+ })
195
+ )
196
+ const runner = createRunner(suite)
197
+
198
+ configureReporter(runner, CypressAnvilReporter, { anvilTag })
199
+
200
+ runner.run(failures => {
201
+ expect(failures).toBe(1)
202
+ expect(generateAnvilResultsMock).toHaveBeenCalledWith(
203
+ 'anvil_cypress_test_results.json',
204
+ {
205
+ test_results: [
206
+ {
207
+ status: 'retried',
208
+ duration: expect.any(Number),
209
+ scenario_name: `${rootSuiteName}/${testName}`,
210
+ description: testName,
211
+ error: null,
212
+ backtrace: null,
213
+ test_type: 'integration',
214
+ pending_message: null,
215
+ tag: anvilTag
216
+ },
217
+ {
218
+ status: 'retried',
219
+ duration: expect.any(Number),
220
+ scenario_name: `${rootSuiteName}/${testName}`,
221
+ description: testName,
222
+ error: null,
223
+ backtrace: null,
224
+ test_type: 'integration',
225
+ pending_message: null,
226
+ tag: anvilTag
227
+ },
228
+ {
229
+ status: 'failed',
230
+ duration: expect.any(Number),
231
+ scenario_name: `${rootSuiteName}/${testName}`,
232
+ description: testName,
233
+ error: expect.anything(),
234
+ backtrace: expect.anything(),
235
+ test_type: 'integration',
236
+ pending_message: null,
237
+ tag: anvilTag
238
+ }
239
+ ]
240
+ }
241
+ )
242
+
243
+ done()
244
+ })
245
+ })
246
+
247
+ it('calls the anvil test result mapper', done => {
248
+ const anvilMapperSpy = jest.fn()
249
+
250
+ jest.mock('./anvil-results-helper', () => ({
251
+ anvilMapper: anvilMapperSpy,
252
+ generateAnvilResults: jest.fn()
253
+ }))
254
+ const CypressAnvilReporter = require('./cypress-anvil-reporter')
255
+
256
+ const suite = new Suite('My suite', 'root').addTest(
257
+ new Test('My test', () => {})
258
+ )
259
+ const runner = createRunner(suite)
260
+
261
+ configureReporter(runner, CypressAnvilReporter, { anvilTag })
262
+
263
+ runner.run(() => {
264
+ expect(anvilMapperSpy).toHaveBeenCalledTimes(1)
265
+
266
+ done()
267
+ })
268
+ })
269
+ })
@@ -1,52 +1,61 @@
1
1
  const { print } = require('@toptal/davinci-cli-shared')
2
- const fs = require('fs')
2
+
3
+ const { anvilMapper, generateAnvilResults } = require('./anvil-results-helper')
3
4
 
4
5
  class JestAnvilReporter {
5
- constructor(globalConfig, options) {
6
- this._globalConfig = globalConfig
7
- this._options = options
8
- }
6
+ constructor(globalConfig, options) {
7
+ this._globalConfig = globalConfig
8
+ this._options = options
9
+ }
10
+
11
+ onRunComplete(contexts, results) {
12
+ print.grey('Starting Anvil reporter processing...')
13
+ console.time('Anvil reporter run')
14
+ const fileTestResults = results.testResults
15
+ const runPath = process.cwd()
16
+ const individualTestResults = fileTestResults.flatMap(fileTestResult => {
17
+ const fileName = fileTestResult.testFilePath.replace(runPath, '.')
18
+
19
+ return fileTestResult.testResults.map(res => ({ ...res, fileName }))
20
+ })
9
21
 
10
- onRunComplete(contexts, results) {
11
- print.grey('Starting Anvil reporter processing...')
12
- console.time('Anvil reporter run')
13
- const fileTestResults = results.testResults
14
- const runPath = process.cwd()
15
- const individualTestResults = fileTestResults.flatMap(fileTestResult => {
16
- const fileName = fileTestResult.testFilePath.replace(runPath, '.')
17
- return fileTestResult.testResults.map(res => ({ ...res, fileName }))
18
- })
19
- const anvilTestResults = individualTestResults.map(testResult => {
20
- const anvilResults = {
21
- file_name: testResult.fileName,
22
- line_number: testResult.location && testResult.location.line,
23
- status: testResult.status === 'todo' ? 'pending' : testResult.status,
24
- duration: testResult.duration,
25
- scenario_name: testResult.ancestorTitles.concat(testResult.title).join('/'),
26
- description: testResult.title,
27
- error: testResult.failureDetails.length > 0 ? testResult.failureDetails[0].message : null,
28
- backtrace: testResult.failureDetails.length > 0 ? testResult.failureDetails.map(failure => failure.stack) : null,
29
- test_type: 'unit',
30
- pending_message: null
31
- }
32
- if (this._options && this._options.anvilTag) {
33
- anvilResults['tag'] = this._options.anvilTag
34
- }
35
- return anvilResults
36
- })
37
- const anvilPayload = {
38
- "test_results": [
39
- ...anvilTestResults
40
- ]
41
- }
42
- fs.writeFileSync('./anvil_test_results.json', JSON.stringify(anvilPayload, undefined, 2), err => {
43
- if (err) {
44
- print.red('Failed to create Anvil test results', err)
45
- throw err
46
- }
47
- })
48
- console.timeEnd('Anvil reporter run')
22
+ if (!this?._options?.anvilTag) {
23
+ print.yellow(
24
+ 'Missing --anvilTag value! Anvil test results will be skipped.'
25
+ )
26
+
27
+ return
28
+ }
29
+ const anvilTestResults = individualTestResults.map(testResult => {
30
+ return anvilMapper({
31
+ fileName: testResult.fileName,
32
+ lineNumber: testResult.location && testResult.location.line,
33
+ status: testResult.status,
34
+ durationInSeconds: testResult.duration / 1000,
35
+ scenarioName: testResult.ancestorTitles
36
+ .concat(testResult.title)
37
+ .join('/'),
38
+ description: testResult.title,
39
+ error:
40
+ testResult.failureDetails.length > 0
41
+ ? testResult.failureDetails[0].message
42
+ : null,
43
+ backtrace:
44
+ testResult.failureDetails.length > 0
45
+ ? testResult.failureDetails.map(failure => failure.stack)
46
+ : null,
47
+ testType: 'unit',
48
+ pendingMessage: null,
49
+ tag: this._options.anvilTag
50
+ })
51
+ })
52
+ const anvilPayload = {
53
+ test_results: [...anvilTestResults]
49
54
  }
55
+
56
+ generateAnvilResults('anvil_jest_test_results.json', anvilPayload)
57
+ console.timeEnd('Anvil reporter run')
58
+ }
50
59
  }
51
-
60
+
52
61
  module.exports = JestAnvilReporter
@@ -1,5 +1,13 @@
1
- const JestAnvilReporter = require('./jest-anvil-reporter.js')
2
- const fs = require('fs')
1
+ const generateAnvilResultsMock = jest.fn()
2
+
3
+ jest.mock('./anvil-results-helper', () => ({
4
+ ...jest.requireActual('./anvil-results-helper'),
5
+ generateAnvilResults: generateAnvilResultsMock
6
+ }))
7
+
8
+ const { print } = require('@toptal/davinci-cli-shared')
9
+
10
+ const JestAnvilReporter = require('./jest-anvil-reporter')
3
11
  const {
4
12
  passedTestResults,
5
13
  failedTestResults,
@@ -12,35 +20,40 @@ describe('Jest Anvil reporter', () => {
12
20
  const expectedPassedTestResults = {
13
21
  test_results: [
14
22
  {
15
- file_name: "/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js",
23
+ file_name:
24
+ '/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js',
16
25
  line_number: 2,
17
- status: "passed",
18
- duration: 3,
19
- scenario_name: "Example test suite/1 + 1 returns 2",
20
- description: "1 + 1 returns 2",
26
+ status: 'passed',
27
+ duration: 0.003,
28
+ scenario_name: 'Example test suite/1 + 1 returns 2',
29
+ description: '1 + 1 returns 2',
21
30
  error: null,
22
31
  backtrace: null,
23
- test_type: "unit",
32
+ test_type: 'unit',
24
33
  pending_message: null,
25
- tag: "platform"
34
+ tag: 'platform'
26
35
  }
27
36
  ]
28
37
  }
29
-
38
+
30
39
  const expectedFailedTestResults = {
31
40
  test_results: [
32
41
  {
33
- file_name: "/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js",
42
+ file_name:
43
+ '/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js',
34
44
  line_number: 2,
35
- status: "failed",
36
- duration: 6,
37
- scenario_name: "Example test suite/1 + 1 returns 3",
38
- description: "1 + 1 returns 3",
39
- error: "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m2\u001b[39m",
40
- backtrace: ["Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m2\u001b[39m\n at Object.<anonymous> (/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js:3:17)\n at Object.asyncJestTest (/davinci/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:106:37)\n at /davinci/node_modules/jest-jasmine2/build/queueRunner.js:45:12\n at new Promise (<anonymous>)\n at mapper (/davinci/node_modules/jest-jasmine2/build/queueRunner.js:28:19)\n at /davinci/node_modules/jest-jasmine2/build/queueRunner.js:75:41"],
41
- test_type: "unit",
45
+ status: 'failed',
46
+ duration: 0.006,
47
+ scenario_name: 'Example test suite/1 + 1 returns 3',
48
+ description: '1 + 1 returns 3',
49
+ error:
50
+ 'Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m2\u001b[39m',
51
+ backtrace: [
52
+ 'Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m2\u001b[39m\n at Object.<anonymous> (/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js:3:17)\n at Object.asyncJestTest (/davinci/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:106:37)\n at /davinci/node_modules/jest-jasmine2/build/queueRunner.js:45:12\n at new Promise (<anonymous>)\n at mapper (/davinci/node_modules/jest-jasmine2/build/queueRunner.js:28:19)\n at /davinci/node_modules/jest-jasmine2/build/queueRunner.js:75:41'
53
+ ],
54
+ test_type: 'unit',
42
55
  pending_message: null,
43
- tag: "platform"
56
+ tag: 'platform'
44
57
  }
45
58
  ]
46
59
  }
@@ -48,17 +61,18 @@ describe('Jest Anvil reporter', () => {
48
61
  const expectedSkippedTestResults = {
49
62
  test_results: [
50
63
  {
51
- file_name: "/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js",
64
+ file_name:
65
+ '/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js',
52
66
  line_number: 2,
53
- status: "pending",
67
+ status: 'pending',
54
68
  duration: 0,
55
- scenario_name: "Example test suite/1 + 1 returns 2",
56
- description: "1 + 1 returns 2",
69
+ scenario_name: 'Example test suite/1 + 1 returns 2',
70
+ description: '1 + 1 returns 2',
57
71
  error: null,
58
72
  backtrace: null,
59
- test_type: "unit",
73
+ test_type: 'unit',
60
74
  pending_message: null,
61
- tag: "platform"
75
+ tag: 'platform'
62
76
  }
63
77
  ]
64
78
  }
@@ -66,17 +80,18 @@ describe('Jest Anvil reporter', () => {
66
80
  const expectedToDoTestResults = {
67
81
  test_results: [
68
82
  {
69
- file_name: "/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js",
83
+ file_name:
84
+ '/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js',
70
85
  line_number: null,
71
- status: "pending",
86
+ status: 'pending',
72
87
  duration: 0,
73
- scenario_name: "Example test suite/1+1 returns 2",
74
- description: "1+1 returns 2",
88
+ scenario_name: 'Example test suite/1+1 returns 2',
89
+ description: '1+1 returns 2',
75
90
  error: null,
76
91
  backtrace: null,
77
- test_type: "unit",
92
+ test_type: 'unit',
78
93
  pending_message: null,
79
- tag: "platform"
94
+ tag: 'platform'
80
95
  }
81
96
  ]
82
97
  }
@@ -84,62 +99,125 @@ describe('Jest Anvil reporter', () => {
84
99
  const expectedCombinedTestResults = {
85
100
  test_results: [
86
101
  {
87
- file_name: "/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js",
102
+ file_name:
103
+ '/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js',
88
104
  line_number: 2,
89
- status: "passed",
90
- duration: 5,
91
- scenario_name: "Example test suite/1 + 1 returns 2 (passed)",
92
- description: "1 + 1 returns 2 (passed)",
105
+ status: 'passed',
106
+ duration: 0.005,
107
+ scenario_name: 'Example test suite/1 + 1 returns 2 (passed)',
108
+ description: '1 + 1 returns 2 (passed)',
93
109
  error: null,
94
110
  backtrace: null,
95
- test_type: "unit",
111
+ test_type: 'unit',
96
112
  pending_message: null,
97
- tag: "platform"
113
+ tag: 'platform'
98
114
  },
99
115
  {
100
- file_name: "/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js",
116
+ file_name:
117
+ '/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js',
101
118
  line_number: 6,
102
- status: "failed",
103
- duration: 7,
104
- scenario_name: "Example test suite/1 + 1 returns 3 (failed)",
105
- description: "1 + 1 returns 3 (failed)",
106
- error: "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m2\u001b[39m",
107
- backtrace: ["Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m2\u001b[39m\n at Object.<anonymous> (/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js:7:17)\n at Object.asyncJestTest (/davinci/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:106:37)\n at /davinci/node_modules/jest-jasmine2/build/queueRunner.js:45:12\n at new Promise (<anonymous>)\n at mapper (/davinci/node_modules/jest-jasmine2/build/queueRunner.js:28:19)\n at /davinci/node_modules/jest-jasmine2/build/queueRunner.js:75:41"],
108
- test_type: "unit",
119
+ status: 'failed',
120
+ duration: 0.007,
121
+ scenario_name: 'Example test suite/1 + 1 returns 3 (failed)',
122
+ description: '1 + 1 returns 3 (failed)',
123
+ error:
124
+ 'Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m2\u001b[39m',
125
+ backtrace: [
126
+ 'Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m2\u001b[39m\n at Object.<anonymous> (/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js:7:17)\n at Object.asyncJestTest (/davinci/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:106:37)\n at /davinci/node_modules/jest-jasmine2/build/queueRunner.js:45:12\n at new Promise (<anonymous>)\n at mapper (/davinci/node_modules/jest-jasmine2/build/queueRunner.js:28:19)\n at /davinci/node_modules/jest-jasmine2/build/queueRunner.js:75:41'
127
+ ],
128
+ test_type: 'unit',
109
129
  pending_message: null,
110
- tag: "platform"
130
+ tag: 'platform'
111
131
  },
112
132
  {
113
- file_name: "/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js",
133
+ file_name:
134
+ '/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js',
114
135
  line_number: 10,
115
- status: "pending",
136
+ status: 'pending',
116
137
  duration: 0,
117
- scenario_name: "Example test suite/1 + 1 returns 2 (skipped)",
118
- description: "1 + 1 returns 2 (skipped)",
138
+ scenario_name: 'Example test suite/1 + 1 returns 2 (skipped)',
139
+ description: '1 + 1 returns 2 (skipped)',
119
140
  error: null,
120
141
  backtrace: null,
121
- test_type: "unit",
142
+ test_type: 'unit',
122
143
  pending_message: null,
123
- tag: "platform"
144
+ tag: 'platform'
124
145
  }
125
146
  ]
126
147
  }
127
148
 
128
149
  beforeEach(() => {
129
- jest.clearAllMocks();
130
- });
131
-
132
- test.each`
133
- input | expectedOutput | testType
134
- ${passedTestResults} | ${expectedPassedTestResults} | ${"passing tests"}
135
- ${failedTestResults} | ${expectedFailedTestResults} | ${"failing tests"}
136
- ${skippedTestResults} | ${expectedSkippedTestResults} | ${"skipped tests"}
137
- ${toDoTestResults} | ${expectedToDoTestResults} | ${"todo tests"}
138
- ${combinedTestResults} | ${expectedCombinedTestResults} | ${"a combination of test results"}
139
- `('returns a payload matching Anvil for $testType', ({ input, expectedOutput, testType }) => {
140
- const spy = jest.spyOn(fs, 'writeFileSync')
141
- const jestAnvilReporter = new JestAnvilReporter(undefined, { anvilTag: 'platform' })
142
- jestAnvilReporter.onRunComplete(undefined, input)
143
- expect(spy).toBeCalledWith("./anvil_test_results.json", JSON.stringify(expectedOutput, undefined, 2), expect.anything())
150
+ jest.resetModules()
151
+ jest.resetAllMocks()
152
+ })
153
+
154
+ it.each`
155
+ input | expectedOutput | testType
156
+ ${passedTestResults} | ${expectedPassedTestResults} | ${'passing tests'}
157
+ ${failedTestResults} | ${expectedFailedTestResults} | ${'failing tests'}
158
+ ${skippedTestResults} | ${expectedSkippedTestResults} | ${'skipped tests'}
159
+ ${toDoTestResults} | ${expectedToDoTestResults} | ${'todo tests'}
160
+ ${combinedTestResults} | ${expectedCombinedTestResults} | ${'a combination of test results'}
161
+ `(
162
+ 'calls generateAnvilResults with a payload matching Anvil for $testType',
163
+ /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
164
+ ({ input, expectedOutput, testType }) => {
165
+ const jestAnvilReporter = new JestAnvilReporter(undefined, {
166
+ anvilTag: 'platform'
167
+ })
168
+
169
+ jestAnvilReporter.onRunComplete(undefined, input)
170
+
171
+ expect(generateAnvilResultsMock).toHaveBeenCalledWith(
172
+ 'anvil_jest_test_results.json',
173
+ expectedOutput
174
+ )
175
+ }
176
+ )
177
+
178
+ it('prints warning and exits when anvilTag is not defined', () => {
179
+ const printSpy = jest.spyOn(print, 'yellow')
180
+ const jestAnvilReporter = new JestAnvilReporter(undefined, {
181
+ anvilTag: undefined
182
+ })
183
+
184
+ jestAnvilReporter.onRunComplete(undefined, passedTestResults)
185
+
186
+ expect(printSpy).toHaveBeenCalledWith(
187
+ 'Missing --anvilTag value! Anvil test results will be skipped.'
188
+ )
189
+ expect(generateAnvilResultsMock).not.toHaveBeenCalled()
190
+ })
191
+
192
+ it('calls the anvil mapper', () => {
193
+ const anvilMapperSpy = jest.fn()
194
+
195
+ jest.mock('./anvil-results-helper', () => ({
196
+ anvilMapper: anvilMapperSpy,
197
+ generateAnvilResults: jest.fn()
198
+ }))
199
+ const JestAnvilReporter = require('./jest-anvil-reporter')
200
+
201
+ const jestAnvilReporter = new JestAnvilReporter(undefined, {
202
+ anvilTag: 'platform'
203
+ })
204
+
205
+ jestAnvilReporter.onRunComplete(undefined, passedTestResults)
206
+
207
+ expect(anvilMapperSpy).toHaveBeenCalledWith({
208
+ backtrace: null,
209
+ description: '1 + 1 returns 2',
210
+ durationInSeconds: expect.any(Number),
211
+ error: null,
212
+ fileName: expect.stringContaining(
213
+ '/davinci/packages/qa/src/reporters/jest-anvil-reporter-mocker.test.js'
214
+ ),
215
+ lineNumber: expect.any(Number),
216
+ pendingMessage: null,
217
+ scenarioName: 'Example test suite/1 + 1 returns 2',
218
+ status: 'passed',
219
+ tag: 'platform',
220
+ testType: 'unit'
221
+ })
144
222
  })
145
223
  })