Examples

The code below comes from the examples/test-dapp directory in the repository. It is a full Scaffold Stellar app with two Soroban contracts deployed on testnet: a Counter and an OpenZeppelin ERC-4626 XLM Vault. The test files live in tests/.

Each example demonstrates a different kind of dApp interaction you can automate with stellar-wallet-mock — from basic wallet connection to Soroban auth entry signing.

Playwright Fixture

A Playwright fixture lets you run setup code before each test. Here we create a wallet fixture that installs the mock wallet so every test starts with a connected wallet automatically.

tests/fixtures.tstypescript
1import { test as base } from "@playwright/test";
2import { installMockStellarWallet, type MockWallet } from "stellar-wallet-mock";
3import { Keypair, Networks } from "@stellar/stellar-sdk";
4
5const SECRET_KEY = "SDPDMYEWFZEL6MW37FTPNTPZFYU2QYX4MLDSA7QBS4VSNZL5JL4IKDVQ";
6const PUBLIC_KEY = Keypair.fromSecret(SECRET_KEY).publicKey();
7
8export const test = base.extend<{ wallet: MockWallet }>({
9 wallet: async ({ page }, use) => {
10 const wallet = await installMockStellarWallet({
11 page,
12 secretKey: SECRET_KEY,
13 network: "TESTNET",
14 networkPassphrase: Networks.TESTNET,
15 });
16 await use(wallet);
17 },
18});
19
20export { expect } from "@playwright/test";
21export { PUBLIC_KEY };

All examples below import test and expect from this fixture file instead of from @playwright/test directly.

Connect Wallet

The simplest test — navigate to the dApp and confirm the wallet connected with the right address. The mock pre-seeds localStorage, so the dApp boots directly into a connected state.

tests/connect.spec.tstypescript
1import { test, expect, PUBLIC_KEY } from "./fixtures";
2
3test("wallet connects and shows address", async ({ page }) => {
4 await page.goto("http://localhost:5173");
5
6 // The dApp reads localStorage and displays the connected address
7 await expect(page.getByText(PUBLIC_KEY.slice(0, 5))).toBeVisible({
8 timeout: 15_000,
9 });
10});

Sign a Transaction

Clicking the increment button on a Soroban counter contract triggers a transaction. The mock intercepts the SUBMIT_TRANSACTION message and signs it in Node.js — no popup, no manual approval.

tests/counter.spec.tstypescript
1import { test, expect, PUBLIC_KEY } from "./fixtures";
2
3test("counter increment signs and updates count", async ({ page }) => {
4 await page.goto("http://localhost:5173");
5
6 // Wait for wallet to connect
7 await expect(page.getByText(PUBLIC_KEY.slice(0, 5))).toBeVisible({
8 timeout: 15_000,
9 });
10
11 // Click increment — triggers a Soroban transaction
12 const incrementBtn = page.getByTestId("increment-btn");
13 await expect(incrementBtn).toBeEnabled({ timeout: 5_000 });
14 await incrementBtn.click();
15
16 // Wait for the on-chain state to update
17 const counterValue = page.getByTestId("counter-value");
18 await expect(counterValue).not.toContainText("—", { timeout: 45_000 });
19
20 // Verify the count is a positive number
21 const text = await counterValue.textContent();
22 const match = text?.match(/(\d+)/);
23 expect(match).toBeTruthy();
24 expect(Number(match![1])).toBeGreaterThan(0);
25});

Vault Deposit (Auth Entry Signing)

Depositing XLM into an OpenZeppelin ERC-4626 vault is more complex than a simple transaction. The vault's deposit() calls require_auth() for both the token transfer and the vault interaction, which means the wallet needs to sign Soroban auth entries (via SUBMIT_AUTH_ENTRY). The mock handles this automatically.

tests/vault-deposit.spec.tstypescript
1import { test, expect, PUBLIC_KEY } from "./fixtures";
2
3test("deposit XLM into vault and receive shares", async ({ page }) => {
4 await page.goto("http://localhost:5173");
5
6 // Wait for wallet
7 await expect(page.getByText(PUBLIC_KEY.slice(0, 5))).toBeVisible({
8 timeout: 15_000,
9 });
10
11 // Enter deposit amount
12 const depositInput = page.getByTestId("deposit-input");
13 await expect(depositInput).toBeVisible({ timeout: 5_000 });
14 await depositInput.fill("1");
15
16 // Click deposit — triggers require_auth + auth entry signing
17 const depositBtn = page.getByTestId("deposit-btn");
18 await expect(depositBtn).toBeEnabled();
19 await depositBtn.click();
20
21 // Verify shares were minted
22 const sharesMinted = page.getByTestId("shares-minted");
23 await expect(sharesMinted).toBeVisible({ timeout: 45_000 });
24 const text = await sharesMinted.textContent();
25 const match = text?.match(/([\d.]+)/);
26 expect(match).toBeTruthy();
27 expect(Number(match![1])).toBeGreaterThan(0);
28});

