Migrating to the Jest reporter
Whatever you come from, the reporter is one entry in reporters, with an optional setup file, and
the probara.* helpers work in every Jest worker, in band, in node and jsdom environments, and
in watch mode. You keep your own testEnvironment.
module.exports = { reporters: ['default', ['@probara/jest-reporter', { projectId: 'SHOP' }]], setupFilesAfterEnv: ['@probara/jest-reporter/setup'],};The setup file is needed only for
captureOutput and
run selection.
| You come from | Jump to |
|---|---|
jest-junit and probara import junit | The JUnit import |
jest-qase-reporter | Qase |
testit-adapter-jest | Test IT |
@reportportal/agent-js-jest | ReportPortal |
allure-jest | Allure |
jest-junit and trcli parse_junit | TestRail |
From the JUnit import
Section titled “From the JUnit import”If your pipeline writes a report with jest-junit and
sends it with probara import junit, the reporter can replace both steps without unlinking a
single case: it gives each test the automation key the import gives it. One setting decides which
keys: whether jest-junit wrote the test file into the report.
- Find how jest-junit ran in your pipeline: with
JEST_JUNIT_ADD_FILE_ATTRIBUTE=true(oraddFileAttribute: 'true'in its options), or without it (its default). - Add the reporter to the Jest config. Without the file attribute, also set
keyIncludesFile: false. - Check the keys of both on the same tests: they must be equal.
- In CI, drop jest-junit and the import step, and give the test step
PROBARA_API_TOKEN.
| jest-junit wrote | Reporter setting |
|---|---|
The file attribute (JEST_JUNIT_ADD_FILE_ATTRIBUTE=true) | Nothing: keyIncludesFile: true is the default |
| No file attribute (jest-junit’s default) | keyIncludesFile: false (PROBARA_KEY_INCLUDES_FILE) |
The keys are the ones the import gives on jest-junit’s default templates: the describe blocks
and the title joined with spaces ({classname} {title}). A report written with other templates
(classNameTemplate, titleTemplate, ancestorSeparator, suiteNameTemplate) has other keys: the
check below shows the difference, and ids in the titles keep those tests linked whatever their keys.
Check the keys before you switch
Section titled “Check the keys before you switch”Run the suite once with jest-junit and once with the reporter’s results file, reporting off, and compare the dry runs of both: each prints one line per result, with its status, its case and its key. For this test file:
describe('refund', () => { test('SHOP-12 refunds an order', () => { expect(25 - 25).toBe(0); });
test('keeps the receipt', () => { expect('receipt').toHaveLength(7); });
test.todo('refunds a gift card');});$ JEST_JUNIT_ADD_FILE_ATTRIBUTE=true npx jest --reporters=default --reporters=jest-junit$ npx @probara/cli import junit junit.xml --dry-runpassed SHOP-12 tests/refund.test.js > refund refunds an orderpassed - tests/refund.test.js > refund keeps the receiptpassed - tests/refund.test.js > refund refunds a gift cardTotal: 3 results from 1 file (3 passed, 0 failed, 0 skipped, 0 blocked)$ PROBARA_ENABLED=false PROBARA_RESULTS_FILE=probara-results.json npx jest[probara] Wrote 3 results to /work/shop/probara-results.json: send them with probara import results /work/shop/probara-results.json$ npx @probara/cli import results probara-results.json --dry-runpassed SHOP-12 tests/refund.test.js > refund refunds an orderpassed - tests/refund.test.js > refund keeps the receiptskipped - tests/refund.test.js > refund refunds a gift cardTotal: 3 results from 1 file (2 passed, 0 failed, 1 skipped, 0 blocked)The keys and cases are the same; only the status of the test.todo differs (see
what changes). On a real suite, write each dry run to a file
and diff them: a line whose key changed is a test that would get a new case. Delete junit.xml and
the results file afterwards.
Before, two steps: jest-junit writes the report, and the CLI sends it.
- name: Run Jest tests run: npx jest --ci --reporters=default --reporters=jest-junit env: JEST_JUNIT_ADD_FILE_ATTRIBUTE: 'true'- name: Send the results if: ${{ !cancelled() }} run: npx @probara/cli import junit junit.xml env: PROBARA_API_TOKEN: ${{ secrets.PROBARA_API_TOKEN }} PROBARA_PROJECT: SHOPAfter, one step: the reporter, registered in the Jest config, sends every result at the end of the test step.
- name: Run Jest tests run: npx jest --ci env: PROBARA_API_TOKEN: ${{ secrets.PROBARA_API_TOKEN }} PROBARA_PROJECT: SHOPRemove --reporters=jest-junit (it would replace the config’s reporters, the reporter included),
and jest-junit itself unless something else reads its report. The run is named, sourced and closed
as the import did it.
What changes from the JUnit import
Section titled “What changes from the JUnit import”| Topic | probara import junit on jest-junit | The reporter |
|---|---|---|
test.todo | passed: jest-junit writes a todo as a passed testcase | skipped, with the note Todo: a test not written yet never counts as passing |
Retries (jest.retryTimes) | The last attempt only | Every attempt, in order; the run case keeps the last outcome |
Tests left out by -t or .only | skipped | skipped too |
A failing afterAll hook | One more failed testcase for the file | The same failed result, with the same key |
| A file that fails to load | Nothing (jest-junit writes nothing for it by default) | Nothing to report, and one warning names the file |
| When the results go | After the whole run, in a later step | At the end of the run, from the test step; a results file keeps what could not be sent |
| Suite of a created case, without the file | The first describe of the file’s first test, for every test of the file | Each test’s own first describe (none for a test outside any describe) |
The last row only changes where a case the report creates goes, with keyIncludesFile: false: a
new test in a file whose tests sit in different top-level describes. Its key, and so the case an
existing test matches, is the same. With the file attribute, both put a created case in a suite
named after the file.
The reporter follows jest-junit’s naming to the letter, quirks included, so the keys stay equal:
{displayName} becomes the Jest project’s name, the $ patterns of String.replace in a title
expand as they do in jest-junit’s templates, and a file whose afterAll throws gets the extra failed
result keyed <file> > Test execution failure: could be caused by test hooks like 'afterAll'.
What you gain: every attempt of a retried test; nested steps; files and console output; links and
issues; parameters and the fields of new cases; run selection; and failed results assigned to the
people you name, as --assign-failed-to did for the import.
From Qase
Section titled “From Qase”Moving from jest-qase-reporter is mostly renaming: the reporter entry, its settings and the
qase.* calls. One thing changes for the better on the way: the probara.* helpers work in every
Jest worker, in jsdom and in watch mode, where Qase’s only work in band.
npm uninstall jest-qase-reporter && npm i -D @probara/jest-reporter.- In the Jest config, replace
['jest-qase-reporter', {...}]with['@probara/jest-reporter', {...}], translating the options (below). Deleteqase.config.jsonand.qaserc: the reporter reads only its options andPROBARA_*variables. - Replace
QASE_*variables withPROBARA_*ones in CI: at leastPROBARA_API_TOKEN(an app token from the Jest card) andPROBARA_PROJECT. - Replace
require('jest-qase-reporter/jest')withrequire('@probara/jest-reporter'), and translate the calls (below). Case ids become Probara display ids (SHOP-12), not numbers. - Remove
--runInBandif you added it only for Qase’s helpers: Probara’s need nothing.
Before:
module.exports = { reporters: [ 'default', [ 'jest-qase-reporter', { mode: 'testops', testops: { project: 'SHOP', run: { title: 'Nightly', complete: true, tags: ['nightly'] }, uploadAttachments: true, }, }, ], ],};After:
module.exports = { reporters: [ 'default', [ '@probara/jest-reporter', { projectId: 'SHOP', run: { name: 'Nightly', tags: ['nightly'] }, captureOutput: true }, ], ], setupFilesAfterEnv: ['@probara/jest-reporter/setup'],};captureOutput and its setup file are optional: they attach each test’s console output, which
Qase’s captureLogs never did for Jest.
Qase options and variables
Section titled “Qase options and variables”Qase option (QASE_* variable) | Probara option (PROBARA_* variable) |
|---|---|
mode: 'testops' (QASE_MODE) | On whenever a token and a project are set |
mode: 'off' | enabled: false (PROBARA_ENABLED=false), or no token |
mode: 'report', fallback (QASE_FALLBACK) | resultsFile (PROBARA_RESULTS_FILE) |
mode: 'testops_multi', testops_multi.* | projectId plus projects and run.ulids |
debug (QASE_DEBUG) | debug (PROBARA_DEBUG) |
environment (QASE_ENVIRONMENT), a slug, ignored when not found | run.environment (PROBARA_ENVIRONMENT), a name, created when missing |
rootSuite (QASE_ROOT_SUITE) | suiteUlid (PROBARA_SUITE_ULID): an existing suite, by ULID |
captureLogs (QASE_CAPTURE_LOGS): ignored by Qase’s Jest reporter | captureOutput (PROBARA_CAPTURE_OUTPUT), with the setup file |
statusMapping (QASE_STATUS_MAPPING) | statusMapping (PROBARA_STATUS_MAPPING), without invalid and disabled |
logging.console, logging.file | Logs go to stderr; the logger option for code |
testops.api.token (QASE_TESTOPS_API_TOKEN) | PROBARA_API_TOKEN (apiToken for code) |
testops.api.host (QASE_TESTOPS_API_HOST) | baseUrl (PROBARA_BASE_URL), a full URL |
testops.api.timeout (seconds) | timeoutMs (milliseconds), for every request |
testops.api.retries, testops.api.retryBackoff | maxRetries, for every request; the backoff is fixed |
testops.project (QASE_TESTOPS_PROJECT) | projectId (PROBARA_PROJECT) |
testops.uploadAttachments | uploadAttachments (PROBARA_UPLOAD_ATTACHMENTS) |
testops.attachments.concurrency, .timeout | attachmentConcurrency; uploads get at least 120 s |
testops.run.id (QASE_TESTOPS_RUN_ID) | run.ulid (PROBARA_RUN_ULID) |
testops.run.title, .description, .tags | run.name, run.description, run.tags (PROBARA_RUN_*) |
testops.run.complete (QASE_TESTOPS_RUN_COMPLETE) | closeRun (PROBARA_CLOSE_RUN); a created run is closed by default |
testops.plan.id (QASE_TESTOPS_PLAN_ID) | run.plan (PROBARA_PLAN): the plan’s display id or name |
testops.configurations.values (group=value) | run.configurations (PROBARA_CONFIGURATIONS=Group=Name) |
testops.statusFilter (QASE_TESTOPS_STATUS_FILTER) | statusFilter (PROBARA_STATUS_FILTER); skipped drops todo tests too |
testops.batch.size (QASE_TESTOPS_BATCH_SIZE) | chunkSize (1 to 500) |
| (none) | run.milestone (PROBARA_MILESTONE): Qase’s reporter has no milestone |
The qase.* calls
Section titled “The qase.* calls”| Qase | Probara |
|---|---|
test(qase(12, 'pays'), ...), (Qase ID: 12) in a title | test('SHOP-12 pays', ...): an id in the title or a describe |
test(qase([12, 13], 'pays'), ...) | test('SHOP-12 SHOP-13 pays', ...), or probara.id(['SHOP-12', 'SHOP-13']) |
qase.projects({ WEB: [3] }, 'pays') | WEB-3 in the title, with projects: ['WEB'] |
(none: Qase’s Jest reporter has no qase.id()) | probara.id('SHOP-12') |
qase.title('...') | probara.title('...') |
qase.suite('Payments') | probara.suite('Payments'), or probara.suite(['Payments', 'Cards']) for nesting |
qase.fields({ severity: 'critical' }) | probara.fields({ severity: 'critical' }) |
qase.tags('smoke') | probara.tags('smoke') |
qase.comment('...') | probara.comment('...') |
qase.ignore() | probara.ignore() |
qase.parameters({...}), qase.groupParameters({...}) | probara.parameters({...}) |
qase.attach({ paths: [a, b] }) | probara.attach({ name, path }), once per file |
qase.attach({ name, content, type }) | probara.attach({ name, body, contentType }) |
await qase.step('Pay', fn, 'Paid', 'card=visa') | await probara.step('Pay', fn, { expected: 'Paid', data: 'card=visa' }) |
s.step(...), s.attach(...) inside a step’s body | probara.step(...), probara.attach(...) inside the body: they nest on their own |
Before:
const { qase } = require('jest-qase-reporter/jest');
test(qase(12, 'pays with a card'), async () => { qase.fields({ severity: 'critical' }); qase.tags('smoke'); await qase.step('Pay', async () => {}, 'The order is paid'); qase.attach({ name: 'order.json', content: '{"id":1042}', type: 'application/json' });});After:
const { probara } = require('@probara/jest-reporter');
test('SHOP-12 pays with a card', async () => { probara.fields({ severity: 'critical' }).tags('smoke'); await probara.step('Pay', async () => {}, { expected: 'The order is paid' }); await probara.attach({ name: 'order.json', body: '{"id":1042}', contentType: 'application/json', });});[ { "caseDisplayId": "SHOP-12", "automationKey": "tests/checkout.test.js > pays with a card", "status": "passed", "steps": [{ "action": "Pay", "expected": "The order is paid", "status": "passed" }], "case": { "tags": ["smoke"], "fields": { "severity": "critical" }, "steps": [{ "action": "Pay", "expected": "The order is paid" }] } }]The id leaves the title and the key: adding or removing it never changes which case the test matches.
The helpers work in every worker
Section titled “The helpers work in every worker”Qase’s qase.* calls reach its reporter through a global of Jest’s main process. That global exists
in a test only when Jest runs in band and the test environment is node: with workers (Jest’s
default for more than one file), in jsdom, and always in watch mode, the calls throw and fail the
test. Jest also picks in band or workers on its own, from the timings of earlier runs, so the same
suite can pass one day and fail the next.
Probara’s helpers write to a channel every worker can reach, and each call names its test by its
file, full name and attempt. They work in workers, in band, in node and jsdom, in watch mode, and
in test.concurrent tests; a call outside a running test is left out with a warning, never thrown.
What is different from Qase, and why
Section titled “What is different from Qase, and why”| Topic | Qase | Probara |
|---|---|---|
| Precedence | Its code lets variables win over qase.config.json, which wins over the reporter options (its docs say the opposite) | Options in the Jest config, then PROBARA_* variables, then defaults: what the config says is what runs |
| Config file | qase.config.json or .qaserc | None: a config file holding a token could be committed. Options live in the Jest config |
| Case ids | Numbers, in the test’s own title only | Display ids (SHOP-12) in the title or any describe: the Project ID routes each id to its project |
| Attribution of metadata | By timing: calls go to the next test that reports (beforeAll calls to the first, afterAll calls to the next file’s) | By identity: each call belongs to the test running it; outside a test it is left out with a warning |
| Steps | Flat, children before their parent; expected result and data encoded into the step name | Nested as they ran, each with its status, duration and error; expected and data as options |
| Step attachments | Only through the step body’s s.attach(); qase.attach() always goes to the test | probara.attach() inside a step goes to that step |
test.failing | A throwing body passes; a passing body is usually invalid | Jest’s verdict: a throwing body passes, a passing body fails |
| Statuses | invalid for a failure whose error does not look like an assertion (a guess from the message) | passed, failed, skipped; blocked only through statusMapping. No guessing from error text |
test.todo | Skipped, but statusFilter needs disabled to drop it | Skipped, with the note Todo; statusFilter: ['skipped'] drops it |
Tests -t or .only left out | Reported as skipped | Reported as skipped too: Jest reports them as skipped, and the reporter sends what Jest reports |
A failing afterAll | Not reported: the tests stay green in Qase | One more failed result for the file, the one jest-junit writes |
| A file that fails to load | Nothing reported, nothing said | Nothing to report (no test ran), and one warning names the file |
qase.comment() | Replaces the error message of a failed result | Written first in the notes, before the error, which is kept |
fields() and parameters() calls | The last call replaces the others | Merged by name |
| Case fields | Sent with every result | Only when the report creates the case: an existing case keeps what people wrote |
| Closing the run | Only with run.complete: true; unset leaves it open | A run the reporter creates is closed at the end; a reused one stays open |
| Shards | Each shard makes its own run unless it gets a run id | probara run create, every shard with PROBARA_RUN_ULID, then probara run close |
| Watch mode | A new run per re-run; the helpers throw (never in band) | One run for the whole session, left open |
| When reporting fails | fallback to a local report, for good, from the first error | A results file, sent later by probara import results into the same runs |
Strings in attach() | A string that looks like base64 is decoded ('Hello' uploads garbage) | A string is sent as it is; pass a Buffer for binary content |
attach({ paths }) | Must be an array (a string is read character by character); the content type is type, not contentType | One file per call, path or body, and contentType |
| The end of the run | Not awaited: --forceExit can cut the last results off | Jest awaits the reporter: every result is sent before Jest exits |
| Running only a plan’s tests | An external CLI (qasectl) builds a -t filter | runCasesOnly runs only the tests of a run’s cases |
Not ported from Qase
Section titled “Not ported from Qase”| Qase feature | Why |
|---|---|
Public report link (showPublicReportLink) | A public link to test results is a sharing decision for the Probara app, not something a CI job should turn on |
Automatic defects (testops.defect) | Filing a defect for every failure floods the defect list; report defects from the result in Probara |
Network profiler (profilers: ['network']) | It records the main process’s HTTP calls, which in Jest means in band only, and is noise; attach what matters instead |
Jira link on runs (run.externalLink) | Probara links Jira issues to cases and defects, where traceability lives; probara.issue() links a result to an issue |
qase.mute() | Documented by Qase, but it does not exist in its reporter |
Creating configurations (createIfNotExists) | Configurations are part of a project’s planning; an unknown one is refused, so a typo never creates a new one |
The invalid status | It is a guess from the error text; Probara keeps four statuses and the whole error in the notes |
Logging to a file (logging.file) | Logs go to stderr, where CI keeps them; redirect stderr to keep a file |
Per-project plan and environment (testops_multi.projects[].plan, .environment) | Planning entities belong to one project, where an unknown name would refuse the report: create that project’s run first and pass it in run.ulids |
From Test IT
Section titled “From Test IT”testit-adapter-jest is a test environment (testEnvironment: 'testit-adapter-jest') with a
globalSetup and a globalTeardown: each worker sends its own results to Test IT, test file by test
file. Replace all three with the reporter entry at the top of this page.
// jest.config.js (before)module.exports = { testEnvironment: 'testit-adapter-jest', globalSetup: 'testit-adapter-jest/dist/globalSetup.js', globalTeardown: 'testit-adapter-jest/dist/globalTeardown.js', testEnvironmentOptions: { url: 'https://testit.example.com', projectId: '5236eb3f-7c05-46f9-a609-dc0278896464', adapterMode: 2, testRunName: 'Nightly', },};| Test IT setting (variable) | Probara (variable) |
|---|---|
url (TMS_URL) | baseUrl (PROBARA_BASE_URL), only for a self-hosted Probara |
privateToken (TMS_PRIVATE_TOKEN) | PROBARA_API_TOKEN: an app token from the Jest card |
projectId (TMS_PROJECT_ID), a UUID | projectId (PROBARA_PROJECT), the Project ID (SHOP) |
configurationId (TMS_CONFIGURATION_ID) | run.configurations (PROBARA_CONFIGURATIONS=Browser=Chrome), by name |
adapterMode: 2, a new run | The default: each jest creates a run and closes it at the end |
adapterMode: 1 and testRunId (TMS_TEST_RUN_ID) | run.ulid (PROBARA_RUN_ULID): report into an existing run, left open |
testRunName (TMS_TEST_RUN_NAME) | run.name (PROBARA_RUN_NAME), for a new run only |
testRunTags (TMS_TEST_RUN_TAGS) | run.tags (PROBARA_RUN_TAGS) |
automaticCreationTestCases | createMissingCases (PROBARA_CREATE_MISSING_CASES), on by default |
syncStorageEnabled, syncStoragePort, importRealtime | Nothing to set: every result goes through the reporter in Jest’s main process |
LOG_LEVEL=debug | debug (PROBARA_DEBUG) |
Test IT call (testit.*) | Probara (probara.*) |
|---|---|
workItemIds(['1042']), workItemId('1042') | id('SHOP-12'), or SHOP-12 in the title or a describe |
displayName('...'), title('...') | title('...'): the title of the case a report creates |
description('...') | fields({ description: '...' }) |
labels(['smoke']), tags(['smoke']) | tags('smoke') |
layer('API') | fields({ layer: 'API' }), a system field of the case |
namespace('...'), classname('...') | suite(['...', '...']): the suite path of the case a report creates |
params({ user: 'ana' }) | parameters({ user: 'ana' }) |
step('Pay', 'description') | step('Pay', () => {...}, { expected: '...' }): nested, with status and duration |
addAttachments(['a.png']) | attach({ name: 'a.png', path: 'a.png' }), once per file |
addAttachments('text', 'log.txt') | attach({ name: 'log.txt', body: 'text' }) |
addLinks({ url, title }), links([...]) | link(url, title), once per link |
addMessage('...') | comment('...') |
| Topic | Test IT | Probara |
|---|---|---|
| Identity | A hash of the file and the test’s own title: two tests with one title in different describes collide | The file, every describe and the title |
Skipped and test.todo tests | Not reported | Reported as skipped (Todo in the notes of a todo) |
| Steps | Flat, without a status or a duration | Nested, each with its status, duration and error |
test.concurrent | One buffer per test file: concurrent tests overwrite each other’s data | Each call belongs to the test that made it |
| Closing the run | Never closed by the adapter | A run the reporter creates is closed at the end; a reused one stays open |
| An existing run’s name | Renamed to testRunName | Never changed: the options of a new run are ignored for a reused one, with a warning |
| Precedence | Differs by type: strings from the options first, booleans from tms.config.json first | Options, then PROBARA_* variables, then defaults, for every setting |
| Running a run’s tests | An external CLI writes a -t filter | runCasesOnly reads the run’s cases itself |
Not ported from Test IT:
| Test IT | Why |
|---|---|
automaticUpdationLinksToTestCases, updating autotests | A report never changes an existing case: people own what is written there |
testit.externalId() | The key comes from the file and the names, the same as probara import junit’s; link a case by its id instead |
| Setup and teardown steps from hooks | Hooks are how a test runs, not what it specifies; wrap what matters in probara.step() |
testRunLinks (links on the run) | The run records its CI build URL, branch and commit on its own; links belong to results (probara.link()) |
certValidation: false | TLS verification cannot be turned off; give Node your certificate authority instead |
From ReportPortal
Section titled “From ReportPortal”@reportportal/agent-js-jest is a reporter, like this one. Swap the entry and its options:
// jest.config.js (before)module.exports = { reporters: [ 'default', [ '@reportportal/agent-js-jest', { endpoint: 'https://rp.example.com/api/v1', project: 'shop', launch: 'Nightly' }, ], ],};| ReportPortal (variable) | Probara (variable) |
|---|---|
apiKey (RP_API_KEY, RP_TOKEN) | PROBARA_API_TOKEN |
endpoint (RP_ENDPOINT) | baseUrl (PROBARA_BASE_URL), the app’s URL without a path |
project (RP_PROJECT_NAME) | projectId (PROBARA_PROJECT) |
launch (RP_LAUNCH) | run.name (PROBARA_RUN_NAME) |
description (RP_DESCRIPTION) | run.description (PROBARA_RUN_DESCRIPTION) |
attributes (RP_ATTRIBUTES=build:42,nightly) | run.tags (PROBARA_RUN_TAGS=build:42,nightly): tags are plain names, a colon included |
launchId (RP_LAUNCH_ID) | run.ulid (PROBARA_RUN_ULID) |
isLaunchMergeRequired, launchUuidPrint | probara run create prints the ULID every shard reports into |
restClientConfig (timeout, retries) | timeoutMs, maxRetries |
debug | debug (PROBARA_DEBUG) |
ReportingApi.attachment(file, description) | probara.attach({ name, path }) or { name, body, contentType } |
| Topic | ReportPortal | Probara |
|---|---|---|
| In-test API | Only attachment, and only in band: it lives in the reporter’s process | Every helper, in every worker |
| Retries | Every attempt, flagged retry when its code reference was seen before in the process (watch re-runs and duplicate names too) | Every attempt is a result, with no flag: the history shows it |
test.todo | Never reported | Skipped, with the note Todo |
| Errors | The first failure message, as a log and in the description | Every error of the attempt, in the notes of its result |
| Structure | A suite item per describe; the file is not an item | Each case in a suite named after its file |
| Settings from jest-junit | Its jest-junit section of package.json and JEST_JUNIT_* variables override its options | Only the reporter’s options and PROBARA_* variables |
Not ported from ReportPortal:
| ReportPortal | Why |
|---|---|
skippedIssue and the NOT_ISSUE defect type | Known issues and defect triage live in Probara, on the result, not in a CI setting |
rerun, rerunOf | Report into the same run with run.ulid: each run case keeps the last outcome and its whole history |
mode: 'DEBUG' launches | A debug run is a run: turn reporting off locally (no token), or check what would be sent |
| OAuth sign-in | CI reports with an app token of the organization, which uses no seat and outlives the person who made it |
From Allure
Section titled “From Allure”allure-jest is a test environment (allure-jest/node, allure-jest/jsdom) that writes result
files into allure-results/; a separate step turns them into a report. The reporter sends results
to Probara as the run ends, and needs no environment: put back node, jsdom or your own.
// jest.config.js (before)module.exports = { testEnvironment: 'allure-jest/node', testEnvironmentOptions: { resultsDir: 'allure-results', links: { issue: { urlTemplate: 'https://jira.example.com/browse/%s' } }, },};| Allure setting | Probara |
|---|---|
resultsDir | Nothing: results go to Probara. A results file keeps what could not be sent |
links.issue.urlTemplate | issueUrlTemplate (PROBARA_ISSUE_URL_TEMPLATE) |
environmentInfo | run.environment (a name) and the run source, detected from the CI |
ALLURE_TESTPLAN_PATH | runCasesOnly with PROBARA_RUN_ULID |
Allure call (allure-js-commons) | Probara (probara.*) |
|---|---|
@allure.id:12 in a title, allureId('12') | SHOP-12 in the title or a describe, or id('SHOP-12') |
displayName('...') | title('...') |
description('...') | fields({ description: '...' }) |
severity('critical'), layer('api') | fields({ severity: 'critical', layer: 'api' }) |
tag('smoke'), tags(...) | tags('smoke') |
epic, feature, story, label(name, value) | fields({ Feature: 'Checkout' }): a custom field of that name, or tags(...) |
parentSuite, suite, subSuite | suite(['Payments', 'Cards']) |
parameter('user', 'ana') | parameters({ user: 'ana' }) |
step('Pay', async () => {...}) | step('Pay', async () => {...}) |
logStep('Paid') | step('Paid'): a step without a body, passed |
attachment(name, content, type) | attach({ name, body: content, contentType: type }) |
attachmentPath(name, path) | attach({ name, path }) |
link(url, name) | link(url, name) |
issue('PAY-7') | issue('PAY-7'), with issueUrlTemplate |
tms('12') | id('SHOP-12') |
| Topic | Allure | Probara |
|---|---|---|
| Statuses | failed for an assertion, broken for any other error | failed for both, with the error in the notes: no guess from the error’s type |
A failing afterEach or afterAll | The test stays passed; only the hook’s fixture is broken | Jest’s verdict: a failing afterEach fails its test, a failing afterAll adds a failed result for the file |
| A test plan | Excluded tests still run, hooks included, and are only left out of the report | runCasesOnly skips them before they run, and does not report them |
| Attachments | Each one wrapped in a step of its own | On the result, or on the step they were attached in |
| Retries and history | One result file per attempt; the report generator groups them | Every attempt is a result of the run; the case keeps its history across runs |
| Watch mode | Result files pile up in resultsDir | One run for the whole session |
| Case fields | Labels on every result | Fields and tags of the case a report creates; an existing case keeps its own |
Not ported from Allure:
| Allure | Why |
|---|---|
testCaseId(), historyId() | The key comes from the file and the names, the same as probara import junit’s; link a case by its id instead |
| Setup and teardown fixtures from hooks | Hooks are how a test runs, not what it specifies; wrap what matters in probara.step() |
broken status | A guess from the error’s type; Probara keeps four statuses and the whole error |
categories, known, muted and flaky flags | Triage lives in Probara, on the results and defects; every attempt is a result, so flakiness shows in the history |
owner() | A Probara case has no owner field; assign failed results instead (assignFailedTo) |
| Masked, hidden and excluded parameters | Parameters are shown with the result and never part of the key: leave a secret out rather than masking it |
logStep() with a failed or broken status | A step’s status is what its body did: a step that throws fails |
globalAttachment(), globalError() | Files and errors belong to a result; attach from the test that produced them |
descriptionHtml() | The description is text; Probara formats it |
listeners | No hook changes a result before it is sent; statusMapping and statusFilter cover statuses |
ALLURE_LABEL_* global labels | Tag the run with run.tags instead; case tags come from probara.tags() in the test |
From TestRail
Section titled “From TestRail”TestRail has no Jest reporter: a pipeline writes JUnit XML with jest-junit, then TestRail’s Python CLI
trcli parse_junit sends it after the run. The reporter replaces both steps, and sends what
jest-junit cannot write: steps, files, comments and every attempt.
# Beforenpx jest --ci --reporters=default --reporters=jest-junittrcli -h https://example.testrail.io --project "Shop" -u ci@example.com -k "$TESTRAIL_KEY" \ parse_junit --title "Nightly" --case-matcher name -f junit.xml --close-runWith the reporter, the test step is enough:
npx jest --ci| trcli | Probara |
|---|---|
-h (host), -u, -p, -k | baseUrl for a self-hosted Probara; PROBARA_API_TOKEN |
--project (a name) | projectId (PROBARA_PROJECT), the Project ID |
--title, --run-description | run.name, run.description |
--run-id | run.ulid (PROBARA_RUN_ULID) |
--plan-id, --config-ids, --milestone-id | run.plan, run.configurations, run.milestone, by display id or name |
--close-run | The default for a run the reporter creates; closeRun for a reused one |
--case-matcher name (C123 in the name) | SHOP-12 in the title or a describe |
--case-matcher auto (classname.name) | The automation key: file, describes and title |
-y (create sections and cases) | createMissingCases, on by default; suites from the test file |
--suite-id, --section-id | suiteUlid (PROBARA_SUITE_ULID): the suite created cases go under |
--case-fields type_id:3 | probara.fields({ type: 'Regression' }) in the test, by name |
-a, --assign (emails) | assignFailedTo (PROBARA_ASSIGN_FAILED_TO) |
-b, --batch-size | chunkSize (1 to 500) |
-t, --timeout (seconds, per request) | timeoutMs (milliseconds, per request) |
--proxy, --noproxy | HTTPS_PROXY and NO_PROXY with NODE_USE_ENV_PROXY=1 |
-v (verbose) | debug (PROBARA_DEBUG) |
testrail_result_comment property | probara.comment() |
testrail_attachment property | probara.attach() |
testrail_result_step property | probara.step() |
jest-junit writes no testcase properties, so the last three rows were out of reach from Jest before: the helpers make them possible.
| Topic | trcli on jest-junit | Probara |
|---|---|---|
| Skipped tests | Retest | Skipped |
test.todo | Passed (jest-junit writes it as passed) | Skipped, with the note Todo |
| Retries | The last attempt only | Every attempt |
| Nested describes | Not supported: one section per test suite | The suite of a created case is its file; probara.suite() names nested suites |
| Durations | Rounded to seconds (--allow-ms for milliseconds) | Milliseconds, and the start of each attempt |
| An unknown case id | Aborts the upload | That result is not recorded, and the log names it; the others are |
| A failed upload | Rolls back the run, cases and sections it created | Nothing is rolled back; a results file keeps what was not sent |
| Shards | add_run -f run.yml, then -c run.yml in every shard | probara run create, PROBARA_RUN_ULID in every shard, probara run close |
Not ported from TestRail:
| trcli | Why |
|---|---|
--update-existing-cases, --update-strategy, refs | A report never changes an existing case: people own what is written there |
custom_automation_id override | The key comes from the file and the names, the same as probara import junit’s; link a case by its id instead |
--result-fields, testrail_result_field | Probara results have no custom fields: parameters (probara.parameters()) and links (probara.link()) carry the extra data |
--test-run-ref | The run records its CI build URL, branch and commit; issues link from results (probara.issue()) |
--insecure | TLS verification cannot be turned off; give Node your certificate authority instead |
The bdd, multisuite and saucectl parsers, quality_rating | Jest has no Gherkin scenarios; a run spans one project, and projects splits by case id |
| Rollback on failure | A result recorded is kept: rolling back would lose the ones that went through |
Upgrading the reporter
Section titled “Upgrading the reporter”@probara/jest-reporter follows semantic versioning. Read the
changelog
before you upgrade: every change that needs an action from you is listed there first.
- 0.x (now): the reporter is young. A minor version (
0.2.0) may change options, log lines or defaults; a patch version (0.1.1) only fixes bugs. Pin the version inpackage.json(or keep the lockfile) and upgrade on purpose. - 1.0 and later: breaking changes only in a major version.
@probara/jest-reporterdepends on@probara/coreof the same release line; install the reporter alone and let it bring its core.
What is a breaking change
Section titled “What is a breaking change”Any of these needs a major version from 1.0 on, and a minor one before:
- An option or a
PROBARA_*variable removed or renamed, or its meaning changed. - A default changed (closing runs, creating missing cases,
keyIncludesFile, what gets attached). - A
probara.*helper or the setup file removed, or its behavior changed. - A status an attempt is sent with changed for the same attempt.
- Any change to the automation key a test gets, with
keyIncludesFileon or off. A different key unlinks the test from its case: the next run creates a new one. - Dropping a Jest or Node.js version that is still supported upstream.
Adding an option, a helper, a log line or a file uploaded is not breaking. Log lines on stderr are for people: do not parse them.
The automation key contract
Section titled “The automation key contract”The key algorithm is
automation key v1,
frozen in @probara/core and pinned by golden vectors: no version of the reporter changes it. What
the reporter adds is how a Jest test becomes the key’s parts, the way probara import junit reads the
report of jest-junit with its default templates:
- the test file relative to
rootDir(the real path of the directoryjestruns in), whenkeyIncludesFileis on, as jest-junit writes it withJEST_JUNIT_ADD_FILE_ATTRIBUTE=true; - the describes and the title, joined by spaces and split on
›(with its spaces), the case ids removed; {displayName}in a describe or a title filled with the Jest project’s name, and$patterns ($$,$&) expanded, as jest-junit’s templates do.
That mapping is part of the contract too: a version that changes the key of a test is a breaking change, named as such in the changelog. To check an upgrade, compare what both versions would send, each run with its own results file (check what would be sent). Any changed key is a test that would get a new case.
Jest and Node.js versions
Section titled “Jest and Node.js versions”| Needs | Supported |
|---|---|
jest | 29.6 and later (the peer range): tested on 29.7 and the latest 30 |
| Node.js | 22.12 and later (the engines field) |
| A runner | jest-circus, Jest’s default, for the probara.* helpers and the setup file |
Jest 29.6 is the first version that tells a reporter when each test starts, hence the peer range. Some details depend on the version:
| Jest | What changes |
|---|---|
| 29 | A test.todo or skipped test gets the start time of its file. jest.retryTimes never retries a test.concurrent test |
| 30.0 to 30.4 | A test.concurrent test retried by jest.retryTimes: the probara.* calls of its retries join those of its first attempt (Jest 30.5 tells each attempt apart) |
| 30.5 and later | Every attempt of every test, test.concurrent included, gets its own probara.* calls |
Under testRunner: 'jest-jasmine2' the tests are reported, but the probara.* calls record nothing
(the first one warns), captureOutput attaches nothing, and runCasesOnly runs and reports every
test, with a warning. 0.1.0 is the first version: there is nothing to migrate from yet.
Related: Get started · Linking · CLI · Source: migrating-from-junit.md, migrating-from-qase.md, coming-from-other-tools.md, upgrade.md