Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dAppwright testing #22

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
83 changes: 83 additions & 0 deletions .github/workflows/ci-playwright.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: playwright-test
aefhm marked this conversation as resolved.
Show resolved Hide resolved
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
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
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 18

- uses: pnpm/action-setup@v4
name: Install pnpm
id: pnpm-install
with:
version: 8
run_install: false

- name: Get pnpm store directory
id: pnpm-cache
run: |
echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT

- uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-

- name: Install dependencies
working-directory: backend
run: pnpm install

- name: Build backend
working-directory: backend
run: pnpm build

- name: Deploy backend
working-directory: backend
run: pnpm hardhat deploy --network sapphire-localnet

- name: Install dependencies
working-directory: frontend
run: pnpm install

- 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

- name: Upload playwright test-results
if: ${{ failure() }}
uses: actions/upload-artifact@v4
with:
name: playwright-test-results
path: frontend/test-results
retention-days: 5
8 changes: 4 additions & 4 deletions backend/contracts/MessageBox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ contract MessageBox {
}

function message() external view returns (string memory) {
if (msg.sender!=author) {
revert("not allowed");
}
return _message;
if (msg.sender != author) {
revert("not allowed");
}
return _message;
}
}
15 changes: 8 additions & 7 deletions backend/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import '@nomicfoundation/hardhat-ethers'
import '@oasisprotocol/sapphire-hardhat'
import '@typechain/hardhat'
import canonicalize from 'canonicalize'
import { JsonRpcProvider } from 'ethers'
import {JsonRpcProvider, Wallet } from "ethers";
import 'hardhat-watcher'
import { TASK_COMPILE } from 'hardhat/builtin-tasks/task-names'
import { HardhatUserConfig, task } from 'hardhat/config'
import 'solidity-coverage'
import { HDAccountsUserConfig } from 'hardhat/types';

const TASK_EXPORT_ABIS = 'export-abis'

Expand Down Expand Up @@ -79,15 +79,16 @@ task('setMessage')
})

// Hardhat Node and sapphire-dev test mnemonic.
const TEST_HDWALLET = {
mnemonic: 'test test test test test test test test test test test junk',
const TEST_HDWALLET: HDAccountsUserConfig = {
mnemonic: "test test test test test test test test test test test junk",
path: "m/44'/60'/0'/0",
initialIndex: 0,
count: 20,
passphrase: '',
}
passphrase: "",
};
const firstPrivateKey = Wallet.fromPhrase(TEST_HDWALLET.mnemonic).privateKey;

const accounts = process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : TEST_HDWALLET
let accounts = process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [firstPrivateKey];

