dánwòdánwò

Examples

Shop on Android and iOS

An Expo app that buys credits through Stripe Checkout in a Custom Tab or a Safari view; danwo tests it on an Android emulator or an iOS Simulator.

An Expo app that buys credits through Stripe Checkout in a Custom Tab or a Safari view; danwo tests it on an Android emulator or an iOS Simulator. See it on GitHub.

What it shows

  • The same Stripe emulator behind an Android emulator bound to danwo's proxy
  • Checkout in a Custom Tab, or a Safari view on iOS, read with the same locators as the app
  • The same tests on an iOS Simulator with DANWO_PLATFORM=ios
  • Agent steps recorded once and replayed with no model
  • Parallel emulators, one per worker

Stack

ExpoReact NativeAndroidiOS

Emulates: Stripe. Runs on: An Expo app on an Android emulator or an iOS Simulator.

Run it

From examples/shop-mobile, after bun install at the repository's root:

bun run build:android   # danwo build-android app
bun run build:ios       # danwo build-ios app
bun run test:e2e        # danwo test
bun run test:ios        # DANWO_PLATFORM=ios danwo test
bun run matrix          # danwo matrix tests/buy-credits.danwo.ts
bun run matrix:ios      # DANWO_PLATFORM=ios danwo matrix tests/buy-credits.danwo.ts

Mobile explains what this example leans on.

The test

tests/buy-credits.danwo.ts
import type { DeviceFixtures } from "@danwo/device"
import { type StripeFixtures, test as base } from "@danwo/sdk"

import { grantedCredits, paidCredits } from "../../shop/src/ledger.ts"

const test = base.extend<StripeFixtures & DeviceFixtures>()

test("buys 500 credits through Stripe Checkout in the in-app browser", async ({
  screen,
  expect,
  device,
}) => {
  /* By its text: iOS's driver reports no heading role. */
  await expect(screen.getByText("Danwo Shop")).toBeVisible()
  await expect(screen.getByTestId("credits")).toHaveText(/^Credits: \d+$/)

  /* The worker's shop keeps its credits from test to test; this purchase adds 500. */
  const before = Number((await screen.getByTestId("credits").innerText()).replace(/\D/g, ""))

  await screen.getByTestId("buy").tap()

  /* Checkout opens over the app (a Custom Tab, or a Safari view on iOS); the same locators read the page. */
  await expect(screen.getByText("Pay danwo")).toBeVisible()
  expect((await device.foreground()).browser).toBe(true)
  await screen.getByLabel("Email").fill("[email protected]")
  /* Paying calls Stripe; readiness waits for the call and plays the webhook it causes. */
  await screen.getByRole("button", { name: "Pay $5.00" }).tap()

  /* Checkout returns to the app through `danwoshop://done`. */
  await expect(screen.getByTestId("status")).toHaveText("Thanks for your order")
  await expect(screen.getByTestId("credits")).toHaveText(`Credits: ${before + 500}`)
}).invariant(async ({ app, stripe, expect }) => {
  /* However the purchase went on the device, credits match the paid Checkout Sessions. */
  const paid = await paidCredits(stripe, "u1")

  expect(await grantedCredits(app.baseUrl, "u1")).toBe(paid)
})

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