← Driving your service: HTTP, gRPC, a browser and a shell

Driving your service through a browser

clientsbrowserui
A scenario tells a client what to do; the client uses the service the way a person or another system would, and the scenario checks what came back.tomato runreads the scenario, drives the client, checks the answerthe scenarioWhensends "POST" to …Thenresponse status is …features/*.feature"todos-client"http-client resourcedoesPOST /api/todos{"title": "Buy milk"}gets back201{"id": 1, "title": "Buy milk"}todosyour serviceHTTP API :3000gRPC API :50051web page /CLI todo add …

Start. tomato starts your service and its dependencies. The client only needs to know where the service is: an address, or nothing at all for the shell.

Act. A When step tells the client what to do, and it does it the way another service would: it sends a request.

Answer. The service answers through the same door: a status, headers and a body.

Assert. Then steps check that answer. Check the effects too, through the other resources: the row in the database, the message on Kafka.

Reset. Before the next scenario the client forgets its headers and cookies, so no scenario leans on another's.

The suite

containers:
  chrome: { preset: chromium }        # a headless Chromium

resources:
  alice-browser: { type: browser, container: chrome, options: { base_url: http://localhost:3000 } }
Scenario: A todo added on the page shows up in the list
  Given "alice-browser" opens "/"
  When "alice-browser" fills "New todo" with "Water the tomatoes"
  And "alice-browser" clicks "Add"
  Then "alice-browser" sees "Water the tomatoes" within "3s"

Scenario: The page says why a todo was refused
  Given "alice-browser" opens "/"
  When "alice-browser" clicks "Add"
  Then "alice-browser" sees "title is required" within "3s"

The page this runs against has nothing special for tests:

<form><label>New todo <input name="title"></label> <button>Add</button></form>

How it finds things: by name, like a person

fills "New todo" and clicks "Add" take no CSS selectors. tomato looks for the element whose accessible name is that text: the label of a field, the text of a button or link, an aria-label. That’s what a person reads and a screen reader announces. So:

  • A test survives a redesign that keeps the words.
  • A field without a label can’t be found, which is a bug a screen-reader user would hit first.
  • Exactly one element must match. Two buttons called “Delete” fail the step and list both.

What it can do

ToStep
go somewhereopens "/path"
fill infills "Email" with "…", types "…" at the end of "…", presses "Enter"
chooseclicks "Add", checks "Remember me", chooses "Express", selects "Austria" from "Country"
check the page nowsees "…", does not see "…", sees "Pay" is disabled, url is "/done"
wait for a changesees "…" within "3s", no longer sees "Saving…" within "5s"
keep a picturesaves screenshot "checkout", and one is saved anyway when a step fails

Notes

  • After a click, wait. Pages update asynchronously, so check an outcome with sees "…" within "3s", not sees "…".
  • Two resources are two people. alice-browser and bob-browser each get their own cookies and storage, in one Chromium: collaboration needs no special steps.
  • localhost just works. The page in the container reaches your service on your machine through a tunnel tomato opens, so cookies, CORS and secure context behave as they would for a real user.