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.
Syntax
Section titled “Syntax”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 onewhen; 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 authoredargumentsand compares its orderedresultblocks. Noresultblocks 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 eventgiven, the commandwhen, or an eventthen. 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 diagnosticPLAY0268. 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.
Rejections
Section titled “Rejections”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 errorWrite 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.
Query results
Section titled “Query results”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.
Example
Section titled “Example”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"Read model state
Section titled “Read model state”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.
Reference execution
Section titled “Reference execution”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.
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.
The specification vocabulary at a glance
Section titled “The specification vocabulary at a glance”| Construct | Meaning |
|---|---|
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. |
arguments | The values supplied to the query. |
result | One expected query result; repeat for many. |
then error "<message>" | A rejection, for the named reason. |
then error | A rejection, for a reason the specification does not name. |
<property> = <value> | A property value, using the same expression grammar as produces/capture mappings. |
Compiling specifications
Section titled “Compiling specifications”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.