Skip to main content

Compact output & the reporter

Since 0.4.0 an API call prints one line, and a request that went wrong gets a detail block directly under it, but only when its test failed.

npx playwright test · the output of npm run demo

Running 3 tests using 1 worker

  ✓  1 [chromium] › cart.spec.ts:8:5 › adds product to cart (1.2s)
    GET    200  https://api.shop.test/v1/products?page=2&limit=20            148ms
    POST   201  https://api.shop.test/v1/cart/items                           86ms
    PUT    200  https://api.shop.test/v1/cart/items/42                        95ms
  ✘  2 [chromium] › checkout.spec.ts:21:5 › places an order (2.4s)
    GET    404  https://api.shop.test/v1/orders/99999                         70ms
    │ ← 404 Not Found  {"error":"order not found"}
    POST   500  https://api.shop.test/v1/checkout?token=***&currency=EUR   ▲ 1.83s
    │ ← 500 Internal Server Error  {"error":"payment gateway timeout"}
    │
    │ curl -X POST 'https://api.shop.test/v1/checkout?token=***&currency=EUR' \
    │   -H 'Authorization: ***' \
    │   -H 'Content-Type: application/json' \
    │   --data-raw '{"cartId":"c_812","cardToken":"***"}'
    │
    │ ↳ replay: curl.sh attached to the HTML report
  ✓  3 [chromium] › profile.spec.ts:14:5 › updates profile and clears sessions (653ms)
    PATCH  204  https://api.shop.test/v1/users/7                              65ms
    DELETE 204  https://api.shop.test/v1/sessions/old                         44ms
    GET    304  https://cdn.shop.test/assets/catalog.json                     10ms

  1 failed
    [chromium] › checkout.spec.ts:21:5 › places an order
  2 passed (5.4s)
  8 requests · 2 errors · 1 slow ≥1s

Set it up​

Register the reporter after list, so its lines follow each result line:

playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
reporter: [['list'], ['playwright-logger-pro/reporter', { /* options */ }]],
});

The test fixture (playwright-logger-pro/fixtures) records each call of a test, masked, and attaches playwright-logger-pro.json (plus the curl*.sh scripts for a failed test, or with details: 'always'). The reporter reads it when the test ends and prints it. Without the reporter the fixture prints the same lines itself, above the result line, and no total.

The request line​

METHOD STATUS URL TIMING
  • The method is padded to 7 columns; the status is 3 characters, a chip (background colour, no extra characters) for 4xx (yellow), 5xx and network errors (red, shown as ERR).
  • The host is dimmed, the path is not. Masked values are ***.
  • The timing ends at timeColumn (82). A call of at least slowThresholdMs (1000) shows ▲ 1.83s.
  • Past the terminal width, the middle of the query string is replaced by …. The host and the path are never shortened.

The detail block​

A request with status >= 400, or no response, gets a │ block when its test failed (details: 'failed-tests'). A 404 in a passing negative test keeps its chip and gets no block.

  • First line: ← 404 Not Found {"error":"order not found"}, the body as one line (JSON minified, text its first line), cut at bodyPreviewChars with …. A network error shows ← ECONNREFUSED connect ECONNREFUSED 127.0.0.1:9.
  • With curl: '5xx' (the default: 5xx and network errors) it adds the curl command and ↳ replay: curl.sh attached to the HTML report.

The call that succeeded but whose test failed​

The curl modes look at the status, so a 201 where the test asserted 200 gets no block and no curl. With curlOnFailedTest the last call made before the failure gets one, whatever its status:

playwright.config.ts
use: { apiLog: { curlOnFailedTest: 'last' } } // false (default) | 'last' (= true) | 'all'
POST 201 https://api.shop.test/v1/orders 88ms
│ ← 201 Created {"id":7,"status":"pending"}
│
│ curl -X POST 'https://api.shop.test/v1/orders' \
│ -H 'Authorization: ***' \
│ --data-raw '{"item":"book"}'
│
│ ↳ replay: curl.sh attached to the HTML report
  • The status chip keeps the success colour and the gutter is neutral: the call did succeed, the test did not.
  • 'all' does the same for every call made before the failure; the scripts are numbered (curl-1-GET-ok.sh, …).
  • Calls made after the first failure (an afterEach cleanup, steps after a failed expect.soft) are never "the last call".
  • The curl is masked like every other one: *** on the console, ${VAR} placeholders in curl.sh. details: 'never' leaves the block out and curlFormat.file: false the file.
  • It is independent of curl (it works with curl: 'never'), only for compact output, and does nothing for a passing test or one that is expected to fail.
  • Cost: a curl is built for every call (about 0.1 ms on a loopback server, nothing next to a real request) and only the last one is kept.

