feat(e2e): Transfers FE e2e tests (#192)
This commit is contained in:
parent
4f2bcea5be
commit
d2225f1b70
26 changed files with 451 additions and 128 deletions
|
@ -18,7 +18,6 @@ Install rust by executing a script from the internet (😅):
|
||||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
Check the version with `cargo version`.
|
Check the version with `cargo version`.
|
||||||
|
|
||||||
Finally add the wasm target:
|
Finally add the wasm target:
|
||||||
|
@ -242,37 +241,7 @@ data come through.
|
||||||
|
|
||||||
### Run the Frontend
|
### Run the Frontend
|
||||||
|
|
||||||
Now on your own machine, checkout the
|
Now on your own machine, follow [these steps](./frontend/README.md#development).
|
||||||
https://github.com/informalsystems/cycles-hackathon-app.
|
|
||||||
|
|
||||||
Copy the `.env.example` file to `.env.local`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp .env.example .env.local
|
|
||||||
```
|
|
||||||
|
|
||||||
and set the relevant fields. You should have the contract address and TEE pubkey
|
|
||||||
from the output of the `deploy.sh` and `handshake.sh` scripts, respectfully. The
|
|
||||||
chain id is probably `testing` and the IP address for the URLs is probably
|
|
||||||
`143.244.186.205`. Modify accordingly. For example:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
#.env.local
|
|
||||||
NEXT_PUBLIC_TRANSFERS_CONTRACT_ADDRESS=wasm1ch9ed27cdu3a4fkx37gnagm7jcthj0rggnmmjwwwe4xhwmk0d65q8fn9pz
|
|
||||||
NEXT_PUBLIC_ENCLAVE_PUBLIC_KEY=030c25e39743fd4c7553d87873919281d567b5c328fb903cbfbe9541518736a2d2
|
|
||||||
NEXT_PUBLIC_CHAIN_ID=testing
|
|
||||||
NEXT_PUBLIC_CHAIN_RPC_URL=http://143.244.186.205:26657
|
|
||||||
NEXT_PUBLIC_CHAIN_REST_URL=http://143.244.186.205:1317
|
|
||||||
```
|
|
||||||
|
|
||||||
Install and run the app:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install -f
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
You can now open the app in http://localhost:3000/.
|
|
||||||
|
|
||||||
Make sure you have Keplr installed in your browser and you should now be able to
|
Make sure you have Keplr installed in your browser and you should now be able to
|
||||||
use the app!
|
use the app!
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
|
# App required variables
|
||||||
NEXT_PUBLIC_TARGET_CHAIN=localWasm
|
NEXT_PUBLIC_TARGET_CHAIN=localWasm
|
||||||
NEXT_PUBLIC_ENCLAVE_PUBLIC_KEY=02360955ff74750f6ea0b539f41cce89451f591e4c835d0a5406e6effa96dd169d
|
NEXT_PUBLIC_ENCLAVE_PUBLIC_KEY=02360955ff74750f6ea0b539f41cce89451f591e4c835d0a5406e6effa96dd169d
|
||||||
NEXT_PUBLIC_TRANSFERS_CONTRACT_ADDRESS=wasm1jfgr0vgunezkhfmdy7krrupu6yjhx224nxtjptll2ylkkqhyzeshrspu9
|
NEXT_PUBLIC_TRANSFERS_CONTRACT_ADDRESS=wasm1jfgr0vgunezkhfmdy7krrupu6yjhx224nxtjptll2ylkkqhyzeshrspu9
|
||||||
|
|
||||||
# E2E Testing
|
# E2E testing required variables
|
||||||
TEST_BASE_URL=http://127.0.0.1:3000
|
TEST_BASE_URL=http://127.0.0.1:3000
|
||||||
TEST_KEPLR_EXTENSION_VERSION=0.12.124
|
TEST_KEPLR_EXTENSION_VERSION=0.12.124
|
||||||
TEST_WALLET_MNEMONIC=debris topic trash february punch advance tackle alert reduce box chase lend buffalo effort napkin drip mountain result rely swear tornado master devote what
|
TEST_WALLET_MNEMONIC=debris topic trash february punch advance tackle alert reduce box chase lend buffalo effort napkin drip mountain result rely swear tornado master devote what
|
||||||
|
TEST_SECONDARY_WALLET_MNEMONIC=employ jungle nuclear clutch general vicious thrive width time asthma shadow orchard wage affair matrix slush room weapon prize that record path grit tourist
|
||||||
|
TEST_SECONDARY_WALLET_ADDRESS=wasm1lejkm8nevz4hgmafyfm03upkkj76cvzj4geapv
|
||||||
TEST_WALLET_PASSWORD=;pzPCXB^@92byC
|
TEST_WALLET_PASSWORD=;pzPCXB^@92byC
|
||||||
|
PLAYWRIGHT_HTML_OPEN=never
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
# Transfer App
|
# Transfer App
|
||||||
|
|
||||||
This is an example frontend that illustrates how to interact with a Transfer Quartz App.
|
This is an example frontend that illustrates how to interact with a Quartz app.
|
||||||
|
|
||||||
This example offers:
|
This example offers the ability to:
|
||||||
|
|
||||||
- Deposit amounts into a balance
|
- Deposit amounts into a balance
|
||||||
- Withdraw the whole deposit
|
- Withdraw the whole deposit
|
||||||
- Transfer amounts between wallet addresses in a private-preserving way
|
- Transfer amounts between wallets in a private-preserving way
|
||||||
- Query your encrypted balance to capture changes
|
- Query your encrypted balance
|
||||||
- Switch between Keplr wallets
|
- Switch between Keplr wallets
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
@ -26,7 +26,7 @@ Install dependencies:
|
||||||
npm ci
|
npm ci
|
||||||
```
|
```
|
||||||
|
|
||||||
The App requires some environment variables to fully work. Be sure to set up those accordingly to your local environment.
|
The app requires some environment variables to fully work. Be sure to set up those accordingly to your local environment.
|
||||||
|
|
||||||
You should start from the template:
|
You should start from the template:
|
||||||
|
|
||||||
|
@ -34,10 +34,52 @@ You should start from the template:
|
||||||
cp .env.example .env.local
|
cp .env.example .env.local
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Required environment variables:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Choose target chain configuration
|
||||||
|
NEXT_PUBLIC_TARGET_CHAIN=<localWasm | localNeutron | doWasm>
|
||||||
|
# Enclave public key to encrypt transfers
|
||||||
|
NEXT_PUBLIC_ENCLAVE_PUBLIC_KEY=<public_key>
|
||||||
|
# Target transfers contract
|
||||||
|
NEXT_PUBLIC_TRANSFERS_CONTRACT_ADDRESS=<contract_address>
|
||||||
|
```
|
||||||
|
|
||||||
Run the app:
|
Run the app:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
And now everything is up & running 🎉
|
App will be running on http://localhost:3000/ and now everything is up & running 🎉
|
||||||
|
|
||||||
|
## E2E Testing
|
||||||
|
|
||||||
|
For tests to work, you need to set up the following required environment variables:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Frontend base url
|
||||||
|
TEST_BASE_URL=<url>
|
||||||
|
# Keplr browser extension version
|
||||||
|
TEST_KEPLR_EXTENSION_VERSION=<version>
|
||||||
|
# Main wallet mnemonic (Use only funded wallets)
|
||||||
|
TEST_WALLET_MNEMONIC=<mnemonic>
|
||||||
|
# Secondary wallet mnemonic (Use only funded wallets)
|
||||||
|
TEST_SECONDARY_WALLET_MNEMONIC=<mnemonic>
|
||||||
|
# Secondary wallet address
|
||||||
|
TEST_SECONDARY_WALLET_ADDRESS=<wallet_address>
|
||||||
|
# Keplr wallet password. It can be whatever
|
||||||
|
TEST_WALLET_PASSWORD=<password>
|
||||||
|
```
|
||||||
|
|
||||||
|
Run all E2E tests:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run test
|
||||||
|
```
|
||||||
|
|
||||||
|
If want to run the tests with the Playwright dedicated interface, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run test:ui
|
||||||
|
```
|
||||||
|
|
6
apps/transfers/frontend/env.d.ts
vendored
6
apps/transfers/frontend/env.d.ts
vendored
|
@ -3,5 +3,11 @@ namespace NodeJS {
|
||||||
NEXT_PUBLIC_ENCLAVE_PUBLIC_KEY: string
|
NEXT_PUBLIC_ENCLAVE_PUBLIC_KEY: string
|
||||||
NEXT_PUBLIC_TARGET_CHAIN: string
|
NEXT_PUBLIC_TARGET_CHAIN: string
|
||||||
NEXT_PUBLIC_TRANSFERS_CONTRACT_ADDRESS: string
|
NEXT_PUBLIC_TRANSFERS_CONTRACT_ADDRESS: string
|
||||||
|
TEST_BASE_URL: string
|
||||||
|
TEST_KEPLR_EXTENSION_VERSION: string
|
||||||
|
TEST_WALLET_MNEMONIC: string
|
||||||
|
TEST_SECONDARY_WALLET_MNEMONIC: string
|
||||||
|
TEST_SECONDARY_WALLET_ADDRESS: string
|
||||||
|
TEST_WALLET_PASSWORD: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,8 @@
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"lint:fix": "eslint . --fix",
|
"lint:fix": "eslint . --fix",
|
||||||
"test": "playwright test",
|
"test": "playwright test",
|
||||||
"test:ui": "playwright test --ui"
|
"test:ui": "playwright test --ui",
|
||||||
|
"prepare": "npx playwright install chromium"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"eciesjs": "^0.4.7",
|
"eciesjs": "^0.4.7",
|
||||||
|
|
|
@ -25,6 +25,8 @@ export default defineConfig({
|
||||||
workers: process.env.CI ? 1 : undefined,
|
workers: process.env.CI ? 1 : undefined,
|
||||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
reporter: 'html',
|
reporter: 'html',
|
||||||
|
timeout: 0,
|
||||||
|
globalTimeout: process.env.CI ? 5 * 60 * 1000 : undefined,
|
||||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
use: {
|
use: {
|
||||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||||
|
|
|
@ -22,6 +22,7 @@ export default function Landing() {
|
||||||
<main className="flex min-h-screen flex-col items-center gap-4 p-24">
|
<main className="flex min-h-screen flex-col items-center gap-4 p-24">
|
||||||
<p>Connect your Keplr wallet to log in</p>
|
<p>Connect your Keplr wallet to log in</p>
|
||||||
<StyledText
|
<StyledText
|
||||||
|
as="button"
|
||||||
variant="button.primary"
|
variant="button.primary"
|
||||||
onClick={connectWallet}
|
onClick={connectWallet}
|
||||||
>
|
>
|
||||||
|
|
|
@ -30,12 +30,14 @@ export default function SetSeed() {
|
||||||
</code>
|
</code>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<StyledText
|
<StyledText
|
||||||
|
as="button"
|
||||||
variant="button.primary"
|
variant="button.primary"
|
||||||
onClick={acceptPhrase}
|
onClick={acceptPhrase}
|
||||||
>
|
>
|
||||||
Continue with the autogenerated seed phrase
|
Continue with the autogenerated seed phrase
|
||||||
</StyledText>
|
</StyledText>
|
||||||
<StyledText
|
<StyledText
|
||||||
|
as="button"
|
||||||
variant="button.secondary"
|
variant="button.secondary"
|
||||||
onClick={() => setIsModalOpen(true)}
|
onClick={() => setIsModalOpen(true)}
|
||||||
>
|
>
|
||||||
|
|
|
@ -96,7 +96,7 @@ export default function Dashboard() {
|
||||||
).then((data) => {
|
).then((data) => {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
setBalance(retrieveBalance(data))
|
setBalance(retrieveBalance(data))
|
||||||
showSuccess('Balance updated correctly')
|
showSuccess('Balance updated successfully')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -167,6 +167,7 @@ export default function Dashboard() {
|
||||||
|
|
||||||
<StyledText
|
<StyledText
|
||||||
className="w-full justify-start bg-emerald-500"
|
className="w-full justify-start bg-emerald-500"
|
||||||
|
as="button"
|
||||||
variant="button.primary"
|
variant="button.primary"
|
||||||
onClick={() => setIsDepositModalOpen(true)}
|
onClick={() => setIsDepositModalOpen(true)}
|
||||||
>
|
>
|
||||||
|
@ -174,6 +175,7 @@ export default function Dashboard() {
|
||||||
Deposit
|
Deposit
|
||||||
</StyledText>
|
</StyledText>
|
||||||
<StyledText
|
<StyledText
|
||||||
|
as="button"
|
||||||
variant="button.primary"
|
variant="button.primary"
|
||||||
className="w-full justify-start bg-violet-500"
|
className="w-full justify-start bg-violet-500"
|
||||||
onClick={() => setIsTransferModalOpen(true)}
|
onClick={() => setIsTransferModalOpen(true)}
|
||||||
|
@ -182,6 +184,7 @@ export default function Dashboard() {
|
||||||
Transfer
|
Transfer
|
||||||
</StyledText>
|
</StyledText>
|
||||||
<StyledText
|
<StyledText
|
||||||
|
as="button"
|
||||||
className="w-full justify-start bg-amber-500"
|
className="w-full justify-start bg-amber-500"
|
||||||
variant="button.primary"
|
variant="button.primary"
|
||||||
onClick={() => setIsWithdrawModalOpen(true)}
|
onClick={() => setIsWithdrawModalOpen(true)}
|
||||||
|
@ -192,6 +195,7 @@ export default function Dashboard() {
|
||||||
<div className="my-1 w-full border-black/25"></div>
|
<div className="my-1 w-full border-black/25"></div>
|
||||||
<StyledText
|
<StyledText
|
||||||
className="w-full justify-start"
|
className="w-full justify-start"
|
||||||
|
as="button"
|
||||||
variant="button.secondary"
|
variant="button.secondary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const res = confirm(
|
const res = confirm(
|
||||||
|
|
|
@ -101,6 +101,7 @@ export function DepositModalWindow(props: ModalWindowProps) {
|
||||||
Deposit
|
Deposit
|
||||||
</StyledText>
|
</StyledText>
|
||||||
<StyledText
|
<StyledText
|
||||||
|
as="button"
|
||||||
variant="button.secondary"
|
variant="button.secondary"
|
||||||
onClick={props.onClose}
|
onClick={props.onClose}
|
||||||
>
|
>
|
||||||
|
|
|
@ -66,6 +66,7 @@ export function EnterSeedModal({
|
||||||
Continue
|
Continue
|
||||||
</StyledText>
|
</StyledText>
|
||||||
<StyledText
|
<StyledText
|
||||||
|
as="button"
|
||||||
variant="button.secondary"
|
variant="button.secondary"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
>
|
>
|
||||||
|
|
|
@ -100,7 +100,7 @@ export function ModalWindow({
|
||||||
onTransitionEnd={handleTransitionEnd}
|
onTransitionEnd={handleTransitionEnd}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
>
|
>
|
||||||
{children}
|
{isOpen && children}
|
||||||
</div>
|
</div>
|
||||||
</>,
|
</>,
|
||||||
document.body,
|
document.body,
|
||||||
|
|
|
@ -154,6 +154,7 @@ export function TransferModalWindow(props: ModalWindowProps) {
|
||||||
Transfer
|
Transfer
|
||||||
</StyledText>
|
</StyledText>
|
||||||
<StyledText
|
<StyledText
|
||||||
|
as="button"
|
||||||
variant="button.secondary"
|
variant="button.secondary"
|
||||||
onClick={props.onClose}
|
onClick={props.onClose}
|
||||||
>
|
>
|
||||||
|
|
|
@ -59,6 +59,7 @@ export function WithdrawModalWindow(props: ModalWindowProps) {
|
||||||
Withdraw
|
Withdraw
|
||||||
</StyledText>
|
</StyledText>
|
||||||
<StyledText
|
<StyledText
|
||||||
|
as="button"
|
||||||
variant="button.secondary"
|
variant="button.secondary"
|
||||||
onClick={props.onClose}
|
onClick={props.onClose}
|
||||||
>
|
>
|
||||||
|
|
7
apps/transfers/frontend/src/config/routes.ts
Normal file
7
apps/transfers/frontend/src/config/routes.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
type Url = 'landing' | 'seed' | 'dashboard'
|
||||||
|
|
||||||
|
export const routes: Record<Url, string> = {
|
||||||
|
landing: '/',
|
||||||
|
seed: '/set-seed',
|
||||||
|
dashboard: '/dashboard',
|
||||||
|
}
|
39
apps/transfers/frontend/tests/e2e/auth.spec.ts
Normal file
39
apps/transfers/frontend/tests/e2e/auth.spec.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import { routes } from '@/config/routes'
|
||||||
|
import test from './fixtures'
|
||||||
|
import { connectWallet } from './helpers/connectWalet'
|
||||||
|
import { setSeedPhrase } from './helpers/setSeedPhrase'
|
||||||
|
|
||||||
|
const { dashboard, landing, seed } = routes
|
||||||
|
|
||||||
|
test.describe('Auth', () => {
|
||||||
|
test('can go nowhere but landing page without a wallet', async ({ page }) => {
|
||||||
|
await page.goto(seed)
|
||||||
|
await page.goto(dashboard)
|
||||||
|
await test
|
||||||
|
.expect(page.getByRole('button', { name: /connect/i }))
|
||||||
|
.toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('can go nowhere but seed page without a seed phrase', async ({
|
||||||
|
context,
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await connectWallet({ context, page })
|
||||||
|
|
||||||
|
await page.goto(landing)
|
||||||
|
await page.goto(dashboard)
|
||||||
|
await test.expect(page.getByText(/recovery seed phrase/i)).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('cannot go to anon pages once fully logged in', async ({
|
||||||
|
context,
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await connectWallet({ context, page })
|
||||||
|
await setSeedPhrase({ page })
|
||||||
|
|
||||||
|
await page.goto(landing)
|
||||||
|
await page.goto(seed)
|
||||||
|
await test.expect(page.getByText(/balance:/i)).toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,88 +1,49 @@
|
||||||
import {
|
import { test as baseTest, chromium } from '@playwright/test'
|
||||||
test as baseTest,
|
|
||||||
chromium,
|
|
||||||
expect,
|
|
||||||
BrowserContext,
|
|
||||||
} from '@playwright/test'
|
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
|
||||||
|
import { importWallet } from './helpers/importWallet'
|
||||||
|
|
||||||
|
let extensionUrl: string
|
||||||
|
const pathToExtension = path.join(
|
||||||
|
__dirname,
|
||||||
|
'extensions',
|
||||||
|
`keplr-extension-manifest-v3-v${process.env.TEST_KEPLR_EXTENSION_VERSION}`,
|
||||||
|
)
|
||||||
|
|
||||||
// Tests fixtures
|
// Tests fixtures
|
||||||
const test = baseTest.extend<{}, { _globalContext: BrowserContext }>({
|
const test = baseTest.extend<{
|
||||||
// Shared context for tests so Keplr initialization runs only once for all tests
|
extensionUrl: string
|
||||||
_globalContext: [
|
}>({
|
||||||
async ({}, use) => {
|
// Overwritten Playwright context to setup Keplr wallet before all tests
|
||||||
const mnemonicWords = process.env.TEST_KEPLR_MNEMONIC!.split(' ')
|
context: async ({}, use) => {
|
||||||
const pathToExtension = path.join(
|
// Launch browser with Keplr installed
|
||||||
__dirname,
|
const context = await chromium.launchPersistentContext('', {
|
||||||
'extensions',
|
headless: false,
|
||||||
`keplr-extension-manifest-v3-v${process.env.TEST_KEPLR_EXTENSION_VERSION}`,
|
args: [
|
||||||
)
|
`--disable-extensions-except=${pathToExtension}`,
|
||||||
// We launch browser with the extension
|
`--load-extension=${pathToExtension}`,
|
||||||
const context = await chromium.launchPersistentContext('', {
|
],
|
||||||
headless: false,
|
})
|
||||||
args: [
|
|
||||||
`--disable-extensions-except=${pathToExtension}`,
|
|
||||||
`--load-extension=${pathToExtension}`,
|
|
||||||
],
|
|
||||||
})
|
|
||||||
const page = await context.waitForEvent('page')
|
|
||||||
const extensionId = /\/\/(.*?)\//.exec(page.url())![1]
|
|
||||||
|
|
||||||
// Keplr import wallet flow
|
const page = await context.waitForEvent('page')
|
||||||
await page.waitForURL(new RegExp(`${extensionId}/register.html`))
|
|
||||||
await expect(page.getByText('Import an existing wallet')).toBeVisible()
|
|
||||||
await page
|
|
||||||
.getByRole('button', { name: 'Import an existing wallet' })
|
|
||||||
.click()
|
|
||||||
await expect(
|
|
||||||
page.getByText('Use recovery phrase or private key'),
|
|
||||||
).toBeVisible()
|
|
||||||
await page
|
|
||||||
.getByRole('button', { name: 'Use recovery phrase or private key' })
|
|
||||||
.click()
|
|
||||||
|
|
||||||
await page.getByText('24 Words').click()
|
// Retrieve target URL to interact with Keplr extension
|
||||||
const seedInputs = await page.locator('input')
|
const extensionId = /\/\/(.*?)\//.exec(page.url())![1]
|
||||||
for (let i = 0; i < mnemonicWords.length; i++) {
|
extensionUrl = `chrome-extension://${extensionId}`
|
||||||
await seedInputs.nth(i).fill(mnemonicWords[i])
|
|
||||||
}
|
|
||||||
await page.getByRole('button', { name: 'Import', exact: true }).click()
|
|
||||||
|
|
||||||
await page
|
// Import a wallet to be used in tests
|
||||||
.getByPlaceholder('e.g. Trading, NFT Vault, Investment')
|
await importWallet({
|
||||||
.fill('Playwright Wallet')
|
extensionUrl,
|
||||||
const inputs = await page.getByPlaceholder(
|
mnemonic: process.env.TEST_WALLET_MNEMONIC,
|
||||||
'At least 8 characters in length',
|
name: 'main',
|
||||||
)
|
page,
|
||||||
for (let i = 0; i < (await inputs.count()); i++) {
|
})
|
||||||
await inputs.nth(i).fill(process.env.TEST_KEPLR_PASSWORD!)
|
|
||||||
}
|
|
||||||
await page.getByRole('button', { name: 'Next' }).click()
|
|
||||||
|
|
||||||
await expect(page.getByText('Select Chains')).toBeVisible()
|
await use(context)
|
||||||
await page.getByRole('button', { name: 'Save' }).click()
|
await context.close()
|
||||||
|
|
||||||
// Accept app suggested testnet info
|
|
||||||
await page.goto('/')
|
|
||||||
const addChainPage = await context.waitForEvent('page')
|
|
||||||
await addChainPage.getByRole('button', { name: 'Approve' }).click()
|
|
||||||
|
|
||||||
// Wait for App to load
|
|
||||||
await test.expect(page.getByText('Balance:')).toBeVisible()
|
|
||||||
|
|
||||||
await use(context)
|
|
||||||
await context.close()
|
|
||||||
},
|
|
||||||
{ scope: 'worker' },
|
|
||||||
],
|
|
||||||
context: async ({ _globalContext }, use) => {
|
|
||||||
await use(_globalContext)
|
|
||||||
},
|
},
|
||||||
page: async ({ context }, use) => {
|
extensionUrl: async ({}, use) => {
|
||||||
const page = await context.newPage()
|
await use(extensionUrl)
|
||||||
|
|
||||||
await use(page)
|
|
||||||
await page.close()
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
21
apps/transfers/frontend/tests/e2e/helpers/connectWalet.ts
Normal file
21
apps/transfers/frontend/tests/e2e/helpers/connectWalet.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { BrowserContext, Page } from '@playwright/test'
|
||||||
|
|
||||||
|
import { routes } from '@/config/routes'
|
||||||
|
|
||||||
|
export const connectWallet = async ({
|
||||||
|
context,
|
||||||
|
page,
|
||||||
|
}: {
|
||||||
|
context: BrowserContext
|
||||||
|
page: Page
|
||||||
|
}) => {
|
||||||
|
// Connect to Keplr wallet
|
||||||
|
await page.goto(routes.landing)
|
||||||
|
await page.getByRole('button', { name: /connect/i }).click()
|
||||||
|
|
||||||
|
// Accept app suggested testnet info
|
||||||
|
const addChainPage = await context.waitForEvent('page')
|
||||||
|
|
||||||
|
await addChainPage.getByRole('button', { name: /approve/i }).click()
|
||||||
|
await addChainPage.waitForEvent('close')
|
||||||
|
}
|
20
apps/transfers/frontend/tests/e2e/helpers/getBalance.ts
Normal file
20
apps/transfers/frontend/tests/e2e/helpers/getBalance.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { BrowserContext, Page } from '@playwright/test'
|
||||||
|
import { signTx } from './signTx'
|
||||||
|
|
||||||
|
export const getBalance = async ({
|
||||||
|
context,
|
||||||
|
page,
|
||||||
|
}: {
|
||||||
|
context: BrowserContext
|
||||||
|
page: Page
|
||||||
|
}) => {
|
||||||
|
// Check new balance
|
||||||
|
await page.getByRole('button', { name: /get/i }).click()
|
||||||
|
|
||||||
|
await signTx({ context, page })
|
||||||
|
|
||||||
|
// Wait for the success alert to appear so we know balance updated
|
||||||
|
await page.getByText(/\$/i).waitFor({ state: 'visible' })
|
||||||
|
|
||||||
|
return page.getByText(/\$/i).textContent()
|
||||||
|
}
|
41
apps/transfers/frontend/tests/e2e/helpers/importWallet.ts
Normal file
41
apps/transfers/frontend/tests/e2e/helpers/importWallet.ts
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import { Page } from '@playwright/test'
|
||||||
|
|
||||||
|
export const importWallet = async ({
|
||||||
|
extensionUrl,
|
||||||
|
mnemonic,
|
||||||
|
name,
|
||||||
|
page,
|
||||||
|
}: {
|
||||||
|
extensionUrl: string
|
||||||
|
mnemonic: string
|
||||||
|
name: string
|
||||||
|
page: Page
|
||||||
|
}) => {
|
||||||
|
await page.goto(`${extensionUrl}/register.html`)
|
||||||
|
|
||||||
|
const mnemonicWords = mnemonic.split(' ')
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: /import/i }).click()
|
||||||
|
await page.getByRole('button', { name: /use/i }).click()
|
||||||
|
await page.getByRole('button', { name: /24/ }).click()
|
||||||
|
|
||||||
|
const seedInputs = await page.locator('input')
|
||||||
|
|
||||||
|
for (let i = 0; i < mnemonicWords.length; i++) {
|
||||||
|
await seedInputs.nth(i).fill(mnemonicWords[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Import', exact: true }).click()
|
||||||
|
await page.getByPlaceholder('e.g. Trading, NFT Vault,').fill(name)
|
||||||
|
|
||||||
|
const inputs = await page.getByPlaceholder('At least 8 characters in length')
|
||||||
|
|
||||||
|
for (let i = 0; i < (await inputs.count()); i++) {
|
||||||
|
await inputs.nth(i).fill(process.env.TEST_WALLET_PASSWORD)
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: /next/i }).click()
|
||||||
|
await page.getByRole('button', { name: /save/i }).click()
|
||||||
|
|
||||||
|
await page.close()
|
||||||
|
}
|
17
apps/transfers/frontend/tests/e2e/helpers/setSeedPhrase.ts
Normal file
17
apps/transfers/frontend/tests/e2e/helpers/setSeedPhrase.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { Page } from '@playwright/test'
|
||||||
|
|
||||||
|
export const setSeedPhrase = async ({
|
||||||
|
page,
|
||||||
|
seedPhrase,
|
||||||
|
}: {
|
||||||
|
page: Page
|
||||||
|
seedPhrase?: string
|
||||||
|
}) => {
|
||||||
|
if (!seedPhrase) {
|
||||||
|
await page.getByRole('button', { name: /continue with/i }).click()
|
||||||
|
} else {
|
||||||
|
await page.getByRole('button', { name: /enter my own/i }).click()
|
||||||
|
await page.locator('input').fill(seedPhrase)
|
||||||
|
await page.getByRole('button', { name: 'Continue', exact: true }).click()
|
||||||
|
}
|
||||||
|
}
|
16
apps/transfers/frontend/tests/e2e/helpers/signTx.ts
Normal file
16
apps/transfers/frontend/tests/e2e/helpers/signTx.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { BrowserContext, Page } from '@playwright/test'
|
||||||
|
|
||||||
|
export const signTx = async ({
|
||||||
|
context,
|
||||||
|
page,
|
||||||
|
}: {
|
||||||
|
context: BrowserContext
|
||||||
|
page: Page
|
||||||
|
}) => {
|
||||||
|
// Sign tx
|
||||||
|
const signPage = await context.waitForEvent('page')
|
||||||
|
|
||||||
|
await signPage.getByRole('button', { name: /approve/i }).click()
|
||||||
|
await signPage.waitForEvent('close')
|
||||||
|
await page.getByText(/successfully/i).waitFor({ state: 'visible' })
|
||||||
|
}
|
19
apps/transfers/frontend/tests/e2e/helpers/swapWallet.ts
Normal file
19
apps/transfers/frontend/tests/e2e/helpers/swapWallet.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { BrowserContext } from '@playwright/test'
|
||||||
|
|
||||||
|
export const swapWallet = async ({
|
||||||
|
context,
|
||||||
|
extensionUrl,
|
||||||
|
name,
|
||||||
|
}: {
|
||||||
|
context: BrowserContext
|
||||||
|
extensionUrl: string
|
||||||
|
name: string
|
||||||
|
}) => {
|
||||||
|
const page = await context.newPage()
|
||||||
|
|
||||||
|
await page.goto(`${extensionUrl}/popup.html`)
|
||||||
|
await page.locator('div[cursor="pointer"] > svg').nth(1).click()
|
||||||
|
await page.getByText(name).click()
|
||||||
|
|
||||||
|
await page.close()
|
||||||
|
}
|
31
apps/transfers/frontend/tests/e2e/seed-phrase.spec.ts
Normal file
31
apps/transfers/frontend/tests/e2e/seed-phrase.spec.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import test from './fixtures'
|
||||||
|
import { connectWallet } from './helpers/connectWalet'
|
||||||
|
import { setSeedPhrase } from './helpers/setSeedPhrase'
|
||||||
|
|
||||||
|
test.beforeEach(async ({ context, page }) => {
|
||||||
|
await connectWallet({ context, page })
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Seed Phrase', () => {
|
||||||
|
test('can use autogenerated seed phrase', async ({ page }) => {
|
||||||
|
await setSeedPhrase({ page })
|
||||||
|
await test
|
||||||
|
.expect(
|
||||||
|
await page.evaluate(() =>
|
||||||
|
window.localStorage.getItem('ephemeral-mnemonic'),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('can enter and use a custom seed phrase', async ({ page }) => {
|
||||||
|
await setSeedPhrase({ page, seedPhrase: process.env.TEST_WALLET_MNEMONIC })
|
||||||
|
await test
|
||||||
|
.expect(
|
||||||
|
await page.evaluate(() =>
|
||||||
|
window.localStorage.getItem('ephemeral-mnemonic'),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toEqual(process.env.TEST_WALLET_MNEMONIC)
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,15 +1,131 @@
|
||||||
import test from './fixtures'
|
import test from './fixtures'
|
||||||
|
import { importWallet } from './helpers/importWallet'
|
||||||
|
import { getBalance } from './helpers/getBalance'
|
||||||
|
import { swapWallet } from './helpers/swapWallet'
|
||||||
|
import { signTx } from './helpers/signTx'
|
||||||
|
import { connectWallet } from './helpers/connectWalet'
|
||||||
|
import { setSeedPhrase } from './helpers/setSeedPhrase'
|
||||||
|
|
||||||
test.beforeEach(async ({ page }) => {
|
test.describe.configure({ mode: 'serial' })
|
||||||
await page.goto('/')
|
test.beforeEach(async ({ context, page }) => {
|
||||||
|
await connectWallet({ context, page })
|
||||||
|
await setSeedPhrase({ page, seedPhrase: process.env.TEST_WALLET_MNEMONIC })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let mainBalance: number
|
||||||
|
|
||||||
test.describe('Transfers', () => {
|
test.describe('Transfers', () => {
|
||||||
test('app should render correctly', async ({ page }) => {
|
test('can deposit a sum successfully', async ({ context, page }) => {
|
||||||
await test.expect(page.getByText('Balance:')).toBeVisible()
|
// Initialize the balance
|
||||||
|
mainBalance = Number(
|
||||||
|
(await getBalance({ context, page }))!.replace('$', ''),
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: /deposit/i }).click()
|
||||||
|
await page.keyboard.type('20')
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: /deposit/i })
|
||||||
|
.nth(1)
|
||||||
|
.click()
|
||||||
|
|
||||||
|
await signTx({ context, page })
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: /cancel/i, includeHidden: false })
|
||||||
|
.click()
|
||||||
|
|
||||||
|
// Check new balance
|
||||||
|
await page.waitForTimeout(4000)
|
||||||
|
|
||||||
|
mainBalance += 20
|
||||||
|
|
||||||
|
await test
|
||||||
|
.expect(await getBalance({ context, page }))
|
||||||
|
.toEqual(`$${mainBalance}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('balance should be 0 at first', async ({ page }) => {
|
test('can transfer to another wallet successfully', async ({
|
||||||
await test.expect(page.getByText('$0')).toBeVisible()
|
context,
|
||||||
|
extensionUrl,
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
// Import a secondary wallet to transfer to
|
||||||
|
await importWallet({
|
||||||
|
extensionUrl,
|
||||||
|
mnemonic: process.env.TEST_SECONDARY_WALLET_MNEMONIC,
|
||||||
|
page: await context.newPage(),
|
||||||
|
name: 'secondary',
|
||||||
|
})
|
||||||
|
|
||||||
|
// Initialize the secondary account balance after importing
|
||||||
|
const secondaryBalance = Number(
|
||||||
|
(await getBalance({ context, page }))!.replace('$', ''),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Swap back to main wallet
|
||||||
|
await swapWallet({ context, extensionUrl, name: 'main' })
|
||||||
|
|
||||||
|
// Transfer to the secondary wallet
|
||||||
|
await page.getByRole('button', { name: /transfer/i }).click()
|
||||||
|
await page.keyboard.type(process.env.TEST_SECONDARY_WALLET_ADDRESS)
|
||||||
|
await page.getByPlaceholder('0.00').fill('10')
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: /transfer/i })
|
||||||
|
.nth(1)
|
||||||
|
.click()
|
||||||
|
|
||||||
|
await signTx({ context, page })
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: /cancel/i, includeHidden: false })
|
||||||
|
.click()
|
||||||
|
|
||||||
|
// Check new balance
|
||||||
|
await page.waitForTimeout(4000)
|
||||||
|
mainBalance -= 10
|
||||||
|
await test
|
||||||
|
.expect(await getBalance({ context, page }))
|
||||||
|
.toEqual(`$${mainBalance}`)
|
||||||
|
|
||||||
|
// Swap to secondary to check if the transfer was received
|
||||||
|
await swapWallet({ context, extensionUrl, name: 'secondary' })
|
||||||
|
|
||||||
|
await test
|
||||||
|
.expect(await getBalance({ context, page }))
|
||||||
|
.toEqual(`$${secondaryBalance + 10}`)
|
||||||
|
|
||||||
|
// Set balance to 0 again for cleaning purposes
|
||||||
|
await page.getByRole('button', { name: /withdraw/i }).click()
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: /withdraw/i })
|
||||||
|
.nth(1)
|
||||||
|
.click()
|
||||||
|
|
||||||
|
await signTx({ context, page })
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: /cancel/i, includeHidden: false })
|
||||||
|
.click()
|
||||||
|
|
||||||
|
// Back to main wallet
|
||||||
|
await swapWallet({ context, extensionUrl, name: 'main' })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('can withdraw deposited sum successfully', async ({ context, page }) => {
|
||||||
|
await page.getByRole('button', { name: /withdraw/i }).click()
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: /withdraw/i })
|
||||||
|
.nth(1)
|
||||||
|
.click()
|
||||||
|
|
||||||
|
await signTx({ context, page })
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: /cancel/i, includeHidden: false })
|
||||||
|
.click()
|
||||||
|
|
||||||
|
// Check new balance
|
||||||
|
await page.waitForTimeout(4000)
|
||||||
|
await test.expect(await getBalance({ context, page })).toEqual('$0')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -13,9 +13,9 @@ async function globalSetup() {
|
||||||
const resp = await fetch(downloadUrl)
|
const resp = await fetch(downloadUrl)
|
||||||
|
|
||||||
if (resp.ok && resp.body) {
|
if (resp.ok && resp.body) {
|
||||||
Readable.fromWeb(resp.body as any).pipe(
|
await Readable.fromWeb(resp.body as any)
|
||||||
unzipper.Extract({ path: folderPath }),
|
.pipe(unzipper.Extract({ path: folderPath }))
|
||||||
)
|
.promise()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue