Share an Appium report

Appium does not produce reports. It drives devices; the report comes from whatever runner is on top — WebdriverIO, pytest, TestNG, Allure, Robot Framework.

Which means the sharing problem is not "convert Appium's output". It is that a mobile test failure without its evidence is unfalsifiable:

NoSuchElementException: An element could not be located on the page
using the given search parameters: ~submit-button

That message is compatible with: the element is genuinely missing, a system permission dialog is covering it, the keyboard is over it, the app is on a different screen because the previous step silently no-opped, the device rotated, or the app crashed and this is the home screen.

The screenshot distinguishes all six in one glance. So capture it.

Capture the evidence

# pytest
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
    outcome = yield
    report = outcome.get_result()
    if report.when == "call" and report.failed:
        driver = item.funcargs["driver"]
        driver.save_screenshot(f"reports/screenshots/{item.name}.png")
        with open(f"reports/logs/{item.name}.log", "w") as f:
            for entry in driver.get_log("logcat"):
                f.write(f"{entry['message']}\n")
// WebdriverIO
afterTest: async function (test, context, { passed }) {
  if (!passed) await browser.saveScreenshot(`./reports/${test.title}.png`);
}

Then generate the report as normal — pytest --html=reports/report.html, allure generate, the WDIO HTML reporter, whichever you use.

Publish the folder, not the file

This is the step that usually goes wrong. The report references screenshots by relative path, so:

  • Sending report.html alone gives the reader broken image icons where the evidence should be.
  • Zipping it makes them unpack a folder and find the entry point.

Drag the whole reports/ folder (or a zip of it) into the app:

  • report.html becomes the report body.
  • The screenshots and device logs upload alongside it as assets, and their relative references are rewritten to the uploaded copies — so the failure screenshot renders inline at the URL.

From CI, per device

Mobile suites run across a device matrix, and the failures that matter are almost always specific to one cell. Keep a report id per configuration:

pytest --html=reports/report.html --self-contained-html || TESTS_FAILED=1

curl -fsS -X PATCH "https://commareports.com/api/v1/reports/$REPORT_ID" \
  -H "Authorization: Bearer $COMMA_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --rawfile html reports/report.html \
        --arg t "Appium — $DEVICE_NAME / $PLATFORM_VERSION" \
        '{title: $t, html: $html}')"

exit "${TESTS_FAILED:-0}"

Run it with if: always() so a red run still publishes — a mobile suite's red runs are the only ones anybody wants to look at.

A stable URL per device turns "only fails on Android 13" from an assertion into a link with a revision history behind it, which is what you need when the question is "did it start with the OS update or with our release?"

What review adds

  • Anchored threads on the failing step, next to its screenshot — see commenting on HTML.
  • Revisions, so a flaky element locator has a visible history.
  • Access per report — screenshots of a real app often contain test-account data and unreleased UI. See the sharing model.

Limits

  • Entry HTML: 5 MB. Assets: 25 MB per file, 250 MB and 500 files total. Screenshots are the constraint — capture on failure only, not on every step, or a long suite will hit the file count.
  • 60 requests/minute per token.

Try it

Comma is free — unlimited reports, unlimited commenters, unlimited revision history.

Publish a test report →

Related