Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,51 @@ Every test run creates a unique user with a timestamped email address (`e2e-<tim
- Avoid assertions that depend on the total count of accounts, transactions, or other global state that may accumulate across runs.
- If a test needs a pre-funded account, fund it programmatically within the test rather than relying on prior state.

## Open Payments purchase (MOPCA)

`open-payments-purchase.feature` exercises a full [Open Payments](https://openpayments.dev/)
workflow end to end using a **Mock Open Payments Client App (MOPCA)** — a
minimal, in-process Open Payments _client_ that plays the role of a merchant's
checkout server ([`mopca/server.ts`](mopca/server.ts)).

The scenario:

1. Creates a fresh **merchant** (EUR) user and generates developer keys via
_Settings → Developer Keys → Generate_ (nickname `e2e`). The private key is
captured from the success dialog; the keyId/public key are read from the
`walletAddressKeys` table.
2. Creates a fresh **customer** (EUR) user and deposits 100 EUR.
3. Starts a MOPCA instance authenticated with the merchant's keys. On startup it
verifies it can reach the ASE with those credentials.
4. The customer browses to the MOPCA storefront and buys "testing stuff" for
9.99 EUR. MOPCA creates an incoming payment (merchant), a quote (customer) and
an interactive outgoing-payment grant, then redirects the customer to the ASE
consent screen.
5. The customer approves; the ASE redirects back to MOPCA, which continues the
grant, creates the outgoing payment and polls the incoming payment until the
funds are received.
6. The merchant's transactions are checked for the incoming 9.99 EUR credit.

### `mopca.testnet.test` host + TLS

MOPCA is served over HTTPS using the local `*.testnet.test` wildcard cert on an
OS-assigned free port, reachable at `https://mopca.testnet.test:<port>`. Multiple
instances can therefore run concurrently.

`mopca.testnet.test` must resolve to loopback. The Playwright config maps it at
the browser level via a Chromium `--host-resolver-rules` launch arg, so **no
`/etc/hosts` change is required** to run the test. `mopca.testnet.test` is also
included in `pnpm local:hosts` (useful for manual `curl`/browser debugging
outside the test) — re-run that once (with sudo) if you want the host entry too:

```bash
pnpm local:hosts
```

The Node-side Open Payments client must trust the self-signed cert. The `test`
scripts set `NODE_EXTRA_CA_CERTS=../local/config/certs/local.crt` for this; no
extra configuration is needed when running via `pnpm e2e:test`.

## Running the tests

**Prerequisites**: full local stack running (`pnpm local:setup && pnpm dev`).
Expand Down
16 changes: 16 additions & 0 deletions e2e/features/open-payments-purchase.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Feature: Open Payments purchase via a Mock Open Payments Client App

As a developer who made a recent change to the codebase
I want a Mock Open Payments Client App running in the e2e test environment
to be able to initiate, approve and verify an Open Payments workflow
so that I can easily verify the consistency of the Open Payments features.

Scenario: Mock Open Payments Client App makes a simple purchase
Given an EUR merchant user with developer keys configured
And an EUR customer user with 100 EUR deposited into their account
And a running Mock Open Payments Client App initiated with the merchant keys
When the customer initiates a payment of 9.99 EUR through the MOPCA
Then the customer should be asked to verify the payment
And the customer should be informed about the success of the payment
When the merchant logs into their account and views transactions
Then a recent incoming transaction of 9.99 EUR should be visible
45 changes: 44 additions & 1 deletion e2e/features/steps/fixtures.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { createBdd, test as base } from 'playwright-bdd'
import type { BrowserContext, Page } from '@playwright/test'
import {
type Credentials,
createUniqueCredentials
} from '../../helpers/local-wallet'
import type { MopcaHandle } from '../../mopca/server'
import { mkdir } from 'node:fs/promises'

type FlowState = {
Expand All @@ -28,7 +30,33 @@ type FlowState = {
takeScreenshot: (name: string) => Promise<void>
}

export const test = base.extend<{ flow: FlowState }>({
/**
* State for the Open Payments purchase scenario, which involves two independent
* users (merchant + customer) and an in-process MOPCA server. The customer uses
* the default `page`; the merchant runs in its own browser context so both
* sessions stay independent. Resources are torn down after the scenario.
*/
type OpPurchaseState = {
merchant: {
credentials?: Credentials
walletAddressUrl?: string
keyId?: string
publicKey?: string
privateKey?: string
context?: BrowserContext
page?: Page
}
customer: {
credentials?: Credentials
walletAddressUrl?: string
}
mopca?: MopcaHandle
}

export const test = base.extend<{
flow: FlowState
opPurchase: OpPurchaseState
}>({
flow: async ({ page }, use, testInfo) => {
// Extract feature name from the generated test file path
// e.g., ".features-gen/auth-signup-dashboard.feature.spec.js" → "auth-signup-dashboard"
Expand Down Expand Up @@ -57,6 +85,21 @@ export const test = base.extend<{ flow: FlowState }>({

// eslint-disable-next-line react-hooks/rules-of-hooks
await use(state)
},

// eslint-disable-next-line no-empty-pattern
opPurchase: async ({}, use) => {
const state: OpPurchaseState = { merchant: {}, customer: {} }

// eslint-disable-next-line react-hooks/rules-of-hooks
await use(state)

if (state.mopca) {
await state.mopca.close().catch(() => {})
}
if (state.merchant.context) {
await state.merchant.context.close().catch(() => {})
}
}
})

Expand Down
Loading
Loading