Repository navigation
Integration Playwright
The Kronikol.Playwright package is the browser-driven end-to-end client for Kronikol. It mints a per-test identity, stamps the Kronikol test-tracking-* headers (and a W3C traceparent) on every request a Playwright IBrowserContext / IPage makes, and opens the matching in-process identity scope — so page→backend calls are attributed to the running test.
The sink is downstream, not the fixture. Where the calls get recorded depends on your topology:
| Backend | Sink | What to do |
|---|---|---|
Kronikol-instrumented (.NET, TestTrackingContextMiddleware + TestTrackingMessageHandler) |
the server | Nothing else — the middleware reads the headers and the handlers log under that identity. |
| Not instrumentable (polyglot, third-party, legacy) | a [[proxy tap | Integration-ProxyTap-Extension]] on each hop |
dotnet add package Kronikol.Playwright
dotnet add package Microsoft.PlaywrightPlaywright versions:
Kronikol.Playwrightasks forMicrosoft.Playwright1.12.1 or later, so add Microsoft.Playwright yourself at the release you run: a project that takes it only through Kronikol resolves 1.12.1. The adapter's own suite runs on the newest 1.x, and in CI on 1.12.1 as well, on Linux and Windows, each with the Chromium of its release. Kronikol.Playwright 5.27.10 asked for 1.12.0, whose driver does not start on Linux (its macOS script has the same CRLF line ends): update to 5.27.11 or later, or reference a newer Microsoft.Playwright yourself.
using Kronikol.Playwright;
using Microsoft.Playwright;
public class OverviewTests : IAsyncLifetime
{
private IPlaywright _pw = null!;
private IBrowser _browser = null!;
public async ValueTask InitializeAsync()
{
_pw = await Playwright.CreateAsync();
_browser = await _pw.Chromium.LaunchAsync(new() { Headless = true });
}
public async ValueTask DisposeAsync() { await _browser.DisposeAsync(); _pw.Dispose(); }
[Fact]
public async Task Overview_renders()
{
// One identity per test: name for the report, id = the correlation key
// (defaults to the identity's W3C trace id, so proxy taps can attribute by traceparent too).
var identity = TestTrackingIdentity.Create("overview › renders");
await using var context = await _browser.NewTrackedContextAsync(identity);
var page = await context.NewPageAsync();
await page.GotoAsync("http://localhost:4000/intelligence");
// ... assertions ...
// Report generation: Scenario.Id must equal identity.TestId.
}
}If your test framework already runs under a Kronikol adapter (xUnit/NUnit/MSTest/TUnit/… with TestIdentityScope set), use TestTrackingIdentity.FromCurrentScope() so the browser traffic joins the same scenario as your in-process HttpClient calls.
| Member | Purpose |
|---|---|
TestTrackingIdentity.Create(testName, testId?, callerName = "Browser", traceId?) |
Mint an identity. TestId defaults to TraceId.ToString("N") (32-hex = the W3C trace id). |
TestTrackingIdentity.FromCurrentScope() |
Build from the ambient TestIdentityScope. |
identity.ToHeaders() |
The four TestTrackingHttpHeaders (+ traceparent unless IncludeTraceparent = false). A value that is not plain printable ASCII is sent in the RFC 8187 UTF-8'' form, which Kronikol's readers decode, so the name and id arrive exactly ([[HTTP Tracking Setup › Header values |
browser.NewTrackedContextAsync(identity, options?) |
New context with the headers merged into ExtraHTTPHeaders. The per-test entry point.
|
context.UseTestTrackingAsync(identity, additionalHeaders?) / page.UseTestTrackingAsync(...)
|
Stamp an existing context/page (SetExtraHTTPHeadersAsync replaces the set — pass other headers you need via additionalHeaders). |
identity.BeginScope() |
Open the matching in-process TestIdentityScope (dispose to close). |
identity.Traceparent() |
A fresh sampled traceparent rooted at the identity's trace id. |
Browser ──(test-tracking-current-test-name / -current-test-id / -caller-name / -trace-id, traceparent)──►
your web app ──► graphql ──► myDotnetService ──► …
- A Kronikol-instrumented hop reads name+id (
TestTrackingContextMiddleware) and its outbound handlers re-stamp all four (TestTrackingMessageHandleron every hop since 4.14.3, the gRPC interceptor since 4.13.0). - A
ProxyTapdoes the same from outside: it reads the headers, falls back to thetraceparenttrace id when a hop dropped them, and re-injects the four headers downstream. - Because
TestIddefaults to the W3C trace id, every hop — instrumented or tapped — lands in the same scenario, and the same id finds the distributed trace in Tempo/Jaeger.
Attribution gets the backend calls into the right scenario; the Playwright reporter pattern gets the cause in too. A reporter reads Playwright's own step tree at onTestEnd and writes Kronikol records — no spec changes:
| Playwright | Kronikol record | Rendered as |
|---|---|---|
pw:api actions — Navigate to, Click, Fill, Press, Select option, Hover, … |
interaction kind: "ui" (callerName: "User", serviceName: "web", method = label, durationMs = until the next action) |
one arrow from the User actor to the app, with the HTTP calls it caused nested under it |
test.step(...) |
tests NDJSON step (level = nesting depth, status, durationMs, error; Given/When/Then prefix → keyword) |
black step delimiter bar (top level) / sub-step |
expect(...) |
tests NDJSON assertion (status, error) |
green ✓ / red ✗ note with the failure message; sub-step of the enclosing step |
Labels are trimmed by default (Click "Accept trial", Open /intelligence-pro/overview, "heading" to have text) with Playwright's full title in the note; a raw style keeps the titles verbatim. The reporter finds the test's scenario id in a kronikol-test-id attachment the fixture adds (testInfo.attach('kronikol-test-id', { body: identity.testId })). Hook/fixture steps are skipped.
The reference implementation (TypeScript, ~150 lines) lives in the sidekick-intelligence-e2e repo (tests/kronikol-reporter.ts + the label helpers in tests/kronikol.ts) and is the first concrete piece of the planned @kronikol/playwright package — see plans/NODE_PORT_PLAN.md in the Kronikol repo. Record shapes: Ingesting External Captures.
Added in v3.0.45
The same reporter turns Playwright's artefacts into report content. For each entry in result.attachments, append an attachment record to the tests NDJSON — the path Playwright already wrote, plus its content type:
for (const a of result.attachments) {
if (a.name === 'kronikol-test-id') continue;
const path = a.path ?? writeInlineBody(a); // inline bodies land outside the tap directory
appendTestRecord({ event: 'attachment', testId, name: a.name, path,
mediaType: a.contentType, timestamp: new Date().toISOString() });
}Playwright's own screenshot: 'only-on-failure' and trace/video: 'retain-on-failure' need no extra work — the failure screenshot arrives as image/png and renders inline with a lightbox (from 4.31.1; before, a scenario's own image opened in a new tab, and only a step's used the lightbox), trace.zip and video.webm render as links. Start/end screenshots are an auto fixture away:
const screenshots = [async ({ page }, use, testInfo) => {
page.once('load', () => page.screenshot().then(
body => testInfo.attach('screenshot-start.png', { body, contentType: 'image/png' })).catch(() => {}));
await use();
if (!page.isClosed())
await page.screenshot().then(
body => testInfo.attach('screenshot-end.png', { body, contentType: 'image/png' })).catch(() => {});
}, { auto: true }] as const;Two rules the fixture must obey: capture at the first load (at fixture setup the page is still about:blank), and swallow every error — nothing in a reporting fixture may fail a test. Under playwright-bdd, AfterStep attaches one image per Gherkin step. The recipe above writes no step index, so with --tests alone each of them is on the scenario; ingested with --cucumber-messages as well, each is on the step row the messages put it on, once (4.24.0, Integration Cucumber Messages).
Playwright attaches its failure screenshot, video, error context and trace when the test ends, after every screenshot the test took itself. From 4.28.0 kronikol query failures prints a list of more than six attachments as its first and its last five, so those are the ones it shows, with a line in place of the rest that names them and kronikol query attachments <report> sN, which lists every attachment of the scenario with the step that holds it. Before 4.28.0 it printed the scenario's first four and nothing else, which with a screenshot after each step left out the whole failure set. With --include-hooks the same artefacts are on the after-hook and fixture steps, where failures counts them on one line naming the same view.
Links belong here too: a path that is a http/https URL is rendered as a plain link and never copied — that is how a "Playwright report" or "Grafana trace" entry gets onto every scenario.
kronikol ingest --attachments-base <dir> resolves relative paths; --clean-attachments empties attachments/ first so the folder holds exactly this run's files. Full contract: Ingesting External Captures.
.NET parity note: Playwright .NET has no reporter/step API, so there is no automatic equivalent here; a .NET suite that wants user actions in its diagrams records them explicitly — e.g. log an InteractionRecord.UserAction(...) (or a RequestResponseLog { IsUserAction = true }) around the page calls it cares about, and use TrackingDiagramOverride.InsertPlantUml / Track.That(...) for steps and assertions as any .NET test does.
playwright-bdd runs .feature files on Playwright: bddgen
generates a spec per feature, each Gherkin step becomes a test.step titled "<Keyword> text", tags become
Playwright tags, a Rule becomes a describe, a Background a beforeEach and an outline one test per
example row. The reporter pattern above already gets bars and notes out of that — but playwright-bdd can also
emit the Cucumber Messages protocol, which carries the whole Gherkin model, and Kronikol reads it directly.
// playwright.config.ts
import { defineConfig } from '@playwright/test';
import { defineBddConfig, cucumberReporter } from 'playwright-bdd';
const testDir = defineBddConfig({
features: 'features/**/*.feature',
steps: 'steps/**/*.ts',
outputDir: '.features-gen', // gitignore this
});
export default defineConfig({
testDir,
reporter: [
['./kronikol-reporter.ts'], // ui / assertion / identity
cucumberReporter('message', { outputFile: '../.logs/cucumber/messages.ndjson' }),
],
});Run npx bddgen before npx playwright test, then ingest both files:
kronikol ingest .logs/taps --tests .logs/taps/tests.jsonl --cucumber-messages .logs/cucumber/messages.ndjson -o .logs/kronikolTwo rules make this work:
-
Write the messages file outside the capture directory.
kronikol ingest(andIngestPipeline) scan input directories recursively for*.ndjson/*.jsonland would try to replay a messages file as traffic. -
Attach the identity. A
Beforehook that attaches the 32-hex Kronikol test id askronikol-test-idis what joins the Gherkin scenario to the traffic the fixture captured — the same attachment the reporter reads:const { Before } = createBdd(test); Before(async ({ $testInfo }) => { await $testInfo.attach('kronikol-test-id', { body: identity.testId, contentType: 'text/plain' }); });
With both sources present the messages win for structure and the reporter still supplies assertions, UI
actions and attachments; with the messages file absent the reporter alone still produces keyword bars and
✓/✗ notes, so the recipe degrades cleanly. Full mapping: Integration Cucumber Messages. The Gherkin tags
become the report's tags: a Feature:'s on the feature, and a Rule:'s on that rule's scenarios
(tag conventions).
The header names and the "test mints the trace, trace id = test id" convention are the contract for the planned @kronikol/playwright (Node) fixture and the Java port — a TypeScript Playwright fixture needs only context.setExtraHTTPHeaders({...}) with the same five headers. See Ingesting External Captures for the capture format those ports share.
Home · 🔍 Search this wiki Full-text search across every page. The “Pages” list above matches page and heading names, not page content.
Getting Started
- Quick Start (xUnit)
- Project Templates
- AI Integration Prompt
- Framework Integration Guides
- Migrating to v5
- Migrating to v4
Common Tasks
Integration Guides
- Integration xUnit3
- Integration xUnit2
- Integration NUnit
- Integration MSTest
- Integration TUnit
- Integration BDDfy xUnit3
- Integration LightBDD xUnit2
- Integration LightBDD xUnit3
- Integration LightBDD TUnit
- Integration ReqNRoll xUnit2
- Integration ReqNRoll xUnit3
- Integration ReqNRoll TUnit
- Integration Playwright
Uninstrumentable / polyglot backends
- Integration ProxyTap Extension
- Integration TcpTap Extension
- Integration Otlp Extension
- Exporting to OpenTelemetry
- Ingesting External Captures
- Ingesting from Jest
- Integration Cucumber Messages
- Capture-Time Redaction
Extensions
- Integration AtlasDataApi Extension
- Integration BigQuery Extension
- Integration Bigtable Extension
- Integration BlobStorage Extension
- Integration ClickHouse Extension
- Integration CloudStorage Extension
- Integration CosmosDB Extension
- Integration Dapper Extension
- Integration DynamoDB Extension
- Integration EF Core Relational Extension
- Integration Elasticsearch Extension
- Integration EventBridge Extension
- Integration EventHubs Extension
- Integration Grpc Extension
- Integration Kafka Extension
- Integration MassTransit Extension
- Integration MongoDB Extension
- Integration MySqlConnector Extension
- Integration Npgsql Extension
- Integration Oracle Extension
- Integration PubSub Extension
- Integration Redis Extension
- Integration S3 Extension
- Integration ServiceBus Extension
- Integration SNS Extension
- Integration Spanner Extension
- Integration SqlClient Extension
- Integration Sqlite Extension
- Integration SQS Extension
- Integration StorageQueues Extension
- Integration OpenTelemetry Extension
- Integration DispatchProxy Extension
- Integration MediatR Extension
- Integration PlantUML IKVM
Configuration
- Tracking Dependencies
- Tracking Custom Dependencies
- HTTP Tracking Setup
- Report Configuration
- Diagram Customisation
- Phase-Aware Tracking
- Content Formatting
- PlantUML Server Configuration
Features
- Generated Reports
- Report Themes
- Search and Filter Syntax
- Component Diagrams
- PlantUML Browser Rendering
- Inline SVG Rendering
- Internal Flow Tracking
- Tags and Attributes
- Excluding Requests
- Excluded Headers
- Multi-Host Test Architectures
- Event-Driven Architecture Testing
- Service Bus Tracking Patterns
- Background Thread Correlation
- Parallel-Safe Background Correlation
- Event & Message Tracking
- Assertion Tracking
- Step Tracking
- Tabular Attributes
- Large Response and Diagram Handling
- Querying Reports
- Service Card
- Cross-Run History
- Diagnostics and Debugging
- CI Summary Integration
- CI Artifact Upload
- Merging Parallel Reports
Reference