Skip to content

Specifications

Specifications express Given/When/Then test scenarios directly against a slice’s own command and events — executable documentation for the behavior a slice implements. A specification block lives inside a slice, alongside its command, event, projection and other constructs, and is compiled by the Screenplay compiler like every other sub-language.

specification <Name>
[file <path>]
given <EventType>
[for <event-source-value>]
<property> = <value>
given readmodel <ReadModelType>
<property> = <value>
when <CommandType>
[for <event-source-value>]
<property> = <value>
then <EventType>
[for <event-source-value>]
<property> = <value>
then readmodel <ReadModelType>
<property> = <value>
then query <Query>
arguments
<argument> = <value>
result
<property> = <value>
then error ["<message>"]
  • given <EventType> — zero or more. Establishes prior state by replaying events onto the slice’s event source before the command runs.
  • given readmodel <ReadModelType> — zero or more. Establishes prior read model state directly, for scenarios where expressing the state as events would be noise.
  • when <CommandType> — zero or one. The command being exercised. A specification can declare at most one when; declaring a second is a compile error.
  • then <EventType> — zero or more. An event expected to be produced by the command.
  • then readmodel <ReadModelType> — zero or more. The read model state expected after the command has run and its events have been projected.
  • then query <Query> — zero or more. Executes the named query with the authored arguments and compares its ordered result blocks. No result blocks means the query is expected to return nothing.
  • then error ["<message>"] — zero or more. An expected rejection. See Rejections.
  • file <path> — zero or one. The repository relative file the specification is realized by. See File references.
  • for <event-source-value> — zero or one inside an event given, the command when, or an event then. It identifies occurrence context rather than an event payload property.

ESM v2 reservation: the parser, printer, and syntax tree preserve for <event-source-value>, but ESM v1 semantic binding reports blocking diagnostic PLAY0268. It cannot execute or render silently until the typed event-context semantics are admitted by ESM v2.

Property values (<property> = <value>) accept the same expressions as produces and capture mappings — string, number and boolean literals, and $context.*/$env.* expressions.

A rejection comes in two forms, and the difference between them is real.

then error "<message>" says rejected, for this reason — a constraint violation, a validation message the specification is deliberately pinning down:

specification RejectingAnInvoiceWithNoLines
when RegisterInvoice
invoiceId = "9c858901-8a57-4791-81fe-4c455b099bc9"
then error "An invoice must have at least one line"

A bare then error says rejected, for a reason this specification does not name. Most specifications are this kind — the reason lives in the specification’s name, not in an assertion, and there is nothing in the behavior under test that names it:

specification RejectingAnInvoiceWhoseNumberIsAlreadyTaken
given InvoiceRegistered
invoiceNumber = "INV-000123"
when RegisterInvoice
invoiceNumber = "INV-000123"
then error

Write the bare form rather than then error "". An empty string reads as a reason someone left blank; the bare form says one was never stated. Both forms may appear in the same specification, and both round-trip through the printer unchanged — which is what keeps generated documents diffable.

A read model assertion proves that projected state exists. A query assertion proves that callers can actually retrieve the expected state through the declared read contract. State must not be used as an implicit query assertion because one read model can have several queries with different arguments and filtering.

specification LookingUpARegisteredProject
when RegisterProject
projectId = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
name = "Screenplay"
then query ProjectById
arguments
projectId = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
result
projectId = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
name = "Screenplay"

Repeat result for a query that returns several items. Their order is the authored comparison order. To assert that an optional or collection query returns nothing, omit result:

specification NotFindingAnUnknownProject
when RegisterProject
projectId = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
name = "Screenplay"
then query ProjectById
arguments
projectId = "00000000-0000-0000-0000-000000000000"

The query declaration already states its return read model, so a result block contains only expected properties. Program v1 compares exact results in authored order. Explicit subset and unordered comparison remain additive future qualifiers rather than implicit behavior.

slice StateChange RegisterInvoice
command RegisterInvoice
invoiceId InvoiceId
customerId CustomerId
event InvoiceRegistered
invoiceId InvoiceId
customerId CustomerId
specification RegisteringADraftInvoice
given CustomerRegistered
customerId = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
name = "Acme Corp"
when RegisterInvoice
invoiceId = "9c858901-8a57-4791-81fe-4c455b099bc9"
customerId = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
then InvoiceRegistered
invoiceId = "9c858901-8a57-4791-81fe-4c455b099bc9"
customerId = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
specification RejectingAnInvoiceWithNoLines
when RegisterInvoice
invoiceId = "9c858901-8a57-4791-81fe-4c455b099bc9"
then error "An invoice must have at least one line"

When a scenario is really about derived state rather than events, given readmodel seeds the read model directly and then readmodel asserts what it should look like afterwards:

specification SendingADraftInvoice
given readmodel InvoiceListReadModel
invoiceId = "9c858901-8a57-4791-81fe-4c455b099bc9"
status = "draft"
when ChangeInvoiceStatus
invoiceId = "9c858901-8a57-4791-81fe-4c455b099bc9"
status = "sent"
then InvoiceSent
invoiceId = "9c858901-8a57-4791-81fe-4c455b099bc9"
then readmodel InvoiceListReadModel
status = "sent"

Both forms combine freely with given/then events in the same specification — establish state with events or read models, and assert on events, read models and errors as the scenario requires.

Screenplay supplies a framework-neutral reference path for admitted semantic capabilities. It does not start Arc, Chronicle, a database, the filesystem, or a network service. It executes against an immutable in-memory world so Stage and rendered targets have one normalized behavior to match.

.play document set

bind to ESM

capability-admitted plan

validate → facts → projection → query

Accepted / Rejected / Conflict / Unsupported

compare specification outcomes

The minimum evaluator currently admits the RegisterProject-style vertical: not empty validation, unconditional event production, one affected read-model instance, optional snapshot lookup, and exact ordered specification results. Unsupported reachable capabilities block plan creation rather than producing a partial or stubbed execution.

var semanticCompilation = semanticCompiler.Compile("Projects", documents);
var planCompilation = SemanticExecutionPlan.Compile(semanticCompilation.Value!.Model);
var specificationId = planCompilation.Plan!.Specifications.Keys.First();
var run = new SemanticSpecificationRunner().Run(planCompilation.Plan, specificationId);

A rejected execution returns the unchanged world. An accepted execution commits its facts and projected state once, then evaluates the requested queries against that tentative committed state. The same normalized specification run is the conformance input for Stage and generated applications.

ConstructMeaning
given <EventType>Prior state, established by one or more events before the command runs.
given readmodel <ReadModelType>Prior read model state, established directly.
when <CommandType>The command under test, with its property values.
then <EventType>An event expected to be produced by the command.
then readmodel <ReadModelType>The read model state expected after the command.
then query <Query>Ordered query results for explicit arguments; no result means empty.
argumentsThe values supplied to the query.
resultOne expected query result; repeat for many.
then error "<message>"A rejection, for the named reason.
then errorA rejection, for a reason the specification does not name.
<property> = <value>A property value, using the same expression grammar as produces/capture mappings.

Specifications compile as part of a full application document via IScreenplayCompiler.Compile, or standalone — source rooted at a specification declaration — via IScreenplayCompiler.CompileSpecification, mirroring CompileProjection for the Projection Declaration Language.