interface TestSession
extends AsyncIterable<RunEvent>

One run, watched as it happens.

Async-iterable over RunEvent: runStart, one test per finished test, notice and browserLog as they arrive, and runEnd carrying the complete RunResult. The same event shape a WatchSession produces, so a progress display written against one works against the other.

The run does not start until you consume it. Not an optimization — the correctness property that makes the feed lossless. A browser cannot be told to slow down, so a run started before anyone was reading would either drop events or buffer them all; starting on the first read means the producer never outruns the consumer by more than one scheduling turn. Awaiting TestSession.result counts as consuming, so a caller that only wants the answer still gets one without iterating.

// Defined, not invoked: a real session launches a browser.
async function progress(session: TestSession) {
  for await (const event of session) {
    if (event.kind === 'test') process.stdout.write('.');
  }
  return (await session.result()).counts;
}

Properties

readonly
droppedEvents: number

How many events the feed dropped because a consumer fell behind. 0 in every ordinary run.

The feed is capped, so a consumer that stalls under a flood of page output loses the oldest events. That has to be visible: RunResult.browserLogsDropped already keeps the result path honest about the same trade, and a silent gap in the event path would be the one kind of loss a consumer cannot detect for itself.

Methods

result(): Promise<RunResult>

The finished run. Starts it if nothing has yet, and resolves with the same RunResult the final runEnd event carries — never undefined, which is the whole reason this is a session rather than a bare event stream.

Awaiting it twice is free: the second call returns the first's result.

abort(): Promise<void>

Cuts the run short. The browser drops the rest of its queue and the run ends where it is, with aborted: true on the result — which TestSession.result still resolves with, so an aborted run is answered rather than left hanging.

Resolves once the run has finished unwinding. Aborting before the run starts makes it a no-op that ends the session immediately.

The same events as iterating the session, as a Stream — so the combinators are there without a Stream.from wrapper.

The session stays a HANDLE rather than becoming a Stream: every combinator returns a new Stream, so session.filter(…) would hand back an object with no close() and a live browser with no owner.

// Defined, not invoked: a real session launches a browser.
async function firstTen(session: TestSession) {
  return await session.events().filter((e) => e.kind === 'test').take(10).collect();
}
close(): Promise<void>

Closes the session, ending iteration. Idempotent; implied by await using.

[[Symbol.asyncDispose]](): Promise<void>

Closes the session at the end of an await using block.

Usage

import { type TestSession } from "lib/api/session.ts";