-
Notifications
You must be signed in to change notification settings - Fork 1
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.Playwrightusing 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). Values are ISO-8859-1-safe and ≤ 512 chars. |
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. - 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 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, 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; Playwright ≥ 1.50 files attachments made inside a step on that step, and Kronikol's step index puts them on the matching step row.
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 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.
Getting Started
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
- Ingesting External Captures
- 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
- Search 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
- Diagnostics and Debugging
- CI Summary Integration
- CI Artifact Upload
- Merging Parallel Reports
Reference