effect-bdd 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +465 -0
- package/dist/Bdd.d.ts +285 -0
- package/dist/Bdd.d.ts.map +1 -0
- package/dist/Bdd.js +304 -0
- package/dist/Bdd.js.map +1 -0
- package/dist/Errors.d.ts +65 -0
- package/dist/Errors.d.ts.map +1 -0
- package/dist/Errors.js +58 -0
- package/dist/Errors.js.map +1 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +7 -0
- package/dist/bin.js.map +1 -0
- package/dist/index.d.ts +147 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/cli/errors.d.ts +30 -0
- package/dist/internal/cli/errors.d.ts.map +1 -0
- package/dist/internal/cli/errors.js +8 -0
- package/dist/internal/cli/errors.js.map +1 -0
- package/dist/internal/cli/glob.d.ts +13 -0
- package/dist/internal/cli/glob.d.ts.map +1 -0
- package/dist/internal/cli/glob.js +29 -0
- package/dist/internal/cli/glob.js.map +1 -0
- package/dist/internal/cli/loaders.d.ts +13 -0
- package/dist/internal/cli/loaders.d.ts.map +1 -0
- package/dist/internal/cli/loaders.js +44 -0
- package/dist/internal/cli/loaders.js.map +1 -0
- package/dist/internal/cli/models.d.ts +102 -0
- package/dist/internal/cli/models.d.ts.map +1 -0
- package/dist/internal/cli/models.js +2 -0
- package/dist/internal/cli/models.js.map +1 -0
- package/dist/internal/cli/moduleLoader.d.ts +14 -0
- package/dist/internal/cli/moduleLoader.d.ts.map +1 -0
- package/dist/internal/cli/moduleLoader.js +39 -0
- package/dist/internal/cli/moduleLoader.js.map +1 -0
- package/dist/internal/cli/reporter.d.ts +22 -0
- package/dist/internal/cli/reporter.d.ts.map +1 -0
- package/dist/internal/cli/reporter.js +227 -0
- package/dist/internal/cli/reporter.js.map +1 -0
- package/dist/internal/cli/runner.d.ts +14 -0
- package/dist/internal/cli/runner.d.ts.map +1 -0
- package/dist/internal/cli/runner.js +178 -0
- package/dist/internal/cli/runner.js.map +1 -0
- package/dist/internal/cli/tagExpression.d.ts +7 -0
- package/dist/internal/cli/tagExpression.d.ts.map +1 -0
- package/dist/internal/cli/tagExpression.js +127 -0
- package/dist/internal/cli/tagExpression.js.map +1 -0
- package/dist/internal/cucumberCompiler.d.ts +5 -0
- package/dist/internal/cucumberCompiler.d.ts.map +1 -0
- package/dist/internal/cucumberCompiler.js +49 -0
- package/dist/internal/cucumberCompiler.js.map +1 -0
- package/dist/internal/expression.d.ts +18 -0
- package/dist/internal/expression.d.ts.map +1 -0
- package/dist/internal/expression.js +59 -0
- package/dist/internal/expression.js.map +1 -0
- package/dist/internal/matching.d.ts +30 -0
- package/dist/internal/matching.d.ts.map +1 -0
- package/dist/internal/matching.js +37 -0
- package/dist/internal/matching.js.map +1 -0
- package/dist/internal/parser.d.ts +54 -0
- package/dist/internal/parser.d.ts.map +1 -0
- package/dist/internal/parser.js +93 -0
- package/dist/internal/parser.js.map +1 -0
- package/dist/internal/runner.d.ts +77 -0
- package/dist/internal/runner.d.ts.map +1 -0
- package/dist/internal/runner.js +117 -0
- package/dist/internal/runner.js.map +1 -0
- package/dist/main.d.ts +23 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +104 -0
- package/dist/main.js.map +1 -0
- package/package.json +102 -0
- package/src/Bdd.ts +575 -0
- package/src/Errors.ts +60 -0
- package/src/bin.ts +10 -0
- package/src/index.ts +155 -0
- package/src/internal/cli/errors.ts +20 -0
- package/src/internal/cli/glob.ts +37 -0
- package/src/internal/cli/loaders.ts +100 -0
- package/src/internal/cli/models.ts +118 -0
- package/src/internal/cli/moduleLoader.ts +41 -0
- package/src/internal/cli/reporter.ts +367 -0
- package/src/internal/cli/runner.ts +336 -0
- package/src/internal/cli/tagExpression.ts +173 -0
- package/src/internal/cucumberCompiler.ts +58 -0
- package/src/internal/expression.ts +103 -0
- package/src/internal/matching.ts +81 -0
- package/src/internal/parser.ts +155 -0
- package/src/internal/runner.ts +373 -0
- package/src/main.ts +169 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tate Barber
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
# effect-bdd
|
|
2
|
+
|
|
3
|
+
An Effect-native runner for testing Gherkin feature source with strongly typed step definitions.
|
|
4
|
+
|
|
5
|
+
`effect-bdd` uses Cucumber's Gherkin parser/compiler for feature-file syntax and exposes a small `Bdd` module for building immutable feature definitions from tagged-template step definitions. Captures, DataTables, and DocStrings are decoded with `Schema`, and each step implementation returns an `Effect` that produces the next state.
|
|
6
|
+
|
|
7
|
+
The package also ships an `effect-bdd` CLI for discovering feature files and step definition modules from globs.
|
|
8
|
+
|
|
9
|
+
This package currently tracks the Effect v4 beta release train. Use matching `4.0.0-beta.x` versions of `effect` and Effect platform packages.
|
|
10
|
+
|
|
11
|
+
## When to Use `effect-bdd`
|
|
12
|
+
|
|
13
|
+
Use `effect-bdd` when a Gherkin feature should drive a typed state machine:
|
|
14
|
+
|
|
15
|
+
- domain acceptance tests
|
|
16
|
+
- reducers and command handlers
|
|
17
|
+
- event-sourced workflows
|
|
18
|
+
- service-backed business rules
|
|
19
|
+
- scenario tests where the state under test is ordinary immutable data
|
|
20
|
+
|
|
21
|
+
In this model, feature files are input. They do not dictate a mutable runtime architecture. State is data returned by each transition, and shared capabilities come from normal Effect services.
|
|
22
|
+
|
|
23
|
+
## When Not to Use `effect-bdd`
|
|
24
|
+
|
|
25
|
+
Use another BDD tool when you primarily need a runner integration rather than a state-machine API. Browser E2E suites that rely on Playwright traces, fixtures, UI mode, or project sharding are usually better served by `playwright-bdd`. Teams that need Cucumber's hook ecosystem, formatter plugins, snippets, retry/shard behavior, or mutable `World` compatibility should use Cucumber directly.
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { Bdd } from "effect-bdd"
|
|
31
|
+
import { Effect, Schema } from "effect"
|
|
32
|
+
|
|
33
|
+
const qty = Bdd.capture("qty", Schema.FiniteFromString)
|
|
34
|
+
|
|
35
|
+
const feature = Bdd.feature("Counter", { initial: 0 }).pipe(
|
|
36
|
+
Bdd.given`zero`(() => Effect.succeed(0)),
|
|
37
|
+
Bdd.when`increment by ${qty}`(({ qty }, state) => Effect.succeed(state + qty))
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
const program = Bdd.run(
|
|
41
|
+
feature,
|
|
42
|
+
`
|
|
43
|
+
Feature: Counter
|
|
44
|
+
|
|
45
|
+
Scenario: Increment
|
|
46
|
+
Given zero
|
|
47
|
+
When increment by 2
|
|
48
|
+
`
|
|
49
|
+
).pipe(Effect.provide(Bdd.GherkinCompiler.Cucumber))
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Model
|
|
53
|
+
|
|
54
|
+
A `Bdd.Feature` is an immutable state machine:
|
|
55
|
+
|
|
56
|
+
- `Bdd.feature(name, { initial })` creates the feature definition.
|
|
57
|
+
- `Bdd.given`, `Bdd.when`, `Bdd.then`, and `Bdd.step` register transitions.
|
|
58
|
+
- Each transition receives decoded captures and the current state.
|
|
59
|
+
- Each transition returns an `Effect` containing the next state.
|
|
60
|
+
- Each scenario starts from the feature's initial state.
|
|
61
|
+
- `Background` steps run before each scenario.
|
|
62
|
+
|
|
63
|
+
This keeps step code pure and explicit. Mutable "world" objects are not required; shared capabilities come from normal Effect services.
|
|
64
|
+
|
|
65
|
+
## Public API Surface
|
|
66
|
+
|
|
67
|
+
Most users should import from `effect-bdd` and use the `Bdd` namespace:
|
|
68
|
+
|
|
69
|
+
- constructors: `Bdd.capture`, `Bdd.table`, `Bdd.docString`, `Bdd.feature`
|
|
70
|
+
- transitions: `Bdd.given`, `Bdd.when`, `Bdd.then`, `Bdd.step`
|
|
71
|
+
- runner: `Bdd.run`
|
|
72
|
+
- parser/compiler service: `Bdd.GherkinCompiler`
|
|
73
|
+
- models and errors: `Bdd.Feature`, `Bdd.Report`, `Bdd.RunError`, `Bdd.ParseError`, `Bdd.MatchError`, `Bdd.StepError`
|
|
74
|
+
|
|
75
|
+
The deeper `effect-bdd/Bdd` module also exposes lower-level types such as `Transition`, `AnyTransition`, `StepBuilder`, and `Expression`. Those types describe the builder and feature-definition machinery for advanced typing and documentation. `Transition` tracks a concrete capture and step argument type, while `AnyTransition` is the existential type used by `Bdd.Feature` to store heterogeneous transitions. They are not intended as a separate registration API; prefer the namespace constructors unless you are writing type-level helpers around `Bdd.feature`.
|
|
76
|
+
|
|
77
|
+
## Captures
|
|
78
|
+
|
|
79
|
+
Captures are named values inside a tagged-template step expression. The source text is always a string, and the capture's `Schema` decides how to decode it before the step implementation runs.
|
|
80
|
+
|
|
81
|
+
Prefer strict schemas. `Schema.FiniteFromString` rejects `"abc"`, `""`, and `"Infinity"`, surfacing a `MatchError` when a Gherkin value is malformed. `Schema.NumberFromString` decodes those to `NaN`, `0`, and `Infinity` instead, so a typo silently runs the step against a non-finite number.
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { Bdd } from "effect-bdd"
|
|
85
|
+
import { Effect, Schema } from "effect"
|
|
86
|
+
|
|
87
|
+
type Cart = {
|
|
88
|
+
readonly total: number
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const expected = Bdd.capture("expected", Schema.FiniteFromString)
|
|
92
|
+
|
|
93
|
+
const feature = Bdd.feature("Cart", { initial: { total: 0 } as Cart }).pipe(
|
|
94
|
+
Bdd.then`the cart total is ${expected}`(({ expected }, state) =>
|
|
95
|
+
state.total === expected
|
|
96
|
+
? Effect.succeed(state)
|
|
97
|
+
: Effect.fail(`expected ${expected}, got ${state.total}` as const)
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The implementation receives `{ expected: number }`, not raw strings.
|
|
103
|
+
|
|
104
|
+
## DataTables
|
|
105
|
+
|
|
106
|
+
Use `Bdd.table(schema)` when a step has a Gherkin DataTable. The first table row is treated as headers. Each following row is converted into an object and decoded with the supplied row schema.
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { Bdd } from "effect-bdd"
|
|
110
|
+
import { Effect, Schema } from "effect"
|
|
111
|
+
|
|
112
|
+
const Item = Schema.Struct({
|
|
113
|
+
sku: Schema.String,
|
|
114
|
+
qty: Schema.FiniteFromString,
|
|
115
|
+
price: Schema.FiniteFromString
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
const feature = Bdd.feature("Cart", { initial: [] as ReadonlyArray<typeof Item.Type> }).pipe(
|
|
119
|
+
Bdd.when`the following items are added:`(
|
|
120
|
+
Bdd.table(Item),
|
|
121
|
+
(_captures, items) => Effect.succeed(items)
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
```gherkin
|
|
127
|
+
When the following items are added:
|
|
128
|
+
| sku | qty | price |
|
|
129
|
+
| book | 2 | 21 |
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## DocStrings
|
|
133
|
+
|
|
134
|
+
Use `Bdd.docString(schema)` when a step has a Gherkin DocString. This is useful for JSON payloads or larger text blocks.
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
import { Bdd } from "effect-bdd"
|
|
138
|
+
import { Effect, Option, Schema } from "effect"
|
|
139
|
+
|
|
140
|
+
const Payload = Schema.Struct({
|
|
141
|
+
sku: Schema.String,
|
|
142
|
+
qty: Schema.Number
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
const feature = Bdd.feature("Payload", { initial: Option.none<typeof Payload.Type>() }).pipe(
|
|
146
|
+
Bdd.when`the request body is:`(
|
|
147
|
+
Bdd.docString(Schema.fromJsonString(Payload)),
|
|
148
|
+
(_captures, payload) => Effect.succeed(Option.some(payload))
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
```gherkin
|
|
154
|
+
When the request body is:
|
|
155
|
+
"""json
|
|
156
|
+
{ "sku": "book", "qty": 2 }
|
|
157
|
+
"""
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Services
|
|
161
|
+
|
|
162
|
+
Step implementations return normal `Effect` values, so they can require services in `R` and fail with typed errors in `E`.
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
import { Bdd } from "effect-bdd"
|
|
166
|
+
import { Context, Effect, Schema } from "effect"
|
|
167
|
+
|
|
168
|
+
class TaxRate extends Context.Service<TaxRate, {
|
|
169
|
+
readonly rate: number
|
|
170
|
+
}>()("TaxRate") {}
|
|
171
|
+
|
|
172
|
+
const expected = Bdd.capture("expected", Schema.FiniteFromString)
|
|
173
|
+
|
|
174
|
+
const feature = Bdd.feature("Cart", { initial: 100 }).pipe(
|
|
175
|
+
Bdd.then`the taxed total is ${expected}`(({ expected }, subtotal) =>
|
|
176
|
+
Effect.gen(function*() {
|
|
177
|
+
const taxRate = yield* TaxRate
|
|
178
|
+
const actual = Math.round(subtotal * (1 + taxRate.rate))
|
|
179
|
+
return actual === expected
|
|
180
|
+
? subtotal
|
|
181
|
+
: yield* Effect.fail(`expected ${expected}, got ${actual}` as const)
|
|
182
|
+
})
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
const program = Bdd.run(feature, source).pipe(
|
|
187
|
+
Effect.provide(Bdd.GherkinCompiler.Cucumber),
|
|
188
|
+
Effect.provideService(TaxRate, { rate: 0.1 })
|
|
189
|
+
)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Supported Gherkin
|
|
193
|
+
|
|
194
|
+
Feature files are parsed and compiled with Cucumber's Gherkin implementation. The runner supports:
|
|
195
|
+
|
|
196
|
+
- `Feature`
|
|
197
|
+
- `Scenario`
|
|
198
|
+
- `Scenario Outline` and `Examples`
|
|
199
|
+
- `Background`
|
|
200
|
+
- `Rule`
|
|
201
|
+
- tags on features, rules, and scenarios
|
|
202
|
+
- `Given`, `When`, `Then`
|
|
203
|
+
- `And` and `But` keyword inheritance
|
|
204
|
+
- DataTables
|
|
205
|
+
- DocStrings
|
|
206
|
+
- comments and descriptions
|
|
207
|
+
|
|
208
|
+
Scenario Outlines are expanded before execution. Every Examples row runs as an independent scenario with its own initial state.
|
|
209
|
+
|
|
210
|
+
`Bdd.given`, `Bdd.when`, and `Bdd.then` are semantic, not decorative. They only match their corresponding concrete kind after `And` / `But` inheritance is resolved. `Bdd.step` is keyword-agnostic and can match any concrete step kind; use it sparingly for transitions that are truly valid as setup, action, or assertion.
|
|
211
|
+
|
|
212
|
+
## Running
|
|
213
|
+
|
|
214
|
+
`Bdd.run(feature, source)` parses the Gherkin source, matches every scenario step, runs each transition in order, and returns a report when all scenarios pass.
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
const program = Bdd.run(feature, source).pipe(
|
|
218
|
+
Effect.provide(Bdd.GherkinCompiler.Cucumber)
|
|
219
|
+
)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
`Bdd.run` depends on the `Bdd.GherkinCompiler` service. The built-in `Bdd.GherkinCompiler.Cucumber` layer uses Cucumber's parser and Pickle compiler; tests and applications can provide another implementation if the parser backend changes.
|
|
223
|
+
|
|
224
|
+
The compiler service is the package boundary around Gherkin parsing. The current internal executable model is still Cucumber Pickle-compatible, so a replacement compiler must preserve the same compiled step, argument, tag, and source-location semantics. This is a deliberate bounded dependency, not a claim that arbitrary Gherkin parsers can be plugged in without an adapter.
|
|
225
|
+
|
|
226
|
+
Reports include the feature name, scenario names, step counts, and inherited tags:
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
{
|
|
230
|
+
feature: "Shopping cart",
|
|
231
|
+
scenarios: [
|
|
232
|
+
{ name: "Adding items", steps: 3, tags: ["@checkout"] }
|
|
233
|
+
]
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Failures
|
|
238
|
+
|
|
239
|
+
`Bdd.run` fails with `Bdd.RunError`:
|
|
240
|
+
|
|
241
|
+
- `ParseError` when Gherkin source is invalid.
|
|
242
|
+
- `MatchError` when the feature definition name does not match the Gherkin `Feature:` name, a step cannot be matched, a step matches only transitions registered under the wrong keyword, a step matches multiple transitions, has a missing/unexpected argument, or a DataTable / DocString fails Schema decoding.
|
|
243
|
+
- `StepError` when a matched step implementation fails.
|
|
244
|
+
|
|
245
|
+
Schema decode failures are preserved on `MatchError.cause`. Step implementation failures are preserved on `StepError.cause`.
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
const program = Effect.exit(
|
|
249
|
+
Bdd.run(feature, source).pipe(Effect.provide(Bdd.GherkinCompiler.Cucumber))
|
|
250
|
+
)
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## CLI
|
|
254
|
+
|
|
255
|
+
`effect-bdd` publishes an `effect-bdd` bin for running `.feature` files from exported `Bdd.feature(...)` definitions.
|
|
256
|
+
|
|
257
|
+
Each matched step module should export one or more feature definitions. The feature definition name must match the Gherkin `Feature:` name.
|
|
258
|
+
|
|
259
|
+
```gherkin
|
|
260
|
+
# features/counter.feature
|
|
261
|
+
Feature: Counter
|
|
262
|
+
|
|
263
|
+
Scenario: Increment
|
|
264
|
+
Given zero
|
|
265
|
+
When increment by 2
|
|
266
|
+
Then the counter is 2
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
// features/counter.step.ts
|
|
271
|
+
import { Bdd } from "effect-bdd"
|
|
272
|
+
import { Effect, Schema } from "effect"
|
|
273
|
+
|
|
274
|
+
const amount = Bdd.capture("amount", Schema.FiniteFromString)
|
|
275
|
+
const expected = Bdd.capture("expected", Schema.FiniteFromString)
|
|
276
|
+
|
|
277
|
+
export const counter = Bdd.feature("Counter", { initial: 0 }).pipe(
|
|
278
|
+
Bdd.given`zero`(() => Effect.succeed(0)),
|
|
279
|
+
Bdd.when`increment by ${amount}`(({ amount }, state) => Effect.succeed(state + amount)),
|
|
280
|
+
Bdd.then`the counter is ${expected}`(({ expected }, state) =>
|
|
281
|
+
state === expected
|
|
282
|
+
? Effect.succeed(state)
|
|
283
|
+
: Effect.fail(`expected ${expected}, got ${state}` as const)
|
|
284
|
+
)
|
|
285
|
+
)
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Add a package script:
|
|
289
|
+
|
|
290
|
+
```json
|
|
291
|
+
{
|
|
292
|
+
"scripts": {
|
|
293
|
+
"bdd": "effect-bdd --features \"features/**/*.feature\" --steps \"features/**/*.step.ts\" --reporter text"
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Then run:
|
|
299
|
+
|
|
300
|
+
```sh
|
|
301
|
+
pnpm bdd
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
You can also invoke the bin directly:
|
|
305
|
+
|
|
306
|
+
```sh
|
|
307
|
+
effect-bdd \
|
|
308
|
+
--features "features/**/*.feature" \
|
|
309
|
+
--steps "features/**/*.step.ts" \
|
|
310
|
+
--reporter text
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
The command exits with status `0` when every scenario passes and with a non-zero status when discovery, parsing, matching, reporting, diagnostics, or any scenario fails. Reports are emitted before the command fails.
|
|
314
|
+
|
|
315
|
+
Diagnostics are contract failures, not warnings. A feature file with no matching exported `Bdd.feature`, a source step with no matching transition, or an exported transition that is never matched means the feature source and step definition module have drifted.
|
|
316
|
+
|
|
317
|
+
### Globs
|
|
318
|
+
|
|
319
|
+
Both `--features` (`-f`) and `--steps` (`-s`) are required, repeatable, and support glob patterns:
|
|
320
|
+
|
|
321
|
+
```sh
|
|
322
|
+
effect-bdd \
|
|
323
|
+
--features "features/cart/**/*.feature" \
|
|
324
|
+
--features "features/checkout/**/*.feature" \
|
|
325
|
+
--steps "features/**/*.step.ts" \
|
|
326
|
+
--steps "test-support/**/*.step.ts"
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Matched paths are deduplicated and sorted before execution so report order is stable.
|
|
330
|
+
|
|
331
|
+
### Reporters
|
|
332
|
+
|
|
333
|
+
Reporters are repeatable:
|
|
334
|
+
|
|
335
|
+
```sh
|
|
336
|
+
effect-bdd \
|
|
337
|
+
--features "features/**/*.feature" \
|
|
338
|
+
--steps "features/**/*.step.ts" \
|
|
339
|
+
--reporter text \
|
|
340
|
+
--reporter html \
|
|
341
|
+
--output-file.html reports/bdd.html
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
The CLI has built-in reporters:
|
|
345
|
+
|
|
346
|
+
- `text`: writes to stdout by default, or `--output-file.text <path>`.
|
|
347
|
+
- `html`: writes to `--output-file.html <path>`.
|
|
348
|
+
- `json`: writes to stdout by default, or `--output-file.json <path>`.
|
|
349
|
+
- `junit`: writes to `--output-file.junit <path>`.
|
|
350
|
+
|
|
351
|
+
The JSON and JUnit reporters are intended for CI consumption, but the package does not expose a stable reporter plugin API yet. If richer reporter interoperability becomes necessary, the preferred direction is a dedicated reporting contract, likely Cucumber Messages output, rather than user code depending on internal reporter functions.
|
|
352
|
+
|
|
353
|
+
The default text reporter is compact. It prints the summary, failed scenarios, and diagnostics. Add `--verbose` to print every passing scenario:
|
|
354
|
+
|
|
355
|
+
```sh
|
|
356
|
+
effect-bdd \
|
|
357
|
+
--features "features/**/*.feature" \
|
|
358
|
+
--steps "features/**/*.step.ts" \
|
|
359
|
+
--reporter text \
|
|
360
|
+
--verbose
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
For example, write human, HTML, and CI reports to files:
|
|
364
|
+
|
|
365
|
+
```sh
|
|
366
|
+
effect-bdd \
|
|
367
|
+
--features "features/**/*.feature" \
|
|
368
|
+
--steps "features/**/*.step.ts" \
|
|
369
|
+
--reporter text \
|
|
370
|
+
--output-file.text reports/bdd.txt \
|
|
371
|
+
--reporter html \
|
|
372
|
+
--output-file.html reports/bdd.html \
|
|
373
|
+
--reporter json \
|
|
374
|
+
--output-file.json reports/bdd.json \
|
|
375
|
+
--reporter junit \
|
|
376
|
+
--output-file.junit reports/bdd.xml
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Diagnostics are reported separately from failed assertions. They include feature files with no matching `Bdd.feature(...)` export, scenarios that cannot run because their feature definition is missing, source steps with no or multiple matching transitions, exported feature definitions that were not matched by any feature file, and step definitions that were never matched.
|
|
380
|
+
|
|
381
|
+
Step diagnostics are match coverage. They check text and keyword matching before execution. DataTable and DocString presence, unexpected step arguments, and Schema decode failures are validated during scenario execution and surface as `MatchError`.
|
|
382
|
+
|
|
383
|
+
### Filtering
|
|
384
|
+
|
|
385
|
+
Use `--tags <expression>` for Cucumber-style tag expressions:
|
|
386
|
+
|
|
387
|
+
```sh
|
|
388
|
+
effect-bdd \
|
|
389
|
+
--features "features/**/*.feature" \
|
|
390
|
+
--steps "features/**/*.step.ts" \
|
|
391
|
+
--tags "@event-sourcing and not @slow"
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Supported tag operators are `and`, `or`, `not`, and parentheses. Repeated `--tags` flags are combined with `and`.
|
|
395
|
+
|
|
396
|
+
Use `--name <text>` to run scenarios whose `Feature / Scenario` name contains the provided text:
|
|
397
|
+
|
|
398
|
+
```sh
|
|
399
|
+
effect-bdd \
|
|
400
|
+
--features "features/**/*.feature" \
|
|
401
|
+
--steps "features/**/*.step.ts" \
|
|
402
|
+
--name "stale append"
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
Repeated `--name` flags are combined with `or`. If filters match no scenarios, the command fails with a clear user error.
|
|
406
|
+
|
|
407
|
+
### Parallel Scenario Execution
|
|
408
|
+
|
|
409
|
+
Use `--parallel <n>` to run scenarios concurrently:
|
|
410
|
+
|
|
411
|
+
```sh
|
|
412
|
+
effect-bdd \
|
|
413
|
+
--features "features/**/*.feature" \
|
|
414
|
+
--steps "features/**/*.step.ts" \
|
|
415
|
+
--reporter text \
|
|
416
|
+
--parallel 4
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
Every scenario starts from the feature definition's initial state. Reports preserve feature/scenario source order even when scenarios run concurrently.
|
|
420
|
+
|
|
421
|
+
Use `--fail-fast` to stop after the first failed scenario. When enabled, scenarios run sequentially so the stop point is deterministic.
|
|
422
|
+
|
|
423
|
+
### TypeScript Step Modules
|
|
424
|
+
|
|
425
|
+
Bun can load `.ts` step definition modules directly when the CLI is executed by Bun:
|
|
426
|
+
|
|
427
|
+
```sh
|
|
428
|
+
bunx --bun effect-bdd --features "features/**/*.feature" --steps "features/**/*.step.ts"
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
Node requires an explicit TypeScript loader. The CLI does not install or register one implicitly:
|
|
432
|
+
|
|
433
|
+
```sh
|
|
434
|
+
node --import tsx ./node_modules/.bin/effect-bdd \
|
|
435
|
+
--features "features/**/*.feature" \
|
|
436
|
+
--steps "features/**/*.step.ts"
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
This keeps runtime behavior visible and avoids hidden loader magic.
|
|
440
|
+
|
|
441
|
+
### Step Definition Services
|
|
442
|
+
|
|
443
|
+
Step definitions can require services when they are run with `Bdd.run` inside an Effect program. The CLI only provides the platform services it needs for file loading and reporting.
|
|
444
|
+
|
|
445
|
+
If a CLI-loaded step definition needs additional services, provide them inside the step module before exporting the feature, or run the feature programmatically with `Bdd.run(...).pipe(Effect.provide(...))`.
|
|
446
|
+
|
|
447
|
+
## Provenance
|
|
448
|
+
|
|
449
|
+
`effect-bdd` started as the `packages/bdd` proposal in [Effect-TS/effect-smol#2332](https://github.com/Effect-TS/effect-smol/pull/2332) and now lives as a standalone community package.
|
|
450
|
+
|
|
451
|
+
## Non-Goals
|
|
452
|
+
|
|
453
|
+
`effect-bdd` is not a drop-in replacement for Cucumber's runtime. The current package deliberately does not include:
|
|
454
|
+
|
|
455
|
+
- Vitest adapter APIs
|
|
456
|
+
- mutable `World` objects or global step registries
|
|
457
|
+
- hooks (`Before`, `After`, `BeforeStep`, `AfterStep`, `BeforeAll`, `AfterAll`)
|
|
458
|
+
- attachments for screenshots, logs, or other report artifacts
|
|
459
|
+
- snippet generation for unmatched steps
|
|
460
|
+
- retry / rerun support
|
|
461
|
+
- dry-run mode
|
|
462
|
+
- Cucumber expression parameter registries such as `defineParameterType`
|
|
463
|
+
- user-pluggable reporter APIs
|
|
464
|
+
|
|
465
|
+
Some of those features can be added later as layers on top of the core runner. Others, especially mutable worlds and global step registration, are intentionally outside the package's state-machine model.
|