{"id":51441083,"url":"https://github.com/tatemz/effect-bdd","last_synced_at":"2026-07-07T10:03:27.288Z","repository":{"id":363913340,"uuid":"1265345490","full_name":"tatemz/effect-bdd","owner":"tatemz","description":"An Effect-native BDD runner for Gherkin feature files.","archived":false,"fork":false,"pushed_at":"2026-06-27T17:01:56.000Z","size":313,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-27T17:03:54.284Z","etag":null,"topics":["bdd","behavior-driven-development","effect","effect-ts","functional-programming","gherkin","testing","testing-tools"],"latest_commit_sha":null,"homepage":"","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/tatemz.png","metadata":{"files":{"readme":"README.md","changelog":null,"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,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-10T17:31:59.000Z","updated_at":"2026-06-27T17:01:09.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/tatemz/effect-bdd","commit_stats":null,"previous_names":["tatemz/effect-bdd"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/tatemz/effect-bdd","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tatemz%2Feffect-bdd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tatemz%2Feffect-bdd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tatemz%2Feffect-bdd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tatemz%2Feffect-bdd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tatemz","download_url":"https://codeload.github.com/tatemz/effect-bdd/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tatemz%2Feffect-bdd/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35223385,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-07T02:00:07.222Z","response_time":90,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["bdd","behavior-driven-development","effect","effect-ts","functional-programming","gherkin","testing","testing-tools"],"created_at":"2026-07-05T12:00:27.329Z","updated_at":"2026-07-07T10:03:27.280Z","avatar_url":"https://github.com/tatemz.png","language":"TypeScript","funding_links":[],"categories":["Open source"],"sub_categories":["Libraries based on effect-ts"],"readme":"# effect-bdd\n\nAn Effect-native BDD runner for Gherkin feature files.\n\nUse `effect-bdd` when you want plain-language `.feature` files, but you do not want\nCucumber's mutable `World` model. Your TypeScript declares explicit, typed scenario\nchains. The runner verifies each compiled Gherkin scenario against those chains before\nrunning the steps.\n\n## Contents\n\n- [Install](#install)\n- [Quick Start](#quick-start)\n- [How It Works](#how-it-works)\n- [CLI Workflows](#cli-workflows)\n- [Writing Step Modules](#writing-step-modules)\n- [Step Patterns](#step-patterns)\n- [Reference](#reference)\n- [Non-Goals](#non-goals)\n\n## Install\n\n`effect-bdd` requires Node `\u003e=22.12.0` and currently tracks the Effect v4 beta release\ntrain. Use matching `4.0.0-beta.x` versions of `effect` and Effect platform packages.\n\n```sh\npnpm add effect-bdd effect@4.0.0-beta.90\npnpm add -D tsx\n```\n\n## Quick Start\n\nStart with a feature:\n\n```gherkin\nFeature: Counter\n\n  Scenario: Creating a counter\n    Given no counter exists\n    When the counter is created\n    Then the counter value is 0\n```\n\nThen declare the scenario chain in TypeScript:\n\n```ts\nimport { Bdd } from \"effect-bdd\";\nimport { Effect, Schema } from \"effect\";\n\nconst expected = Bdd.capture(\"expected\", Schema.FiniteFromString);\n\nconst givenNoCounter = Bdd.given`no counter exists`(() =\u003e Effect.void);\nconst whenCounterIsCreated = Bdd.when`the counter is created`(() =\u003e Effect.succeed(0));\nconst thenCounterValueIs = Bdd.then`the counter value is ${expected}`(\n  ({ expected }: { readonly expected: number }, state: number) =\u003e\n    state === expected\n      ? Effect.succeed(state)\n      : Effect.fail(`expected ${expected}, got ${state}` as const),\n);\n\nconst creatingACounter = Bdd.scenario(\"Creating a counter\").pipe(\n  givenNoCounter,\n  whenCounterIsCreated,\n  thenCounterValueIs,\n);\n\nconst counter = Bdd.feature(\"Counter\").pipe(creatingACounter);\n\nconst program = Bdd.run(\n  counter,\n  `\nFeature: Counter\n\n  Scenario: Creating a counter\n    Given no counter exists\n    When the counter is created\n    Then the counter value is 0\n`,\n).pipe(Effect.provide(Bdd.layerCucumber));\n```\n\nMost projects run feature files through the CLI:\n\n```sh\nNODE_OPTIONS=\"--import tsx\" pnpm exec effect-bdd \\\n  --features \"features/**/*.feature\" \\\n  --steps \"features/**/*.steps.ts\"\n```\n\nThere is a fuller counter example in [`examples/`](examples/):\n\n```sh\npnpm --dir examples install\npnpm --dir examples test\n```\n\n## How It Works\n\nA feature is made from explicit scenario chains:\n\n- `Bdd.feature(title)` creates a feature definition.\n- `Bdd.scenario(title)` creates a pipeable scenario chain.\n- `Bdd.given`, `Bdd.when`, `Bdd.then`, and `Bdd.step` create reusable step values.\n- Steps pipe into scenarios; scenarios pipe into features.\n- Each step returns an `Effect` containing the next state.\n\nThe runner parses each `.feature` file with Cucumber's Gherkin compiler, pairs each\nsource scenario with the `Bdd.scenario(...)` chain of the same title, verifies the steps\nposition by position, then runs the chain.\n\nThat strict pairing is the point. If the feature says one thing and the TypeScript chain\nsays another, the run fails before the test can lie to you.\n\n## CLI Workflows\n\n### Local Full Suite\n\nRun all feature files against all step modules:\n\n```sh\nNODE_OPTIONS=\"--import tsx\" pnpm exec effect-bdd \\\n  --features \"features/**/*.feature\" \\\n  --steps \"features/**/*.steps.ts\"\n```\n\nQuote globs so `effect-bdd` receives the pattern instead of your shell expanding it.\n\n### Focused Runs\n\nDuring local development, run one feature or a filtered set of scenarios:\n\n```sh\nNODE_OPTIONS=\"--import tsx\" pnpm exec effect-bdd \\\n  --features \"features/counter.feature\" \\\n  --steps \"features/**/*.steps.ts\" \\\n  --title \"Creating a counter\"\n```\n\nFocused runs often load shared step modules that contain other features' scenario chains.\nThose unrelated chains may appear under `Unused definitions:` because their `.feature`\nfiles were not selected. That is expected and non-fatal by default.\n\nDo not add `--strict` to this workflow unless the selected feature files and loaded step\nmodules are meant to be complete.\n\n### CI\n\nUse broad globs and `--strict` in CI so unused feature or scenario definitions fail the\nbuild:\n\n```sh\nNODE_OPTIONS=\"--import tsx\" pnpm exec effect-bdd \\\n  --features \"features/**/*.feature\" \\\n  --steps \"features/**/*.steps.ts\" \\\n  --reporter text \\\n  --strict\n```\n\nIf a hung promise, socket, browser, or polling loop should fail quickly, add a run-level\nstep timeout:\n\n```sh\nNODE_OPTIONS=\"--import tsx\" pnpm exec effect-bdd \\\n  --features \"features/**/*.feature\" \\\n  --steps \"features/**/*.steps.ts\" \\\n  --reporter text \\\n  --strict \\\n  --step-timeout \"5 seconds\"\n```\n\nBun can load `.ts` step modules directly:\n\n```sh\nbunx --bun effect-bdd --features \"features/**/*.feature\" --steps \"features/**/*.steps.ts\"\n```\n\n## Writing Step Modules\n\nKeep step modules boring:\n\n```text\nimports\nschemas and captures\ndomain types\npure helpers\nreusable given/when/then step values\nscenario chains\none exported Bdd.feature(...)\n```\n\nOne Gherkin `Feature:` should map to one exported `Bdd.feature(\"Feature name\")`. Reusable\nsteps can live anywhere, but compose them into a single feature export per feature name.\nWhen the CLI loads shared step modules, every exported `Bdd.feature(...)` is considered\nduring discovery.\n\nScenario state flows through the chain. There is no feature-level `initial` state; the\nfirst step sets up the first useful state.\n\n## Step Patterns\n\n### Captures\n\nCaptures are named values inside a tagged-template step expression. The source text is a\nstring; the capture's `Schema` decodes it before the step implementation runs.\n\n```ts\nimport { Bdd } from \"effect-bdd\";\nimport { Effect, Schema } from \"effect\";\n\nconst expected = Bdd.capture(\"expected\", Schema.FiniteFromString);\n\nconst thenTotalIs = Bdd.then`the cart total is ${expected}`(\n  ({ expected }: { readonly expected: number }, state: { readonly total: number }) =\u003e\n    state.total === expected\n      ? Effect.succeed(state)\n      : Effect.fail(`expected ${expected}, got ${state.total}` as const),\n);\n```\n\nPrefer strict schemas. `Schema.FiniteFromString` rejects `\"abc\"`, `\"\"`, and `\"Infinity\"`\nas `MatchError`s.\n\n### DataTables and DocStrings\n\nUse `Bdd.table(schema)` for Gherkin DataTables. The first row is headers; each later row\nis decoded by the row schema.\n\n```ts\nimport { Bdd } from \"effect-bdd\";\nimport { Effect, Schema } from \"effect\";\n\nconst Item = Schema.Struct({\n  sku: Schema.String,\n  qty: Schema.FiniteFromString,\n});\n\nconst whenItemsAreAdded = Bdd.when`the following items are added:`(\n  Bdd.table(Item),\n  (items: ReadonlyArray\u003ctypeof Item.Type\u003e, state: ReadonlyArray\u003ctypeof Item.Type\u003e) =\u003e\n    Effect.succeed([...state, ...items]),\n);\n```\n\nUse `Bdd.docString(schema)` for larger step arguments, including JSON payloads.\n\n```ts\nimport { Bdd } from \"effect-bdd\";\nimport { Effect, Option, Schema } from \"effect\";\n\nconst Payload = Schema.Struct({\n  sku: Schema.String,\n  qty: Schema.Number,\n});\n\nconst whenRequestBodyIs = Bdd.when`the request body is:`(\n  Bdd.docString(Schema.fromJsonString(Payload)),\n  (payload: typeof Payload.Type) =\u003e Effect.succeed(Option.some(payload)),\n);\n```\n\nSchema decode failures are preserved on `MatchError.cause`.\n\n### Services\n\nStep implementations return normal `Effect` values, so they can require services in `R`\nand fail with typed errors in `E`.\n\n```ts\nimport { Bdd } from \"effect-bdd\";\nimport { Context, Effect, Schema } from \"effect\";\n\nclass TaxRate extends Context.Service\u003c\n  TaxRate,\n  {\n    readonly rate: number;\n  }\n\u003e()(\"TaxRate\") {}\n\nconst expected = Bdd.capture(\"expected\", Schema.FiniteFromString);\n\nconst thenTaxedTotalIs = Bdd.then`the taxed total is ${expected}`(\n  ({ expected }: { readonly expected: number }, subtotal: number) =\u003e\n    Effect.gen(function* () {\n      const taxRate = yield* TaxRate;\n      const actual = Math.round(subtotal * (1 + taxRate.rate));\n      return actual === expected\n        ? subtotal\n        : yield* Effect.fail(`expected ${expected}, got ${actual}` as const);\n    }),\n);\n```\n\n### Scenario Resources\n\nEvery matched scenario runs in a fresh Effect `Scope`. A step can acquire a scoped\nresource and return it as scenario state; the runner keeps that resource open until the\nscenario finishes, even if a later step fails or times out.\n\n```ts\nimport { Bdd } from \"effect-bdd\";\nimport { Effect } from \"effect\";\n\ninterface Page {\n  readonly close: () =\u003e Effect.Effect\u003cvoid\u003e;\n}\n\ndeclare const openPage: Effect.Effect\u003cPage\u003e;\n\nconst givenAppIsOpen = Bdd.given`the app is open`(() =\u003e\n  Effect.acquireRelease(openPage, (page) =\u003e page.close()),\n);\n\nconst whenUserClicks = Bdd.when`the user clicks submit`((page: Page) =\u003e Effect.succeed(page));\n```\n\nUse `Bdd.provide` when infrastructure should be available to steps but should not appear as\na Gherkin step. It mirrors `Effect.provide`, but provision is applied once per matched\nscenario after the scenario program is built.\n\n```ts\nimport { Bdd } from \"effect-bdd\";\nimport { Context, Effect, Layer } from \"effect\";\n\ninterface Page {\n  readonly close: () =\u003e Effect.Effect\u003cvoid\u003e;\n}\n\ndeclare const openPage: Effect.Effect\u003cPage\u003e;\n\nclass BrowserPage extends Context.Service\u003cBrowserPage, Page\u003e()(\"BrowserPage\") {}\n\nconst BrowserPageLive = Layer.effect(\n  BrowserPage,\n  Effect.acquireRelease(openPage, (page) =\u003e page.close()),\n);\n\nconst whenUserClicks = Bdd.when`the user clicks submit`(() =\u003e\n  Effect.gen(function* () {\n    const page = yield* BrowserPage;\n    return page;\n  }),\n);\n\nconst submitsForm = Bdd.scenario(\"Submits form\").pipe(whenUserClicks, Bdd.provide(BrowserPageLive));\n```\n\nScenario-owned resources are intentionally not Cucumber-style `Before` / `After` hooks.\nIf a resource should live across the whole feature run, provide it around `Bdd.run` with\nnormal Effect APIs instead of acquiring it inside a step.\n\n### Backgrounds\n\nBackgrounds are explicit leading steps in the chain. There is intentionally no\n`Bdd.background(...)` helper.\n\n```gherkin\nFeature: Cart\n\n  Background:\n    Given an empty cart\n\n  Scenario: Adding items\n    When 2 books are added\n    Then the total is 44\n```\n\nThe scenario chain must include the background step first:\n\n```text\nBdd.scenario(\"Adding items\").pipe(\n  givenEmptyCart,\n  whenBooksAreAdded,\n  thenTotalIs\n)\n```\n\n`And` and `But` inherit the previous concrete keyword before verification. Use `Bdd.step`\nonly for phrases that are genuinely valid in any keyword position.\n\n### Timeouts\n\nSteps are unbounded by default. Configure a run-level timeout when blocked work should\nfail the scenario instead of hanging the run:\n\n```ts\nimport { Bdd } from \"effect-bdd\";\nimport { Duration, Effect } from \"effect\";\n\ndeclare const counter: Bdd.Feature;\ndeclare const source: string;\n\nconst program = Bdd.run(counter, source, {\n  stepTimeout: Duration.seconds(5),\n}).pipe(Effect.provide(Bdd.layerCucumber));\n```\n\nOverride the run-level timeout for a single slow step with `Bdd.withTimeout`:\n\n```ts\nimport { Bdd } from \"effect-bdd\";\nimport { Duration, Effect } from \"effect\";\n\nconst thenProjectionCatchesUp = Bdd.then`the projection catches up`(() =\u003e Effect.void).pipe(\n  Bdd.withTimeout(Duration.seconds(30)),\n);\n```\n\nTimeouts fail as `StepError`s whose `cause` is a `StepTimeoutError`. Effect timeouts\ninterrupt fibers, but synchronous infinite loops or non-interruptible native work can\nstill block the process.\n\n## Reference\n\n### Important CLI Flags\n\n- `--features`, `-f`: required, repeatable, supports `*`, `?`, and `**`.\n- `--steps`, `-s`: required, repeatable.\n- `--reporter`, `-r`: repeatable; `text`, `html`, `json`, or `junit`.\n- `--output-file.\u003creporter\u003e`: write a reporter to a file.\n- `--tags`: Cucumber-style tag expression with `and`, `or`, `not`, and parentheses.\n- `--title`, `-n`: run scenarios whose `Feature / Scenario` title contains the text.\n- `--parallel`: run scenarios concurrently.\n- `--fail-fast`: stop after the first failed scenario.\n- `--step-timeout`: maximum duration for each step, using Effect Duration input such as\n  `\"500 millis\"` or `\"5 seconds\"`.\n- `--strict`: fail the CLI on unused feature or scenario definitions.\n- `--verbose`: show passing scenarios in text output.\n\nWithout `--strict`, failed scenarios and unmatched selected feature files/scenarios still\nfail the run. Unused definitions are reported but do not fail the run.\n\n### Glob Syntax\n\n`--features` and `--steps` use effect-bdd's built-in glob resolver, not your shell's full\nglob language.\n\n- `*`: zero or more characters inside one path segment.\n- `?`: exactly one character inside one path segment.\n- `**`: zero or more path segments.\n\nPatterns without wildcards are treated as literal file paths. Brace expansion\n(`{unit,e2e}`), extglob, and shell character classes are not supported. Pass multiple\n`--features` or `--steps` flags instead; matches are unioned, deduped, and sorted.\n\n### Supported Gherkin\n\nFeature files are parsed and compiled with Cucumber's Gherkin implementation. The runner\nsupports `Feature`, `Scenario`, `Scenario Outline`, `Examples`, `Background`, `Rule`, tags,\n`Given`, `When`, `Then`, `And`, `But`, DataTables, DocStrings, comments, and descriptions.\n\nScenario Outlines are expanded before execution. Every Examples row runs the same source\nscenario chain independently.\n\n### Errors\n\n`Bdd.run` fails with:\n\n- `ParseError`: invalid Gherkin.\n- `MatchError`: feature/scenario/step verification or argument decoding failed.\n- `ScenarioSetupError`: scenario-level provider setup failed before steps ran.\n- `StepError`: a matched step implementation failed or exceeded its configured timeout.\n- `ScenarioTeardownError`: scenario scope finalization failed after steps finished.\n\n### Public API\n\nMost users should import from `effect-bdd` and use the `Bdd` namespace:\n\n- constructors: `Bdd.capture`, `Bdd.table`, `Bdd.docString`, `Bdd.feature`, `Bdd.scenario`\n- steps: `Bdd.given`, `Bdd.when`, `Bdd.then`, `Bdd.step`\n- scenario wiring: `Bdd.provide`\n- step metadata: `Bdd.withTimeout`\n- runner: `Bdd.run`\n- compiler service: `Bdd.GherkinCompiler`, `Bdd.layerCucumber`\n- guards: `Bdd.isFeature`, `Bdd.isStepTimeoutError`\n- models/errors: `Bdd.Feature`, `Bdd.Scenario`, `Bdd.Step`, `Bdd.Report`,\n  `Bdd.RunOptions`, `Bdd.RunError`, `Bdd.ParseError`, `Bdd.MatchError`, `Bdd.StepError`,\n  `Bdd.ScenarioSetupError`, `Bdd.ScenarioTeardownError`, `Bdd.StepTimeoutError`\n\nThe error classes are also importable from `effect-bdd/Errors`.\n\n## Non-Goals\n\n`effect-bdd` is not a Cucumber runtime clone. It does not include mutable worlds, hooks,\nscreenshot/log attachments, snippet generation, retries, dry-run mode, parameter\nregistries, generated chain code, or user-pluggable reporter APIs.\n\n## Provenance\n\n`effect-bdd` started as the `packages/bdd` proposal in\n[Effect-TS/effect-smol#2332](https://github.com/Effect-TS/effect-smol/pull/2332) and now\nlives as a standalone community package.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftatemz%2Feffect-bdd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftatemz%2Feffect-bdd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftatemz%2Feffect-bdd/lists"}