{"id":14969254,"url":"https://github.com/cucumber/screenplay.js.examples","last_synced_at":"2025-10-19T09:32:24.925Z","repository":{"id":65234561,"uuid":"548900619","full_name":"cucumber/screenplay.js.examples","owner":"cucumber","description":"Examples using @cucumber/screenplay","archived":false,"fork":false,"pushed_at":"2024-04-13T14:25:36.000Z","size":2379,"stargazers_count":6,"open_issues_count":6,"forks_count":0,"subscribers_count":7,"default_branch":"main","last_synced_at":"2024-04-14T12:07:39.530Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cucumber.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null},"funding":{"open_collective":"cucumber"}},"created_at":"2022-10-10T11:10:07.000Z","updated_at":"2024-04-15T22:36:00.221Z","dependencies_parsed_at":"2024-01-17T06:50:31.576Z","dependency_job_id":"3e0df6b3-8fba-4007-88c7-81ade25ed6ef","html_url":"https://github.com/cucumber/screenplay.js.examples","commit_stats":{"total_commits":898,"total_committers":11,"mean_commits":81.63636363636364,"dds":"0.36636971046770606","last_synced_commit":"c009d17dda4a40c8b2d669198fb033590a7c025b"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cucumber%2Fscreenplay.js.examples","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cucumber%2Fscreenplay.js.examples/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cucumber%2Fscreenplay.js.examples/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cucumber%2Fscreenplay.js.examples/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cucumber","download_url":"https://codeload.github.com/cucumber/screenplay.js.examples/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":219869479,"owners_count":16555292,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-09-24T13:41:26.661Z","updated_at":"2025-10-19T09:32:24.565Z","avatar_url":"https://github.com/cucumber.png","language":"TypeScript","funding_links":["https://opencollective.com/cucumber"],"categories":[],"sub_categories":[],"readme":"# @cucumber/screenplay\n\n[![test-javascript](https://github.com/cucumber/screenplay.js/actions/workflows/test-javascript.yaml/badge.svg)](https://github.com/cucumber/screenplay.js/actions/workflows/test-javascript.yaml)\n\nCucumber Screenplay is a small library for [Cucumber.js](https://github.com/cucumber/cucumber-js/) that enables better\nacceptance tests (Gherkin Scenarios):\n\n* 🚅 Full-stack acceptance tests that run in **milliseconds**\n* 🔓 Encourages loosely coupled system components that are easier to test in isolation\n* 🧒 Incremental development - get **feedback** before you've implemented the full stack\n* 🧩 Assembles system components in several ways, so you can optimize for **speed** or **test coverage**\n* 📗 Readable scenarios that describe the **what** instead of the **how**\n* 🧰 Maintainable automation code\n\nSee the [credits](#credits) section for details about prior work that inspired this library.\n\nWhen you use Cucumber Screenplay, your step definitions are typically one-liners:\n\n```typescript\nWhen('{actor} logs in successfully', async function (actor: Actor) {\n  await actor.attemptsTo(logIn(`${actor.name}@test.com`, 'valid-password'))\n})\n```\n\nYou can provide several implementations of `logIn` - one that interacts with the user interface, but *also* one that\ninteracts with the API layer *underneath* the user interface via direct function calls or HTTP requests.\n\nThis forces you to avoid UI language in your scenarios like \"fill in field\" and \"click button\", because it doesn't make\nsense to do that in a `logIn` implementation that isn't using the UI. Likewise, it forces you to avoid using HTTP\nlanguage like \"execute HTTP POST /login\", because it doesn't make sense to do this in the `logIn` implementation that\nuses the UI.\n\nThese constraints encourage you to write *readable* scenarios that describe *what users can do* rahter than\n*how your system is implemented*. Your scenarios become living documentation that can be understood by everyone on\nthe team.\n\n## Assemblies\n\nWith Cucumber Screenplay you can evolve an acceptance test suite that you can run with multiple configurations, or\n*assemblies*. The [assembly diagrams](https://github.com/subsecondtdd/assembly-diagrams#readme) below\nillustrate how:\n\n* ![#FFB000](https://via.placeholder.com/15/FFB000/000000?text=+) Test components\n* ![#DC267F](https://via.placeholder.com/15/DC267F/000000?text=+) Infrastructure components\n* ![#648FFF](https://via.placeholder.com/15/648FFF/000000?text=+) Production components\n\n| DOM-HTTP-Domain                                | DOM-Domain                           | HTTP-Domain                            | Domain                       |\n| ---------------------------------------------- | ------------------------------------ | ---------------------------------------| ---------------------------- |\n| ![DOM-HTTP-Domain](images/dom-http-domain.svg) | ![DOM-Domain](images/dom-domain.svg) | ![HTTP-Domain](images/http-domain.svg) | ![Domain](images/domain.svg) |\n\nWatch Cucumber creator Aslak Hellesøy explain how assemblies can be used to build acceptance tests that run in milliseconds:\n\n[![Watch the video](https://img.youtube.com/vi/AJ7u_Z-TS-A/hq3.jpg)](https://www.youtube.com/watch?v=AJ7u_Z-TS-A)\n\n## Installation\n\nFirst, add the library to your project:\n\n    npm install @cucumber/screenplay --save-dev\n\n## Usage\n\nThis guide will walk you through the usage of the `@cucumber/screenplay` step by step. For a full example, please refer\nto the files in the `features` directory (which are also acceptance tests for this library).\n\n### Actors\n\nThe central concept in `@cucumber/screenplay` is the `Actor`. An actor object represents a user interacting with the\nsystem.\n\nIn order to access actor objects from your step definitions, you first need to define an `{actor}`\n[parameter type](https://cucumber.io/docs/cucumber/cucumber-expressions/#parameter-types).\n\nCreate a file called `features/support/World.ts` (if you haven't already got one) and add the following code:\n\n```typescript\nimport { defineParameterType, setWorldConstructor } from '@cucumber/cucumber'\nimport { ActorWorld, ActorParameterType } from '@cucumber/screenplay'\n\n// Define an {actor} parameter type that creates Actor objects\ndefineParameterType(ActorParameterType)\n\n// Define your own World class that extends from ActorWorld\nexport default class World extends ActorWorld {\n}\nsetWorldConstructor(World)\n```\n\nYour step definitions will now be passed `Actor` objects for `{actor}` parameters, for example:\n\n```gherkin\nWhen Martha logs in\n```\n\n```typescript\nWhen('{actor} logs in', async function (actor: Actor) {\n  // The logIn() function is an Action\n  await actor.attemptsTo(logIn(`${actor.name}@test.com`, 'valid-password'))\n})\n```\n\nKeep reading to learn how to define *tasks*.\n\n### Perfoming tasks\n\nNow that your step definitions can be passed `Actor` objects, we need to define *tasks* that the actor can perform\nto achieve a particular goal.\n\nA task is a function that returns another function that expects an `Actor` parameter.\n\nAdd the following to `features/support/tasks/logIn.ts`:\n\n```typescript\ntype LogIn = (email: string, password: string) =\u003e Action\u003cstring\u003e\n\nexport const logIn: LogIn = (email, password) =\u003e {\n  return (actor: Actor) =\u003e {\n    // Just a dummy implementation for now - we'll come back and flesh this out later\n    return '42'\n  }\n}\n```\n\nBack in the step definition we can now import this task:\n\n```typescript\nimport { logIn } from '../support/tasks/logIn'\n\nWhen('{actor} logs in', async function (actor: Actor) {\n  const userId = await actor.attemptsTo(logIn(`${actor.name}@test.com`, 'valid-password'))\n})\n```\n\n#### Tasks and Interactions\n\nThe screenplay pattern encourages you to decompose complex tasks into multiple *interaction*:\n\n\n                             +--------+\n                             | Action |\n                             +--------+\n                                  ^\n                                  |\n                        +---------+---------+\n                        |                   |\n    +-------+       +---+--+          +-----+-------+\n    | actor |------\u003e| task |---------\u003e| interaction |\n    +-------+       +------+    0..N  +-------------+\n\nIn `@cucumber/screenplay`, both *tasks* and *interactions* are of type `Action`. The library does not\nmake a distinction between them, it is up to you how you decompose tasks into interactions.\n\nSee [shout.ts](features/support/tasks/dom/shout.ts) for an example of a task that delegates to two interactions.\n\n#### Tasks and Questions\n\nIn addition to `Actor#attemptsTo` there is also an `Actor#ask` method. It has exactly the same signature\nand behaviour as `Actor#attemptsTo`. It often makes your code more readable if you use `Actor#attemptsTo`\nin your `When` step definitions that *modify* system state, and `Actor#ask` in `Then` step definitions\nthat *query* system state.\n\nFor example:\n\n```typescript\nexport type InboxMessages = () =\u003e Action\u003creadonly string[]\u003e\n\nexport const inboxMessages: InboxMessages = (userId) =\u003e {\n  return (actor: Actor) =\u003e {\n    return ['hello', 'world']\n  }\n}\n```\n\nAnd in the step definition:\n\n```typescript\nimport { inboxMessages } from '../support/tasks/inboxMessages'\n\nThen('{actor} should have received the following messages:', function (actor: Actor, expectedMessages: DataTable) {\n  const receivedMessages = actor.ask(inboxMessages())\n  assert.deepStrictEqual(receivedMessages, expectedMessages.rows.map(row =\u003e row[0]))\n})\n```\n\n### Using different task implementations\n\nIt can often be useful to have multiple implementations of the same task. This allows you\nto build new functionality incrementally with fast feedback.\n\nFor example, you might be working on a new requirement that allows users to log in. You can start\nby building just the server side domain logic before you implement any of the HTTP layer or UI around it and get\nquick feedback as you progress.\n\nLater, you can run the same scenarios again, but this time swapping out your tasks with implementations\nthat make HTTP requests or interact with a DOM - without changing any code.\n\nIf you look at the [shouty example included in this repo](./features), you will see that we organized\nour tasks in two directories:\n\n```\nfeatures\n├── hear_shout.feature\n└── support\n    └── tasks\n        ├── dom\n        │   ├── inboxMessages.ts\n        │   ├── moveTo.ts\n        │   └── shout.ts\n        └── session\n            ├── inboxMessages.ts\n            ├── moveTo.ts\n            └── shout.ts\n```\n\nIf your `World` class extends from `ActorWorld`, it will automatically load tasks from the directory specified\nby the `tasks` [world parameter](https://github.com/cucumber/cucumber-js/blob/main/docs/support_files/world.md#world-parameters):\n\n```\n# Use the dom tasks\ncucumber-js --world-parameters '{ \"tasks\": \"support/tasks/dom\" }'\n\n# Use the session tasks\ncucumber-js --world-parameters '{ \"tasks\": \"support/tasks/session\" }'\n```\n\nHere is what the `World` looks like:\n\n```typescript\nimport { setWorldConstructor } from '@cucumber/cucumber'\nimport { ActorWorld, defineActorParameterType, Action } from '@cucumber/screenplay'\nimport { InboxMessages, Shout, StartSession } from './tasks/types'\n\nexport default class World extends ActorWorld {\n  // These tasks will be loaded automatically\n  public startSession: StartSession\n  public shout: Shout\n  public inboxMessages: InboxMessages\n}\nsetWorldConstructor(World)\n```\n\nIf you're using this technique, you also need to adapt your step definitions to reference tasks from the\n*world* (`this`):\n\n```typescript\nWhen('{actor} shouts {string}', async function (this: World, actor: Actor, message: string) {\n  await actor.attemptsTo(this.shout(message))\n})\n```\n\n### Sharing data between steps\n\nYour actors have the abililty to `remember` and `recall` data between steps. For example:\n\n```typescript\nWhen('{actor} logs in', async function (actor: Actor\u003cWorld\u003e) {\n  const userId = await actor.attemptsTo(logIn(`${actor.name}@test.com`, 'valid-password'))\n  actor.remember('userId', userId)\n})\n\nThen('{actor} should be logged in', function (actor: Actor) {\n  assert.ok(actor.recall('userId'))\n})\n```\n\nYou can also pass a function to `remember` to memoize a value:\n\n```typescript\nfunction getSomething(actor: Actor) {\n  actor.recall('something', () =\u003e ({ foo: 'bar' }))\n}\n\n// The same object is returned every time\nassert.strictEqual(getSomething(actor), getSomething(actor))\n```\n\n**Note:** the data remembered is scoped by `Actor`, so you cannot access data remembered by one actor from another\none. You can have multiple actors storing different data with the same key. Every `Actor` is discarded at the end of\neach scenario, so you won't be able to `recall` anything from previous scenarios.\n\n### Accessing the world from actors\n\nIf your tasks need to access data in the `world`, they can do so via the `Actor#world` property. If you're\ndoing this you should also declare the generic type of the actor in the task implementation:\n\n```typescript\nexport const moveTo: MoveTo = (coordinate) =\u003e {\n  // We're declaring the World type of the actor so that we can access its members\n  return async (actor: Actor\u003cWorld\u003e) =\u003e {\n    actor.world.shouty.moveTo(actor.name, coordinate)\n  }\n}\n```\n\n### Asynchronous behaviour and eventual consistency\n\nIn a distributed system it may take some time before the outcome of an action propagates around the whole system.\n\nFor example, in a chat application, when one user sends a message, it may take a few milliseconds before the\nother users receive the message, because it travels through a network, even when it's all on your machine.\n\nIn cases like this you can use the `eventually` function to periodically check for a specific condition:\n\n```typescript\nThen('{actor} hears {actor}’s message', async function (this: World, listener: Actor\u003cWorld\u003e, shouter: Actor) {\n  const shouterLastMessage = shouter.recall('lastMessage')\n\n  await eventually(() =\u003e {\n    const listenerMessages = listener.ask(this.inboxMessages())\n    assert.deepStrictEqual(listenerMessages, [shouterLastMessage])\n  })\n})\n```\n\nThe `eventually` function accepts a single argument - a zero argument `condition` function. If the `condition` function\nthrows an error, it will be called again at a regular `interval` until it passes without throwing an exception.\nIf it doesn't pass or finish within a `timeout` period, a timeout error is thrown.\n\nThe default `interval` is `50ms` and the default `timeout` is `1000ms`. This can be overridden with a second\n`{ interval: number, timeout: number }` argument after the `condition`.\n\n## Advanced Configuration\n\nBelow are some guidelines for more advanced configuration.\n\n### Using Promises\n\nThe default type of an `Action` is `void`. If your system is asynchronous\n(i.e. uses `async` functions that return a `Promise`), you can use the `PromiseAction`\ntype instead of `Action`.\n\n### Using an explicit ActorLookup\n\nIf you cannot extend `ActorWorld`, you can add an `ActorLookup` field to your existing world class like so:\n\n```typescript\nimport { ActorLookup } from '@cucumber/screenplay'\n\nclass World {\n  private readonly actorLookUp = new ActorLookup()\n\n  public findOrCreateActor(actorName: string): Actor\u003cWorld\u003e {\n    return this.actorLookUp.findOrCreateActor(this, actorName)\n  }\n}\n```\n\n### Overriding ActorParameterType options\n\nThe `defineParameterType(ActorParameterType)` function call defines a parameter type named `{actor}` by default,\nand it uses the RegExp `/[A-Z][a-z]+/` (a capitailsed string).\n\nIf you want to use a different name or regexp, you can override these defaults:\n\n```typescript\ndefineParameterType({ ...ActorParameterType, name: 'acteur' })\ndefineParameterType({ ...ActorParameterType, regexp: /Marcel|Bernadette|Hubert/ })\ndefineParameterType({ ...ActorParameterType, name: 'acteur', regexp: /Marcel|Bernadette|Hubert/ })\n```\n\n## Design recommendations\n\nWhen you're working with `@cucumber/screenplay` and testing against multiple layers, we recommend you use only two\ntask implementations:\n\n* `dom` for tasks that use the DOM\n* `session` for tasks that use a `Session`\n\nA `Session` represents a user (actor) having an interactive session with your system. A `Session` will typically be used\nin two places of your code:\n\n* From your `session` tasks\n* From your UI code (React/Vue components etc)\n\n`Session` is an interface that is specific to your implementation that you should implement yourself. Your UI code\nwill use it to interact with the server. This separation of concerns prevents network implementation details to\nbleed into the UI code.\n\nYou'll typically have two implementations of your `Session` interface - `HttpSession` and `DomainSession`.\n\nThe `HttpSession` is where you encapsulate all of the `fetch`, `WebSocket` and `EventSource` logic. This is the class\nyour UI will use in production. You will also use it in tests.\n\nThe `DomainSession` is an implementation that talks directly to the server side domain layer with direct function calls\n(without any networking). This implementation will only be used in tests.\n\nBy organising your code this way, you have four ways you can assemble your system when you run your Cucumber Scenarios.\n\n* `session` tasks using `DomainSession` (fastest tests, domain layer coverage)\n* `session` tasks using `HttpSession` (slower tests, http + domain layer coverage)\n* `dom` tasks using `DomainSession` (slower tests, UI + domain layer coverage)\n* `dom` tasks using `HttpSession` (slowest tests, UI + http + domain layer coverage)\n\nIn the example we use [world parameters](https://github.com/cucumber/cucumber-js/blob/main/docs/support_files/world.md#world-parameters)\nto control how to interact with the system and how the system is assembled.\n\n## Credits\n\nThis library is inspired by the *Screenplay Pattern*. This list is a chronological order of events, implementations and writings related to the evolution of the pattern.\n\n* 2007: [In praise of abstraction](https://developertesting.com/archives/month200710/20071013-In%20Praise%20of%20Abstraction.html) - talk by Kevin Lawrence.\n* 2007: Antony Marcano [demonstrates the Journey Pattern](http://www.russmiles.com/essais/on-the-origins-of-the-journey-pattern-in-testing) at the Agile Alliance Functional Test Tools workshop.\n* 2009: Antony Marcano and Andy Palmer showed Aslak Hellesøy the pattern at Agile 2009 in Chicago.\n* 2012: [Screenplay4j](https://bitbucket-archive.softwareheritage.org/projects/te/testingreflections/screenplay4j.html) - the first public implementation by Antony Marcano and Andy Palmer.\n* 2012: [User Centred Scenarios: Describing capabilities, not solutions](https://skillsmatter.com/skillscasts/3141-user-centred-scenarios-describing-capabilities-not-solutions) - talk by Andy Palmer and Antony Marcano where \"millisecond acceptance\" tests are described towards the end.\n* 2015: John Ferguson Smart and Jan Molak, along with Andy and Antony, added native support for the Screenplay pattern to [Serenity BDD](http://serenity-bdd.info/) for Web and API testing, popularising the pattern in the Java testing community.\n* 2016: [Beyond Page Objects: Next Generation Test Automation with Serenity and the Screenplay Pattern](https://www.infoq.com/articles/Beyond-Page-Objects-Test-Automation-Serenity-Screenplay/) by Andy Palmer, Antony Marcano, John Ferguson Smart and Jan Molak.\n* 2016: [Screenplays and Journeys, Not Page Objects](https://testerstories.com/2016/06/screenplays-and-journeys-not-page-objects/) - blog post by Jeff Nyman\n* 2016: [Screenplay Pattern](https://serenity-js.org/handbook/thinking-in-serenity-js/screenplay-pattern.html) as described by Jan Molak.\n* 2016: [Serenity/JS](https://serenity-js.org) - the original JavaScript/TypeScript implementation of the Screenplay Pattern by Jan Molak.\n* 2017: Nat Pryce explored using the Screenplay Pattern to write tests that run in milliseconds - https://speakerdeck.com/npryce/having-our-cake-and-eating-it-1\n* 2019: [ScreenPy](https://pypi.org/project/screenpy/) - a Python implementation of the Screenplay pattern inspired by the Serenity BDD implementation\n* 2020: [Boa Constrictor](https://automationpanda.com/2020/10/16/introducing-boa-constrictor-the-net-screenplay-pattern/) - a .NET implementation of Screenplay by Andrew Knight, inspired by Serenity BDD and Serenity/JS\n* 2020: [Understanding Screenplay](https://cucumber.io/blog/bdd/understanding-screenplay-(part-1)/) - blog series by Matt Wynne.\n* 2021: [BDD in Action, 2nd Edition](https://www.manning.com/books/bdd-in-action-second-edition) (by John Ferguson Smart with Jan Molak) includes a full chapter and many examples of the Screenplay pattern in Java and Typescript\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcucumber%2Fscreenplay.js.examples","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcucumber%2Fscreenplay.js.examples","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcucumber%2Fscreenplay.js.examples/lists"}