Skip to content

Commit ebf6aac

Browse files
Feat/screenshot (#22)
* feat: add screenshot and attachments
1 parent c072797 commit ebf6aac

File tree

7 files changed

+661
-42
lines changed

7 files changed

+661
-42
lines changed

.github/workflows/main.yaml

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ on:
66
pull_request:
77
branches: [main]
88

9+
env:
10+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
11+
912
jobs:
1013
build:
1114
runs-on: ubuntu-latest
@@ -28,21 +31,17 @@ jobs:
2831
- run: npm ci
2932
- run: npm run build
3033
- run: npm run test
31-
- name: Publish Test Results summary
32-
run: npx github-actions-ctrf summary ctrf/ctrf-report.json
33-
- name: Publish CTRF Failed Test Summary Results
34-
run: npx github-actions-ctrf failed ctrf/ctrf-report.json
35-
- name: Publish CTRF Flaky Test Summary Results
36-
run: npx github-actions-ctrf flaky ctrf/ctrf-report.json
37-
- name: Publish CTRF Detailed Test Summary Results
38-
run: npx github-actions-ctrf tests ctrf/ctrf-report.json
39-
- name: Annotate failed tests
40-
run: npx github-actions-ctrf annotate ctrf/ctrf-report.json
4134
- run: npm run lint-check
4235
- run: npm run format-check
43-
44-
# - name: Upload report as artifact
45-
# uses: actions/upload-artifact@v2
46-
# with:
47-
# name: ctrf-report
48-
# path: ctrf/ctrf-report.json
36+
- name: Publish Test Report
37+
uses: ctrf-io/github-test-reporter@v1
38+
with:
39+
report-path: './ctrf/*.json'
40+
upload-artifact: true
41+
if: always()
42+
- name: Publish Test Report
43+
uses: ctrf-io/github-test-reporter@v1
44+
with:
45+
report-path: './ctrf/*.json'
46+
pull-request-report: true
47+
if: always()

README.md

Lines changed: 69 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -66,16 +66,6 @@ Explore more <a href="https://www.ctrf.io/integrations">integrations</a>
6666
}
6767
```
6868

69-
## What is CTRF?
70-
71-
CTRF is a universal JSON test report schema that addresses the lack of a standardized format for JSON test reports.
72-
73-
**Consistency Across Tools:** Different testing tools and frameworks often produce reports in varied formats. CTRF ensures a uniform structure, making it easier to understand and compare reports, regardless of the testing tool used.
74-
75-
**Language and Framework Agnostic:** It provides a universal reporting schema that works seamlessly with any programming language and testing framework.
76-
77-
**Facilitates Better Analysis:** With a standardized format, programatically analyzing test outcomes across multiple platforms becomes more straightforward.
78-
7969
## Installation
8070

8171
```bash
@@ -215,23 +205,79 @@ module.exports = defineConfig({
215205
})
216206
```
217207

208+
## Screenshots
209+
210+
Enable base-64 screenshots in your test report by setting the `screenshot` option to `true`.
211+
212+
Supports only the default [Cypress screenshot naming convention](https://docs.cypress.io/api/commands/screenshot#Naming-conventions).
213+
214+
Captures a single screenshot per test, prioritizing failed screenshots.
215+
216+
Uses the following file structure:
217+
218+
`{screenshotsFolder}/{adjustedSpecPath}/{testName} (failed).png`
219+
220+
If no failed screenshots are found, the last captured screenshot for the test is used (if following the default naming convention).
221+
222+
If no screenshots are available, the screenshot property is omitted from the report.
223+
224+
base-64 screenshots can increase the size of the report significantly.
225+
226+
Screenshot file paths are also included as attachments in the report as follows:
227+
228+
```json
229+
"attachments": [
230+
{
231+
"name": "screenshot",
232+
"contentType": "image/png",
233+
"path": "/path/to/screenshot.png"
234+
}
235+
]
236+
```
237+
238+
## Video
239+
240+
Videos are included as attachments in the report.
241+
242+
```json
243+
"attachments": [
244+
{
245+
"name": "video",
246+
"contentType": "video/mp4",
247+
"path": "/path/to/video.mp4"
248+
}
249+
]
250+
```
251+
218252
## Test Object Properties
219253

220254
The test object in the report includes the following [CTRF properties](https://ctrf.io/docs/schema/test):
221255

222-
| Name | Type | Required | Details |
223-
| ----------- | ------- | -------- | ----------------------------------------------------------------------------------- |
224-
| `name` | String | Required | The name of the test. |
225-
| `status` | String | Required | The outcome of the test. One of: `passed`, `failed`, `skipped`, `pending`, `other`. |
226-
| `duration` | Number | Required | The time taken for the test execution, in milliseconds. |
227-
| `message` | String | Optional | The failure message if the test failed. |
228-
| `trace` | String | Optional | The stack trace captured if the test failed. |
229-
| `rawStatus` | String | Optional | The original cypress status of the test before mapping to CTRF status. |
230-
| `type` | String | Optional | The type of test (e.g., `api`, `e2e`). |
231-
| `filepath` | String | Optional | The file path where the test is located in the project. |
232-
| `retries` | Number | Optional | The number of retries attempted for the test. |
233-
| `flaky` | Boolean | Optional | Indicates whether the test result is flaky. |
234-
| `browser` | String | Optional | The browser used for the test. |
256+
| Name | Type | Required | Details |
257+
| ------------- | ------- | -------- | ----------------------------------------------------------------------------------- |
258+
| `name` | String | Required | The name of the test. |
259+
| `status` | String | Required | The outcome of the test. One of: `passed`, `failed`, `skipped`, `pending`, `other`. |
260+
| `duration` | Number | Required | The time taken for the test execution, in milliseconds. |
261+
| `message` | String | Optional | The failure message if the test failed. |
262+
| `trace` | String | Optional | The stack trace captured if the test failed. |
263+
| `rawStatus` | String | Optional | The original cypress status of the test before mapping to CTRF status. |
264+
| `type` | String | Optional | The type of test (e.g., `api`, `e2e`). |
265+
| `filepath` | String | Optional | The file path where the test is located in the project. |
266+
| `retries` | Number | Optional | The number of retries attempted for the test. |
267+
| `flaky` | Boolean | Optional | Indicates whether the test result is flaky. |
268+
| `browser` | String | Optional | The browser used for the test. |
269+
| `screenshot` | String | Optional | The base-64 screenshot of the test. |
270+
| `attachments` | Array | Optional | The attachments of the test. |
271+
272+
## What is CTRF?
273+
274+
CTRF is a universal JSON test report schema that addresses the lack of a standardized format for JSON test reports.
275+
276+
**Consistency Across Tools:** Different testing tools and frameworks often produce reports in varied formats. CTRF ensures a uniform structure, making it easier to understand and compare reports, regardless of the testing tool used.
277+
278+
**Language and Framework Agnostic:** It provides a universal reporting schema that works seamlessly with any programming language and testing framework.
279+
280+
**Facilitates Better Analysis:** With a standardized format, programatically analyzing test outcomes across multiple platforms becomes more straightforward.
235281

236282
## Support Us
237283

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "cypress-ctrf-json-reporter",
3-
"version": "0.0.11",
3+
"version": "0.0.12",
44
"description": "Generate a common JSON test report for Cypress tests",
55
"main": "dist/index.js",
66
"scripts": {

src/generate-report.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import fs = require('fs')
22
import path = require('path')
33

44
import {
5+
type CtrfAttachment,
56
type CtrfEnvironment,
67
type CtrfReport,
78
type CtrfTest,
@@ -54,6 +55,7 @@ export class GenerateCtrfReport {
5455
outputFile: reporterOptions?.outputFile ?? this.defaultOutputFile,
5556
outputDir: reporterOptions?.outputDir ?? this.defaultOutputDir,
5657
minimal: reporterOptions?.minimal ?? false,
58+
screenshot: reporterOptions?.screenshot ?? false,
5759
testType: reporterOptions?.testType ?? 'e2e',
5860
appName: reporterOptions?.appName ?? undefined,
5961
appVersion: reporterOptions?.appVersion ?? undefined,
@@ -178,6 +180,18 @@ export class GenerateCtrfReport {
178180
ctrfTest.retries = attemptsLength - 1
179181
ctrfTest.flaky = isFlaky
180182
ctrfTest.browser = this.browser
183+
const screenshot = this.getScreenshot(test, cypressResults, false)
184+
if (screenshot !== undefined && typeof screenshot === 'string') {
185+
ctrfTest.screenshot = screenshot
186+
}
187+
}
188+
const attachments = this.getAttachments(test, cypressResults)
189+
if (attachments.length > 0) {
190+
if (ctrfTest.attachments !== undefined) {
191+
ctrfTest.attachments = [...ctrfTest.attachments, ...attachments]
192+
} else {
193+
ctrfTest.attachments = attachments
194+
}
181195
}
182196
this.ctrfReport.results.tests.push(ctrfTest)
183197
})
@@ -265,6 +279,107 @@ export class GenerateCtrfReport {
265279
return failureDetails
266280
}
267281

282+
private getScreenshot(
283+
test: CypressTest,
284+
results: CypressAfterSpecResults,
285+
returnAttachmentObjects: boolean = false
286+
): string | CtrfAttachment[] | undefined {
287+
if (
288+
this.reporterConfigOptions.screenshot === false &&
289+
!returnAttachmentObjects
290+
) {
291+
return undefined
292+
}
293+
294+
if (results.screenshots === undefined || results.screenshots.length === 0) {
295+
return undefined
296+
}
297+
298+
const testScreenshots = results.screenshots.filter(
299+
(screenshot) =>
300+
screenshot.path !== undefined &&
301+
screenshot.path !== '' &&
302+
screenshot.path.includes(test.title.join(' -- '))
303+
)
304+
305+
if (testScreenshots.length === 0) {
306+
return undefined
307+
}
308+
309+
if (returnAttachmentObjects) {
310+
return testScreenshots.map((screenshot) => ({
311+
name: 'screenshot',
312+
contentType: 'image/png',
313+
path: screenshot.path,
314+
}))
315+
}
316+
317+
let matchingScreenshot = testScreenshots.find((screenshot) =>
318+
screenshot.path.includes('(failed)')
319+
)
320+
321+
if (matchingScreenshot === undefined && testScreenshots.length > 0) {
322+
matchingScreenshot = testScreenshots[testScreenshots.length - 1]
323+
}
324+
325+
if (matchingScreenshot !== undefined) {
326+
try {
327+
const screenshotData = fs.readFileSync(matchingScreenshot.path, {
328+
encoding: 'base64',
329+
})
330+
return screenshotData
331+
} catch (error) {
332+
console.warn(
333+
`Error reading screenshot file ${matchingScreenshot.path}:`,
334+
error
335+
)
336+
return undefined
337+
}
338+
}
339+
340+
return undefined
341+
}
342+
343+
private getAttachments(
344+
test: CypressTest,
345+
results: CypressAfterSpecResults
346+
): CtrfAttachment[] {
347+
const attachments: CtrfAttachment[] = []
348+
349+
if (results.screenshots !== undefined && results.screenshots.length > 0) {
350+
try {
351+
const screenshots = this.getScreenshot(test, results, true)
352+
if (screenshots !== undefined && Array.isArray(screenshots)) {
353+
attachments.push(...screenshots)
354+
}
355+
} catch (error) {
356+
console.error('Error processing screenshot attachments:', error)
357+
}
358+
}
359+
360+
if (
361+
results.video !== undefined &&
362+
results.video !== null &&
363+
results.video.trim() !== ''
364+
) {
365+
try {
366+
if (fs.existsSync(results.video)) {
367+
attachments.push({
368+
name: 'video',
369+
contentType: 'video/mp4',
370+
path: results.video,
371+
})
372+
} else {
373+
console.warn(`Video file not found: ${results.video}`)
374+
}
375+
} catch (error) {
376+
console.warn('Error processing video attachment:', error)
377+
}
378+
}
379+
380+
return attachments
381+
}
382+
268383
private writeReportToFile(data: CtrfReport): void {
269384
const filePath = path.join(
270385
this.reporterConfigOptions.outputDir ?? this.defaultOutputDir,

0 commit comments

Comments
 (0)