The curl.sh replay script​

The attachment never holds a secret. Every masked value becomes an environment variable named after its key:

curl.sh
#!/usr/bin/env bash
set -euo pipefail
: "${TOKEN:?export TOKEN first}"
: "${AUTHORIZATION:?export AUTHORIZATION first}"
: "${CARD_TOKEN:?export CARD_TOKEN first}"

curl -X POST 'https://api.shop.test/v1/checkout?token='"${TOKEN}"'&currency=EUR' \
-H 'Authorization: '"${AUTHORIZATION}" \
-H 'Content-Type: application/json' \
--data-raw '{"cartId":"c_812","cardToken":"'"${CARD_TOKEN}"'"}'

export the variables, then run it. The script has the whole body; the terminal cuts bodies over 2 KB with …. A test with several curls gets curl-{n}-{METHOD}-{lastPathSegment}.sh. A test that passed gets no script unless details: 'always'. Two masked values with the same key name share one variable.

What curl: 'always' means

Every call gets a curl in the playwright-logger-pro.json attachment. Its curl*.sh script is attached only when the test failed or with details: 'always'. The console still prints a curl only under a failed call, with status >= 400 or a network error, so a successful call keeps its single line. Open the attachment (or, when there is one, the script) to replay a successful call.

Masking​

The mask is always ***, never the length of the value. The built-in lists are listed in Configuration; add yours with mask: { headers, query, bodyKeys } (merged with them, and with the older redactHeaders, redactQueryParams and redactBodyKeys). A whole header value is masked, Bearer included.

Colours, CI and ASCII​

  • The default theme: 'ansi' uses bold plus the 16 basic colours, so it reads on dark and light terminals. 'dark' and 'light' are true-colour palettes.
  • Colours follow the rules of curlFormat.colors: 'auto': none on CI, with NO_COLOR, FORCE_COLOR=0 or TERM=dumb, or when the output is not a terminal. FORCE_COLOR alone does not colour CI logs.
  • To force them: colors: true in the fixture's apiLog (the fixture records it in the JSON attachment, so the reporter colours its output too), colors: true on the reporter, or curlFormat.colors: true for what the fixture prints without the reporter. colors: false keeps the output plain. The reporter's own colors wins over apiLog.colors; 'auto' (the default) leaves it to the fixture. Nothing set: unchanged.
  • An explicit true is an override, as everywhere in this package: it also beats NO_COLOR and CI. Register the reporter for it to work when the output is piped: the reporter writes from the runner process, whereas Playwright strips the ANSI codes from what a worker prints when its own output is not coloured.
  • Without colours the status column is 5 wide so chips still stand out: 200 and [404].
VS Code play button

The play button (and other panels that capture the output) is not a terminal, so the compact output is plain. Colour it everywhere except on CI:

playwright.config.ts
use: { apiLog: { colors: !process.env.CI } },

Or on the reporter alone, leaving apiLog as it is:

playwright.config.ts
reporter: [['list'], ['playwright-logger-pro/reporter', { colors: !process.env.CI }]],

On CI that sets colors: false, so CI logs stay plain.

  • ascii: true (automatic on legacy Windows consoles) swaps │ ← ▲ ↳ for | <- ^ ->.

The list reporter and terminals​

Printing from the worker with console.log cannot produce this layout: worker output reaches the terminal before the result line and interleaves between parallel tests. That is why a reporter in the runner process does the printing.

The list reporter behaves differently in a terminal and without one, and the reporter handles both:

  • No terminal (CI, a pipe), any number of workers: the list reporter prints each result line when its test ends. Without colours it writes through a stream wrapper that finishes a line on the next tick, so the reporter waits one setImmediate before printing, and the lines sit right under the result line. If several tests end in that same tick, their blocks carry a title line.
  • Terminal, one worker: the list reporter rewrites its line in place, counting the rows it printed. Nothing runs in parallel, so the lines go out at once, right under the result line.
  • Terminal, several workers: extra lines printed while another test is still running shift those rows, and the next rewrite lands on the wrong line, garbling the output. So the lines are held until no test is running, then printed with a title line (✓ [chromium] › cart.spec.ts:8:5 › adds product), at the latest at the end of the run. Nothing is garbled, but with many workers most blocks come after the result lines instead of under them.
  • With NO_COLOR set in a terminal, Playwright's list reporter strips its own cursor movements and glues its lines together; that is Playwright's behaviour, with or without this package.

Keeping the old output​

format: 'verbose' prints the 0.3 output (request, headers, bodies, response, curl) live, as before. See Migration.