Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Worked examples

The example suites in the repository, as they are: a shop, a sign-in, data rows and shared flows, and a Kubernetes Job.

The suites in the repository’s examples/bench-site/, as they are: CI runs them against navigera’s bench site, a small shop.

$ python3 ../navigera/bench/site/server.py --port 8000 &
$ bitti run examples/bench-site --base-url http://127.0.0.1:8000

shop.bt

A cart, a search and a coupon field inside a web component.

# navigera's bench site (navigera/bench/site/server.py): a small shop.
# bitti run examples/bench-site --base-url http://127.0.0.1:<port>

before each
  goto /shop
  click --selector "#cookie-accept"

scenario "Adding a product counts it in the cart" @smoke
  click --text "Add to cart"
  expect --selector "#cart-count" text is 1
  goto /cart
  expect text "Your cart"
  expect --selector "#subtotal" text starts-with "$"

scenario "Every scenario starts with an empty cart" @smoke
  expect --selector "#cart-count" text is 0

scenario "Search narrows the list"
  fill --label "Search products" valve
  press Enter
  expect --role status text contains "Showing"
  expect --selector ".card" count >= 1
  expect no text "Titanium Bolt"

scenario "A coupon in a web component lowers the total" @cart
  click --text "Add to cart"
  expect --selector "#cart-count" text is 1
  goto /cart
  save subtotal = get text --selector "#subtotal"
  expect ${subtotal} matches '^\$[0-9]+\.[0-9]{2}$'
  fill --label "Coupon code" SAVE10
  click --role button --name "Apply coupon"
  expect text "Coupon SAVE10 applied"
  reload
  expect text "Discount (SAVE10)"

account.bt

Signing in, and the orders table behind it.

set email = demo@acme.test

scenario "Signing in shows the orders"
  goto /login
  fill --label Email ${email}
  fill --label Password ${PASSWORD:-hunter2}
  click --role button --name "Sign in"
  expect url ends-with /account
  expect text "Signed in as"
  expect --selector "#pageinfo" text starts-with "Page 1 of"
  expect no errors

scenario "A wrong password stays on the sign-in page"
  goto /login
  fill --label Email ${email}
  fill --label Password wrong
  click --role button --name "Sign in"
  expect --role alert text contains "Wrong email or password"
  expect title contains "Sign in"

scenario "The returns FAQ opens"
  goto /docs
  click --text "Returns FAQ"
  expect text "days from delivery"
  expect --selector "#returns-faq" attr open is ""

data.bt

Data rows, flows from another file, optional steps, page-state checks.

# Data rows, flows from another file, optional steps, page-state checks.
use flows/account.bt

scenario "Searching for ${term} shows only ${what}" @search
  examples
    | term  | what   |
    | valve | Valve  |
    | hinge | Hinge  |
  goto /shop
  run accept-cookies
  fill --label "Search products" ${term}
  press Enter
  expect --role status text contains "Showing"
  expect js "[...document.querySelectorAll('.card h3')].every((h) => h.textContent.includes('${what}'))"

scenario "A signed-in cart is kept by the server"
  run sign-in demo@acme.test ${PASSWORD:-hunter2}
  expect cookie sid
  goto /shop
  run accept-cookies
  click --text "Add to cart"
  expect request POST /api/cart status 200
  expect --selector "#cart-count" text is 1
  expect no request GET /api/tracking*

flows/account.bt

The flows data.bt takes with use.

# Flows other files `use`.

flow sign-in(email, password)
  goto /login
  fill --label Email ${email}
  fill --label Password ${password}
  click --role button --name "Sign in"
  expect url ends-with /account

flow accept-cookies
  optional click --selector "#cookie-accept" --timeout-ms 500

A Kubernetes Job

examples/kubernetes/job.yaml: the tests from a ConfigMap, JUnit and evidence in /work/out, a memory-backed /dev/shm.

# bitti as a Kubernetes (or OpenShift) Job: the tests from a ConfigMap,
# the app at a Service in the cluster, the run's lines on the pod's log,
# JUnit and evidence in its /work/out (an emptyDir: copy them out, or mount
# a volume that outlives the pod, before the pod goes). The image: `docker build -f bitti/Dockerfile`
# (see bitti's README), pushed where the cluster pulls from.
#
#   kubectl create configmap bt-tests --from-file=tests/
#   kubectl apply -f job.yaml && kubectl logs -f job/bitti
apiVersion: batch/v1
kind: Job
metadata:
  name: bitti
spec:
  backoffLimit: 0
  activeDeadlineSeconds: 1800
  template:
    spec:
      restartPolicy: Never
      securityContext:
        runAsNonRoot: true
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: bitti
          image: registry.example.com/bitti:latest
          args: ["run", "/tests", "--base-url", "http://shop.default.svc:8080", "--workers", "2",
                 "--junit", "/work/out/junit.xml", "--evidence", "/work/out/evidence"]
          env:
            # The root filesystem is read-only: Chrome's caches go here.
            - { name: HOME, value: /tmp }
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop: ["ALL"]
          resources:
            requests: { cpu: "1", memory: 1Gi }
            limits: { memory: 2Gi }
          volumeMounts:
            - { name: tests, mountPath: /tests, readOnly: true }
            - { name: work, mountPath: /work }
            - { name: tmp, mountPath: /tmp }
            # Chrome's shared memory: the default 64 MB is too small.
            - { name: shm, mountPath: /dev/shm }
      volumes:
        - name: tests
          configMap: { name: bt-tests }
        - name: work
          emptyDir: {}
        - name: tmp
          emptyDir: {}
        - name: shm
          emptyDir: { medium: Memory, sizeLimit: 512Mi }

Made from docs/examples.md.