Skip to content

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.

jest.config.js
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 fromJump to
jest-junit and probara import junitThe JUnit import
jest-qase-reporterQase
testit-adapter-jestTest IT
@reportportal/agent-js-jestReportPortal
allure-jestAllure
jest-junit and trcli parse_junitTestRail

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.

  1. Find how jest-junit ran in your pipeline: with JEST_JUNIT_ADD_FILE_ATTRIBUTE=true (or addFileAttribute: 'true' in its options), or without it (its default).
  2. Add the reporter to the Jest config. Without the file attribute, also set keyIncludesFile: false.
  3. Check the keys of both on the same tests: they must be equal.
  4. In CI, drop jest-junit and the import step, and give the test step PROBARA_API_TOKEN.
jest-junit wroteReporter 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.

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:

tests/refund.test.js
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-run
passed SHOP-12 tests/refund.test.js > refund refunds an order
passed - tests/refund.test.js > refund keeps the receipt
passed - tests/refund.test.js > refund refunds a gift card
Total: 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-run
passed SHOP-12 tests/refund.test.js > refund refunds an order
passed - tests/refund.test.js > refund keeps the receipt
skipped - tests/refund.test.js > refund refunds a gift card
Total: 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: SHOP

After, 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: SHOP

Remove --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.

Topicprobara import junit on jest-junitThe reporter
test.todopassed: jest-junit writes a todo as a passed testcaseskipped, with the note Todo: a test not written yet never counts as passing
Retries (jest.retryTimes)The last attempt onlyEvery attempt, in order; the run case keeps the last outcome
Tests left out by -t or .onlyskippedskipped too
A failing afterAll hookOne more failed testcase for the fileThe same failed result, with the same key
A file that fails to loadNothing (jest-junit writes nothing for it by default)Nothing to report, and one warning names the file
When the results goAfter the whole run, in a later stepAt 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 fileThe first describe of the file’s first test, for every test of the fileEach 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.

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.

  1. npm uninstall jest-qase-reporter && npm i -D @probara/jest-reporter.
  2. In the Jest config, replace ['jest-qase-reporter', {...}] with ['@probara/jest-reporter', {...}], translating the options (below). Delete qase.config.json and .qaserc: the reporter reads only its options and PROBARA_* variables.
  3. Replace QASE_* variables with PROBARA_* ones in CI: at least PROBARA_API_TOKEN (an app token from the Jest card) and PROBARA_PROJECT.
  4. Replace require('jest-qase-reporter/jest') with require('@probara/jest-reporter'), and translate the calls (below). Case ids become Probara display ids (SHOP-12), not numbers.
  5. Remove --runInBand if you added it only for Qase’s helpers: Probara’s need nothing.

Before:

jest.config.js
module.exports = {
reporters: [
'default',
[
'jest-qase-reporter',
{
mode: 'testops',
testops: {
project: 'SHOP',
run: { title: 'Nightly', complete: true, tags: ['nightly'] },
uploadAttachments: true,
},
},
],
],
};

After:

