Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
c39a1a2
feat: added artifacts upload support for jest reporter
miguelangarano Feb 19, 2026
f0d844a
feat: add artifacts upload support for junit xml (vitest sample)
miguelangarano Feb 19, 2026
8f9a729
chore: update lock file
miguelangarano Feb 19, 2026
4c035e0
chore: added unit test for artifacts convert command
miguelangarano Feb 19, 2026
212cae5
chore: added unit tests for jest reporter artifacts upload
miguelangarano Feb 19, 2026
f078d00
chore: refactor artifact functions
miguelangarano Feb 20, 2026
2df7588
chore: no artifacts on empty instance
miguelangarano Feb 20, 2026
47c80fc
chore: import artifact
miguelangarano Feb 20, 2026
f6ba824
chore: extract to function
miguelangarano Feb 20, 2026
dc9d6fa
chore: use path package
miguelangarano Feb 20, 2026
575e395
chore: remove md files
miguelangarano Feb 23, 2026
b825c4d
chore: add attachment prefix
miguelangarano Feb 23, 2026
694e597
chore: rename logs
miguelangarano Feb 23, 2026
87150c9
chore: attachments
miguelangarano Feb 23, 2026
50e5ef8
chore: extract repetitive code
miguelangarano Feb 23, 2026
927d009
chore: refactor functions
miguelangarano Feb 23, 2026
eeefc91
chore: fix stdout upload
miguelangarano Feb 23, 2026
f77eedd
chore: add more artifacts
miguelangarano Feb 23, 2026
5c87d5d
chore: fix stdout upload and add attachment sample in jest
miguelangarano Feb 23, 2026
430e2cc
chore: set stderr in junit and added vitest example
miguelangarano Feb 23, 2026
d97172f
chore: mark stderr for those logs
miguelangarano Feb 24, 2026
cb5c86c
chore: combine stdout with stderr logs
miguelangarano Feb 25, 2026
dd55724
feat: implemented multiple level artifacts support
miguelangarano Feb 26, 2026
5d1253b
chore: include support for level in log artifacts
miguelangarano Feb 26, 2026
91d92b1
chore: fix snapshot test
miguelangarano Feb 26, 2026
680e34e
chore: fix attempts artifacts
miguelangarano Feb 26, 2026
c355849
chore: add gitignore
miguelangarano Feb 26, 2026
1fdc612
chore: added artifacts helper functions
miguelangarano Feb 27, 2026
65de857
chore: fix mermaid doc
miguelangarano Feb 27, 2026
95e240e
chore: fix example
miguelangarano Feb 27, 2026
660e6cc
chore: type helper functions
miguelangarano Feb 27, 2026
b34dc04
chore: update prefix log mark
miguelangarano Feb 27, 2026
8c652ef
chore: remove file
miguelangarano Feb 27, 2026
a54907a
chore: use env variables for keys
miguelangarano Feb 27, 2026
54bdf28
chore: await for request
miguelangarano Feb 27, 2026
bb10c15
chore: remove unused function
miguelangarano Feb 27, 2026
e241443
chore: fix attempts and urls message
miguelangarano Feb 27, 2026
35280a0
chore: fix default
miguelangarano Feb 27, 2026
47758e9
chore: add comment, fix prefix
miguelangarano Feb 27, 2026
c61cafe
chore: take all attempts
miguelangarano Feb 27, 2026
90b164a
chore: restart ount attempt
miguelangarano Feb 27, 2026
19ec86c
chore: fix infer
miguelangarano Feb 27, 2026
65781f0
chore: fix docs
miguelangarano Feb 27, 2026
0dc8d52
chore: add webm
miguelangarano Feb 27, 2026
00aaae4
chore: add webm
miguelangarano Feb 27, 2026
d238b96
chore: veirfy artifact paths
miguelangarano Feb 27, 2026
63a130d
chore: use void
miguelangarano Feb 27, 2026
27fc884
chore: temp dirs
miguelangarano Feb 27, 2026
f0a9a19
chore: update readme instructions
miguelangarano Feb 27, 2026
cef0e17
chore: reuse artifacts dir
miguelangarano Feb 27, 2026
df3b165
chore: refactor long function
miguelangarano Feb 27, 2026
5e7f295
chore: extract types
miguelangarano Feb 27, 2026
4bd7c9f
chore: refactor of artifacts logic
miguelangarano Feb 27, 2026
a98af92
chore: replaced for loops
miguelangarano Feb 27, 2026
3afeee4
chore: map types with extensions
miguelangarano Feb 27, 2026
c60d595
chore: change api file name
miguelangarano Feb 27, 2026
cad0c1f
chore: use maps instead of for
miguelangarano Feb 27, 2026
9f193c9
chore: extract function
miguelangarano Feb 27, 2026
8364c9c
chore: extract and flatmap
miguelangarano Feb 27, 2026
6080123
chore: early exit
miguelangarano Feb 27, 2026
9df50c0
chore: add jsdoc for function
miguelangarano Feb 27, 2026
505689e
chore: updated comment
miguelangarano Feb 27, 2026
376d4eb
chore: refactors from jest reporter
miguelangarano Feb 27, 2026
420fddc
chore: refactor utils
miguelangarano Feb 27, 2026
9327e23
chore: dedup code
miguelangarano Feb 27, 2026
20b32ab
chore: refactor helper
miguelangarano Feb 27, 2026
6fff265
chore: move to different file
miguelangarano Feb 27, 2026
6b7a5ad
chore: updated docs
miguelangarano Feb 27, 2026
bd0922f
chore: remove stdout endpoint uploads
miguelangarano Feb 27, 2026
552200e
chore: fix vitest example
miguelangarano Mar 2, 2026
862ae5c
chore: removed function
miguelangarano Mar 2, 2026
60f4cf6
chore: remove unnecessary files
miguelangarano Mar 2, 2026
041f293
chore: update guide documents
miguelangarano Mar 2, 2026
0cc5bbd
chore: use regex patterns as constants
miguelangarano Mar 2, 2026
4da8099
chore: refactor functions
miguelangarano Mar 2, 2026
119dcda
chore: added unit tests for artifacts functions
miguelangarano Mar 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,5 @@ screenshots

