Skip to main content

Test Results and Artifacts

Test Packages produce standardized results for consistent reporting across different test frameworks.

Result Types​

CTRF (Common Test Results Format)​

Standardized JSON format for test results:

{
"results": {
"tool": {
"name": "playwright"
},
"summary": {
"tests": 10,
"passed": 8,
"failed": 2,
"skipped": 0,
"pending": 0,
"other": 0,
"suites": 3,
"start": 1704900000000,
"stop": 1704900060000
},
"tests": [
{
"name": "Checkout > Guest checkout completes successfully",
"status": "passed",
"duration": 3456,
"suite": "Checkout"
},
{
"name": "Checkout > Payment fails with invalid card",
"status": "failed",
"duration": 2100,
"suite": "Checkout",
"message": "Expected payment to fail",
"trace": "at checkout.spec.js:45:10"
}
]
}
}

Blob Artifacts​

Binary files and assets:

  • Screenshots
  • Videos
  • Logs
  • HTML reports
  • Network traces

Allure Results​

Advanced reporting format (optional):

  • Test history
  • Categories
  • Attachments
  • Steps
  • Links

Configuring Results​

In Manifest​

{
"test": {
"results": {
"ctrf-json": "./test-results/ctrf.json",
"blob-dir": "./test-results/artifacts",
"allure-dir": "./allure-results"
}
}
}

Path Requirements​

  • Paths relative to package directory
  • Directories created automatically
  • Files must exist after run phase

Playwright Integration​

Test Packages primarily use Playwright for E2E testing. Here's how to configure Playwright to generate the required CTRF output:

Basic Configuration​

playwright.config.js:

module.exports = {
reporter: [
['ctrf-json', {
outputFile: './test-results/ctrf.json'
}],
['html', {
outputFolder: './test-results/artifacts/html'
}]
],
use: {
screenshot: 'only-on-failure',
video: 'retain-on-failure',
trace: 'on-first-retry'
},
outputDir: './test-results/artifacts'
};

Installing CTRF Reporter​

npm install --save-dev ctrf-playwright-reporter

Multiple Reporter Configuration​

module.exports = {
reporter: [
['ctrf-json', {
outputFile: './test-results/ctrf.json'
}],
['junit', {
outputFile: './test-results/junit.xml'
}],
['html', {
outputFolder: './test-results/html'
}],
['line'] // Console output
]
};

Blob Directory Structure​

Recommended organization:

blob-dir/
├── screenshots/
│ ├── test-1-failed.png
│ └── test-2-failed.png
├── videos/
│ ├── checkout-flow.webm
│ └── payment-flow.webm
├── traces/
│ └── trace-12345.zip
├── logs/
│ ├── console.log
│ └── network.har
└── reports/
└── html/
└── index.html

Result Collection​

How It Works​

  1. Test framework generates results
  2. QIT validates paths exist
  3. Copies CTRF JSON
  4. Copies blob directory
  5. Merges with orchestrator CTRF
  6. Generates final report

Validation​

Results are validated after run phase:

  • CTRF file must exist
  • Must be valid JSON
  • Blob directory must exist
  • Missing results = test failure

Orchestrator CTRF​

Lifecycle Commands​

QIT generates CTRF for non-test phases:

{
"name": "[globalSetup] utilities/setup: wp plugin install",
"status": "passed",
"duration": 1234,
"suite": "lifecycle"
}

Merged Output​

Final CTRF contains:

  • Orchestrator lifecycle results
  • Test package results
  • Combined summary statistics

Viewing Results​

Local Execution​

Results saved to:

./qit-results/
├── ctrf.json # Merged CTRF
├── artifacts/ # All blob artifacts
├── reports/
│ └── index.html # HTML report
└── logs/
└── execution.log # Execution details

CI Systems​

Results typically uploaded as artifacts:

GitHub Actions​

- uses: actions/upload-artifact@v3
if: always()
with:
name: test-results
path: qit-results/

GitLab CI​

artifacts:
when: always
paths:
- qit-results/
reports:
junit: qit-results/junit.xml

Allure Integration​

Configuration​

qit-test.json:

{
"test": {
"results": {
"ctrf-json": "./test-results/ctrf.json",
"blob-dir": "./test-results/artifacts",
"allure-dir": "./allure-results"
}
}
}

Generation​

With Playwright:

module.exports = {
reporter: [
['allure-playwright', {
outputFolder: './allure-results'
}]
]
};

Upload Behavior​

  • Uploaded only when tests fail
  • Requires Allure server configured
  • Provides detailed failure analysis

