Automation Reporting and Debugging

Automation reporting is how a test run tells people what happened: which tests ran, which failed, how long they took and what evidence each failure left behind. Debugging a failed test is the work that follows: reading that evidence to find out why it failed and whether the cause lies in the product, the test or the environment.

For example, a nightly run reports that VAT is added to the order total failed: expected 172.50 SAR, received 150.00 SAR. The screenshot, trace and server response saved with the failure show, without re-running anything, that the server stopped applying VAT.

Why it matters

A suite is only as useful as the time it takes to act on a red result. If a failure leaves only expected true, received false, someone must re-run the test and guess the CI conditions; teams then re-run until the build is green, and real regressions pass with the noise.

Good evidence turns a failure into a short investigation: the screenshot and the trace usually show whether to fix the test, call the environment owner or report a defect. It also makes a bug report actionable, with the failing request, the response and the trace attached instead of “checkout test failed”.

Reports: one run, several readers

One run has several readers. Whoever started it reads the console. Whoever investigates a failure needs an HTML report that opens each test into its steps, error, retries and attachments. The CI server needs a machine-readable file, usually JUnit XML, to show results per test and keep their history. In Playwright, list the formats you need under reporter.

A good report answers, without asking anyone, what failed, where, in which environment and with what evidence. Name tests after the behaviour they check, wrap business-level actions in test.step() so the report reads like a test case, and attach data the screen does not show.

  • Run context: build or commit, browser, base URL and start time, to match a failure with deployments and server logs.
  • Flaky tests (failed, then passed on retry) counted apart from passed ones; otherwise the report hides them.
// playwright.config.ts (excerpt): one run, three readers
import { defineConfig } from '@playwright/test';

export default defineConfig({
  reporter: [
    ['list'],                                        // the terminal
    ['html', { open: 'never' }],                     // people: playwright-report/
    ['junit', { outputFile: 'results/junit.xml' }],  // the CI server
  ],
});

// checkout.spec.ts: steps and attachments make the report read like a test case
import { test, expect } from '@playwright/test';

test('VAT is added to the order total', async ({ page }, testInfo) => {
  const cart = await test.step('Create a cart with one 150.00 SAR book', () =>
    createCart(page, { sku: 'BOOK-01', qty: 1 }));     // createCart: a project helper
  await testInfo.attach('cart', {
    body: JSON.stringify(cart), contentType: 'application/json',
  });
  await test.step('Open checkout and check the total', async () => {
    await page.goto('/checkout/' + cart.id);
    await expect(page.getByTestId('order-total')).toHaveText('172.50 SAR');
  });
});

Try it: XML Formatter

Screenshots, video and traces: what each one shows

Keep evidence for failed tests only: each kind answers a different question and costs run time and storage.

  • Screenshot: the page at the moment of failure, answering “what did the user see?”: an error banner, a spinner, a dialog over the button. Playwright: screenshot: 'only-on-failure'; Cypress takes one automatically when a test fails in cypress run; Selenium: call takeScreenshot() (JavaScript) or getScreenshotAs (Java) in the runner’s failure hook.
  • Video: the whole test, showing order and movement such as a dialog that flashed, but no DOM, network or console data. Files are large: in Playwright use video: 'retain-on-failure'; Cypress records only when its video option is enabled (off by default).
  • Trace: a recording you step through. A Playwright trace holds each action with DOM snapshots before and after it, the call log, console, network requests and responses, and the source line. Open it with npx playwright show-trace, from the HTML report, or at trace.playwright.dev, which loads it in your browser without uploading it.
  • Logs: runner output, browser console and the server logs of the same time window, matched by timestamp or request id.
  • Evidence is data: a trace can hold request headers, cookies and everything the test typed, so use synthetic data and let artifacts expire.
// playwright.config.ts (excerpt): keep evidence for failures only
import { defineConfig } from '@playwright/test';

export default defineConfig({
  retries: process.env.CI ? 1 : 0,
  use: {
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
    trace: 'on-first-retry',   // needs retries; with none, use 'retain-on-failure'
  },
});

// Then, in a terminal, open a trace downloaded from a CI run:
//   npx playwright show-trace trace.zip

Debugging a failed test: from the first error to the cause

ISTQB separates testing, which shows failures, from debugging, which finds, analyses and removes their causes. For a failed automated test, the tester’s part is failure analysis: is the defect in the product, the test code or the environment? The tester debugs the test code; a developer debugs the product, starting from the evidence.

Read the first error, not the last; later errors are usually consequences. Its type tells you which artifact to open. A setup error means the test never reached its check: read the hook’s error and the server log. A timed-out action means an element could not be found or used: open the DOM snapshot before that action and its call log. A failed assertion means the page showed something else: compare the screenshot with the expected value, then check in the network tab whether the data arrived wrong or late.