jest.config.js
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 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 foundrun.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 reportercaptureOutput (PROBARA_CAPTURE_OUTPUT), with the setup file
statusMapping (QASE_STATUS_MAPPING)statusMapping (PROBARA_STATUS_MAPPING), without invalid and disabled
logging.console, logging.fileLogs 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.retryBackoffmaxRetries, for every request; the backoff is fixed
testops.project (QASE_TESTOPS_PROJECT)projectId (PROBARA_PROJECT)
testops.uploadAttachmentsuploadAttachments (PROBARA_UPLOAD_ATTACHMENTS)
testops.attachments.concurrency, .timeoutattachmentConcurrency; uploads get at least 120 s
testops.run.id (QASE_TESTOPS_RUN_ID)run.ulid (PROBARA_RUN_ULID)
testops.run.title, .description, .tagsrun.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
QaseProbara
test(qase(12, 'pays'), ...), (Qase ID: 12) in a titletest('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 bodyprobara.step(...), probara.attach(...) inside the body: they nest on their own

Before:

tests/checkout.test.js
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:

tests/checkout.test.js
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.

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.

TopicQaseProbara
PrecedenceIts 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 fileqase.config.json or .qasercNone: a config file holding a token could be committed. Options live in the Jest config
Case idsNumbers, in the test’s own title onlyDisplay ids (SHOP-12) in the title or any describe: the Project ID routes each id to its project
Attribution of metadataBy 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
StepsFlat, children before their parent; expected result and data encoded into the step nameNested as they ran, each with its status, duration and error; expected and data as options
Step attachmentsOnly through the step body’s s.attach(); qase.attach() always goes to the testprobara.attach() inside a step goes to that step
test.failingA throwing body passes; a passing body is usually invalidJest’s verdict: a throwing body passes, a passing body fails
Statusesinvalid 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.todoSkipped, but statusFilter needs disabled to drop itSkipped, with the note Todo; statusFilter: ['skipped'] drops it
Tests -t or .only left outReported as skippedReported as skipped too: Jest reports them as skipped, and the reporter sends what Jest reports
A failing afterAllNot reported: the tests stay green in QaseOne more failed result for the file, the one jest-junit writes
A file that fails to loadNothing reported, nothing saidNothing to report (no test ran), and one warning names the file
qase.comment()Replaces the error message of a failed resultWritten first in the notes, before the error, which is kept
fields() and parameters() callsThe last call replaces the othersMerged by name
Case fieldsSent with every resultOnly when the report creates the case: an existing case keeps what people wrote
Closing the runOnly with run.complete: true; unset leaves it openA run the reporter creates is closed at the end; a reused one stays open
ShardsEach shard makes its own run unless it gets a run idprobara run create, every shard with PROBARA_RUN_ULID, then probara run close
Watch modeA new run per re-run; the helpers throw (never in band)One run for the whole session, left open
When reporting failsfallback to a local report, for good, from the first errorA 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 contentTypeOne file per call, path or body, and contentType
The end of the runNot awaited: --forceExit can cut the last results offJest awaits the reporter: every result is sent before Jest exits
Running only a plan’s testsAn external CLI (qasectl) builds a -t filterrunCasesOnly runs only the tests of a run’s cases
Qase featureWhy
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 statusIt 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

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 UUIDprojectId (PROBARA_PROJECT), the Project ID (SHOP)
configurationId (TMS_CONFIGURATION_ID)run.configurations (PROBARA_CONFIGURATIONS=Browser=Chrome), by name
adapterMode: 2, a new runThe 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)
automaticCreationTestCasescreateMissingCases (PROBARA_CREATE_MISSING_CASES), on by default
syncStorageEnabled, syncStoragePort, importRealtimeNothing to set: every result goes through the reporter in Jest’s main process
LOG_LEVEL=debugdebug (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('...')
TopicTest ITProbara
IdentityA hash of the file and the test’s own title: two tests with one title in different describes collideThe file, every describe and the title
Skipped and test.todo testsNot reportedReported as skipped (Todo in the notes of a todo)
StepsFlat, without a status or a durationNested, each with its status, duration and error
test.concurrentOne buffer per test file: concurrent tests overwrite each other’s dataEach call belongs to the test that made it
Closing the runNever closed by the adapterA run the reporter creates is closed at the end; a reused one stays open
An existing run’s nameRenamed to testRunNameNever changed: the options of a new run are ignored for a reused one, with a warning
PrecedenceDiffers by type: strings from the options first, booleans from tms.config.json firstOptions, then PROBARA_* variables, then defaults, for every setting
Running a run’s testsAn external CLI writes a -t filterrunCasesOnly reads the run’s cases itself

Not ported from Test IT:

Test ITWhy
automaticUpdationLinksToTestCases, updating autotestsA 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 hooksHooks 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: falseTLS verification cannot be turned off; give Node your certificate authority instead

@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, launchUuidPrintprobara run create prints the ULID every shard reports into
restClientConfig (timeout, retries)timeoutMs, maxRetries
debugdebug (PROBARA_DEBUG)
ReportingApi.attachment(file, description)probara.attach({ name, path }) or { name, body, contentType }
TopicReportPortalProbara
In-test APIOnly attachment, and only in band: it lives in the reporter’s processEvery helper, in every worker
RetriesEvery 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.todoNever reportedSkipped, with the note Todo
ErrorsThe first failure message, as a log and in the descriptionEvery error of the attempt, in the notes of its result
StructureA suite item per describe; the file is not an itemEach case in a suite named after its file
Settings from jest-junitIts jest-junit section of package.json and JEST_JUNIT_* variables override its optionsOnly the reporter’s options and PROBARA_* variables

Not ported from ReportPortal:

ReportPortalWhy
skippedIssue and the NOT_ISSUE defect typeKnown issues and defect triage live in Probara, on the result, not in a CI setting
rerun, rerunOfReport into the same run with run.ulid: each run case keeps the last outcome and its whole history
mode: 'DEBUG' launchesA debug run is a run: turn reporting off locally (no token), or check what would be sent
OAuth sign-inCI reports with an app token of the organization, which uses no seat and outlives the person who made it

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 settingProbara
resultsDirNothing: results go to Probara. A results file keeps what could not be sent
links.issue.urlTemplateissueUrlTemplate (PROBARA_ISSUE_URL_TEMPLATE)
environmentInforun.environment (a name) and the run source, detected from the CI
ALLURE_TESTPLAN_PATHrunCasesOnly 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, subSuitesuite(['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')
TopicAllureProbara
Statusesfailed for an assertion, broken for any other errorfailed for both, with the error in the notes: no guess from the error’s type
A failing afterEach or afterAllThe test stays passed; only the hook’s fixture is brokenJest’s verdict: a failing afterEach fails its test, a failing afterAll adds a failed result for the file
A test planExcluded tests still run, hooks included, and are only left out of the reportrunCasesOnly skips them before they run, and does not report them
AttachmentsEach one wrapped in a step of its ownOn the result, or on the step they were attached in
Retries and historyOne result file per attempt; the report generator groups themEvery attempt is a result of the run; the case keeps its history across runs
Watch modeResult files pile up in resultsDirOne run for the whole session
Case fieldsLabels on every resultFields and tags of the case a report creates; an existing case keeps its own

Not ported from Allure:

AllureWhy
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 hooksHooks are how a test runs, not what it specifies; wrap what matters in probara.step()
broken statusA guess from the error’s type; Probara keeps four statuses and the whole error
categories, known, muted and flaky flagsTriage 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 parametersParameters 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 statusA 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
listenersNo hook changes a result before it is sent; statusMapping and statusFilter cover statuses
ALLURE_LABEL_* global labelsTag the run with run.tags instead; case tags come from probara.tags() in the test

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.

Terminal window
# Before
npx jest --ci --reporters=default --reporters=jest-junit
trcli -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-run

With the reporter, the test step is enough:

Terminal window
npx jest --ci
trcliProbara
-h (host), -u, -p, -kbaseUrl for a self-hosted Probara; PROBARA_API_TOKEN
--project (a name)projectId (PROBARA_PROJECT), the Project ID
--title, --run-descriptionrun.name, run.description
--run-idrun.ulid (PROBARA_RUN_ULID)
--plan-id, --config-ids, --milestone-idrun.plan, run.configurations, run.milestone, by display id or name
--close-runThe 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-idsuiteUlid (PROBARA_SUITE_ULID): the suite created cases go under
--case-fields type_id:3probara.fields({ type: 'Regression' }) in the test, by name
-a, --assign (emails)assignFailedTo (PROBARA_ASSIGN_FAILED_TO)
-b, --batch-sizechunkSize (1 to 500)
-t, --timeout (seconds, per request)timeoutMs (milliseconds, per request)
--proxy, --noproxyHTTPS_PROXY and NO_PROXY with NODE_USE_ENV_PROXY=1
-v (verbose)debug (PROBARA_DEBUG)
testrail_result_comment propertyprobara.comment()
testrail_attachment propertyprobara.attach()
testrail_result_step propertyprobara.step()

jest-junit writes no testcase properties, so the last three rows were out of reach from Jest before: the helpers make them possible.

Topictrcli on jest-junitProbara
Skipped testsRetestSkipped
test.todoPassed (jest-junit writes it as passed)Skipped, with the note Todo
RetriesThe last attempt onlyEvery attempt
Nested describesNot supported: one section per test suiteThe suite of a created case is its file; probara.suite() names nested suites
DurationsRounded to seconds (--allow-ms for milliseconds)Milliseconds, and the start of each attempt
An unknown case idAborts the uploadThat result is not recorded, and the log names it; the others are
A failed uploadRolls back the run, cases and sections it createdNothing is rolled back; a results file keeps what was not sent
Shardsadd_run -f run.yml, then -c run.yml in every shardprobara run create, PROBARA_RUN_ULID in every shard, probara run close

Not ported from TestRail:

trcliWhy
--update-existing-cases, --update-strategy, refsA report never changes an existing case: people own what is written there
custom_automation_id overrideThe 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_fieldProbara results have no custom fields: parameters (probara.parameters()) and links (probara.link()) carry the extra data
--test-run-refThe run records its CI build URL, branch and commit; issues link from results (probara.issue())
--insecureTLS verification cannot be turned off; give Node your certificate authority instead
The bdd, multisuite and saucectl parsers, quality_ratingJest has no Gherkin scenarios; a run spans one project, and projects splits by case id
Rollback on failureA result recorded is kept: rolling back would lose the ones that went through

@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 in package.json (or keep the lockfile) and upgrade on purpose.
  • 1.0 and later: breaking changes only in a major version.
  • @probara/jest-reporter depends on @probara/core of the same release line; install the reporter alone and let it bring its core.

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 keyIncludesFile on 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 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 directory jest runs in), when keyIncludesFile is on, as jest-junit writes it with JEST_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.

NeedsSupported
jest29.6 and later (the peer range): tested on 29.7 and the latest 30
Node.js22.12 and later (the engines field)
A runnerjest-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:

JestWhat changes
29A test.todo or skipped test gets the start time of its file. jest.retryTimes never retries a test.concurrent test
30.0 to 30.4A 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 laterEvery 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