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.
From Qase
Section titled “From Qase”Moving from playwright-qase-reporter is mostly renaming: the reporter registration, the settings,
and the qase.* calls.
-
Swap the packages:
Terminal window npm uninstall playwright-qase-reporter && npm i -D @probara/playwright-reporter -
In
playwright.config, replace['playwright-qase-reporter', {...}]with['@probara/playwright-reporter', {...}], translating the options (below). Deleteqase.config.json: the reporter reads only its options andPROBARA_*variables. -
Replace the
QASE_*variables withPROBARA_*ones in CI: at leastPROBARA_API_TOKEN(an app token from the Playwright card, see get started) andPROBARA_PROJECT. -
Replace
import { qase } from 'playwright-qase-reporter'withimport { probara } from '@probara/playwright-reporter', and translate the calls (below). Case ids become Probara display ids (SHOP-12), not numbers.
Before:
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:
import { defineConfig } from '@playwright/test';
export default defineConfig({ reporter: [ ['list'], [ '@probara/playwright-reporter', { projectId: 'SHOP', run: { name: 'Nightly', tags: ['nightly'] }, captureOutput: true, }, ], ],});Options and variables
Section titled “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 | 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 | 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) | captureOutput (PROBARA_CAPTURE_OUTPUT) |
statusMapping (QASE_STATUS_MAPPING) | statusMapping (PROBARA_STATUS_MAPPING), without invalid |
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) |
testops.api.retries, testops.api.retryBackoff | maxRetries; 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 | 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) |
testops.batch.size | chunkSize (1 to 500) |
framework.browser.addAsParameter | Nothing 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.
The qase.* calls
Section titled “The qase.* calls”| Qase | Probara |
|---|---|
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 title | SHOP-12 in a title |
qase.id(12) | probara.id('SHOP-12') |
qase.title('...') | probara.title('...') |
qase.suite('Payments\tCards'), a QaseSuite annotation | probara.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 annotation | WEB-3 as a case id, with projects: ['WEB'] |
Before:
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:
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" } } }]What is different, and why
Section titled “What is different, 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 playwright.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 playwright.config |
| Case ids | Numbers, per project mapping | Display 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 passed | Playwright’s verdict: an expected failure passes, an unexpected pass fails (statuses) |
| 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 |
| Interrupted attempts | failed (its docs say skipped) | failed |
| Case fields | Sent with every result | Only when the report creates the case: an existing case keeps what people wrote |
fields() calls | The last call replaces the others | Merged by name |
| Closing the run | Only with run.complete: true | 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 | merge-reports, or probara run create and run close (sharding) |
| When reporting fails | fallback to a local report | A results file, sent later by probara import results into the same runs |
Strings in attach() | A string that looks like base64 is decoded | A string is sent as it is; pass a Buffer for binary content |
| Error of a result | The error context text in a field of its own | Every error in the notes; error-context.md as a file |
| Flaky attempts | framework.markAsFlaky flags a passing retry | Every attempt is a result; no flag (retries) |
Not ported
Section titled “Not ported”| 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 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.markAsFlaky | Every 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 |
From probara import junit
Section titled “From probara import junit”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:
- Keep
rootDir(ortestDir) as it was: the JUnit reporter writes file paths relative to it. - Replace the JUnit import step with the reporter in
playwright.config, and keepPROBARA_API_TOKENandPROBARA_PROJECTin the test step’s environment. Store an app token from the Playwright card there, so the new runs show the Playwright app. - Check the keys before you switch:
probara import junit junit.xml --dry-runprints 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).
Upgrading
Section titled “Upgrading”@probara/playwright-reporter follows semantic versioning. Read the
changelog
before you upgrade: every change that needs an action from you is listed there first.
Versioning policy
Section titled “Versioning policy”- 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.
- The reporter depends 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, 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 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 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.
Supported versions
Section titled “Supported versions”| Needs | Supported |
|---|---|
@playwright/test | 1.42 and later (the peer range): tested on 1.42 and the latest |
| Node.js | 22.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.
Version notes
Section titled “Version notes”- 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.