Debugging one failed test from its evidence: the type of the first error picks the artifact to open, and the screenshot, DOM snapshot and network response separate test problems, environment problems and front-end or back-end defects.
Question 1: What type is the first error?Read the first error in the report, not the last one
  • Answer: Setup: a setup step, hook or fixture failed

    Outcome: Setup failure: data, sign-in or a serviceThe test never reached its check. Read the hook’s error and the server log before suspecting the feature.
  • Answer: Action timeout: a click, fill or wait ran out of time

    Question 2: In the DOM snapshot before the action, is the element there?Open the trace at that action and read its call log
    • Answer: No: an error page, a spinner or an empty area

      Outcome: The page did not load: environment or productA failed or slow request in Network, or a script error in Console, explains it. Not a locator problem.
    • Answer: Yes, but its role, text or test id changed

      Outcome: Outdated locator or an unannounced UI changeCheck the requirement: update the test if the change is intended, report it if not.
    • Answer: Yes, but covered, disabled or still moving

      Outcome: The call log names the blockerWait for the app’s own state or handle the overlay in setup, never force the action; report a UI defect if users are blocked too.
  • Answer: Assertion: expected vs received differ

    Question 3: Does the screenshot show the value the test received?
    • Answer: No: it shows the expected value

      Outcome: Test problem: checked too early or the wrong elementUse a retrying assertion and a locator that points at the right element.
    • Answer: Yes: the page shows the wrong value

      Question 4: In the trace’s Network tab, how did the response arrive?
      • Answer: Wrong: the server sent the wrong data

        Outcome: Back end: a defect, wrong test data or a changed settingCompare the response with the last green run; report it with the request, the response and the build range.Worked example
      • Answer: Right, but after the assertion timed out

        Outcome: Slow response: overloaded environment or a performance regressionDo not raise the timeout; compare the time with the last green run and read the server log for that request.
      • Answer: Right and on time

        Outcome: Front-end defectThe page displays correct data wrongly; report it with the screenshot and the trace.

For one failure whose evidence you have. A test that passes when re-run without any change goes to the flaky-test process first.

  • Reproduce locally: npx playwright test --debug steps through the test in the Playwright Inspector, --ui opens UI mode with a time-travel view of each run, and await page.pause() stops at a chosen line in a headed run. Change one thing at a time, as in the lesson on reproducing failures.
  • Do not raise a timeout before you know why the time ran out: a slower response can itself be the defect.

Try it: Text Diff Try it: Timestamp Converter

Worked example: a wrong order total in the nightly run

The nightly UI run on staging, build 512, reports one failure; its retry failed too, so it is failed, not flaky. The report and trace excerpts are below, shortened.

The first error is an assertion: the element was found but showed the wrong value. The screenshot shows VAT 0.00 in the cart, so the test read what the page displayed. In the trace, the response to GET /api/cart/8812 already contains "vatRate": 0: the browser rendered what the server sent. The last green run, build 509, kept no trace, but its report still holds the cart JSON the test attaches in every run, with "vatRate": 0.15; a JSON diff of the two shows that only vatRate and total changed.

So the cause is a back-end change between builds 509 and 512 or a tax setting changed on staging; one question to the developer or the environment owner settles which. The test is right and stays as it is. The report “Cart API returns vatRate 0 for a Saudi cart since build 512” carries both responses, the trace and the build range.

✘ checkout.spec.ts:42 › checkout › VAT is added to the order total
  failed (retry #1 failed too) · chromium · staging · build 512

  Error: expect(locator).toHaveText(expected)
  Locator:  getByTestId('order-total')
  Expected: "172.50 SAR"
  Received: "150.00 SAR"
    at checkout.spec.ts:51

  Attachments: cart · screenshot · video · trace

Build 512 › trace › Network › GET /api/cart/8812 → 200
  { "subtotal": 150.00, "vatRate": 0,    "total": 150.00 }

Build 509 (last green run, no trace kept) › HTML report › attachment "cart"
  { "subtotal": 150.00, "vatRate": 0.15, "total": 172.50 }

Try it: JSON Diff

Common mistakes

  • Re-running a red test until it passes without reading the evidence of the failed attempt.
  • Raising a timeout or adding a sleep before knowing why the time ran out.
  • Changing the expected value to whatever the application now shows, without checking the requirement.
  • Recording video and traces for every passing test, or recording nothing and leaving CI failures with only a message.
  • Sharing reports and traces that contain real customer data, passwords or tokens.

The QA perspective

On most teams the tester takes the first look at a red automated run: classify each failure the same day, link product failures to bug reports with the trace attached, fix test problems and pass environment problems to their owner.

Report evidence, not conclusions nobody can check: the failing step, expected and received values, request and response, build range and a link to the trace. Ask what the evidence cannot answer, such as whether a UI change was intended.

Practice exercise

Classify three failed tests from a nightly report

Your task

The nightly run below has three failed tests, each with its first error and what the screenshot and the trace show. Classify each one with the decision tree from this lesson and decide what you do next.

Material
Nightly UI run · build 731 · chromium · staging · 212 passed, 3 failed, 0 flaky

1) login.spec.ts:18 › login › shows the dashboard after sign-in
   Error: locator.click: Timeout 10000ms exceeded.
   Call log: getByRole('button', { name: 'Sign in' }): visible, enabled and stable
             <div class="cookie-banner"> intercepts pointer events, retrying click
   Screenshot: a new cookie banner covers the lower half of the sign-in form

