npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

serenity-scenario-context

v1.2.1

Published

A Serenity/JS ability that lets actors use a ScenarioContext - an advanced, qualifier- and type-aware form of notes.

Readme

Serenity Scenario Context

A type-safe way to share and retrieve scenario state in Serenity/JS.

serenity-scenario-context provides two things that make scenario state easier to work with:

  1. Decentralized typing — capabilities can add their own state without maintaining a central Notes type.
  2. Multi-instance collision handling — when a scenario holds several objects of the same type, they don't collide: qualifiers tell them apart, collisions are caught instead of silently overwritten, and ambiguous lookups can be made to fail loudly.

Why Scenario Context?

Serenity/JS Notepad works well when scenario state is small and explicitly named:

interface Notes {
    customer: Customer;
    order: Order;
}

notes<Notes>().set('customer', customer);
notes<Notes>().set('order', order);

As a test suite grows, two challenges can emerge.

1. Decentralized typing

A shared Notes type can become a central registry of unrelated scenario state:

interface Notes {
    customer: Customer;
    order: Order;
    payment: Payment;
    session: Session;
    // ...
}

A capability that introduces Payment now needs to modify a shared type.

With ScenarioContext, the capability can simply register what it produces:

context.add(payment);

and consumers retrieve it by type:

context.find(Payment);

There is no central schema containing every possible type of scenario state.

2. Multiple instances of the same type

Looking state up by type has an obvious catch: what if a scenario needs two objects of the same type?

CreateTicket.called('Billing problem');
CreateTicket.called('Technical problem');

Both are a Ticket. A context keyed by type alone would have to either overwrite the first one or pick one arbitrarily — and the test would quietly operate on the wrong ticket.

ScenarioContext makes the collision explicit. Instances of the same type are told apart by qualifiers:

context.add(billingTicket, {
    label: 'billing',
});

context.add(technicalTicket, {
    label: 'technical',
});
context.find(Ticket, {
    label: 'billing',
});

The rules keep collisions from going unnoticed:

  • Every instance of a type is qualified by the same set of keys (here: label). Adding a Ticket with different keys throws UnexpectedQualifierKeysError.
  • Adding a second instance with exactly the same qualifiers throws DuplicateQualifiersError, rather than overwriting the first. If overwriting is what you want, use addOrReplace.
  • Looking up an unknown qualifier key throws UnknownQualifierKeyError, and looking up something that isn't there throws PieceNotFoundError.

Resolving ambiguity: you choose how strict to be

A lookup only needs as many qualifier keys as it takes to tell the instance apart from the rest. When it's still ambiguous, you decide what happens:

// Strict: throws MultiplePiecesFoundError if more than one Ticket matches.
context.findOne(Ticket);

// Convenient: returns the most recently used matching Ticket.
context.find(Ticket);

find falls back to the spotlight: the most recently added or looked-up instance of a type. This lets a capability like ResolveTicket() operate on "the current ticket" without every step having to pass around a key:

CreateTicket.called('Billing problem');
ResolveTicket();                         // resolves the billing ticket

CreateTicket.called('Technical problem');
ResolveTicket();                         // resolves the technical ticket

This gives you:

  • explicit, qualified lookup when several objects matter;
  • strict lookup (findOne) when an ambiguous match would be a bug;
  • implicit, convenient access to the current object (find) when it wouldn't.

Notepad vs. Scenario Context

The two approaches model scenario state differently:

Notepad
    key → value
    centrally typed

ScenarioContext
    type + qualifiers → value
    decentralized
    + qualifiers for multiple instances of a type
    + collisions detected, ambiguity optionally strict

Notepad is a great choice for small, stable, explicitly named scenario state.

ScenarioContext is designed for scenarios where state is more dynamic, multiple objects of the same type are common (and must not collide), or capabilities should contribute state independently.

Basic usage

Give an actor access to a context:

const context = new ScenarioContext();

actorCalled('Alice')
    .whoCan(
        UseScenarioContext.using(context)
    );

Store an object:

UseScenarioContext
    .as(actor)
    .add(customer);

Retrieve it:

const customer =
    UseScenarioContext
        .as(actor)
        .find(Customer);

Replacing a value in place

add throws if a piece qualified exactly the same already exists. When you'd rather overwrite it than be told it's already there, use addOrReplace:

UseScenarioContext
    .as(actor)
    .addOrReplace(updatedCustomer, { id: 'CUST-1' });

If no piece matches those qualifiers yet, it's added as usual.

Sharing between actors

A context can be shared by multiple actors:

const context = new ScenarioContext();

alice.whoCan(UseScenarioContext.using(context));
bob.whoCan(UseScenarioContext.using(context));

State added by one actor can therefore be retrieved by another.

In short

Notepad is a named scenario state store.

ScenarioContext is a decentralized, type-aware context that handles multiple instances of the same type:

             Scenario Context
                    │
          ┌─────────┴─────────┐
          │                   │
   decentralized         multi-instance
      typing              collisions
          │                   │
   type + qualifiers    qualify, detect,
                        or use the spotlight

It keeps scenario state close to the capabilities that produce and consume it, while making sure that several objects of the same type can coexist without colliding — and that the most recently used one is still naturally available when explicit identification is unnecessary.