Skip to main content

Diagnostic receipts

Diagnostic receipts are an optional, structured artifact for focused investigations. They are separate from pumpo5.log and are disabled by default.

Add this setting to src/test/resources/config.conf when you need receipts:

pn5.diagnostics.enabled=true

When enabled, Pumpo collects receipts in memory and writes one target/reports/{test-name}/diagnostics.jsonl file at the end of a test, whether the test passed or failed. Each line is one JSON object with a schemaVersion, type, operationId, status, timestamp, and fields. No file is created if no producer emits a receipt. The normal log only reports the artifact path.

The recorder limits each test to 256 receipts, each receipt to 16 scalar fields, and each field value to 256 characters. If more receipts arrive, the file ends with a recorder receipt marked truncated. Each producer has its own fixed payload schema.

Framework producers can use dev.pumpo5.logging.Diagnostics.record(...). The payload supplier runs only when diagnostics are enabled. Producers must sanitize every field before recording it: do not include raw URLs, query strings, selectors, page content, or secrets. Use stable action identifiers and explicitly redacted route values where needed. Use a shared operationId to correlate receipts from one operation.

As with per-test logging, work on another thread must carry the test's LogContext and finish before the test ends.

Web click receipts​

Each @Click dispatch emits one web.click receipt when diagnostics are on. The receipt includes the number of attempts, branch history, the final branch, the located element's stable data-testid and sanitized link path, the observed trusted click target's data-testid, its closest action's data-testid and sanitized link path, and the sanitized route after the click. A failed dispatch also records the exception class. The browser event listener captures the event target; Pumpo does not derive it from the locator or a visual class.

The listener stores a short, operation-specific observation before navigation, which lets Pumpo retrieve it after a same-origin page load. If it cannot observe or retrieve the event, the receipt says no-event or unknown and leaves target fields as unknown. Cross-origin navigation or blocked browser storage can make the event target unknown. Paths omit origins, query strings and fragments; path segments that do not match a short, simple identifier are shown as :redacted. Review application route names before sharing receipts outside the test team.

Private click receipts and failed-click snapshots​

With both diagnostic receipts and pn5:selenide:privateDiagnostics=true opted in, the existing web.click.private receipt retains its 16 fixed-vocabulary fields, operation ID and attempt index. It reports the invocation outcome and bounded trusted-event counters, not application success. It does not expose IDs, classes, selectors, URLs, text or the original exception.

Only a SELENIDE attempt that threw CLICK_INTERCEPTED without selecting fallback also emits version-1 companion rows. The already armed target is sampled in the same existing collection command and disposed in its finally. There is no new command, listener, input, scroll, focus, retry or background observer. Diagnostics off, success, pointer actions, fallback and other failures do not sample geometry. The original receipt is recorded before its companions; diagnostic failure cannot replace the click outcome. A lost transport response leaves cleanup unknown.

The additional row types are web.click.private.frame (at most one), web.click.private.hit (at most eight) and web.click.private.ancestor (at most sixteen). They share the original operation ID and contain snapshotVersion=1 and attemptIndex. Consumers must explicitly support these additive types. Recorder limits remain 256 data rows, 16 fields per row and 256 characters per field, with the existing final truncation marker when the recorder fills.

The frame has 16 fields: version, attempt, sample state, point basis, target flags, target rect, viewport, sample point, hit count/truncation, ancestor count/truncation, root state, candidate-ancestor ordinal, document-ancestor ordinal and completeness. The point basis is always POST_FAILURE_CLIPPED_CENTER: the current first-client- rect clipped center, not the historical driver's attempted coordinate. NO_RECT, OUTSIDE_VIEWPORT, DETACHED and UNAVAILABLE remain distinct. No hit is synthesized outside the viewport. A nearest vertical scrollable ancestor is a CANDIDATE; this does not establish which ancestor moved earlier. Clipping-only overflow and the document scrolling element are distinguished. The owned dialog or root comes only from the target's actual bounded ancestry.

Hits have at most 13 fields and ancestors at most 16: version/attempt/ordinal, relationship, fixed tag/role categories, rect, display/visibility/pointer-events/ position/overflow categories and flags; ancestors additionally carry scroll state, scroll kind and an owned-root flag. There is no arbitrary string projection. Browser output is allowlisted and Java independently validates exact schemas, vocabulary, arity, counts and bounds. Numbers are integer-rounded, finite and within +/-65535; unavailable/out-of-range numbers are UNKNOWN, never asserted zero.

Tuples use fixed comma-separated arities: rect (x,y,width,height), viewport (width,height), point (x,y), flags (connected,disabled,inert,hidden), overflow (x,y) and scroll (clientWidth,clientHeight,scrollWidth,scrollHeight,scrollLeft,scrollTop). Only sanitized enum/Boolean/number components enter those fields. Missing, malformed, unknown, truncated or incomplete groups are inconclusive. Even a complete group is one post-failure sample, not evidence of handler completion, dispatch cardinality, historical driver geometry or product success. Sampling has bounded synchronous work and timing cost; no unmeasured overhead or changed outcome is claimed.