Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.vers.sh/llms.txt

Use this file to discover all available pages before exploring further.

The expensive part of end-to-end testing isn’t the test — it’s getting to the point where the test can run. You sign in, navigate four pages deep, seed some data, and then you’re ready to exercise one assertion. Do that serially for every scenario and your suite spends its life re-setting-up. Vers lets you set up once, branch at the decision point, and run every scenario in parallel against its own live copy of that state.

What you’ll build

  • A small Flask shopping-cart app running inside a Vers VM
  • A “pre-checkout” state with a user signed in and items in the cart
  • Three branches exercising different checkout scenarios (valid card / expired card / empty cart edge case) — all in parallel, against live isolated state
  • Time: ~20 minutes

Prerequisites

  • Vers CLI installed and authenticated
  • Basic familiarity with HTTP and the command line

The idea

Traditionally, each test re-creates its setup. Scenario A does login → browse → add-to-cart → test A. Scenario B does login → browse → add-to-cart → test B. The shared prefix gets paid over and over. With Vers you do login → browse → add-to-cart once, branch the running VM at that exact moment (memory, sockets, in-flight session — all of it), then run scenario A on one branch, scenario B on another, scenario C on a third. Every branch is live and independent. The setup prefix is paid once and inherited for free.

Step 1: Initialize the project

mkdir parallel-scenario-testing && cd parallel-scenario-testing
vers init
Edit vers.toml — the Flask app is tiny, so default resources are fine:
[machine]
mem_size_mib = 1024
vcpu_count = 1
fs_size_mib = 2048

[rootfs]
name = "default"

[kernel]
name = "default.bin"

Step 2: Boot the VM and install the app

vers run --vm-alias root
vers connect
Inside the VM:
apt-get update -qq && apt-get install -y -qq python3 python3-flask curl jq
mkdir -p /app && cd /app
Write a minimal shopping-cart service (/app/server.py) — two endpoints, in-memory state:
cat > /app/server.py << 'PYEOF'
from flask import Flask, request, jsonify
app = Flask(__name__)

state = {"user": None, "cart": [], "inventory": {"book": 12.00, "mug": 8.00}}

@app.post("/login")
def login():
    state["user"] = request.json["user"]
    return {"ok": True, "user": state["user"]}

@app.post("/cart/add")
def add():
    item = request.json["item"]
    if item not in state["inventory"]:
        return {"ok": False, "error": "unknown_item"}, 400
    state["cart"].append(item)
    return {"ok": True, "cart": state["cart"]}

@app.post("/checkout")
def checkout():
    if not state["user"]:
        return {"ok": False, "error": "not_logged_in"}, 401
    if not state["cart"]:
        return {"ok": False, "error": "empty_cart"}, 400
    card = request.json.get("card", {})
    if card.get("exp", "") < "2026-01":
        return {"ok": False, "error": "card_expired"}, 402
    total = sum(state["inventory"][i] for i in state["cart"])
    return {"ok": True, "total": total, "items": state["cart"]}

app.run(host="0.0.0.0", port=8080)
PYEOF

# Run it in the background
nohup python3 /app/server.py > /app/server.log 2>&1 &
sleep 1
curl -s http://localhost:8080/login -X POST -H 'content-type: application/json' -d '{}' && echo
The last line confirms the server boots. You’ll see {"ok":true,"user":null}.

Step 3: Navigate to the pre-checkout state

Still inside the VM, drive the app into a meaningful state — a signed-in user with two items in the cart:
curl -s -X POST http://localhost:8080/login \
  -H 'content-type: application/json' \
  -d '{"user": "alice"}' | jq

curl -s -X POST http://localhost:8080/cart/add \
  -H 'content-type: application/json' \
  -d '{"item": "book"}' | jq

curl -s -X POST http://localhost:8080/cart/add \
  -H 'content-type: application/json' \
  -d '{"item": "mug"}' | jq
You’re now at the state every checkout scenario wants to start from: alice logged in, cart has ["book", "mug"], server process is running and holding that state in memory. Exit back to your host shell:
exit

Step 4: Branch the live state

Branch the running VM. Each child inherits the Flask process, the in-memory cart, and alice’s session — live.
vers branch --alias happy-path
vers branch --alias expired-card
vers branch --alias double-checkout
Three forks from the same moment. In a traditional setup this would have been “run the login/add-to-cart prefix three more times.” Here it’s one branch call each.
vers status
You’ll see four VMs running — the root plus three branches. All are at exactly the state you left the cart in.

Step 5: Run divergent scenarios in parallel

Scenario A — happy path (valid card)

vers execute happy-path "curl -s -X POST http://localhost:8080/checkout \
  -H 'content-type: application/json' \
  -d '{\"card\": {\"exp\": \"2027-05\"}}' | jq"
Expected: {"ok": true, "total": 20.0, "items": ["book", "mug"]}

Scenario B — expired card

vers execute expired-card "curl -s -X POST http://localhost:8080/checkout \
  -H 'content-type: application/json' \
  -d '{\"card\": {\"exp\": \"2024-01\"}}' | jq"
Expected: {"ok": false, "error": "card_expired"}

Scenario C — double checkout edge case

This one exercises a more interesting property of branching: the scenario mutates state and asserts a second call fails cleanly.
vers execute double-checkout "curl -s -X POST http://localhost:8080/checkout \
  -H 'content-type: application/json' \
  -d '{\"card\": {\"exp\": \"2027-05\"}}' && \
  curl -s -X POST http://localhost:8080/checkout \
    -H 'content-type: application/json' \
    -d '{\"card\": {\"exp\": \"2027-05\"}}' | jq"
Because this branch has its own cart state, the mutation doesn’t touch the other two branches. Run them again and they’ll still see the original cart.

True parallelism across terminals

For visible parallelism, open three terminals and run the three scenarios simultaneously. They’ll finish in roughly the time of the slowest single scenario — not the sum.

Step 6: Verify isolation

Re-run scenario A after scenario C has mutated state in its branch. It still succeeds because each branch has its own memory:
vers execute happy-path "curl -s -X POST http://localhost:8080/checkout \
  -H 'content-type: application/json' \
  -d '{\"card\": {\"exp\": \"2027-05\"}}' | jq"
Still {"ok": true, ...}. This is the key property: branches aren’t shared mutable state. They’re siblings.

Step 7: Clean up

vers checkout root
vers kill root -r
The -r tears down the root and all descendants.

The pattern

Set up expensive state once, branch at the decision point, run every scenario in parallel. This works anywhere the setup cost is asymmetric with the test cost:
  • End-to-end tests that require login + navigation + seeding
  • Database migrations where you want to try three approaches against a populated dataset
  • Fuzzing — branch a running server, throw divergent inputs at each branch
  • Reproducing stateful bugs — branch at the suspicious moment, explore from there without losing the setup
Once you stop paying the setup cost per scenario, the shape of your test suite changes. You start writing scenarios you wouldn’t have written before because the cost was too high.

What’s next

Database state testing

Same pattern, applied to testing parallel schema migrations against a seeded database.

Agent swarms

Branch the same golden state into N parallel agents.

vers branch

Every flag on the branch primitive.

Architecture

How branches inherit live memory without paying for a full copy.