Custom Result Processing​

Post-Processing Hook​

qit-test.json:

{
"test": {
"phases": {
"run": ["npm test"],
"teardown": [
"node ./scripts/process-results.js"
]
}
}
}

process-results.js:

const fs = require('fs');
const ctrf = JSON.parse(
fs.readFileSync('./test-results/ctrf.json')
);

// Custom processing
console.log(`Tests run: ${ctrf.results.summary.tests}`);
console.log(`Pass rate: ${
(ctrf.results.summary.passed / ctrf.results.summary.tests * 100).toFixed(1)
}%`);

Result Formats​

CTRF Structure​

Required fields:

{
"results": {
"summary": {
"tests": 10,
"passed": 8,
"failed": 2
},
"tests": []
}
}

Optional fields:

{
"results": {
"tool": {
"name": "playwright",
"version": "1.40.0"
},
"summary": {
"skipped": 0,
"pending": 0,
"other": 0,
"suites": 3,
"start": 1704900000000,
"stop": 1704900060000
},
"tests": [
{
"suite": "Checkout",
"message": "Assertion failed",
"trace": "Stack trace",
"tags": ["smoke", "critical"],
"type": "e2e",
"filepath": "checkout.spec.js",
"retries": 1,
"flaky": false
}
],
"environment": {
"browser": "chromium",
"os": "linux"
}
}
}

Test Status Values​

  • passed: Test succeeded
  • failed: Test failed
  • skipped: Test was skipped
  • pending: Test is pending implementation
  • other: Other status

Troubleshooting Results​

CTRF Not Generated​

Check:

  • Reporter configured correctly
  • Output path matches manifest
  • Test framework supports CTRF

Solution for unsupported frameworks:

// Convert native format to CTRF
const results = convertToCtrf(nativeResults);
fs.writeFileSync('./test-results/ctrf.json',
JSON.stringify(results, null, 2)
);

Artifacts Not Collected​

Verify:

  • Blob directory path correct
  • Files generated before collection
  • Directory exists

Results Missing After Success​

Ensure:

  • Results generated even on success
  • Not cleaning results in teardown
  • Paths are relative to package

Best Practices​

1. Consistent Paths​

Always use same structure:

{
"results": {
"ctrf-json": "./test-results/ctrf.json",
"blob-dir": "./test-results/artifacts"
}
}

2. Create Directories​

In setup phase:

{
"setup": [
"mkdir -p test-results/artifacts"
]
}

3. Capture on Failure​

Configure frameworks to capture artifacts on failure:

screenshot: 'only-on-failure',
video: 'retain-on-failure'

4. Meaningful Test Names​

Good:

{
"name": "Checkout > Guest user > Completes purchase with credit card",
"suite": "Checkout"
}

Bad:

{
"name": "test1",
"suite": "tests"
}

5. Include Context​

Add helpful information:

{
"tests": [{
"name": "Payment processing",
"tags": ["payment", "stripe", "critical"],
"type": "integration",
"filepath": "payment/stripe.spec.js"
}]
}

6. Compress Large Artifacts​

{
"teardown": [
"tar -czf test-results/artifacts/traces.tar.gz test-results/traces/",
"rm -rf test-results/traces/"
]
}

Advanced Patterns​

Multiple Report Formats​

module.exports = {
reporter: [
['ctrf-json', { outputFile: './test-results/ctrf.json' }],
['junit', { outputFile: './test-results/junit.xml' }],
['html', { outputFolder: './test-results/html' }]
]
};

Conditional Artifacts​

const shouldRecordVideo = process.env.CI === 'true';

module.exports = {
use: {
video: shouldRecordVideo ? 'on' : 'off'
}
};

Result Aggregation​

// Aggregate results from multiple test runs
const results = [];
for (const file of resultFiles) {
const ctrf = JSON.parse(fs.readFileSync(file));
results.push(...ctrf.results.tests);
}

const aggregated = {
results: {
summary: calculateSummary(results),
tests: results
}
};

Custom CTRF Generation​

If you need to generate CTRF from Playwright test results programmatically:

class CtrfReporter {
onTestEnd(test, result) {
this.tests.push({
name: test.title,
status: result.status,
duration: result.duration,
suite: test.parent.title
});
}

onEnd() {
const ctrf = {
results: {
summary: this.calculateSummary(),
tests: this.tests
}
};
fs.writeFileSync('./test-results/ctrf.json',
JSON.stringify(ctrf, null, 2)
);
}
}