Files from JUnit & Allure
A report your runner already writes arrives in Plune whole — Bring an existing suite covers the results themselves. From 0.17.1 the files a test left behind travel with them: a screenshot, a trace, a log, the answer of an API.
Two formats carry those files, and both are read by the same command:
| What your runner writes | How a file is named |
|---|---|
| JUnit XML | a [[ATTACHMENT|path]] line the test itself prints |
an allure-results folder | the source of an attachment Allure wrote |
Neither needs a package installed into your test suite beyond the adapter your runner already uses, and neither needs a provider key.
Attachments in JUnit XML
Section titled “Attachments in JUnit XML”JUnit XML has no field for a file, so the runners that keep one print a line of their own in the test’s output, the way Jenkins and GitLab read it:
[[ATTACHMENT|path]]It goes on a line by itself, in the test’s system-out or system-err. plune run import reads
that line in every test case and sends the file the way the Playwright reporter sends one. Any
extension is read, and a relative path counts from the folder of the report — so run the import
where the report and its files were written.
-
Print the marker from the test, after writing the file:
# pytest — junit_logging=system-out is what puts the output into the reportdef test_checkout(page):page.screenshot(path="artifacts/checkout.png")print("[[ATTACHMENT|artifacts/checkout.png]]")// JUnit 5 — Maven Surefire writes the test's output into its testcase@Testvoid checkout() {// ...System.out.println("[[ATTACHMENT|target/artifacts/checkout.png]]");} -
Produce the report and import it:
Terminal window pytest --junitxml=report.xml -o junit_logging=system-outplune run import report.xml
Check that the runner really carries the test’s own output into its testcase — Gradle writes it
only with reports.junitXml.outputPerTestCase = true, and Maven Surefire moves it out of the XML when
redirectTestOutputToFile is true. Without that, the marker never reaches the report and no file is
sent.
Where the files go
Section titled “Where the files go”Through the same path the reporter uses, and the same rules:
- With a bucket connected to the project, every file goes there — see Files in your own
bucket. The first time a run finds the bucket it says so, and the summary reads
N file(s) uploaded to your bucket. - Without one, a PNG, JPEG, WebP, JSON or plain text goes to the test’s result, where a person opens it from the result’s panel. Any other file — a trace, a video — needs the bucket and is counted as not stored without one, which the log says once.
A file whose name starts with an underscore is skipped, and a file several tests name is uploaded once per test.
A file that is not there
Section titled “A file that is not there”A cleaned-up workspace, a typo, a screenshot that was never taken: it does not fail the import, and
every result still goes to Plune. Each such file gets a line — always, not only with --verbose —
and the end of the output counts them:
plune: attachment not found — "shop.Cart › rejects a negative quantity": artifacts/missing-trace.zipImported 120 results · 34 files uploaded · 2 files not foundA CI step that should go red for it passes --fail-on-missing-attachments: exit code 1, with the
results already sent.
Allure results
Section titled “Allure results”Every Allure adapter — pytest, JUnit 5, TestNG, Playwright, Cypress, Jest, Vitest, WebdriverIO, .NET —
writes the same folder: a *-result.json per test, a *-container.json per suite, and the files the
tests attached. Point plune run import at the folder; a folder is read as Allure, no flag needed.
plune run import ./allure-resultsEach test arrives with:
- its name and the status as Allure shows it —
passed,failed,broken,skipped, read as it is and mapped by the project’s status map, as for every other source; - its time, and the failure message and trace from
statusDetails; - its steps, as the test declared them;
- its files — from the test, from its steps and from its fixtures (
befores,afters), each with thetypeAllure wrote.
A test run again — the same historyId — is one test with attempts, and the last one counts.
Where the files go
Section titled “Where the files go”The way a JUnit file goes: to your bucket when the project has one, otherwise to
the result for a picture or a text. A source counts from the folder, and one that leaves it is
refused. A missing file, --fail-on-missing-attachments and the summary line all work as they do for
JUnit above.
pytest (allure-pytest)
Section titled “pytest (allure-pytest)”pip install allure-pytestpytest --alluredir=allure-resultsplune run import allure-resultsimport allure
def test_checkout(page): with allure.step("pay"): allure.attach.file("artifacts/checkout.png", name="checkout", attachment_type=allure.attachment_type.PNG)Java, JUnit 5 (allure-junit5, with the AspectJ agent from Allure’s setup)
Section titled “Java, JUnit 5 (allure-junit5, with the AspectJ agent from Allure’s setup)”The results folder is target/allure-results unless allure.results.directory says otherwise:
mvn testplune run import target/allure-results@Testvoid checkout() { Allure.addAttachment("checkout", "image/png", new FileInputStream("target/artifacts/checkout.png"), ".png");}Matching a test to its case
Section titled “Matching a test to its case”Tests are matched to Plune cases by the same key as a JUnit report’s, classname#name, where the
adapter’s names allow it — pytest, JUnit 5, Playwright. For Vitest, Jest and Cypress the key is the
runner, the file and the test’s full name (jest:test/cart.test.js#cart adds an item). A file whose
name starts with _ is not sent: the core hides it as it does in Playwright’s own reports, and Allure
has no such convention, so name attachments without a leading underscore.
- name: Test run: pytest --junitxml=report.xml -o junit_logging=system-out
- name: Report the run to Plune if: always() continue-on-error: true env: PLUNE_TOKEN: ${{ secrets.PLUNE_TOKEN }} PLUNE_ENV: ci run: npx -y @plune-ai/cli run import report.xmlTwo choices worth copying, both from Bring an existing suite:
if: always() because a failed run is the one worth recording, and continue-on-error because a
hiccup on our side must never turn your build red.