End-to-End Testing
Many modern web applications utilize Playwright tests during the development and release process to increase shipping speed and improve quality. While the Web3 dApps ecosystem is still evolving, tools exist to do the same. We recommend using dAppwright for dApps on the Sapphire Network. In this tutorial, we will examine the e2e testing involved in the demo-starter project.
dAppwright
The dAppwright package builds on Playwright and includes tooling to support testing with a MetaMask or Coinbase wallet as an extension on a Chromium browser.
Installation
We need to install both dAppwright
and Playwright
. Navigate to your
frontend application directory:
-
Install dAppwright:
- npm
- pnpm
- Yarn
npm install -D @tenkeylabs/dappwright
pnpm add -D @tenkeylabs/dappwright
yarn add --dev @tenkeylabs/dappwright
-
Install Playwright (we recommend the TypeScript option):
- npm
- pnpm
- Yarn
npm init playwright@latest
pnpm create playwright
yarn create playwright
-
A successful installation should allow the running of the example tests:
npx playwright test
Setup
We suggest starting a local dev server with each test run to consistently iterate over the same state.
import { defineConfig } from '@playwright/test';
export default defineConfig({
/* Run your local dev server before starting the tests */
webServer: {
command: 'pnpm dev',
url: process.env.FRONTEND_URL || 'http://localhost:8080/',
reuseExistingServer: !process.env.CI,
stdout: 'pipe',
stderr: 'pipe',
},
});
Adding Test Context
We begin with a test file extending the testing context to include dAppwright:
import { BrowserContext, expect, test as baseTest } from '@playwright/test'
import dappwright, { Dappwright, MetaMaskWallet } from '@tenkeylabs/dappwright'
export const test = baseTest.extend<{
context: BrowserContext
wallet: Dappwright
}>({
context: async ({}, use) => {
// Launch context with extension
const [wallet, _, context] = await dappwright.bootstrap('', {
wallet: 'metamask',
version: MetaMaskWallet.recommendedVersion,
seed: 'test test test test test test test test test test test junk', // Hardhat's default https://hardhat.org/hardhat-network/docs/reference#accounts
headless: false,
})
// Add Sapphire Localnet as a custom network
await wallet.addNetwork({
networkName: 'Sapphire Localnet',
rpc: 'http://localhost:8545',
chainId: 23293,
symbol: 'ROSE',
})
await use(context)
},
wallet: async ({ context }, use) => {
const metamask = await dappwright.getWallet('metamask', context)
await use(metamask)
},
})
...
The above snippet includes the Sapphire Localnet as a network with the correct RPC for testing, and sets up the default MetaMask wallet to use the same seed as you would in a Hardhat test.
Writing a Test
Writing a test with dAppwright is very similar to how you would write a Playwright one. The first step is to navigate to our application:
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:5173')
})
Next, we can load the application and confirm using the Sapphire network in
Metamask. Note that we will need to use wallet.approve
to access the
MetaMask extension which waits for the MetaMask dom to reload. Depending on
your use case, you may force your extension page to reload with
wallet.page.reload()
.
test('set and view message', async ({ wallet, page }) => {
// Load page
await page.getByTestId('rk-connect-button').click()
await page.getByTestId('rk-wallet-option-injected-sapphire').click()
await wallet.approve()
})
Otherwise, we write selectors and assertions in the same way.
// Set a message
await page.locator(':text-matches("0x.{40}")').fill('hola amigos')
const submitBtn = page.getByRole('button', { name: 'Set Message' })
await submitBtn.click()
await wallet.confirmTransaction()
// Reveal the message
await expect(submitBtn).toBeEnabled()
await page.locator('[data-label="Tap to reveal"]').click()
await wallet.confirmTransaction()
// Assert message has been set
await expect(page.locator('[data-label="Tap to reveal"]').locator('input')).toHaveValue('hola amigos')
You can make assertions in the same way on the wallet page, but wallet actions will significantly simplify the amount of boilerplate testing code.
await expect(wallet.page.getByText("My Account Name")).toBeVisible();
Debugging
Playwright's UI mode is very beneficial to debugging your tests as you develop. The pick locator button will help you refine element selectors while giving visual feedback.
npx playwright test --ui
Alternatively, you can leverage the debug mode which allows you to set breakpoints, pause testing, and examine network requests.
npx playwright test --debug
CI
Running your dAppwright tests on CI environments like GitHub is possible with
the right configurations. You will need to install playwright
itself as a
dependency before you can install Playwright's dependency packages, and
run a headed execution in Linux agents with Xvfb
. We recommend uploading
test results on failure to more quickly move through CI cycles.
You will need a Sapphire Localnet service to provide an RPC endpoint during testing.
playwright-test:
runs-on: ubuntu-latest
services:
sapphire-localnet-ci:
image: ghcr.io/oasisprotocol/sapphire-localnet:latest
ports:
- 8545:8545
- 8546:8546
env:
OASIS_DOCKER_START_EXPLORER: no
options: >-
--rm
--health-cmd="test -f /CONTAINER_READY"
--health-start-period=90s
We recommend saving deployed smart contract addresses as environment variables
and passing $GITHUB_OUTPUT
to a subsequent testing step.
- name: Deploy backend
working-directory: backend
id: deploy
run: |
echo "message_box_address=$(pnpm hardhat deploy localhost --network sapphire-localnet | grep -o '0x.*')" >> $GITHUB_OUTPUT
Finally, run the test and upload results on failure:
- name: Build
working-directory: frontend
run: pnpm build
- name: Install Playwright dependencies
run: pnpm test:setup
working-directory: frontend
- name: Run playwright tests (with xvfb-run to support headed extension test)
working-directory: frontend
run: xvfb-run pnpm test
env:
VITE_MESSAGE_BOX_ADDR: ${{ steps.deploy.outputs.message_box_address }}
- name: Upload playwright test-results
if: ${{ failure() }}
uses: actions/upload-artifact@v4
with:
name: playwright-test-results
path: frontend/test-results
retention-days: 5
If you are interested in seeing how dAppwright is integrated into an example application, check out the demo-starter.
If you are interested in seeing how dAppwright is integrated into a Sapphire dApp with Wagmi, check out the Wagmi example.