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.