๐ฅ jevcumber
September 19, 2026 ยท View on GitHub
jevcumber.dev โ site and demo video
Write Cucumber tests with just the .feature file. No step definitions.
Feature: Wikipedia search
Scenario: Looking up bagels
Given I am on https://en.wikipedia.org
When I search for "bagel"
Then I should see "From Wikipedia, the free encyclopedia"
And the URL should contain "/wiki/Bagel"
$ jevcumber examples/
Feature: Wikipedia search
Scenario: Looking up bagels
โ Given I am on https://en.wikipedia.org
โ When I search for "bagel"
โ Then I should see "From Wikipedia, the free encyclopedia"
โ Then the URL should contain "/wiki/Bagel"
1 scenarios (1 passed) ยท 4 steps (4 passed)
That's the whole test. There is no glue code behind it.
How it works
jevcumber opens your site in a real browser with Playwright and, for each Gherkin step, asks Jev โ TypeSafe AI's fast System One model โ what the step means on the page in front of it.
Jev never writes code or invents values. jevcumber lists what's actually there โ the page's buttons, fields and links, and the literal values in your step โ and Jev picks among them:
| Your step | jevcumber asks | Jev picks |
|---|---|---|
When I search for "bagel" | what kind of action is this? | fill |
which control? Search Wikipedia, Log in, Donateโฆ | Search Wikipedia | |
| which literal is the text to type? | "bagel" | |
| type only, or type and submit? | submit |
All of those questions go out in one request (about a second), each answer comes back with a probability, and jevcumber performs the result with Playwright. If Jev isn't confident, jevcumber refuses to guess and tells you what it was torn between:
? When I click it (ambiguous)
Jev was not confident about the element (0.10 < 0.6). Candidates: button "Log in" (45%),
link "Sign up" (35%), none (20%). Reword the step to be more specific.
The lockfile makes it deterministic
The first run records what each step resolved to in <name>.feature.lock.json, next to your feature.
Commit it, like a package-lock.json. After that:
- runs replay the lockfile โ no API calls, no API key, no model variance;
- a step you add or reword is resolved on the next run, and only that step;
- if your UI changes and a recorded control disappears, the step is re-resolved and reported as
healed (
โป) โ and the change shows up in the lockfile's diff for review.
Try it in two minutes (no API key needed)
Requires Node.js 20+.
npm install -g jevcumber
jevcumber install-browser # downloads the Chromium build jevcumber drives
This repo ships an example with its lockfile already recorded, so you can watch a replay without signing up for anything:
git clone https://github.com/RubyBrewsday/jevcumber && cd jevcumber
jevcumber examples/ --frozen --headed
Write your own
-
Get a TypeSafe API key (see the TypeSafe quickstart) and export it:
export TYPESAFE_API_KEY=... -
Write a feature file anywhere โ say
features/login.feature:Feature: Login Scenario: successful login Given I am on https://myapp.example.com/login When I fill in the email field with "alice@example.com" And I fill in the password field with "correct horse" And I click the Log in button Then I should see "Welcome, alice" -
Run it:
jevcumber features/ # add --headed to watch -
Commit
features/login.feature.lock.json. In CI, runjevcumber features/ --frozenโ no key needed.
CLI
jevcumber <paths...> [options]
| Option | |
|---|---|
| (no flags) | Replay the lockfile; resolve new or changed steps with Jev; heal steps whose control has gone. |
--frozen | Replay only. Never calls Jev, never writes the lockfile. A missing or stale entry fails. Use in CI. |
--update | Ignore the lockfile and re-resolve every step. |
--base-url <url> | What paths like "/login" resolve against. Not needed when steps use full URLs. |
--headed | Show the browser. |
--tags <expr> | Cucumber tag expression, e.g. "@smoke and not @wip". |
--min-confidence <n> | Refuse to act below this confidence (default 0.6). |
Exit code is 1 if any step failed, was ambiguous, or was undefined.
Backgrounds, Scenario Outlines, data tables, doc strings, And/But, and tags all work โ parsing is
done by the official @cucumber/gherkin.
Writing steps Jev can resolve
- Put data in quotes. Jev selects values, it never invents them:
I fill in the email field with "alice@example.com". - Navigate with a literal URL or path.
Given I am on https://example.com/login,Given I am on example.com/login(https://assumed),Given I am on localhost:3000(http://assumed) โ orGiven I am on "/login"together with--base-url, which keeps features portable across environments. A path is resolved from the base URL's host root. "Given I am on the login page" gives jevcumber nothing to select from. - Name controls as they appear on the page: "the Log in button", "the email field", or quoted:
I click "Log in". - "Search for", "submit", "look up" type and press Enter.
When I search for "bagels"fills the field and submits it;When I fill in the search box with "bagels"only types. - Two kinds of
Then. Quoted text, a named control, or a URL fragment becomes a fast Playwright assertion that is cached and replayed:Then I should see "Welcome",Then the "Save" button is visible,Then the URL should contain "/todos". A described expectation โThen I see an article about bagelsโ is judged by Jev against the page on every run, so it needs the API key and cannot run under--frozen. - An empty literal is ignored, so a step can't clear a field with
"".
What a step can do today: navigate, click, fill (optionally submitting), select from a dropdown, check/uncheck, press a key, and assert.
Some sites block automated browsers
Google, for one, answers a scripted search with a "prove you're not a robot" page โ and jevcumber will correctly report that your results aren't there. jevcumber doesn't try to evade bot detection. Test your own app, or sites that permit automation.
What is sent to TypeSafe
For each step that is not replayed from the lockfile, jevcumber sends Jev: the step text and its literals, the scenario name and earlier step texts, the page URL and title, the page's interactive elements (role, name, current value โ never the value of a password field), and up to 8 000 characters of visible page text.
Literals in your steps โ including a password you write in a step โ live in your .feature file and
are stored in the lockfile too, so use throwaway test credentials. Under --frozen, nothing is sent
anywhere.
CI example (GitHub Actions)
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm install -g jevcumber
- run: jevcumber install-browser --with-deps
- run: jevcumber features/ --frozen --base-url http://localhost:3000
Limits (v0.1)
Scenarios run sequentially. No hooks, iframes, file uploads, drag and drop, or multi-tab flows yet. One action per step. Web UIs only.
Development
git clone https://github.com/RubyBrewsday/jevcumber && cd jevcumber
npm install && npx playwright install chromium
npm test # unit + end-to-end against a fixture app; no API key needed
TYPESAFE_API_KEY=... npm test # also runs the live tests against Jev
npm run jevcumber -- examples/ # build, then run the CLI from source
(tsx src/cli.ts doesn't work โ the transpiler injects a helper into the script Playwright evaluates
in the page. Use the built CLI.)
The layout is one small module per job under src/: gherkin โ snapshot + candidates โ
resolver (the only module that talks to Jev) โ lockfile โ executor โ reporter, orchestrated
by runner. The design doc is in docs/superpowers/specs.
The website
jevcumber.dev is the single static page in site/, served by a Cloudflare Worker
(wrangler.jsonc, site-worker/index.js) that also redirects jevcumber.com and the www. hosts to
jevcumber.dev and answers byte-range requests for the demo video. Deploy with npm run deploy:site
(needs npx wrangler login first).
Tuning the questions Jev is asked
e2e/resolve-eval.test.ts asks Jev about every fixture step on the page that step really sees,
compares the answer with the known-good resolution in fixtures/expected.ts, and prints every
answer's probability distribution:
npx vitest run e2e/resolve-eval.test.ts --silent=false --reporter=verbose
When a kind of step resolves badly: add a scenario for it to the fixtures, watch it miss, then adjust
the wording in src/resolver.ts. Don't paste the fixture's own sentence into a question's examples.
License
MIT ยฉ Mike Poage. Not affiliated with TypeSafe AI or the Cucumber project.