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.
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=***¤cy=EUR ▲ 1.83s
│ ← 500 Internal Server Error {"error":"payment gateway timeout"}
│
│ curl -X POST 'https://api.shop.test/v1/checkout?token=***¤cy=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 ≥1sSet it up
Register the reporter after list, so its lines follow each result line:
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 leastslowThresholdMs(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 atbodyPreviewCharswith…. 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:
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
afterEachcleanup, steps after a failedexpect.soft) are never "the last call". - The curl is masked like every other one:
***on the console,${VAR}placeholders incurl.sh.details: 'never'leaves the block out andcurlFormat.file: falsethe file. - It is independent of
curl(it works withcurl: '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:
#!/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}"'¤cy=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.
curl: 'always' meansEvery 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, withNO_COLOR,FORCE_COLOR=0orTERM=dumb, or when the output is not a terminal.FORCE_COLORalone does not colour CI logs. - To force them:
colors: truein the fixture'sapiLog(the fixture records it in the JSON attachment, so the reporter colours its output too),colors: trueon the reporter, orcurlFormat.colors: truefor what the fixture prints without the reporter.colors: falsekeeps the output plain. The reporter's owncolorswins overapiLog.colors;'auto'(the default) leaves it to the fixture. Nothing set: unchanged. - An explicit
trueis an override, as everywhere in this package: it also beatsNO_COLORand 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:
200and[404].
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:
use: { apiLog: { colors: !process.env.CI } },
Or on the reporter alone, leaving apiLog as it is:
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
setImmediatebefore 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_COLORset 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.