๐Ÿฅ’ jevcumber

September 19, 2026 ยท View on GitHub

jevcumber.dev โ€” site and demo video

npm CI License: MIT

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 stepjevcumber asksJev 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

  1. Get a TypeSafe API key (see the TypeSafe quickstart) and export it:

    export TYPESAFE_API_KEY=...
    
  2. 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"
    
  3. Run it:

    jevcumber features/            # add --headed to watch
    
  4. Commit features/login.feature.lock.json. In CI, run jevcumber 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.
--frozenReplay only. Never calls Jev, never writes the lockfile. A missing or stale entry fails. Use in CI.
--updateIgnore the lockfile and re-resolve every step.
--base-url <url>What paths like "/login" resolve against. Not needed when steps use full URLs.
--headedShow 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) โ€” or Given 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.