dánwòdánwò

Examples

Flutter waitlist

A Flutter app on the Android emulator or the iOS Simulator joins a waitlist through a Hono API on Postgres, which sends a welcome email through Resend.

A Flutter app on the Android emulator or the iOS Simulator joins a waitlist through a Hono API on Postgres, which sends a welcome email through Resend. See it on GitHub.

What it shows

  • danwo build-android and danwo build-ios building one Flutter app, with danwo's network config added to the APK for the build only
  • The app's HTTPS calls to its production host reaching a local API through danwo's proxy, over the platform's own HTTP stack
  • Locators on Flutter: a Semantics identifier for getByTestId, and a Material button found by its role and label
  • An invariant that reads both sides: exactly one row in Postgres and exactly one welcome email
  • danwo matrix faulting the app's own API, Resend and the API's SQL, on either platform

Stack

FlutterAndroid emulatoriOS SimulatorHonoPostgres

Emulates: Resend. Runs on: A Flutter app on an Android emulator or an iOS Simulator, with Postgres in Docker.

Run it

Copy examples/with-flutter anywhere, then from its folder:

npm install
npx @danwo/cli test

Its scripts:

npm run build:android   # danwo build-android app
npm run build:ios       # danwo build-ios app
npm run api             # node api/server.ts
npm run typecheck       # tsc --noEmit
npm run test:e2e        # danwo test
npm run matrix          # danwo matrix tests/deterministic.danwo.ts --grep "joins the waitlist"
npm run matrix:plan     # danwo matrix tests/deterministic.danwo.ts --grep "joins the waitlist" --plan

Its README says what else it needs and what the last full matrix found.

Until the danwo version it pins is on npm, install it from a local build instead: from the repository's root, bun run release <version> --pack, then node examples/link.ts with-flutter, which installs a copy and prints where it is.

Flutter explains what this example leans on.

The test

tests/deterministic.danwo.ts
import { joinedOnce, test } from "./waitlist.ts"

const EMAIL = "[email protected]"

test("shows the waitlist form", async ({ screen, expect }) => {
  /*
   * Every test starts the app afresh on the device; no `app.open` is needed. The field is found by
   * its Semantics identifier: on Android, Flutter hands a text field's label to the accessibility
   * tree only as its hint, which the device driver does not read.
   */
  await expect(screen.getByTestId("email")).toBeVisible()
  await expect(screen.getByRole("button", { name: "Join the waitlist" })).toBeVisible()
})

test("asks for a valid email", async ({ screen, expect }) => {
  await screen.getByTestId("email").fill("ada")
  await screen.getByRole("button", { name: "Join the waitlist" }).tap()

  /* The API refuses it; the app shows the message it answered. */
  await expect(screen.getByTestId("error")).toHaveText("Enter a valid email address.")
})

test("joins the waitlist", async ({ screen, expect }) => {
  await screen.getByTestId("email").fill(EMAIL)
  /* Joining saves the row and calls Resend; readiness waits for both before the next line. */
  await screen.getByTestId("join").tap()

  await expect(screen.getByTestId("status")).toHaveText("You're on the list")
  await expect(screen.getByTestId("waitlist-count")).toHaveText("1")
}).invariant((fixtures) => joinedOnce({ ...fixtures, email: EMAIL }))

On this page

Run it on every pull request

A GitHub check and one comment per pull request, with the flight records attached.

Set up CI