2) profile.spec.ts:44 › profile › saves a new phone number
   Error: expect(locator).toHaveText(expected)
   Expected: "Saved"    Received: "Saving..."    Timeout: 5000ms
   Trace: PUT /api/profile → 200 after 7.8 s
   Server log, last green run (build 728): the same request took 0.4 s

3) orders.spec.ts:71 › orders › lists the newest order first
   Error: expect(locator).toHaveText(expected)
   Expected: "ORD-1042"    Received: "ORD-1039"
   Screenshot: the list shows ORD-1039 in the first row, ORD-1042 in the second
   Trace: GET /api/orders?sort=newest → [ORD-1039 (10:02), ORD-1042 (10:05)]
Steps
  1. Name the type of the first error: setup, action timeout or assertion.
  2. Name the artifact that answers the next question and what it shows.
  3. Classify: test problem, environment problem, front-end or back-end defect, or not yet decided.
  4. Write the next action: the test change, the bug title or the question to ask.
Show the model answer
Model answer

Test 1, action timeout: the button is ready, but the call log and the screenshot show a new cookie banner over it. If the banner is intended, it is a test problem: set or accept consent in the shared setup and never force the click. Also ask whether users on small screens face the same covered button; if so, report a UI defect.

Test 2, assertion: Saving... shows the page was still waiting. The trace says why: the save request succeeded, but late, after 7.8 s, against 0.4 s in build 728 and a 5 s assertion timeout. Raising the timeout would hide a slowdown users feel. Not yet decided: the server logs for that request, found by the trace time, tell an overloaded staging environment from a regression in build 731.

Test 3, assertion: the page displays what it received, and the trace shows that the API itself returned ORD-1039 (10:02) before ORD-1042 (10:05) for sort=newest. The back end sorts in the wrong direction: report “Orders API with sort=newest returns the oldest order first” with the request, the response and the trace.

Frequently asked questions

Should I record traces and video for every test?

Usually not: recording everything slows the run and produces large files nobody opens. A common setup is one retry in CI with trace: 'on-first-retry', or 'retain-on-failure' when there are no retries (with zero retries, on-first-retry records nothing), plus video and screenshots only on failure. Turn on full recording only while hunting a specific problem.

Is debugging the tester’s job or the developer’s?

Both, for different defects. ISTQB treats testing and debugging as separate activities: testing shows failures; debugging finds, analyses and removes their causes. The tester analyses an automated failure far enough to show where the defect is and debugs the test code; a developer debugs the product code, starting from the evidence the tester attached.

I use Selenium or Cypress. Does this still apply?

Yes; only the recording differs. Cypress takes screenshots on failure in cypress run, records video when the video option is enabled, and shows a snapshot per command in the Command Log of cypress open. Selenium WebDriver records nothing by itself: take a screenshot and save the page source in the runner’s failure hook, and log each step with a timestamp. The reading order stays the same: first error, then evidence, then comparison.

Where do I start when many tests fail in the same run?

Look for a shared cause first. If many tests fail with the same first error at the same step, such as sign-in or a setup hook, the cause is usually the environment or a shared fixture, not each test. Fix or report it once and re-run; the CI/CD testing guide has a triage flow for pipeline-level failures.

References

Practise with Mutqan tools

Compare two JSON documents semantically and highlight added, removed and changed values.

JSON & Data

Compare two texts line by line or word by word and highlight differences.

Formatting

Pretty-print XML with configurable indentation, and minify it back.

Formatting

Convert timestamps (seconds, milliseconds, microseconds) to human dates and back, in any timezone.

Utilities

Explain stack traces and error messages, identify likely root causes and next debugging steps with Mutqan AI.

Bug Analysis AI

Go deeper

Your progress on this topic

Part of the QA Roadmap