# Currents reports
.currents

artifacts/
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ For a custom path for the report directory, set an absolute path to the `reportD

Set the `projectId`, `recordKey` and optionally the `ciBuildId`. Run `npx currents upload --help` for details.

Run `npm run report` or `CURRENTS_API_URL=http://localhost:1234 CURRENTS_PROJECT_ID=xxx CURRENTS_RECORD_KEY=yyy npx currents upload`
Run the "report" script after configuring it, or use the command: `CURRENTS_RECORD_KEY=<key> npm run report` or run `CURRENTS_API_URL=http://localhost:1234 CURRENTS_PROJECT_ID=xxx CURRENTS_RECORD_KEY=yyy npx currents upload`

To enable the debug mode, prefix the command with `DEBUG=currents,currents:*` or use the `--debug` option.

Expand Down
3 changes: 2 additions & 1 deletion examples/jest/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/** @type {import('jest').Config} */
const config = {
reporters: ['default', ['@currents/jest', {}]],
verbose: false,
reporters: [['@currents/jest', {}], 'default'],
projects: [
{
displayName: 'spec',
Expand Down
2 changes: 1 addition & 1 deletion examples/jest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"private": true,
"scripts": {
"test": "jest",
"report": "CURRENTS_API_URL=http://localhost:1234 currents --project-id=xW2Ijf --key=9bqJY1huXL2l3ONF",
"report": "CURRENTS_API_URL=http://localhost:1234 currents --project-id=$CURRENTS_PROJECT_ID --key=$CURRENTS_RECORD_KEY",
"build": "echo \"No build specified\" && exit 0"
},
"devDependencies": {
Expand Down
59 changes: 59 additions & 0 deletions examples/jest/src/artifacts/artifacts.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import * as fs from 'fs';
import * as path from 'path';
import { attachFile } from '@currents/jest';

const artifactsDir = path.join(__dirname, '..', '..', 'artifacts');
if (!fs.existsSync(artifactsDir)) {
fs.mkdirSync(artifactsDir, { recursive: true });
}

describe('Artifacts Test', () => {
it('should upload spec, test, and attempt level artifacts', () => {
// Prepare artifact files
const specArtifact = path.join(artifactsDir, 'spec-artifact.txt');
fs.writeFileSync(specArtifact, 'Spec level artifact content\n', 'utf8');

const testArtifact = path.join(artifactsDir, 'test-artifact.txt');
fs.writeFileSync(testArtifact, 'Test level artifact content\n', 'utf8');

const attemptArtifact = path.join(artifactsDir, 'attempt-artifact.txt');
fs.writeFileSync(attemptArtifact, 'Attempt level artifact content\n', 'utf8');

attachFile(specArtifact, undefined, 'spec');
attachFile(testArtifact, undefined, 'test');
attachFile(attemptArtifact);

expect(true).toBe(true);
});

// Jest retry support
// Jest.retryTimes(n) allows retrying failed tests.
// We can simulate failure on first attempt and success on second.
// We need to persist state between retries. Since Jest runs tests in isolation, we use a file.

it('generates artifacts for multiple attempts', () => {
const artifactsDir = path.join(__dirname, '..', '..', 'artifacts');
const attemptFile = path.join(artifactsDir, 'jest-attempt-count.txt');

let attempt = 0;
if (fs.existsSync(attemptFile)) {
attempt = parseInt(fs.readFileSync(attemptFile, 'utf8'), 10);
}
attempt++;
fs.writeFileSync(attemptFile, attempt.toString(), 'utf8');

const artifactPath = path.join(artifactsDir, `jest-attempt-${attempt}.txt`);
fs.writeFileSync(artifactPath, `Jest Artifact for attempt ${attempt}`, 'utf8');

// Attach artifact using helper (default level is attempt)
attachFile(artifactPath);

if (attempt < 2) {
throw new Error('Simulated Jest failure for attempt ' + attempt);
}

expect(true).toBe(true);
});
});

jest.retryTimes(2);
84 changes: 84 additions & 0 deletions examples/jest/src/artifacts/media.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@

const fs = require('fs');
const path = require('path');
const { attachVideo, attachScreenshot } = require('@currents/jest');

describe('Media Artifacts Test', () => {
const artifactsDir = path.join(__dirname, '..', '..', 'artifacts');

beforeAll(() => {
if (!fs.existsSync(artifactsDir)) {
fs.mkdirSync(artifactsDir, { recursive: true });
}
});

it('should generate a video artifact', () => {
const videoPath = path.join(artifactsDir, 'test-video.mp4');
// A minimal valid MP4 file (H.264)
const validMp4Base64 = 'AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAAAZtZGF0AAAC6W1vb3YAAABsbXZoZAAAAAAAAAAAAAAAAAAAA+gAAAPoAAEAAAEAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAABZdHJhawAAAFx0a2hkAAAAAwAAAAAAAAAAAAAAAQAAAAAAAAPoAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAIAAAACAAAAAAAJGVkdHMAAAAcZWxzdAAAAAAAAAABAAAD6AAAAAAAAQAAAAABTmdkaWEAAAAgbWRoZAAAAAAAAAAAAAAAAAAAQAAAAEAAVcQAAAAAAC1oZGxyAAAAAAAAAAB2aWRlAAAAAAAAAAAAAAAAVmlkZW9IYW5kbGVyAAAAAVxtaW5mAAAAFHZtaGQAAAABAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADcc3RibAAAALhzdHNkAAAAAAAAAAEAAACQbXA0dgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAIAAgASAAAAEgAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABj//wAAAFJlc2RzAAAAAANEAAEABDwgEQAAAAADDUAAAAAABS0AAAGwAQAAAgAAAA0AAAAAAAAAAAAAAgAAAA9hdmNDAWQACv/hABhnZAAKrNlCjfkhAAADAAEAAAMAAg8SJZYBAAZo6+JLIsAAAAAYc3R0cwAAAAAAAAABAAAAAQAAQAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAEAAAABAAAAFHN0c3oAAAAAAAAC5QAAAAEAAAAUc3RjbwAAAAAAAAABAAAAMAAAAGJ1ZHRhAAAAWm1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAALWlsc3QAAAAlqXRvbwAAAB1kYXRhAAAAAQAAAABMYXZmNTguMTIuMTAw';
const videoBuffer = Buffer.from(validMp4Base64, 'base64');
fs.writeFileSync(videoPath, videoBuffer);

attachVideo(videoPath);

expect(fs.existsSync(videoPath)).toBe(true);
});

it('should generate a screenshot artifact', () => {
const screenshotPath = path.join(artifactsDir, 'test-screenshot.bmp');

// Generate a simple 100x100 red BMP image
const width = 100;
const height = 100;
const headerSize = 54;
const rowPadding = (4 - (width * 3) % 4) % 4;
const pixelArraySize = (width * 3 + rowPadding) * height;
const fileSize = headerSize + pixelArraySize;

const buffer = Buffer.alloc(fileSize);

// BMP Header
buffer.write('BM', 0); // Signature
buffer.writeUInt32LE(fileSize, 2); // File size
buffer.writeUInt32LE(0, 6); // Reserved
buffer.writeUInt32LE(headerSize, 10); // Offset to pixel array

// DIB Header
buffer.writeUInt32LE(40, 14); // DIB Header size
buffer.writeInt32LE(width, 18); // Width
buffer.writeInt32LE(height, 22); // Height
buffer.writeUInt16LE(1, 26); // Planes
buffer.writeUInt16LE(24, 28); // Bits per pixel (RGB)
buffer.writeUInt32LE(0, 30); // Compression (BI_RGB)
buffer.writeUInt32LE(pixelArraySize, 34); // Image size
buffer.writeInt32LE(2835, 38); // X pixels per meter
buffer.writeInt32LE(2835, 42); // Y pixels per meter
buffer.writeUInt32LE(0, 46); // Colors used
buffer.writeUInt32LE(0, 50); // Important colors

// Pixel Array (Bottom-up, BGR format)
let offset = headerSize;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
// Generate a gradient pattern: Red varies with X, Green varies with Y, Blue is constant
const b = 50;
const g = Math.floor((y / height) * 255);
const r = Math.floor((x / width) * 255);

buffer.writeUInt8(b, offset++); // Blue
buffer.writeUInt8(g, offset++); // Green
buffer.writeUInt8(r, offset++); // Red
}
// Padding for 4-byte alignment
for (let p = 0; p < rowPadding; p++) {
buffer.writeUInt8(0, offset++);
}
}

fs.writeFileSync(screenshotPath, buffer);

attachScreenshot(screenshotPath);

expect(fs.existsSync(screenshotPath)).toBe(true);
});
});
2 changes: 1 addition & 1 deletion examples/node-test-reporter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"test": "node --test --test-reporter @currents/node-test-reporter --test-reporter-destination=./report.xml **.test.mjs || true && echo '✅ Script executed successfully'",
"test-2": "node --test --test-reporter @currents/node-test-reporter --test-reporter-destination=./report.xml **.test.mjs || true && echo '✅ Script executed successfully'",
"convert": "npx currents convert --input-format=junit --input-file=./report.xml --framework=node",
"report": "CURRENTS_API_URL=https://cy-staging.currents.dev currents upload",
"report": "CURRENTS_API_URL=http://localhost:1234 currents --project-id=$CURRENTS_PROJECT_ID --key=$CURRENTS_RECORD_KEY",
"build": "echo \"No build specified\" && exit 0"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion examples/postman/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"private": true,
"scripts": {
"convert": "npx currents convert --input-format=junit --input-file=tests.xml --framework=postman",
"report": "CURRENTS_API_URL=http://localhost:1234 currents --project-id=oB2sOh --key=X7BlDKpmGvrwQoOh",
"report": "CURRENTS_API_URL=http://localhost:1234 currents --project-id=$CURRENTS_PROJECT_ID --key=$CURRENTS_RECORD_KEY",
"build": "echo \"No build specified\" && exit 0"
},
"devDependencies": {
Expand Down
181 changes: 181 additions & 0 deletions examples/vitest/artifacts.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import { attachArtifact } from '@currents/cmd/helpers';
import fs from 'node:fs';
import { join } from 'node:path';
import { beforeAll, describe, expect, it } from 'vitest';

describe('Vitest JUnit artifacts', () => {
const artifactsDir = join(process.cwd(), 'artifacts');

beforeAll(() => {
if (!fs.existsSync(artifactsDir)) {
fs.mkdirSync(artifactsDir, { recursive: true });
}
// Reset attempt counter for retry test
const attemptFile = join(artifactsDir, 'attempt-count.txt');
if (fs.existsSync(attemptFile)) {
fs.unlinkSync(attemptFile);
}
});

it('generates stdout and stderr', () => {
console.log('This is a stdout message from the test');
console.error('This is a stderr message from the test');
expect(true).toBe(true);
});

it('generates spec, test, and attempt level artifacts via JSON logs', () => {
// Prepare artifact files
const specArtifact = join(artifactsDir, 'spec-artifact.json.txt');
fs.writeFileSync(
specArtifact,
'Spec level artifact content (JSON)\n',
'utf8'
);

const testArtifact = join(artifactsDir, 'test-artifact.json.txt');
fs.writeFileSync(
testArtifact,
'Test level artifact content (JSON)\n',
'utf8'
);

const attemptArtifact = join(artifactsDir, 'attempt-artifact.json.txt');
fs.writeFileSync(
attemptArtifact,
'Attempt level artifact content (JSON)\n',
'utf8'
);

// 1. Spec Level Artifact via JSON
attachArtifact(specArtifact, 'attachment', 'text/plain', 'spec');

// 2. Test Level Artifact via JSON
attachArtifact(testArtifact, 'attachment', 'text/plain', 'test');

// 3. Attempt Level Artifact via JSON
attachArtifact(attemptArtifact, 'attachment', 'text/plain', 'attempt');

expect(true).toBe(true);
});

it('generates artifacts via [[CURRENTS.ATTACHMENT]] tag with explicit levels', () => {
const specPath = join(artifactsDir, 'spec-attachment.txt');
fs.writeFileSync(specPath, 'Spec attachment', 'utf8');

const testPath = join(artifactsDir, 'test-attachment.txt');
fs.writeFileSync(testPath, 'Test attachment', 'utf8');

const attemptPath = join(artifactsDir, 'attempt-attachment.txt');
fs.writeFileSync(attemptPath, 'Attempt attachment', 'utf8');

// [[CURRENTS.ATTACHMENT|path|level]]
console.log(`[[CURRENTS.ATTACHMENT|${specPath}|spec]]`);
console.log(`[[CURRENTS.ATTACHMENT|${testPath}|test]]`);
console.log(`[[CURRENTS.ATTACHMENT|${attemptPath}|attempt]]`);

// Default is attempt
const defaultPath = join(artifactsDir, 'default-attachment.txt');
fs.writeFileSync(defaultPath, 'Default attachment', 'utf8');
console.log(`[[CURRENTS.ATTACHMENT|${defaultPath}]]`);

expect(true).toBe(true);
});

it('attaches log files as artifacts', () => {
const logPath = join(artifactsDir, 'app.log');
fs.writeFileSync(
logPath,
'2024-01-01 12:00:00 [INFO] Application started\n2024-01-01 12:00:01 [WARN] Low memory',
'utf8'
);

// Attach as a generic file/log
attachArtifact(logPath, 'attachment', 'text/plain', 'test');

expect(true).toBe(true);
});

it('generates a video artifact and logs attachment marker', () => {
const videoPath = join(artifactsDir, 'vitest-video.mp4');
const validMp4Base64 =
'AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAAAZtZGF0AAAC6W1vb3YAAABsbXZoZAAAAAAAAAAAAAAAAAAAA+gAAAPoAAEAAAEAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAABZdHJhawAAAFx0a2hkAAAAAwAAAAAAAAAAAAAAAQAAAAAAAAPoAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAIAAAACAAAAAAAJGVkdHMAAAAcZWxzdAAAAAAAAAABAAAD6AAAAAAAAQAAAAABTmdkaWEAAAAgbWRoZAAAAAAAAAAAAAAAAAAAQAAAAEAAVcQAAAAAAC1oZGxyAAAAAAAAAAB2aWRlAAAAAAAAAAAAAAAAVmlkZW9IYW5kbGVyAAAAAVxtaW5mAAAAFHZtaGQAAAABAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADcc3RibAAAALhzdHNkAAAAAAAAAAEAAACQbXA0dgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAIAAgASAAAAEgAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABj//wAAAFJlc2RzAAAAAANEAAEABDwgEQAAAAADDUAAAAAABS0AAAGwAQAAAgAAAA0AAAAAAAAAAAAAAgAAAA9hdmNDAWQACv/hABhnZAAKrNlCjfkhAAADAAEAAAMAAg8SJZYBAAZo6+JLIsAAAAAYc3R0cwAAAAAAAAABAAAAAQAAQAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAEAAAABAAAAFHN0c3oAAAAAAAAC5QAAAAEAAAAUc3RjbwAAAAAAAAABAAAAMAAAAGJ1ZHRhAAAAWm1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAALWlsc3QAAAAlqXRvbwAAAB1kYXRhAAAAAQAAAABMYXZmNTguMTIuMTAw';
const videoBuffer = Buffer.from(validMp4Base64, 'base64');
fs.writeFileSync(videoPath, videoBuffer);
attachArtifact(videoPath, 'video', 'video/mp4', 'test');
expect(fs.existsSync(videoPath)).toBe(true);
});

it('generates a screenshot artifact and logs attachment marker', () => {
const screenshotPath = join(artifactsDir, 'vitest-screenshot.bmp');

const width = 100;
const height = 100;
const headerSize = 54;
const rowPadding = (4 - ((width * 3) % 4)) % 4;
const pixelArraySize = (width * 3 + rowPadding) * height;
const fileSize = headerSize + pixelArraySize;

const buffer = Buffer.alloc(fileSize);

buffer.write('BM', 0);
buffer.writeUInt32LE(fileSize, 2);
buffer.writeUInt32LE(0, 6);
buffer.writeUInt32LE(headerSize, 10);

buffer.writeUInt32LE(40, 14);
buffer.writeInt32LE(width, 18);
buffer.writeInt32LE(height, 22);
buffer.writeUInt16LE(1, 26);
buffer.writeUInt16LE(24, 28);
buffer.writeUInt32LE(0, 30);
buffer.writeUInt32LE(pixelArraySize, 34);
buffer.writeInt32LE(2835, 38);
buffer.writeInt32LE(2835, 42);
buffer.writeUInt32LE(0, 46);
buffer.writeUInt32LE(0, 50);

let offset = headerSize;
for (let y = 0; y < height; y += 1) {
for (let x = 0; x < width; x += 1) {
const b = 50;
const g = Math.floor((y / height) * 255);
const r = Math.floor((x / width) * 255);

buffer.writeUInt8(b, offset);
buffer.writeUInt8(g, offset + 1);
buffer.writeUInt8(r, offset + 2);
offset += 3;
}

for (let p = 0; p < rowPadding; p += 1) {
buffer.writeUInt8(0, offset);
offset += 1;
}
}

fs.writeFileSync(screenshotPath, buffer);
attachArtifact(screenshotPath, 'screenshot', 'image/bmp', 'test');

expect(fs.existsSync(screenshotPath)).toBe(true);
});

it('generates a PNG screenshot artifact and logs attachment marker', () => {
const pngPath = join(artifactsDir, 'vitest-screenshot.png');
// Minimal 1x1 red PNG
const minimalPngBase64 =
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==';
fs.writeFileSync(pngPath, Buffer.from(minimalPngBase64, 'base64'));
attachArtifact(pngPath, 'screenshot', 'image/png', 'test');
expect(fs.existsSync(pngPath)).toBe(true);
});

it('generates a JPEG screenshot artifact and logs attachment marker', () => {
const jpegPath = join(artifactsDir, 'vitest-screenshot.jpg');
// Minimal valid JPEG (tiny image)
const minimalJpegBase64 =
'/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBEQACEQADAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAT8A0n//2Q==';
fs.writeFileSync(jpegPath, Buffer.from(minimalJpegBase64, 'base64'));
attachArtifact(jpegPath, 'screenshot', 'image/jpeg', 'test');
expect(fs.existsSync(jpegPath)).toBe(true);
});
});
20 changes: 20 additions & 0 deletions examples/vitest/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "examples-vitest",
"version": "0.0.0",
"description": "",
"private": true,
"scripts": {
"test": "vitest --reporter=default --reporter=junit --outputFile=./junit.xml || true",
"convert": "npx currents convert --input-format=junit --input-file=./junit.xml --framework=vitest",
"report": "CURRENTS_API_URL=http://localhost:1234 currents --project-id=$CURRENTS_PROJECT_ID --key=$CURRENTS_RECORD_KEY",
"build": "echo \"No build specified\" && exit 0"
},
"devDependencies": {
"@currents/cmd": "*",
"vitest": "^2.1.9"
},
"keywords": [],
"author": "",
"license": "ISC"
}

Loading