@harnessed-ts/gherkin 0.0.0-stage → 0.7.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/CHANGELOG.md +53 -0
- package/LICENSE +21 -0
- package/README.md +27 -2
- package/dist/chunk-6AIO5EWN.js +40 -0
- package/dist/chunk-6AIO5EWN.js.map +1 -0
- package/dist/chunk-MMY7SRBU.js +28 -0
- package/dist/chunk-MMY7SRBU.js.map +1 -0
- package/dist/cucumber.cjs +37 -0
- package/dist/cucumber.cjs.map +1 -0
- package/dist/cucumber.d.ts +44 -0
- package/dist/cucumber.d.ts.map +1 -0
- package/dist/cucumber.js +4 -0
- package/dist/cucumber.js.map +1 -0
- package/dist/cypress.cjs +41 -0
- package/dist/cypress.cjs.map +1 -0
- package/dist/cypress.d.ts +49 -0
- package/dist/cypress.d.ts.map +1 -0
- package/dist/cypress.js +36 -0
- package/dist/cypress.js.map +1 -0
- package/dist/index.cjs +44 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/nightwatch.cjs +53 -0
- package/dist/nightwatch.cjs.map +1 -0
- package/dist/nightwatch.d.ts +38 -0
- package/dist/nightwatch.d.ts.map +1 -0
- package/dist/nightwatch.js +20 -0
- package/dist/nightwatch.js.map +1 -0
- package/dist/playwright-bdd.cjs +23 -0
- package/dist/playwright-bdd.cjs.map +1 -0
- package/dist/playwright-bdd.d.ts +29 -0
- package/dist/playwright-bdd.d.ts.map +1 -0
- package/dist/playwright-bdd.js +18 -0
- package/dist/playwright-bdd.js.map +1 -0
- package/dist/yadda.cjs +118 -0
- package/dist/yadda.cjs.map +1 -0
- package/dist/yadda.d.ts +90 -0
- package/dist/yadda.d.ts.map +1 -0
- package/dist/yadda.js +90 -0
- package/dist/yadda.js.map +1 -0
- package/package.json +124 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# @harnessed-ts/gherkin
|
|
2
|
+
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- ec06bdf: Vue, Solid, Svelte and Angular join React and Ember as conformance-tested UI frameworks, and node:test, Bun and Nightwatch join the runners.
|
|
8
|
+
|
|
9
|
+
New packages: `@harnessed-ts/nightwatch` drives Nightwatch 3 through the injected resolver, under Nightwatch's own runner and its Mocha runner, in end-to-end and component tests (`@nightwatch/react`, `@nightwatch/vue`), with cross-origin frames. `@harnessed-ts/angular` drives zoneless Angular 21+: every action settles at `fixture.whenStable()`, or at the `ApplicationRef`'s for an app bootstrapped without TestBed, and `goto()` drives a provided router.
|
|
10
|
+
|
|
11
|
+
`@harnessed-ts/core/register` loads TypeScript harnesses under `node --import`: esbuild strips the types, lowers standard decorators and `accessor`, and compiles JSX. `@harnessed-ts/core/bun` does the same as a `bun test` preload, so harnesses keep the standard decorator protocol whatever `experimentalDecorators` the tsconfig sets.
|
|
12
|
+
|
|
13
|
+
`@harnessed-ts/dom/matchers/bun` registers the matchers with `bun:test`. The dom driver runs on happy-dom too: it focuses the frames leading to an interaction's target, finds a frame's `<iframe>` where `frameElement` is unset, and reads a select's selection off `options`.
|
|
14
|
+
|
|
15
|
+
`@harnessed-ts/resolve/webdriver` holds the WebDriver half of injected resolution, shared by the WebdriverIO and Nightwatch drivers. `@harnessed-ts/gherkin/nightwatch` adapts the cucumber-js world to Nightwatch's Cucumber runner.
|
|
16
|
+
|
|
17
|
+
`@harnessed-ts/playwright`, and `@harnessed-ts/cypress` under `realEvents`, give the page a task after every action, so a read straight after it sees what a framework that renders on a timer (zoneless Angular) has rendered. The injectable resolver installs on the page's `window` rather than the script's global, which under Firefox's WebDriver is a sandbox in front of it.
|
|
18
|
+
|
|
19
|
+
`@harnessed-ts/conformance` exports `fixtureParity()`, the per-view parity check a port of the fixture to another framework runs, naming the first place its markup parts from the React original; it detects a simulated DOM from the DOM's own window.
|
|
20
|
+
|
|
21
|
+
`@harnessed-ts/eslint-plugin` recognises Nightwatch's, Angular's and Vue Test Utils' raw queries, and looks in `nightwatch/` by default. `npx @harnessed-ts/claude install` detects Angular, Nightwatch, node:test and Bun.
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- Updated dependencies [ec06bdf]
|
|
26
|
+
- @harnessed-ts/core@0.7.0
|
|
27
|
+
- @harnessed-ts/nightwatch@0.7.0
|
|
28
|
+
|
|
29
|
+
## 0.6.0
|
|
30
|
+
|
|
31
|
+
### Patch Changes
|
|
32
|
+
|
|
33
|
+
- Updated dependencies [e324e94]
|
|
34
|
+
- @harnessed-ts/core@0.6.0
|
|
35
|
+
|
|
36
|
+
## 0.5.0
|
|
37
|
+
|
|
38
|
+
### Patch Changes
|
|
39
|
+
|
|
40
|
+
- @harnessed-ts/core@0.5.0
|
|
41
|
+
|
|
42
|
+
## 0.4.0
|
|
43
|
+
|
|
44
|
+
### Minor Changes
|
|
45
|
+
|
|
46
|
+
- 9666a3d: New `@harnessed-ts/gherkin`: a scenario-scoped world, a typed page registry (`definePages`) and a `{page}` parameter type (`pageParameter`) that rejects an unknown page name at step matching, with adapters for playwright-bdd (`/playwright-bdd`, `withWorld`), cucumber-js (`/cucumber`, `HarnessedWorld`), @badeball/cypress-cucumber-preprocessor (`/cypress`, `cypressWorld`) and ember-cli-yadda or plain Yadda (`/yadda`, `yaddaWorld`, with a `$page` term from `pageDictionary`). `@harnessed-ts/playwright/bdd` re-exports `withWorld` from it, deprecated for one release.
|
|
47
|
+
|
|
48
|
+
### Patch Changes
|
|
49
|
+
|
|
50
|
+
- Updated dependencies [9666a3d]
|
|
51
|
+
- Updated dependencies [9666a3d]
|
|
52
|
+
- Updated dependencies [1823546]
|
|
53
|
+
- @harnessed-ts/core@0.4.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Joe Gaudet, Jay Seo
|
|
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
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @harnessed-ts/gherkin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Gherkin support for [harnessed](https://github.com/joegaudet/harnessed#readme)
|
|
4
|
+
harnesses: a scenario-scoped world for the pages a scenario builds up, a typed
|
|
5
|
+
page registry, and a `{page}` parameter type that matches only registered
|
|
6
|
+
names, so a typo in a feature fails at step matching.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { definePages, pageParameter } from '@harnessed-ts/gherkin'
|
|
10
|
+
|
|
11
|
+
export const pages = definePages({ checkout: CheckoutPage })
|
|
12
|
+
defineParameterType(pageParameter(pages)) // the runner's own defineParameterType
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
One adapter per runner gives the world a scenario's lifetime:
|
|
16
|
+
|
|
17
|
+
| Import | Runner | Gives you |
|
|
18
|
+
| -------------------------------------- | --------------------------------------- | --------------------------------- |
|
|
19
|
+
| `@harnessed-ts/gherkin/playwright-bdd` | playwright-bdd | `withWorld`, a `world` fixture |
|
|
20
|
+
| `@harnessed-ts/gherkin/cucumber` | cucumber-js | `HarnessedWorld` |
|
|
21
|
+
| `@harnessed-ts/gherkin/nightwatch` | Nightwatch's Cucumber runner | `HarnessedWorld`, `nightwatchEnv` |
|
|
22
|
+
| `@harnessed-ts/gherkin/cypress` | @badeball/cypress-cucumber-preprocessor | `cypressWorld` |
|
|
23
|
+
| `@harnessed-ts/gherkin/yadda` | ember-cli-yadda | `yaddaWorld`, `pageDictionary` |
|
|
24
|
+
|
|
25
|
+
Each runner is an optional peer dependency; install the one you use. Setup for
|
|
26
|
+
each, with examples: [Using with Gherkin](https://github.com/joegaudet/harnessed#using-with-gherkin).
|
|
27
|
+
|
|
28
|
+
MIT © Joe Gaudet, Jay Seo
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
function definePages(pages) {
|
|
3
|
+
const names = Object.keys(pages).sort((a, b) => b.length - a.length);
|
|
4
|
+
if (names.length === 0) {
|
|
5
|
+
throw new Error("harnessed: definePages() needs at least one page.");
|
|
6
|
+
}
|
|
7
|
+
const has = (name) => Object.prototype.hasOwnProperty.call(pages, name);
|
|
8
|
+
return {
|
|
9
|
+
names,
|
|
10
|
+
has,
|
|
11
|
+
open(name, env) {
|
|
12
|
+
if (!has(name)) {
|
|
13
|
+
throw new Error(`harnessed: no page named "${name}". Registered: ${names.join(", ")}.`);
|
|
14
|
+
}
|
|
15
|
+
const Page = pages[name];
|
|
16
|
+
return new Page(env);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function escape(text) {
|
|
21
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
22
|
+
}
|
|
23
|
+
function pageParameter(registry, options = {}) {
|
|
24
|
+
return {
|
|
25
|
+
name: options.name ?? "page",
|
|
26
|
+
regexp: new RegExp(registry.names.map(escape).join("|")),
|
|
27
|
+
transformer: (match) => {
|
|
28
|
+
if (!registry.has(match)) throw new Error(`harnessed: no page named "${match}".`);
|
|
29
|
+
return match;
|
|
30
|
+
},
|
|
31
|
+
useForSnippets: false
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function createWorld() {
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export { createWorld, definePages, pageParameter };
|
|
39
|
+
//# sourceMappingURL=chunk-6AIO5EWN.js.map
|
|
40
|
+
//# sourceMappingURL=chunk-6AIO5EWN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";AAkCO,SAAS,YAA+B,KAAA,EAA2B;AACxE,EAAA,MAAM,KAAA,GAAS,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA,CAA8B,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,CAAE,MAAA,GAAS,CAAA,CAAE,MAAM,CAAA;AAChG,EAAA,IAAI,KAAA,CAAM,WAAW,CAAA,EAAG;AAGtB,IAAA,MAAM,IAAI,MAAM,mDAAmD,CAAA;AAAA,EACrE;AACA,EAAA,MAAM,GAAA,GAAM,CAAC,IAAA,KACX,MAAA,CAAO,UAAU,cAAA,CAAe,IAAA,CAAK,OAAO,IAAI,CAAA;AAClD,EAAA,OAAO;AAAA,IACL,KAAA;AAAA,IACA,GAAA;AAAA,IACA,IAAA,CAAK,MAAM,GAAA,EAAK;AAEd,MAAA,IAAI,CAAC,GAAA,CAAI,IAAI,CAAA,EAAG;AACd,QAAA,MAAM,IAAI,MAAM,CAAA,0BAAA,EAA6B,IAAI,kBAAkB,KAAA,CAAM,IAAA,CAAK,IAAI,CAAC,CAAA,CAAA,CAAG,CAAA;AAAA,MACxF;AACA,MAAA,MAAM,IAAA,GAAO,MAAM,IAAI,CAAA;AACvB,MAAA,OAAO,IAAI,KAAK,GAAG,CAAA;AAAA,IACrB;AAAA,GACF;AACF;AAcA,SAAS,OAAO,IAAA,EAAsB;AACpC,EAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,qBAAA,EAAuB,MAAM,CAAA;AACnD;AAYO,SAAS,aAAA,CACd,QAAA,EACA,OAAA,GAA6B,EAAC,EACa;AAC3C,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,QAAQ,IAAA,IAAQ,MAAA;AAAA,IACtB,MAAA,EAAQ,IAAI,MAAA,CAAO,QAAA,CAAS,KAAA,CAAM,IAAI,MAAM,CAAA,CAAE,IAAA,CAAK,GAAG,CAAC,CAAA;AAAA,IACvD,aAAa,CAAA,KAAA,KAAS;AAGpB,MAAA,IAAI,CAAC,QAAA,CAAS,GAAA,CAAI,KAAK,CAAA,QAAS,IAAI,KAAA,CAAM,CAAA,0BAAA,EAA6B,KAAK,CAAA,EAAA,CAAI,CAAA;AAChF,MAAA,OAAO,KAAA;AAAA,IACT,CAAA;AAAA,IACA,cAAA,EAAgB;AAAA,GAClB;AACF;AAMO,SAAS,WAAA,GAA6B;AAC3C,EAAA,OAAO,EAAC;AACV","file":"chunk-6AIO5EWN.js","sourcesContent":["import type { EnvConfig } from '@harnessed-ts/core'\n\n/**\n * Gherkin support for harnessed: what every runner needs, with no runner in it.\n *\n * - A **world** — the scenario-scoped bag where steps keep the pages a scenario\n * builds up. Steps are separate functions, so the obvious place for a harness\n * is a module-level `let`, which leaks into the next scenario the moment a\n * runner reuses a worker. Each runner adapter gives the bag the right lifetime.\n * - A **page registry** — the pages a feature can name, so a step reads\n * `Given I open the checkout page` and gets a `CheckoutPage`, typed.\n * - A **`{page}` parameter type** built from the registry, so a feature naming\n * an unknown page fails at step matching, where the typo is, rather than deep\n * inside a step.\n *\n * Step bodies stay hand-written: the feature's wording is the team's, not a\n * mirror of method names.\n */\n\n/** Anything a page registry can construct: a page or component harness class. */\nexport type PageConstructor<T = object> = new (env: EnvConfig) => T\n\nexport type PageMap = Record<string, PageConstructor>\n\n/** The pages a feature can name. */\nexport interface PageRegistry<P extends PageMap> {\n /** Every registered name, longest first — the order a matcher must try them. */\n readonly names: ReadonlyArray<keyof P & string>\n /** Whether `name` is registered: narrows a string from a step to a page name. */\n has(name: string): name is keyof P & string\n /** Constructs the named page with the scenario's env. */\n open<N extends keyof P & string>(name: N, env: EnvConfig): InstanceType<P[N]>\n}\n\nexport function definePages<P extends PageMap>(pages: P): PageRegistry<P> {\n const names = (Object.keys(pages) as Array<keyof P & string>).sort((a, b) => b.length - a.length)\n if (names.length === 0) {\n // `{page}` is an alternation of the names; with none it is the empty\n // pattern, which matches anywhere and would let any step through.\n throw new Error('harnessed: definePages() needs at least one page.')\n }\n const has = (name: string): name is keyof P & string =>\n Object.prototype.hasOwnProperty.call(pages, name)\n return {\n names,\n has,\n open(name, env) {\n // Own names only: `constructor` and the rest of Object.prototype are not pages.\n if (!has(name)) {\n throw new Error(`harnessed: no page named \"${name}\". Registered: ${names.join(', ')}.`)\n }\n const Page = pages[name]\n return new Page(env) as InstanceType<P[typeof name]>\n },\n }\n}\n\n/**\n * A cucumber-expression parameter type, in the shape every runner's\n * `defineParameterType` takes: @cucumber/cucumber, playwright-bdd and\n * @badeball/cypress-cucumber-preprocessor alike.\n */\nexport interface ParameterTypeDefinition<T> {\n name: string\n regexp: RegExp\n transformer: (match: string) => T\n useForSnippets?: boolean\n}\n\nfunction escape(text: string): string {\n return text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n/**\n * `{page}` for step patterns: matches exactly the registered names (longest\n * first, so `checkout summary` wins over `checkout`) and hands the step the\n * name, typed. Name it something else with `{ name }`.\n *\n * ```ts\n * defineParameterType(pageParameter(pages))\n * Given('I open the {page} page', async function (name) { … pages.open(name, env) })\n * ```\n */\nexport function pageParameter<P extends PageMap>(\n registry: PageRegistry<P>,\n options: { name?: string } = {},\n): ParameterTypeDefinition<keyof P & string> {\n return {\n name: options.name ?? 'page',\n regexp: new RegExp(registry.names.map(escape).join('|')),\n transformer: match => {\n // The regexp only admits registered names, so this cannot fail; it is\n // here so a hand-built pattern that bypasses it still gets a clear error.\n if (!registry.has(match)) throw new Error(`harnessed: no page named \"${match}\".`)\n return match\n },\n useForSnippets: false,\n }\n}\n\n/**\n * A fresh, empty world. Adapters call this once per scenario; it exists so the\n * bag's type is written once, where the steps declare it.\n */\nexport function createWorld<W>(): Partial<W> {\n return {}\n}\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createWorld } from './chunk-6AIO5EWN.js';
|
|
2
|
+
import { World } from '@cucumber/cucumber';
|
|
3
|
+
|
|
4
|
+
var HarnessedWorld = class extends World {
|
|
5
|
+
/** The scenario's bag. Starts empty; cucumber builds a new world per scenario. */
|
|
6
|
+
bag = createWorld();
|
|
7
|
+
/** The env every harness in the scenario is built with. Set it in a `Before`. */
|
|
8
|
+
env;
|
|
9
|
+
/** The pages features can name. Assign the registry in a subclass. */
|
|
10
|
+
pages;
|
|
11
|
+
constructor(options) {
|
|
12
|
+
super(options);
|
|
13
|
+
}
|
|
14
|
+
/** Constructs a registered page with the scenario's env. */
|
|
15
|
+
open(name) {
|
|
16
|
+
if (this.pages === void 0) {
|
|
17
|
+
throw new Error("harnessed: this world has no page registry. Assign `pages` in a subclass.");
|
|
18
|
+
}
|
|
19
|
+
if (this.env === void 0) {
|
|
20
|
+
throw new Error("harnessed: this world has no env yet. Set `this.env` in a Before hook.");
|
|
21
|
+
}
|
|
22
|
+
return this.pages.open(name, this.env);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export { HarnessedWorld };
|
|
27
|
+
//# sourceMappingURL=chunk-MMY7SRBU.js.map
|
|
28
|
+
//# sourceMappingURL=chunk-MMY7SRBU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cucumber.ts"],"names":[],"mappings":";;;AAkCO,IAAM,cAAA,GAAN,cAGG,KAAA,CAAM;AAAA;AAAA,EAEd,MAAkB,WAAA,EAAe;AAAA;AAAA,EAEjC,GAAA;AAAA;AAAA,EAEA,KAAA;AAAA,EAEA,YAAY,OAAA,EAAwB;AAClC,IAAA,KAAA,CAAM,OAAO,CAAA;AAAA,EACf;AAAA;AAAA,EAGA,KAAiC,IAAA,EAA6B;AAC5D,IAAA,IAAI,IAAA,CAAK,UAAU,MAAA,EAAW;AAC5B,MAAA,MAAM,IAAI,MAAM,2EAA2E,CAAA;AAAA,IAC7F;AACA,IAAA,IAAI,IAAA,CAAK,QAAQ,MAAA,EAAW;AAC1B,MAAA,MAAM,IAAI,MAAM,wEAAwE,CAAA;AAAA,IAC1F;AACA,IAAA,OAAO,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,IAAA,EAAM,KAAK,GAAG,CAAA;AAAA,EACvC;AACF","file":"chunk-MMY7SRBU.js","sourcesContent":["import { World } from '@cucumber/cucumber'\nimport type { IWorldOptions } from '@cucumber/cucumber'\nimport type { EnvConfig } from '@harnessed-ts/core'\nimport { createWorld } from './index'\nimport type { PageMap, PageRegistry } from './index'\n\n/**\n * A cucumber-js World with the harnessed pieces on it. Cucumber constructs one\n * per scenario, so everything here is scenario-scoped by construction.\n *\n * ```ts\n * const pageMap = { checkout: CheckoutPage }\n * const pages = definePages(pageMap)\n *\n * class AppWorld extends HarnessedWorld<{ checkout: CheckoutPage }, typeof pageMap> {\n * override pages = pages\n * }\n * setWorldConstructor(AppWorld)\n * defineParameterType(pageParameter(pages))\n *\n * // One browser for the run; a page per scenario.\n * let browser: Browser\n * BeforeAll(async () => {\n * browser = await chromium.launch()\n * })\n * Before(async function (this: AppWorld) {\n * this.env = pw(await browser.newPage()) // or wdio(browser), …\n * })\n * Given('I open the {page} page', async function (this: AppWorld, name: 'checkout') {\n * this.bag.checkout = this.open(name)\n * await this.bag.checkout.goto()\n * })\n * ```\n */\nexport class HarnessedWorld<\n W = Record<string, unknown>,\n P extends PageMap = PageMap,\n> extends World {\n /** The scenario's bag. Starts empty; cucumber builds a new world per scenario. */\n bag: Partial<W> = createWorld<W>()\n /** The env every harness in the scenario is built with. Set it in a `Before`. */\n env: EnvConfig | undefined\n /** The pages features can name. Assign the registry in a subclass. */\n pages: PageRegistry<P> | undefined\n\n constructor(options: IWorldOptions) {\n super(options)\n }\n\n /** Constructs a registered page with the scenario's env. */\n open<N extends keyof P & string>(name: N): InstanceType<P[N]> {\n if (this.pages === undefined) {\n throw new Error('harnessed: this world has no page registry. Assign `pages` in a subclass.')\n }\n if (this.env === undefined) {\n throw new Error('harnessed: this world has no env yet. Set `this.env` in a Before hook.')\n }\n return this.pages.open(name, this.env)\n }\n}\n"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var cucumber = require('@cucumber/cucumber');
|
|
4
|
+
|
|
5
|
+
// src/cucumber.ts
|
|
6
|
+
|
|
7
|
+
// src/index.ts
|
|
8
|
+
function createWorld() {
|
|
9
|
+
return {};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// src/cucumber.ts
|
|
13
|
+
var HarnessedWorld = class extends cucumber.World {
|
|
14
|
+
/** The scenario's bag. Starts empty; cucumber builds a new world per scenario. */
|
|
15
|
+
bag = createWorld();
|
|
16
|
+
/** The env every harness in the scenario is built with. Set it in a `Before`. */
|
|
17
|
+
env;
|
|
18
|
+
/** The pages features can name. Assign the registry in a subclass. */
|
|
19
|
+
pages;
|
|
20
|
+
constructor(options) {
|
|
21
|
+
super(options);
|
|
22
|
+
}
|
|
23
|
+
/** Constructs a registered page with the scenario's env. */
|
|
24
|
+
open(name) {
|
|
25
|
+
if (this.pages === void 0) {
|
|
26
|
+
throw new Error("harnessed: this world has no page registry. Assign `pages` in a subclass.");
|
|
27
|
+
}
|
|
28
|
+
if (this.env === void 0) {
|
|
29
|
+
throw new Error("harnessed: this world has no env yet. Set `this.env` in a Before hook.");
|
|
30
|
+
}
|
|
31
|
+
return this.pages.open(name, this.env);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
exports.HarnessedWorld = HarnessedWorld;
|
|
36
|
+
//# sourceMappingURL=cucumber.cjs.map
|
|
37
|
+
//# sourceMappingURL=cucumber.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/cucumber.ts"],"names":["World"],"mappings":";;;;;;;AAwGO,SAAS,WAAA,GAA6B;AAC3C,EAAA,OAAO,EAAC;AACV;;;ACxEO,IAAM,cAAA,GAAN,cAGGA,cAAA,CAAM;AAAA;AAAA,EAEd,MAAkB,WAAA,EAAe;AAAA;AAAA,EAEjC,GAAA;AAAA;AAAA,EAEA,KAAA;AAAA,EAEA,YAAY,OAAA,EAAwB;AAClC,IAAA,KAAA,CAAM,OAAO,CAAA;AAAA,EACf;AAAA;AAAA,EAGA,KAAiC,IAAA,EAA6B;AAC5D,IAAA,IAAI,IAAA,CAAK,UAAU,MAAA,EAAW;AAC5B,MAAA,MAAM,IAAI,MAAM,2EAA2E,CAAA;AAAA,IAC7F;AACA,IAAA,IAAI,IAAA,CAAK,QAAQ,MAAA,EAAW;AAC1B,MAAA,MAAM,IAAI,MAAM,wEAAwE,CAAA;AAAA,IAC1F;AACA,IAAA,OAAO,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,IAAA,EAAM,KAAK,GAAG,CAAA;AAAA,EACvC;AACF","file":"cucumber.cjs","sourcesContent":["import type { EnvConfig } from '@harnessed-ts/core'\n\n/**\n * Gherkin support for harnessed: what every runner needs, with no runner in it.\n *\n * - A **world** — the scenario-scoped bag where steps keep the pages a scenario\n * builds up. Steps are separate functions, so the obvious place for a harness\n * is a module-level `let`, which leaks into the next scenario the moment a\n * runner reuses a worker. Each runner adapter gives the bag the right lifetime.\n * - A **page registry** — the pages a feature can name, so a step reads\n * `Given I open the checkout page` and gets a `CheckoutPage`, typed.\n * - A **`{page}` parameter type** built from the registry, so a feature naming\n * an unknown page fails at step matching, where the typo is, rather than deep\n * inside a step.\n *\n * Step bodies stay hand-written: the feature's wording is the team's, not a\n * mirror of method names.\n */\n\n/** Anything a page registry can construct: a page or component harness class. */\nexport type PageConstructor<T = object> = new (env: EnvConfig) => T\n\nexport type PageMap = Record<string, PageConstructor>\n\n/** The pages a feature can name. */\nexport interface PageRegistry<P extends PageMap> {\n /** Every registered name, longest first — the order a matcher must try them. */\n readonly names: ReadonlyArray<keyof P & string>\n /** Whether `name` is registered: narrows a string from a step to a page name. */\n has(name: string): name is keyof P & string\n /** Constructs the named page with the scenario's env. */\n open<N extends keyof P & string>(name: N, env: EnvConfig): InstanceType<P[N]>\n}\n\nexport function definePages<P extends PageMap>(pages: P): PageRegistry<P> {\n const names = (Object.keys(pages) as Array<keyof P & string>).sort((a, b) => b.length - a.length)\n if (names.length === 0) {\n // `{page}` is an alternation of the names; with none it is the empty\n // pattern, which matches anywhere and would let any step through.\n throw new Error('harnessed: definePages() needs at least one page.')\n }\n const has = (name: string): name is keyof P & string =>\n Object.prototype.hasOwnProperty.call(pages, name)\n return {\n names,\n has,\n open(name, env) {\n // Own names only: `constructor` and the rest of Object.prototype are not pages.\n if (!has(name)) {\n throw new Error(`harnessed: no page named \"${name}\". Registered: ${names.join(', ')}.`)\n }\n const Page = pages[name]\n return new Page(env) as InstanceType<P[typeof name]>\n },\n }\n}\n\n/**\n * A cucumber-expression parameter type, in the shape every runner's\n * `defineParameterType` takes: @cucumber/cucumber, playwright-bdd and\n * @badeball/cypress-cucumber-preprocessor alike.\n */\nexport interface ParameterTypeDefinition<T> {\n name: string\n regexp: RegExp\n transformer: (match: string) => T\n useForSnippets?: boolean\n}\n\nfunction escape(text: string): string {\n return text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n/**\n * `{page}` for step patterns: matches exactly the registered names (longest\n * first, so `checkout summary` wins over `checkout`) and hands the step the\n * name, typed. Name it something else with `{ name }`.\n *\n * ```ts\n * defineParameterType(pageParameter(pages))\n * Given('I open the {page} page', async function (name) { … pages.open(name, env) })\n * ```\n */\nexport function pageParameter<P extends PageMap>(\n registry: PageRegistry<P>,\n options: { name?: string } = {},\n): ParameterTypeDefinition<keyof P & string> {\n return {\n name: options.name ?? 'page',\n regexp: new RegExp(registry.names.map(escape).join('|')),\n transformer: match => {\n // The regexp only admits registered names, so this cannot fail; it is\n // here so a hand-built pattern that bypasses it still gets a clear error.\n if (!registry.has(match)) throw new Error(`harnessed: no page named \"${match}\".`)\n return match\n },\n useForSnippets: false,\n }\n}\n\n/**\n * A fresh, empty world. Adapters call this once per scenario; it exists so the\n * bag's type is written once, where the steps declare it.\n */\nexport function createWorld<W>(): Partial<W> {\n return {}\n}\n","import { World } from '@cucumber/cucumber'\nimport type { IWorldOptions } from '@cucumber/cucumber'\nimport type { EnvConfig } from '@harnessed-ts/core'\nimport { createWorld } from './index'\nimport type { PageMap, PageRegistry } from './index'\n\n/**\n * A cucumber-js World with the harnessed pieces on it. Cucumber constructs one\n * per scenario, so everything here is scenario-scoped by construction.\n *\n * ```ts\n * const pageMap = { checkout: CheckoutPage }\n * const pages = definePages(pageMap)\n *\n * class AppWorld extends HarnessedWorld<{ checkout: CheckoutPage }, typeof pageMap> {\n * override pages = pages\n * }\n * setWorldConstructor(AppWorld)\n * defineParameterType(pageParameter(pages))\n *\n * // One browser for the run; a page per scenario.\n * let browser: Browser\n * BeforeAll(async () => {\n * browser = await chromium.launch()\n * })\n * Before(async function (this: AppWorld) {\n * this.env = pw(await browser.newPage()) // or wdio(browser), …\n * })\n * Given('I open the {page} page', async function (this: AppWorld, name: 'checkout') {\n * this.bag.checkout = this.open(name)\n * await this.bag.checkout.goto()\n * })\n * ```\n */\nexport class HarnessedWorld<\n W = Record<string, unknown>,\n P extends PageMap = PageMap,\n> extends World {\n /** The scenario's bag. Starts empty; cucumber builds a new world per scenario. */\n bag: Partial<W> = createWorld<W>()\n /** The env every harness in the scenario is built with. Set it in a `Before`. */\n env: EnvConfig | undefined\n /** The pages features can name. Assign the registry in a subclass. */\n pages: PageRegistry<P> | undefined\n\n constructor(options: IWorldOptions) {\n super(options)\n }\n\n /** Constructs a registered page with the scenario's env. */\n open<N extends keyof P & string>(name: N): InstanceType<P[N]> {\n if (this.pages === undefined) {\n throw new Error('harnessed: this world has no page registry. Assign `pages` in a subclass.')\n }\n if (this.env === undefined) {\n throw new Error('harnessed: this world has no env yet. Set `this.env` in a Before hook.')\n }\n return this.pages.open(name, this.env)\n }\n}\n"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { World } from '@cucumber/cucumber';
|
|
2
|
+
import type { IWorldOptions } from '@cucumber/cucumber';
|
|
3
|
+
import type { EnvConfig } from '@harnessed-ts/core';
|
|
4
|
+
import type { PageMap, PageRegistry } from './index';
|
|
5
|
+
/**
|
|
6
|
+
* A cucumber-js World with the harnessed pieces on it. Cucumber constructs one
|
|
7
|
+
* per scenario, so everything here is scenario-scoped by construction.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* const pageMap = { checkout: CheckoutPage }
|
|
11
|
+
* const pages = definePages(pageMap)
|
|
12
|
+
*
|
|
13
|
+
* class AppWorld extends HarnessedWorld<{ checkout: CheckoutPage }, typeof pageMap> {
|
|
14
|
+
* override pages = pages
|
|
15
|
+
* }
|
|
16
|
+
* setWorldConstructor(AppWorld)
|
|
17
|
+
* defineParameterType(pageParameter(pages))
|
|
18
|
+
*
|
|
19
|
+
* // One browser for the run; a page per scenario.
|
|
20
|
+
* let browser: Browser
|
|
21
|
+
* BeforeAll(async () => {
|
|
22
|
+
* browser = await chromium.launch()
|
|
23
|
+
* })
|
|
24
|
+
* Before(async function (this: AppWorld) {
|
|
25
|
+
* this.env = pw(await browser.newPage()) // or wdio(browser), …
|
|
26
|
+
* })
|
|
27
|
+
* Given('I open the {page} page', async function (this: AppWorld, name: 'checkout') {
|
|
28
|
+
* this.bag.checkout = this.open(name)
|
|
29
|
+
* await this.bag.checkout.goto()
|
|
30
|
+
* })
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare class HarnessedWorld<W = Record<string, unknown>, P extends PageMap = PageMap> extends World {
|
|
34
|
+
/** The scenario's bag. Starts empty; cucumber builds a new world per scenario. */
|
|
35
|
+
bag: Partial<W>;
|
|
36
|
+
/** The env every harness in the scenario is built with. Set it in a `Before`. */
|
|
37
|
+
env: EnvConfig | undefined;
|
|
38
|
+
/** The pages features can name. Assign the registry in a subclass. */
|
|
39
|
+
pages: PageRegistry<P> | undefined;
|
|
40
|
+
constructor(options: IWorldOptions);
|
|
41
|
+
/** Constructs a registered page with the scenario's env. */
|
|
42
|
+
open<N extends keyof P & string>(name: N): InstanceType<P[N]>;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=cucumber.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cucumber.d.ts","sourceRoot":"","sources":["../src/cucumber.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAC1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AACvD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAEnD,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,cAAc,CACzB,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC3B,CAAC,SAAS,OAAO,GAAG,OAAO,CAC3B,SAAQ,KAAK;IACb,kFAAkF;IAClF,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,CAAmB;IAClC,iFAAiF;IACjF,GAAG,EAAE,SAAS,GAAG,SAAS,CAAA;IAC1B,sEAAsE;IACtE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,SAAS,CAAA;gBAEtB,OAAO,EAAE,aAAa;IAIlC,4DAA4D;IAC5D,IAAI,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAS9D"}
|
package/dist/cucumber.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"cucumber.js"}
|
package/dist/cypress.cjs
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
function createWorld() {
|
|
5
|
+
return {};
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// src/cypress.ts
|
|
9
|
+
function isHookRegistrar(value) {
|
|
10
|
+
return typeof value === "function";
|
|
11
|
+
}
|
|
12
|
+
function globalBeforeEach() {
|
|
13
|
+
const hook = Reflect.get(globalThis, "beforeEach");
|
|
14
|
+
if (!isHookRegistrar(hook)) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
"harnessed: cypressWorld() found no global beforeEach. Call it at the top level of a step definitions file, which Cypress bundles into the spec, or pass { beforeEach }."
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
return hook;
|
|
20
|
+
}
|
|
21
|
+
function cypressWorld(options = {}) {
|
|
22
|
+
const beforeEach = options.beforeEach ?? globalBeforeEach();
|
|
23
|
+
let current;
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
current = createWorld();
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
world() {
|
|
29
|
+
if (current === void 0) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
"harnessed: no scenario is running yet, so there is no world. Call world() inside a step, not at the top level of a step definitions file."
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
return current;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
exports.cypressWorld = cypressWorld;
|
|
40
|
+
//# sourceMappingURL=cypress.cjs.map
|
|
41
|
+
//# sourceMappingURL=cypress.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/cypress.ts"],"names":[],"mappings":";;;AAwGO,SAAS,WAAA,GAA6B;AAC3C,EAAA,OAAO,EAAC;AACV;;;ACrFA,SAAS,gBAAgB,KAAA,EAAwC;AAC/D,EAAA,OAAO,OAAO,KAAA,KAAU,UAAA;AAC1B;AAEA,SAAS,gBAAA,GAAkC;AACzC,EAAA,MAAM,IAAA,GAAgB,OAAA,CAAQ,GAAA,CAAI,UAAA,EAAY,YAAY,CAAA;AAC1D,EAAA,IAAI,CAAC,eAAA,CAAgB,IAAI,CAAA,EAAG;AAC1B,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KAEF;AAAA,EACF;AACA,EAAA,OAAO,IAAA;AACT;AAiCO,SAAS,YAAA,CAAgB,OAAA,GAA+B,EAAC,EAAoB;AAClF,EAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,UAAA,IAAc,gBAAA,EAAiB;AAC1D,EAAA,IAAI,OAAA;AACJ,EAAA,UAAA,CAAW,MAAM;AACf,IAAA,OAAA,GAAU,WAAA,EAAe;AAAA,EAC3B,CAAC,CAAA;AACD,EAAA,OAAO;AAAA,IACL,KAAA,GAAQ;AACN,MAAA,IAAI,YAAY,MAAA,EAAW;AACzB,QAAA,MAAM,IAAI,KAAA;AAAA,UACR;AAAA,SAEF;AAAA,MACF;AACA,MAAA,OAAO,OAAA;AAAA,IACT;AAAA,GACF;AACF","file":"cypress.cjs","sourcesContent":["import type { EnvConfig } from '@harnessed-ts/core'\n\n/**\n * Gherkin support for harnessed: what every runner needs, with no runner in it.\n *\n * - A **world** — the scenario-scoped bag where steps keep the pages a scenario\n * builds up. Steps are separate functions, so the obvious place for a harness\n * is a module-level `let`, which leaks into the next scenario the moment a\n * runner reuses a worker. Each runner adapter gives the bag the right lifetime.\n * - A **page registry** — the pages a feature can name, so a step reads\n * `Given I open the checkout page` and gets a `CheckoutPage`, typed.\n * - A **`{page}` parameter type** built from the registry, so a feature naming\n * an unknown page fails at step matching, where the typo is, rather than deep\n * inside a step.\n *\n * Step bodies stay hand-written: the feature's wording is the team's, not a\n * mirror of method names.\n */\n\n/** Anything a page registry can construct: a page or component harness class. */\nexport type PageConstructor<T = object> = new (env: EnvConfig) => T\n\nexport type PageMap = Record<string, PageConstructor>\n\n/** The pages a feature can name. */\nexport interface PageRegistry<P extends PageMap> {\n /** Every registered name, longest first — the order a matcher must try them. */\n readonly names: ReadonlyArray<keyof P & string>\n /** Whether `name` is registered: narrows a string from a step to a page name. */\n has(name: string): name is keyof P & string\n /** Constructs the named page with the scenario's env. */\n open<N extends keyof P & string>(name: N, env: EnvConfig): InstanceType<P[N]>\n}\n\nexport function definePages<P extends PageMap>(pages: P): PageRegistry<P> {\n const names = (Object.keys(pages) as Array<keyof P & string>).sort((a, b) => b.length - a.length)\n if (names.length === 0) {\n // `{page}` is an alternation of the names; with none it is the empty\n // pattern, which matches anywhere and would let any step through.\n throw new Error('harnessed: definePages() needs at least one page.')\n }\n const has = (name: string): name is keyof P & string =>\n Object.prototype.hasOwnProperty.call(pages, name)\n return {\n names,\n has,\n open(name, env) {\n // Own names only: `constructor` and the rest of Object.prototype are not pages.\n if (!has(name)) {\n throw new Error(`harnessed: no page named \"${name}\". Registered: ${names.join(', ')}.`)\n }\n const Page = pages[name]\n return new Page(env) as InstanceType<P[typeof name]>\n },\n }\n}\n\n/**\n * A cucumber-expression parameter type, in the shape every runner's\n * `defineParameterType` takes: @cucumber/cucumber, playwright-bdd and\n * @badeball/cypress-cucumber-preprocessor alike.\n */\nexport interface ParameterTypeDefinition<T> {\n name: string\n regexp: RegExp\n transformer: (match: string) => T\n useForSnippets?: boolean\n}\n\nfunction escape(text: string): string {\n return text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n/**\n * `{page}` for step patterns: matches exactly the registered names (longest\n * first, so `checkout summary` wins over `checkout`) and hands the step the\n * name, typed. Name it something else with `{ name }`.\n *\n * ```ts\n * defineParameterType(pageParameter(pages))\n * Given('I open the {page} page', async function (name) { … pages.open(name, env) })\n * ```\n */\nexport function pageParameter<P extends PageMap>(\n registry: PageRegistry<P>,\n options: { name?: string } = {},\n): ParameterTypeDefinition<keyof P & string> {\n return {\n name: options.name ?? 'page',\n regexp: new RegExp(registry.names.map(escape).join('|')),\n transformer: match => {\n // The regexp only admits registered names, so this cannot fail; it is\n // here so a hand-built pattern that bypasses it still gets a clear error.\n if (!registry.has(match)) throw new Error(`harnessed: no page named \"${match}\".`)\n return match\n },\n useForSnippets: false,\n }\n}\n\n/**\n * A fresh, empty world. Adapters call this once per scenario; it exists so the\n * bag's type is written once, where the steps declare it.\n */\nexport function createWorld<W>(): Partial<W> {\n return {}\n}\n","import { createWorld } from './index'\n\n/**\n * How a hook gets registered: Mocha's `beforeEach`, which Cypress provides as a\n * global, or anything with its shape.\n */\nexport type HookRegistrar = (fn: () => void) => void\n\nexport interface CypressWorldOptions {\n /**\n * Registers the reset that runs before each scenario. Defaults to the global\n * `beforeEach` Cypress provides in a spec bundle.\n */\n beforeEach?: HookRegistrar\n}\n\nexport interface CypressWorld<W> {\n /** The current scenario's bag. Read it inside a step, never at module level. */\n world(): Partial<W>\n}\n\nfunction isHookRegistrar(value: unknown): value is HookRegistrar {\n return typeof value === 'function'\n}\n\nfunction globalBeforeEach(): HookRegistrar {\n const hook: unknown = Reflect.get(globalThis, 'beforeEach')\n if (!isHookRegistrar(hook)) {\n throw new Error(\n 'harnessed: cypressWorld() found no global beforeEach. Call it at the top level of a ' +\n 'step definitions file, which Cypress bundles into the spec, or pass { beforeEach }.',\n )\n }\n return hook\n}\n\n/**\n * A scenario-scoped world for @badeball/cypress-cucumber-preprocessor.\n *\n * Every scenario in a feature runs in the same spec bundle, so a module-level\n * `let` in a step file carries the last scenario's pages into the next one. This\n * registers a `beforeEach` that starts each scenario with an empty world; steps\n * run as Cypress commands, in order, so each one sees what the steps before it\n * left there. Steps reach the world through `world()`, never through `this`.\n *\n * Harness calls are promises, so a step runs them inside the Cypress driver's\n * bridge (`cy.harnessEnv`, `cy.harness`) — one command each:\n *\n * ```ts\n * import { defineParameterType, When } from '@badeball/cypress-cucumber-preprocessor'\n *\n * const pages = definePages({ checkout: CheckoutPage })\n * defineParameterType(pageParameter(pages))\n * const { world } = cypressWorld<{ checkout: CheckoutPage }>()\n *\n * When('I open the {page} page', (name: 'checkout') => {\n * cy.harnessEnv(async env => {\n * const page = pages.open(name, env)\n * await page.goto()\n * world().checkout = page\n * })\n * })\n * ```\n *\n * Call it once, at the top level of a step definitions file, and share the\n * `world` it returns: each call is a separate world.\n */\nexport function cypressWorld<W>(options: CypressWorldOptions = {}): CypressWorld<W> {\n const beforeEach = options.beforeEach ?? globalBeforeEach()\n let current: Partial<W> | undefined\n beforeEach(() => {\n current = createWorld<W>()\n })\n return {\n world() {\n if (current === undefined) {\n throw new Error(\n 'harnessed: no scenario is running yet, so there is no world. Call world() inside ' +\n 'a step, not at the top level of a step definitions file.',\n )\n }\n return current\n },\n }\n}\n"]}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a hook gets registered: Mocha's `beforeEach`, which Cypress provides as a
|
|
3
|
+
* global, or anything with its shape.
|
|
4
|
+
*/
|
|
5
|
+
export type HookRegistrar = (fn: () => void) => void;
|
|
6
|
+
export interface CypressWorldOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Registers the reset that runs before each scenario. Defaults to the global
|
|
9
|
+
* `beforeEach` Cypress provides in a spec bundle.
|
|
10
|
+
*/
|
|
11
|
+
beforeEach?: HookRegistrar;
|
|
12
|
+
}
|
|
13
|
+
export interface CypressWorld<W> {
|
|
14
|
+
/** The current scenario's bag. Read it inside a step, never at module level. */
|
|
15
|
+
world(): Partial<W>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A scenario-scoped world for @badeball/cypress-cucumber-preprocessor.
|
|
19
|
+
*
|
|
20
|
+
* Every scenario in a feature runs in the same spec bundle, so a module-level
|
|
21
|
+
* `let` in a step file carries the last scenario's pages into the next one. This
|
|
22
|
+
* registers a `beforeEach` that starts each scenario with an empty world; steps
|
|
23
|
+
* run as Cypress commands, in order, so each one sees what the steps before it
|
|
24
|
+
* left there. Steps reach the world through `world()`, never through `this`.
|
|
25
|
+
*
|
|
26
|
+
* Harness calls are promises, so a step runs them inside the Cypress driver's
|
|
27
|
+
* bridge (`cy.harnessEnv`, `cy.harness`) — one command each:
|
|
28
|
+
*
|
|
29
|
+
* ```ts
|
|
30
|
+
* import { defineParameterType, When } from '@badeball/cypress-cucumber-preprocessor'
|
|
31
|
+
*
|
|
32
|
+
* const pages = definePages({ checkout: CheckoutPage })
|
|
33
|
+
* defineParameterType(pageParameter(pages))
|
|
34
|
+
* const { world } = cypressWorld<{ checkout: CheckoutPage }>()
|
|
35
|
+
*
|
|
36
|
+
* When('I open the {page} page', (name: 'checkout') => {
|
|
37
|
+
* cy.harnessEnv(async env => {
|
|
38
|
+
* const page = pages.open(name, env)
|
|
39
|
+
* await page.goto()
|
|
40
|
+
* world().checkout = page
|
|
41
|
+
* })
|
|
42
|
+
* })
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* Call it once, at the top level of a step definitions file, and share the
|
|
46
|
+
* `world` it returns: each call is a separate world.
|
|
47
|
+
*/
|
|
48
|
+
export declare function cypressWorld<W>(options?: CypressWorldOptions): CypressWorld<W>;
|
|
49
|
+
//# sourceMappingURL=cypress.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cypress.d.ts","sourceRoot":"","sources":["../src/cypress.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,EAAE,EAAE,MAAM,IAAI,KAAK,IAAI,CAAA;AAEpD,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,UAAU,CAAC,EAAE,aAAa,CAAA;CAC3B;AAED,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,gFAAgF;IAChF,KAAK,IAAI,OAAO,CAAC,CAAC,CAAC,CAAA;CACpB;AAiBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,GAAE,mBAAwB,GAAG,YAAY,CAAC,CAAC,CAAC,CAiBlF"}
|
package/dist/cypress.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createWorld } from './chunk-6AIO5EWN.js';
|
|
2
|
+
|
|
3
|
+
// src/cypress.ts
|
|
4
|
+
function isHookRegistrar(value) {
|
|
5
|
+
return typeof value === "function";
|
|
6
|
+
}
|
|
7
|
+
function globalBeforeEach() {
|
|
8
|
+
const hook = Reflect.get(globalThis, "beforeEach");
|
|
9
|
+
if (!isHookRegistrar(hook)) {
|
|
10
|
+
throw new Error(
|
|
11
|
+
"harnessed: cypressWorld() found no global beforeEach. Call it at the top level of a step definitions file, which Cypress bundles into the spec, or pass { beforeEach }."
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
return hook;
|
|
15
|
+
}
|
|
16
|
+
function cypressWorld(options = {}) {
|
|
17
|
+
const beforeEach = options.beforeEach ?? globalBeforeEach();
|
|
18
|
+
let current;
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
current = createWorld();
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
world() {
|
|
24
|
+
if (current === void 0) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
"harnessed: no scenario is running yet, so there is no world. Call world() inside a step, not at the top level of a step definitions file."
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
return current;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { cypressWorld };
|
|
35
|
+
//# sourceMappingURL=cypress.js.map
|
|
36
|
+
//# sourceMappingURL=cypress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cypress.ts"],"names":[],"mappings":";;;AAqBA,SAAS,gBAAgB,KAAA,EAAwC;AAC/D,EAAA,OAAO,OAAO,KAAA,KAAU,UAAA;AAC1B;AAEA,SAAS,gBAAA,GAAkC;AACzC,EAAA,MAAM,IAAA,GAAgB,OAAA,CAAQ,GAAA,CAAI,UAAA,EAAY,YAAY,CAAA;AAC1D,EAAA,IAAI,CAAC,eAAA,CAAgB,IAAI,CAAA,EAAG;AAC1B,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KAEF;AAAA,EACF;AACA,EAAA,OAAO,IAAA;AACT;AAiCO,SAAS,YAAA,CAAgB,OAAA,GAA+B,EAAC,EAAoB;AAClF,EAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,UAAA,IAAc,gBAAA,EAAiB;AAC1D,EAAA,IAAI,OAAA;AACJ,EAAA,UAAA,CAAW,MAAM;AACf,IAAA,OAAA,GAAU,WAAA,EAAe;AAAA,EAC3B,CAAC,CAAA;AACD,EAAA,OAAO;AAAA,IACL,KAAA,GAAQ;AACN,MAAA,IAAI,YAAY,MAAA,EAAW;AACzB,QAAA,MAAM,IAAI,KAAA;AAAA,UACR;AAAA,SAEF;AAAA,MACF;AACA,MAAA,OAAO,OAAA;AAAA,IACT;AAAA,GACF;AACF","file":"cypress.js","sourcesContent":["import { createWorld } from './index'\n\n/**\n * How a hook gets registered: Mocha's `beforeEach`, which Cypress provides as a\n * global, or anything with its shape.\n */\nexport type HookRegistrar = (fn: () => void) => void\n\nexport interface CypressWorldOptions {\n /**\n * Registers the reset that runs before each scenario. Defaults to the global\n * `beforeEach` Cypress provides in a spec bundle.\n */\n beforeEach?: HookRegistrar\n}\n\nexport interface CypressWorld<W> {\n /** The current scenario's bag. Read it inside a step, never at module level. */\n world(): Partial<W>\n}\n\nfunction isHookRegistrar(value: unknown): value is HookRegistrar {\n return typeof value === 'function'\n}\n\nfunction globalBeforeEach(): HookRegistrar {\n const hook: unknown = Reflect.get(globalThis, 'beforeEach')\n if (!isHookRegistrar(hook)) {\n throw new Error(\n 'harnessed: cypressWorld() found no global beforeEach. Call it at the top level of a ' +\n 'step definitions file, which Cypress bundles into the spec, or pass { beforeEach }.',\n )\n }\n return hook\n}\n\n/**\n * A scenario-scoped world for @badeball/cypress-cucumber-preprocessor.\n *\n * Every scenario in a feature runs in the same spec bundle, so a module-level\n * `let` in a step file carries the last scenario's pages into the next one. This\n * registers a `beforeEach` that starts each scenario with an empty world; steps\n * run as Cypress commands, in order, so each one sees what the steps before it\n * left there. Steps reach the world through `world()`, never through `this`.\n *\n * Harness calls are promises, so a step runs them inside the Cypress driver's\n * bridge (`cy.harnessEnv`, `cy.harness`) — one command each:\n *\n * ```ts\n * import { defineParameterType, When } from '@badeball/cypress-cucumber-preprocessor'\n *\n * const pages = definePages({ checkout: CheckoutPage })\n * defineParameterType(pageParameter(pages))\n * const { world } = cypressWorld<{ checkout: CheckoutPage }>()\n *\n * When('I open the {page} page', (name: 'checkout') => {\n * cy.harnessEnv(async env => {\n * const page = pages.open(name, env)\n * await page.goto()\n * world().checkout = page\n * })\n * })\n * ```\n *\n * Call it once, at the top level of a step definitions file, and share the\n * `world` it returns: each call is a separate world.\n */\nexport function cypressWorld<W>(options: CypressWorldOptions = {}): CypressWorld<W> {\n const beforeEach = options.beforeEach ?? globalBeforeEach()\n let current: Partial<W> | undefined\n beforeEach(() => {\n current = createWorld<W>()\n })\n return {\n world() {\n if (current === undefined) {\n throw new Error(\n 'harnessed: no scenario is running yet, so there is no world. Call world() inside ' +\n 'a step, not at the top level of a step definitions file.',\n )\n }\n return current\n },\n }\n}\n"]}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
function definePages(pages) {
|
|
5
|
+
const names = Object.keys(pages).sort((a, b) => b.length - a.length);
|
|
6
|
+
if (names.length === 0) {
|
|
7
|
+
throw new Error("harnessed: definePages() needs at least one page.");
|
|
8
|
+
}
|
|
9
|
+
const has = (name) => Object.prototype.hasOwnProperty.call(pages, name);
|
|
10
|
+
return {
|
|
11
|
+
names,
|
|
12
|
+
has,
|
|
13
|
+
open(name, env) {
|
|
14
|
+
if (!has(name)) {
|
|
15
|
+
throw new Error(`harnessed: no page named "${name}". Registered: ${names.join(", ")}.`);
|
|
16
|
+
}
|
|
17
|
+
const Page = pages[name];
|
|
18
|
+
return new Page(env);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function escape(text) {
|
|
23
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
24
|
+
}
|
|
25
|
+
function pageParameter(registry, options = {}) {
|
|
26
|
+
return {
|
|
27
|
+
name: options.name ?? "page",
|
|
28
|
+
regexp: new RegExp(registry.names.map(escape).join("|")),
|
|
29
|
+
transformer: (match) => {
|
|
30
|
+
if (!registry.has(match)) throw new Error(`harnessed: no page named "${match}".`);
|
|
31
|
+
return match;
|
|
32
|
+
},
|
|
33
|
+
useForSnippets: false
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function createWorld() {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
exports.createWorld = createWorld;
|
|
41
|
+
exports.definePages = definePages;
|
|
42
|
+
exports.pageParameter = pageParameter;
|
|
43
|
+
//# sourceMappingURL=index.cjs.map
|
|
44
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";;;AAkCO,SAAS,YAA+B,KAAA,EAA2B;AACxE,EAAA,MAAM,KAAA,GAAS,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA,CAA8B,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,CAAE,MAAA,GAAS,CAAA,CAAE,MAAM,CAAA;AAChG,EAAA,IAAI,KAAA,CAAM,WAAW,CAAA,EAAG;AAGtB,IAAA,MAAM,IAAI,MAAM,mDAAmD,CAAA;AAAA,EACrE;AACA,EAAA,MAAM,GAAA,GAAM,CAAC,IAAA,KACX,MAAA,CAAO,UAAU,cAAA,CAAe,IAAA,CAAK,OAAO,IAAI,CAAA;AAClD,EAAA,OAAO;AAAA,IACL,KAAA;AAAA,IACA,GAAA;AAAA,IACA,IAAA,CAAK,MAAM,GAAA,EAAK;AAEd,MAAA,IAAI,CAAC,GAAA,CAAI,IAAI,CAAA,EAAG;AACd,QAAA,MAAM,IAAI,MAAM,CAAA,0BAAA,EAA6B,IAAI,kBAAkB,KAAA,CAAM,IAAA,CAAK,IAAI,CAAC,CAAA,CAAA,CAAG,CAAA;AAAA,MACxF;AACA,MAAA,MAAM,IAAA,GAAO,MAAM,IAAI,CAAA;AACvB,MAAA,OAAO,IAAI,KAAK,GAAG,CAAA;AAAA,IACrB;AAAA,GACF;AACF;AAcA,SAAS,OAAO,IAAA,EAAsB;AACpC,EAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,qBAAA,EAAuB,MAAM,CAAA;AACnD;AAYO,SAAS,aAAA,CACd,QAAA,EACA,OAAA,GAA6B,EAAC,EACa;AAC3C,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,QAAQ,IAAA,IAAQ,MAAA;AAAA,IACtB,MAAA,EAAQ,IAAI,MAAA,CAAO,QAAA,CAAS,KAAA,CAAM,IAAI,MAAM,CAAA,CAAE,IAAA,CAAK,GAAG,CAAC,CAAA;AAAA,IACvD,aAAa,CAAA,KAAA,KAAS;AAGpB,MAAA,IAAI,CAAC,QAAA,CAAS,GAAA,CAAI,KAAK,CAAA,QAAS,IAAI,KAAA,CAAM,CAAA,0BAAA,EAA6B,KAAK,CAAA,EAAA,CAAI,CAAA;AAChF,MAAA,OAAO,KAAA;AAAA,IACT,CAAA;AAAA,IACA,cAAA,EAAgB;AAAA,GAClB;AACF;AAMO,SAAS,WAAA,GAA6B;AAC3C,EAAA,OAAO,EAAC;AACV","file":"index.cjs","sourcesContent":["import type { EnvConfig } from '@harnessed-ts/core'\n\n/**\n * Gherkin support for harnessed: what every runner needs, with no runner in it.\n *\n * - A **world** — the scenario-scoped bag where steps keep the pages a scenario\n * builds up. Steps are separate functions, so the obvious place for a harness\n * is a module-level `let`, which leaks into the next scenario the moment a\n * runner reuses a worker. Each runner adapter gives the bag the right lifetime.\n * - A **page registry** — the pages a feature can name, so a step reads\n * `Given I open the checkout page` and gets a `CheckoutPage`, typed.\n * - A **`{page}` parameter type** built from the registry, so a feature naming\n * an unknown page fails at step matching, where the typo is, rather than deep\n * inside a step.\n *\n * Step bodies stay hand-written: the feature's wording is the team's, not a\n * mirror of method names.\n */\n\n/** Anything a page registry can construct: a page or component harness class. */\nexport type PageConstructor<T = object> = new (env: EnvConfig) => T\n\nexport type PageMap = Record<string, PageConstructor>\n\n/** The pages a feature can name. */\nexport interface PageRegistry<P extends PageMap> {\n /** Every registered name, longest first — the order a matcher must try them. */\n readonly names: ReadonlyArray<keyof P & string>\n /** Whether `name` is registered: narrows a string from a step to a page name. */\n has(name: string): name is keyof P & string\n /** Constructs the named page with the scenario's env. */\n open<N extends keyof P & string>(name: N, env: EnvConfig): InstanceType<P[N]>\n}\n\nexport function definePages<P extends PageMap>(pages: P): PageRegistry<P> {\n const names = (Object.keys(pages) as Array<keyof P & string>).sort((a, b) => b.length - a.length)\n if (names.length === 0) {\n // `{page}` is an alternation of the names; with none it is the empty\n // pattern, which matches anywhere and would let any step through.\n throw new Error('harnessed: definePages() needs at least one page.')\n }\n const has = (name: string): name is keyof P & string =>\n Object.prototype.hasOwnProperty.call(pages, name)\n return {\n names,\n has,\n open(name, env) {\n // Own names only: `constructor` and the rest of Object.prototype are not pages.\n if (!has(name)) {\n throw new Error(`harnessed: no page named \"${name}\". Registered: ${names.join(', ')}.`)\n }\n const Page = pages[name]\n return new Page(env) as InstanceType<P[typeof name]>\n },\n }\n}\n\n/**\n * A cucumber-expression parameter type, in the shape every runner's\n * `defineParameterType` takes: @cucumber/cucumber, playwright-bdd and\n * @badeball/cypress-cucumber-preprocessor alike.\n */\nexport interface ParameterTypeDefinition<T> {\n name: string\n regexp: RegExp\n transformer: (match: string) => T\n useForSnippets?: boolean\n}\n\nfunction escape(text: string): string {\n return text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n/**\n * `{page}` for step patterns: matches exactly the registered names (longest\n * first, so `checkout summary` wins over `checkout`) and hands the step the\n * name, typed. Name it something else with `{ name }`.\n *\n * ```ts\n * defineParameterType(pageParameter(pages))\n * Given('I open the {page} page', async function (name) { … pages.open(name, env) })\n * ```\n */\nexport function pageParameter<P extends PageMap>(\n registry: PageRegistry<P>,\n options: { name?: string } = {},\n): ParameterTypeDefinition<keyof P & string> {\n return {\n name: options.name ?? 'page',\n regexp: new RegExp(registry.names.map(escape).join('|')),\n transformer: match => {\n // The regexp only admits registered names, so this cannot fail; it is\n // here so a hand-built pattern that bypasses it still gets a clear error.\n if (!registry.has(match)) throw new Error(`harnessed: no page named \"${match}\".`)\n return match\n },\n useForSnippets: false,\n }\n}\n\n/**\n * A fresh, empty world. Adapters call this once per scenario; it exists so the\n * bag's type is written once, where the steps declare it.\n */\nexport function createWorld<W>(): Partial<W> {\n return {}\n}\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { EnvConfig } from '@harnessed-ts/core';
|
|
2
|
+
/**
|
|
3
|
+
* Gherkin support for harnessed: what every runner needs, with no runner in it.
|
|
4
|
+
*
|
|
5
|
+
* - A **world** — the scenario-scoped bag where steps keep the pages a scenario
|
|
6
|
+
* builds up. Steps are separate functions, so the obvious place for a harness
|
|
7
|
+
* is a module-level `let`, which leaks into the next scenario the moment a
|
|
8
|
+
* runner reuses a worker. Each runner adapter gives the bag the right lifetime.
|
|
9
|
+
* - A **page registry** — the pages a feature can name, so a step reads
|
|
10
|
+
* `Given I open the checkout page` and gets a `CheckoutPage`, typed.
|
|
11
|
+
* - A **`{page}` parameter type** built from the registry, so a feature naming
|
|
12
|
+
* an unknown page fails at step matching, where the typo is, rather than deep
|
|
13
|
+
* inside a step.
|
|
14
|
+
*
|
|
15
|
+
* Step bodies stay hand-written: the feature's wording is the team's, not a
|
|
16
|
+
* mirror of method names.
|
|
17
|
+
*/
|
|
18
|
+
/** Anything a page registry can construct: a page or component harness class. */
|
|
19
|
+
export type PageConstructor<T = object> = new (env: EnvConfig) => T;
|
|
20
|
+
export type PageMap = Record<string, PageConstructor>;
|
|
21
|
+
/** The pages a feature can name. */
|
|
22
|
+
export interface PageRegistry<P extends PageMap> {
|
|
23
|
+
/** Every registered name, longest first — the order a matcher must try them. */
|
|
24
|
+
readonly names: ReadonlyArray<keyof P & string>;
|
|
25
|
+
/** Whether `name` is registered: narrows a string from a step to a page name. */
|
|
26
|
+
has(name: string): name is keyof P & string;
|
|
27
|
+
/** Constructs the named page with the scenario's env. */
|
|
28
|
+
open<N extends keyof P & string>(name: N, env: EnvConfig): InstanceType<P[N]>;
|
|
29
|
+
}
|
|
30
|
+
export declare function definePages<P extends PageMap>(pages: P): PageRegistry<P>;
|
|
31
|
+
/**
|
|
32
|
+
* A cucumber-expression parameter type, in the shape every runner's
|
|
33
|
+
* `defineParameterType` takes: @cucumber/cucumber, playwright-bdd and
|
|
34
|
+
* @badeball/cypress-cucumber-preprocessor alike.
|
|
35
|
+
*/
|
|
36
|
+
export interface ParameterTypeDefinition<T> {
|
|
37
|
+
name: string;
|
|
38
|
+
regexp: RegExp;
|
|
39
|
+
transformer: (match: string) => T;
|
|
40
|
+
useForSnippets?: boolean;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* `{page}` for step patterns: matches exactly the registered names (longest
|
|
44
|
+
* first, so `checkout summary` wins over `checkout`) and hands the step the
|
|
45
|
+
* name, typed. Name it something else with `{ name }`.
|
|
46
|
+
*
|
|
47
|
+
* ```ts
|
|
48
|
+
* defineParameterType(pageParameter(pages))
|
|
49
|
+
* Given('I open the {page} page', async function (name) { … pages.open(name, env) })
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare function pageParameter<P extends PageMap>(registry: PageRegistry<P>, options?: {
|
|
53
|
+
name?: string;
|
|
54
|
+
}): ParameterTypeDefinition<keyof P & string>;
|
|
55
|
+
/**
|
|
56
|
+
* A fresh, empty world. Adapters call this once per scenario; it exists so the
|
|
57
|
+
* bag's type is written once, where the steps declare it.
|
|
58
|
+
*/
|
|
59
|
+
export declare function createWorld<W>(): Partial<W>;
|
|
60
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAEnD;;;;;;;;;;;;;;;GAeG;AAEH,iFAAiF;AACjF,MAAM,MAAM,eAAe,CAAC,CAAC,GAAG,MAAM,IAAI,KAAK,GAAG,EAAE,SAAS,KAAK,CAAC,CAAA;AAEnE,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;AAErD,oCAAoC;AACpC,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,OAAO;IAC7C,gFAAgF;IAChF,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,CAAA;IAC/C,iFAAiF;IACjF,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,IAAI,MAAM,CAAC,GAAG,MAAM,CAAA;IAC3C,yDAAyD;IACzD,IAAI,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,SAAS,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;CAC9E;AAED,wBAAgB,WAAW,CAAC,CAAC,SAAS,OAAO,EAAE,KAAK,EAAE,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAqBxE;AAED;;;;GAIG;AACH,MAAM,WAAW,uBAAuB,CAAC,CAAC;IACxC,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,CAAC,CAAA;IACjC,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB;AAMD;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,OAAO,EAC7C,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,EACzB,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAC9B,uBAAuB,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,CAY3C;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAE3C"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|