Skip to content

Migrate to the Playwright reporter

This page maps playwright-qase-reporter to the Probara reporter option by option and call by call, shows how to switch from probara import junit without losing a case, and explains how the reporter is versioned.

Moving from playwright-qase-reporter is mostly renaming: the reporter registration, the settings, and the qase.* calls.

  1. Swap the packages:

    Terminal window
    npm uninstall playwright-qase-reporter && npm i -D @probara/playwright-reporter
  2. In playwright.config, replace ['playwright-qase-reporter', {...}] with ['@probara/playwright-reporter', {...}], translating the options (below). Delete qase.config.json: the reporter reads only its options and PROBARA_* variables.

  3. Replace the QASE_* variables with PROBARA_* ones in CI: at least PROBARA_API_TOKEN (an app token from the Playwright card, see get started) and PROBARA_PROJECT.

  4. Replace import { qase } from 'playwright-qase-reporter' with import { probara } from '@probara/playwright-reporter', and translate the calls (below). Case ids become Probara display ids (SHOP-12), not numbers.

Before:

playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
reporter: [
['list'],
[
'playwright-qase-reporter',
{
mode: 'testops',
testops: {
project: 'SHOP',
run: { title: 'Nightly', complete: true, tags: ['nightly'] },
uploadAttachments: true,
},
captureLogs: true,
},
],
],
});

After:

playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
reporter: [
['list'],
[
'@probara/playwright-reporter',
{
projectId: 'SHOP',
run: { name: 'Nightly', tags: ['nightly'] },
captureOutput: true,
},
],
],
});
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', fallbackresultsFile (PROBARA_RESULTS_FILE)
mode: 'testops_multi', testops_multi.*projectId plus projects and run.ulids
debug (QASE_DEBUG)debug (PROBARA_DEBUG)
environment (QASE_ENVIRONMENT), a slugrun.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)captureOutput (PROBARA_CAPTURE_OUTPUT)
statusMapping (QASE_STATUS_MAPPING)statusMapping (PROBARA_STATUS_MAPPING), without invalid
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)
testops.api.retries, testops.api.retryBackoffmaxRetries; 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.completecloseRun (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)
testops.batch.sizechunkSize (1 to 500)
framework.browser.addAsParameterNothing to set: the Playwright project is sent as a project parameter, not part of the key

Every Probara option is described in the configuration reference. Qase settings without a counterpart are listed under not ported.

QaseProbara
test(qase(12, 'pays'), ...)test('SHOP-12 pays', ...), or an annotation
test(qase([12, 13], 'pays'), ...)test('pays', { annotation: { type: 'probara_case', description: 'SHOP-12, SHOP-13' } }, ...)
{ type: 'QaseID', description: '12' } annotation{ type: 'probara_case', description: 'SHOP-12' }
(Qase ID: 12) in a titleSHOP-12 in a title
qase.id(12)probara.id('SHOP-12')
qase.title('...')probara.title('...')
qase.suite('Payments\tCards'), a QaseSuite annotationprobara.suite(['Payments', 'Cards'])
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] })await probara.attach({ name, path }), once per file
qase.attach({ name, content, contentType })await probara.attach({ name, body, contentType })
qase.step('Pay', 'Paid', 'card=visa')probara.step('Pay', 'Paid', 'card=visa')
qase.projects({ WEB: [3] }), qase.projectsTitle(...), a QaseProjects annotationWEB-3 as a case id, with projects: ['WEB']

Before:

tests/checkout.spec.ts
import { test } from '@playwright/test';
import { qase } from 'playwright-qase-reporter';
test(qase(12, 'pays with a card'), async () => {
qase.fields({ severity: 'critical' }).tags('smoke');
await test.step(qase.step('Pay', 'The order is paid'), async () => {});
qase.attach({ name: 'order.json', content: '{"id":1042}', contentType: 'application/json' });
});

After:

tests/checkout.spec.ts
import { test } from '@playwright/test';
import { probara } from '@probara/playwright-reporter';
test('SHOP-12 pays with a card', async () => {
probara.fields({ severity: 'critical' }).tags('smoke');
await test.step(probara.step('Pay', 'The order is paid'), async () => {});
await probara.attach({
name: 'order.json',
body: '{"id":1042}',
contentType: 'application/json',
});
});

What Probara receives:

[
{
"caseDisplayId": "SHOP-12",
"automationKey": "checkout.spec.ts > pays with a card",
"steps": [{ "action": "Pay", "expected": "The order is paid" }],
"case": { "tags": ["smoke"], "fields": { "severity": "critical" } }
}
]
TopicQaseProbara
PrecedenceIts code lets variables win over qase.config.json, which wins over the reporter options (its docs say the opposite)Options in playwright.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 playwright.config
Case idsNumbers, per project mappingDisplay ids (SHOP-12): the Project ID routes each id to its project
test.fail()Ignored: an expected failure is sent as failed (or invalid), an unexpected pass as passedPlaywright’s verdict: an expected failure passes, an unexpected pass fails (statuses)
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
Interrupted attemptsfailed (its docs say skipped)failed
Case fieldsSent with every resultOnly when the report creates the case: an existing case keeps what people wrote
fields() callsThe last call replaces the othersMerged by name
Closing the runOnly with run.complete: trueA 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 idmerge-reports, or probara run create and run close (sharding)
When reporting failsfallback to a local reportA results file, sent later by probara import results into the same runs
Strings in attach()A string that looks like base64 is decodedA string is sent as it is; pass a Buffer for binary content
Error of a resultThe error context text in a field of its ownEvery error in the notes; error-context.md as a file
Flaky attemptsframework.markAsFlaky flags a passing retryEvery attempt is a result; no flag (retries)
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 test process’s HTTP calls as steps, which is noise for browser tests; attach what matters with probara.attach()
Jira link on runs (run.externalLink)Probara links Jira issues to cases and defects, where traceability lives
A separate error field (error_context)Errors go in the notes, and the error context is attached as a file, so nothing is cut at a field’s size
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
framework.markAsFlakyEvery attempt is a result: the history shows flakiness without a flag
Logging to a file (logging.file)Logs go to stderr, where CI keeps them; redirect stderr to keep a file

The reporter builds the same automation keys the Probara CLI gives Playwright’s JUnit reporter (--reporter=junit), and reads the same links: probara_case annotations (which the JUnit reporter writes as properties) and case ids in titles. A project that imported Playwright’s JUnit XML with probara import junit can switch and keep every case:

  1. Keep rootDir (or testDir) as it was: the JUnit reporter writes file paths relative to it.
  2. Replace the JUnit import step with the reporter in playwright.config, and keep PROBARA_API_TOKEN and PROBARA_PROJECT in the test step’s environment. Store an app token from the Playwright card there, so the new runs show the Playwright app.
  3. Check the keys before you switch: probara import junit junit.xml --dry-run prints the key of each test, and a results file written by the reporter with reporting off prints its own the same way (check what would be sent).

What the reporter sends on top of the JUnit import: every attempt as its own result, steps, attachments of steps, metadata, and the files Playwright keeps (videos, traces).

@probara/playwright-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.
  • The 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, what gets attached).
  • A probara.* helper 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. A different key unlinks the test from its case: the next run creates a new one.
  • Dropping a Playwright or Node.js version that is still supported upstream.

Adding an option, a helper, a log line or an uploaded file 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 Playwright test becomes the key’s parts (the file relative to rootDir, the describes and the title, the project), the same way probara import junit reads Playwright’s JUnit XML (automation keys). 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: run the suite with reporting off once per version, each with its own results file (PROBARA_RESULTS_FILE=before.json, then after.json, neither there beforehand), and diff the two dry runs (check what would be sent). Any changed key is a test that would get a new case.

NeedsSupported
@playwright/test1.42 and later (the peer range): tested on 1.42 and the latest
Node.js22.12 and later (the engines field)

Some features depend on the Playwright version: files of steps and skipped steps need 1.50, and the error context of a failed attempt needs 1.51 (Playwright versions). Nothing fails on an older Playwright of the range: those details are just not there.

  • 0.1.0 (2026-09-30) is the first version, and the first one published on npm: there is nothing to migrate from.

Next: get started, or the configuration reference.