Skip to content

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 writesHow a file is named
JUnit XMLa [[ATTACHMENT|path]] line the test itself prints
an allure-results folderthe 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.

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.

  1. Print the marker from the test, after writing the file:

    # pytest — junit_logging=system-out is what puts the output into the report
    def 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
    @Test
    void checkout() {
    // ...
    System.out.println("[[ATTACHMENT|target/artifacts/checkout.png]]");
    }
  2. Produce the report and import it:

    Terminal window
    pytest --junitxml=report.xml -o junit_logging=system-out
    plune 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.

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 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.zip
Imported 120 results · 34 files uploaded · 2 files not found

A CI step that should go red for it passes --fail-on-missing-attachments: exit code 1, with the results already sent.

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.

Terminal window
plune run import ./allure-results

Each 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 the type Allure wrote.

A test run again — the same historyId — is one test with attempts, and the last one counts.

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.

Terminal window
pip install allure-pytest
pytest --alluredir=allure-results
plune run import allure-results
import 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:

Terminal window
mvn test
plune run import target/allure-results
@Test
void checkout() {
Allure.addAttachment("checkout", "image/png", new FileInputStream("target/artifacts/checkout.png"), ".png");
}

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

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