const config: HardhatUserConfig = {
networks: {
Expand Down
11 changes: 6 additions & 5 deletions frontend/.env.development
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# VITE_NETWORK=0x5afd
# VITE_WEB3_GATEWAY=http://localhost:8545
VITE_NETWORK=0x5aff
VITE_WEB3_GATEWAY=https://testnet.sapphire.oasis.dev
VITE_MESSAGE_BOX_ADDR=0x2dE080e97B0caE9825375D31f5D0eD5751fDf16D
VITE_NETWORK=0x5afd
VITE_WEB3_GATEWAY=http://localhost:8545
VITE_MESSAGE_BOX_ADDR=0x5FbDB2315678afecb367f032d93F642f64180aa3
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirm this is correct/as first deployed contract and then pass it via env variable

# VITE_NETWORK=0x5aff
# VITE_WEB3_GATEWAY=https://testnet.sapphire.oasis.dev
# VITE_MESSAGE_BOX_ADDR=0x2dE080e97B0caE9825375D31f5D0eD5751fDf16D
30 changes: 30 additions & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
.DS_Store
dist
dist-ssr
coverage
*.local

playwright-report
test-results

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

stats.html
14 changes: 13 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,17 @@
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview"
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --noEmit",
"lint:eslint": "eslint . --ext .vue,.ts --ignore-path .gitignore",
"lint:prettier": "prettier --check --cache '**/*.{vue,js,ts,json}'",
"lint": "npm-run-all lint:**",
"format": "npm-run-all format:**",
"format:eslint": "eslint --fix . --ext .vue,.ts --ignore-path .gitignore",
"format:prettier": "prettier --write --cache '**/*.{vue,js,ts,json}'",
"test": "playwright test",
"test:setup": "playwright install --with-deps"
},
"dependencies": {
"@fontsource-variable/figtree": "^5.1.1",
Expand All @@ -17,6 +27,8 @@
"@metamask/jazzicon": "^2.0.0",
"@oasisprotocol/demo-starter-backend": "workspace:^",
"@oasisprotocol/sapphire-paratime": "1.3.2",
"@playwright/test": "^1.49.0",
"@tenkeylabs/dappwright": "^2.9.1",
"ethers": "^6.10.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
Expand Down
46 changes: 46 additions & 0 deletions frontend/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { defineConfig, devices } from "@playwright/test";

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
timeout: 2 * 60 * 1000,
testDir: "./test/",
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 1 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: [["html"], ["list", { printSteps: true }]],
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
baseURL: process.env.FRONTEND_URL || "http://localhost:5173/",
trace: "on-first-retry",
headless: false,
screenshot: {
mode: "only-on-failure",
fullPage: true,
},
},
/* Configure projects for major browsers */
projects: [
{
name: 'main',
testDir: './test/',
use: {
...devices['Desktop Chrome'],
},
},
],
/* Run your local dev server before starting the tests */
webServer: {
command: 'pnpm dev',
url: process.env.FRONTEND_URL || "http://localhost:5173/",
reuseExistingServer: !process.env.CI,
stdout: 'pipe',
stderr: 'pipe',
},
});
14 changes: 14 additions & 0 deletions frontend/src/constants/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,20 @@ interface AddEthereumChainParameter {
}

export const CHAINS: Map<bigint, AddEthereumChainParameter> = new Map([
[
23293n,
{
chainId: '0x5afd',
chainName: 'Sapphire Localnet',
nativeCurrency: {
name: 'TEST',
symbol: 'TEST',
decimals: 18,
},
rpcUrls: ['http://localhost:8545'],
blockExplorerUrls: ['http://localhost:8548'],
},
],
[
23294n,
{
Expand Down
65 changes: 65 additions & 0 deletions frontend/test/e2e.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
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);
},
});

test.beforeEach(async ({ wallet, page }) => {
// Use first account from seed. dAppwright adds two accounts by default.
await wallet.switchAccount(1); // Selector queries as a string
await page.bringToFront();
});

// TODO: improve test without reloading wallet page
test("set and view message", async ({ wallet, page }) => {
await page.goto("http://localhost:5173");

// Load page
await wallet.page.reload();
await wallet.page.getByRole('button', { name: 'Connect' }).click();

// Set a message
await page.locator(':text-matches("0x.{40}")').fill('hola amigos');
await page.getByRole('button', { name: 'Set Message' }).click();
await wallet.page.reload();
await wallet.page.getByRole('button', { name: 'Confirm' }).click();

// Reveal the message
await expect(page.getByRole('button', { name: 'Set Message' })).toBeVisible({ timeout: 60_000 });
await page.locator('[data-label="Tap to reveal"]').click();
await wallet.page.reload();
await wallet.page.getByTestId('confirm-footer-button').click();
await wallet.page.getByTestId('confirm-footer-button').click();

// Assert message has been set
// await page.pause();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// await page.pause();

await expect(page.locator('[data-label="Tap to reveal"]').locator('input')).toHaveValue('hola amigos', { timeout: 60_000 });
Copy link
Contributor Author

@aefhm aefhm Jan 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
await expect(page.locator('[data-label="Tap to reveal"]').locator('input')).toHaveValue('hola amigos', { timeout: 60_000 });
await expect(page.locator('[data-label="Tap to reveal"]').locator('input')).toHaveValue('hola amigos');

});
Loading