Snapline Integration
Push test reports from Snapline for Node.js and Python into Hub. Covers programmatic APIs, environment variables, CLI flags, and CI pipelines.
Overview
Snapline produces TestSuiteResult objects from every test run. To send data to Hub:
- Collect one or more
TestSuiteResultvalues - Build a
TestRunReportwithbuildReport()/build_report() - Push with
pushTestReportToHub()/push_test_report_to_hub()
You can also write a local JSON file and push to Hub in the same run — they are independent.
Node.js integration
Package: @vaagatech/snapline-core
Exports
| Function | Purpose |
|---|---|
buildReport(suites, meta?) | Build TestRunReport from suite results |
pushTestReportToHub(report, config) | POST report to Hub API |
resolveHubConfig(argv?) | Read SNAPLINE_HUB_URL / CLI flags |
writeTestReport(suites, config, meta?) | Write local file (optional, independent of Hub) |
resolveReportConfig(argv?) | Read REPORT_FORMAT / CLI flags for local files |
Basic push
import {
testSuite,
buildReport,
pushTestReportToHub,
} from '@vaagatech/snapline-core';
const startedAt = Date.now();
const result = await testSuite('User sync', {
baseUrl: process.env.API_BASE_URL,
api: {
endpoint: '/api/v1/users/me',
method: 'GET',
expectedFile: './fixtures/expected.json',
},
});
const report = buildReport([result], {
durationMs: Date.now() - startedAt,
environment: {
baseUrl: process.env.API_BASE_URL ?? '',
branch: process.env.CI_BRANCH ?? 'local',
},
});
await pushTestReportToHub(report, {
hubUrl: 'http://localhost:3847',
label: 'User sync — staging',
});
process.exitCode = result.passed ? 0 : 1;
Fixture cases
import {
fixturesDir,
runApiFixtureCases,
buildReport,
pushTestReportToHub,
} from '@vaagatech/snapline-core';
const startedAt = Date.now();
const result = await runApiFixtureCases({
suiteName: 'Customer account — GraphQL cases',
fixturesRoot: fixturesDir(import.meta.url),
baseUrl: process.env.API_BASE_URL,
defaults: { endpoint: '/graphql', protocol: 'graphql' },
});
const report = buildReport([result], {
durationMs: Date.now() - startedAt,
environment: { baseUrl: process.env.API_BASE_URL ?? '' },
});
await pushTestReportToHub(report, {
hubUrl: process.env.SNAPLINE_HUB_URL!,
label: 'GraphQL fixture suite',
});
Environment variables (Node.js)
| Variable | Description |
|---|---|
SNAPLINE_HUB_URL | Hub base URL, e.g. http://localhost:3847 or https://hub.yourcompany.com |
SNAPLINE_HUB_LABEL | Optional label shown in the Hub UI |
SNAPLINE_HUB_PROJECT | Logical project grouping (e.g. snapline-demo, my-app) |
SNAPLINE_HUB_TAGS | Comma-separated tags (e.g. node,demo,ci) |
CLI flags (Node.js)
| Flag | Description |
|---|---|
--hub-url=<url> | Hub base URL (overrides env) |
--hub-label=<label> | Run label (overrides env) |
node tests/run.mjs --hub-url=http://localhost:3847 --hub-label="PR #42"
Using resolveHubConfig
import { buildReport, pushTestReportToHub, resolveHubConfig } from '@vaagatech/snapline-core';
const report = buildReport(results, { durationMs });
const hubConfig = resolveHubConfig();
if (hubConfig) {
const { url } = await pushTestReportToHub(report, hubConfig);
console.log(`Report pushed to ${url}`);
}
Full demo runner
The Snapline monorepo demo automatically pushes when SNAPLINE_HUB_URL is set:
cd snapline
npm install
SNAPLINE_HUB_URL=http://localhost:3847 npm run demo
Pushes all 21 scenario results as one report labeled Full integration demo.
Python integration
Package: snapline-core (import snapline.core)
Exports
| Function | Node.js equivalent |
|---|---|
build_report(suites, meta=None) | buildReport() |
push_test_report_to_hub(report, ...) | pushTestReportToHub() |
resolve_hub_config(argv=None) | resolveHubConfig() |
write_test_report(suites, config, meta=None) | writeTestReport() |
Basic push
import asyncio
import time
from snapline.core import test_suite, build_report, push_test_report_to_hub
async def main():
started = time.time() * 1000
result = await test_suite("User sync", {
"baseUrl": "https://api.example.com",
"api": {
"endpoint": "/api/v1/users/me",
"method": "GET",
"expectedFile": "fixtures/expected.json",
},
})
report = build_report([result], {
"durationMs": int(time.time() * 1000 - started),
"environment": {"baseUrl": "https://api.example.com"},
})
push_test_report_to_hub(
report,
hub_url="http://localhost:3847",
label="User sync — staging",
)
asyncio.run(main())
Using resolve_hub_config
from snapline.core import build_report, push_test_report_to_hub, resolve_hub_config
report = build_report(results, {"durationMs": duration_ms})
hub_config = resolve_hub_config()
if hub_config:
result = push_test_report_to_hub(report, config=hub_config)
print(f"Report pushed to {result['url']}")
Environment variables (Python)
Same as Node.js: SNAPLINE_HUB_URL and SNAPLINE_HUB_LABEL.
SNAPLINE_HUB_URL=http://localhost:3847 uv run demo
CLI flags (Python)
python run_demo.py --hub-url=http://localhost:3847 --hub-label="nightly"
CI integration
Typical pattern: run tests in CI, push report to a persistent Hub instance, fail the job if tests failed.
GitHub Actions (Node.js)
jobs:
integration-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- name: Run integration tests
env:
API_BASE_URL: ${{ secrets.STAGING_API_URL }}
SNAPLINE_HUB_URL: ${{ vars.SNAPLINE_HUB_URL }}
SNAPLINE_HUB_LABEL: "CI ${{ github.run_number }} — ${{ github.sha }}"
run: node tests/run.mjs
# tests/run.mjs should call pushTestReportToHub when resolveHubConfig() returns config
GitHub Actions (Python)
- name: Run integration tests
env:
SNAPLINE_HUB_URL: ${{ vars.SNAPLINE_HUB_URL }}
SNAPLINE_HUB_LABEL: "CI ${{ github.run_number }}"
run: uv run pytest tests/ -v
Combined: local file + Hub
import { buildReport, pushTestReportToHub, resolveHubConfig, resolveReportConfig, writeTestReport } from '@vaagatech/snapline-core';
const report = buildReport(results, { durationMs, environment: { baseUrl } });
const reportConfig = resolveReportConfig();
if (reportConfig) {
writeTestReport(results, reportConfig, { durationMs, environment: { baseUrl } });
}
const hubConfig = resolveHubConfig();
if (hubConfig) {
await pushTestReportToHub(report, hubConfig);
}
TestRunReport schema
Hub accepts the same JSON schema produced by Snapline's buildReport:
{
"generatedAt": "2026-07-07T10:00:00.000Z",
"framework": "@vaagatech/snapline-engine",
"summary": {
"total": 2,
"passed": 1,
"failed": 1,
"durationMs": 1200
},
"environment": {
"baseUrl": "http://localhost:3000",
"branch": "main"
},
"suites": [
{
"name": "GraphQL fixture cases",
"passed": false,
"results": [
{ "step": "01-pass-customer-account", "passed": true },
{
"step": "04-fail-segment-mapping",
"passed": false,
"message": "Expected mismatch at segment",
"diff": {
"path": "segment",
"actual": "premium",
"expected": "standard",
"message": "Value mismatch"
}
}
]
}
]
}
See Architecture for the full type reference.
Error handling
| Situation | Behavior |
|---|---|
| Hub unreachable | pushTestReportToHub throws with HTTP status and body |
| Invalid report JSON | Hub returns 400 with validation hint |
SNAPLINE_HUB_URL not set | resolveHubConfig() returns undefined — skip push silently |
| Tests failed but push succeeded | Hub stores the report; CI should still fail on result.passed === false |
Recommended: don't fail tests when Hub push fails
const hubConfig = resolveHubConfig();
if (hubConfig) {
try {
const { url } = await pushTestReportToHub(report, hubConfig);
console.log(`Hub: ${url}`);
} catch (err) {
console.warn('Hub push failed (tests still evaluated):', err);
}
}
Redaction
Sensitive fields should be redacted before pushing. Use Snapline's built-in redaction when building the report:
import { buildReport, redactSuiteResults } from '@vaagatech/snapline-core';
const sanitized = redactSuiteResults(results, ['data.token', 'processed.password']);
const report = buildReport(sanitized, { durationMs });
Hub stores whatever JSON you send. Treat the Hub database like any test artifact store — do not push secrets.