Vault Withdraw

After depositing, withdraw XLM and verify the vault balance decreases. This also uses auth entry signing under the hood.

tests/vault-withdraw.spec.tstypescript
1import { test, expect, PUBLIC_KEY } from "./fixtures";
2
3test("withdraw XLM from vault", async ({ page }) => {
4 await page.goto("http://localhost:5173");
5
6 // Wait for wallet
7 await expect(page.getByText(PUBLIC_KEY.slice(0, 5))).toBeVisible({
8 timeout: 15_000,
9 });
10
11 // Refresh to get current vault balance
12 const refreshBtn = page.getByTestId("refresh-balance-btn");
13 await expect(refreshBtn).toBeVisible({ timeout: 5_000 });
14 await refreshBtn.click();
15
16 const vaultBalance = page.getByTestId("vault-balance");
17 await expect(vaultBalance).toContainText("XLM", { timeout: 15_000 });
18
19 // Record balance before withdrawal
20 const beforeText = await vaultBalance.textContent();
21 const beforeMatch = beforeText?.match(/([\d.]+)\s*XLM/);
22 const beforeBalance = beforeMatch ? Number(beforeMatch[1]) : 0;
23
24 // Withdraw 0.5 XLM
25 const withdrawInput = page.getByTestId("withdraw-input");
26 await expect(withdrawInput).toBeVisible({ timeout: 5_000 });
27 await withdrawInput.fill("0.5");
28
29 const withdrawBtn = page.getByTestId("withdraw-btn");
30 await expect(withdrawBtn).toBeEnabled();
31 await withdrawBtn.click();
32
33 // Verify vault balance decreased
34 await expect(async () => {
35 const afterText = await vaultBalance.textContent();
36 const afterMatch = afterText?.match(/([\d.]+)\s*XLM/);
37 const afterBalance = afterMatch ? Number(afterMatch[1]) : 0;
38 expect(afterBalance).toBeLessThan(beforeBalance);
39 }).toPass({ timeout: 45_000 });
40});

Read-Only Contract Calls

Not every interaction needs signing. Reading contract state (counter value, vault balance) works without wallet authorization — but you still need the mock installed so the dApp boots into a connected state.

tests/read-only.spec.tstypescript
1import { test, expect } from "./fixtures";
2
3test("read counter value without signing", async ({ page }) => {
4 await page.goto("http://localhost:5173");
5
6 const getCountBtn = page.getByTestId("get-count-btn");
7 await expect(getCountBtn).toBeVisible({ timeout: 10_000 });
8 await getCountBtn.click();
9
10 const counterValue = page.getByTestId("counter-value");
11 await expect(counterValue).toContainText(/\d+/, { timeout: 15_000 });
12});

Network Configuration

The examples above use testnet. Pass different network and networkPassphrase values for other networks:

Mainnet

1import { Networks } from "@stellar/stellar-sdk";
2
3await installMockStellarWallet({
4 page,
5 secretKey: SECRET_KEY,
6 network: "PUBLIC",
7 networkPassphrase: Networks.PUBLIC,
8});

Local Standalone

1await installMockStellarWallet({
2 page,
3 secretKey: SECRET_KEY,
4 network: "STANDALONE",
5 networkPassphrase: "Standalone Network ; February 2017",
6});

Playwright Configuration

The Playwright config used by the example dApp. It starts the dev server automatically and runs tests serially (required when tests share a testnet account to avoid sequence number conflicts).

playwright.config.tstypescript
1import { defineConfig } from "@playwright/test";
2
3export default defineConfig({
4 testDir: "./tests",
5 timeout: 60_000,
6 use: { headless: true },
7 projects: [
8 {
9 name: "chromium",
10 use: { browserName: "chromium" },
11 },
12 ],
13 webServer: {
14 command: "npm run dev",
15 port: 5173,
16 reuseExistingServer: true,
17 },
18});

Running the Example dApp

To run the example dApp and its tests locally:

1# Build the Soroban contracts and generate TS clients
2cd examples/test-dapp
3npm install
4stellar scaffold build --build-clients
5
6# Start the dev server
7npm run dev
8
9# In another terminal — run the tests
10cd ../..
11npm run build
12npx playwright test