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

chore(pilot-app): e2e for companion app #571

Merged
merged 18 commits into from
Jan 17, 2025
Merged
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
36 changes: 34 additions & 2 deletions .github/workflows/app-preview.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Pilot App
name: Companion App

on:
pull_request:
Expand All @@ -10,7 +10,10 @@ defaults:
jobs:
deploy:
runs-on: ubuntu-latest
name: Deploy
name: Deploy & Test
defaults:
run:
working-directory: ./deployables/app
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
Expand All @@ -19,12 +22,41 @@ jobs:
node-version: latest
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'

- run: pnpm install --prefer-offline

- run: pnpm build

- name: Deploy
id: deploy
uses: cloudflare/[email protected]
with:
workingDirectory: deployables/app
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
command: versions upload

# If you think this sucks, you are right.
# Check https://github.com/cloudflare/wrangler-action/issues/343
# and we can just use the deployment-url output of
# the deploy action
- uses: kaisugi/[email protected]
id: regex
with:
text: ${{ steps.deploy.outputs.command-output }}
regex: 'https:\/\/.+\.gnosisguild\.workers\.dev'

- name: Install Playwright Browsers
run: pnpm playwright install --with-deps chromium

- name: Run Playwright tests
run: xvfb-run pnpm playwright test
env:
PLAYWRIGHT_TEST_BASE_URL: ${{ steps.regex.outputs.match }}

- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 30
2 changes: 1 addition & 1 deletion .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
- run: pnpm install --prefer-offline
- run: pnpm test:unit
- run: pnpm test
- name: 'Report Coverage'
# Set if: always() to also generate the report if tests are failing
# Only works if you set `reportOnFailure: true` in your vite config as specified above
Expand Down
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@ coverage
.turbo

.env
*.log
*.log

playwright/.cache/
playwright-report/
test-results/
8 changes: 4 additions & 4 deletions deployables/app/app/routes/edit-route.$data.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ describe('Edit route', () => {
const roleId = randomAddress()

await userEvent.type(
screen.getByRole('textbox', { name: 'Role ID' }),
await screen.findByRole('textbox', { name: 'Role ID' }),
roleId,
)

Expand Down Expand Up @@ -520,9 +520,9 @@ describe('Edit route', () => {

await render(`/edit-route/${btoa(JSON.stringify(route))}`)

expect(screen.getByRole('textbox', { name: 'Role Key' })).toHaveValue(
'TEST-KEY',
)
expect(
await screen.findByRole('textbox', { name: 'Role Key' }),
).toHaveValue('TEST-KEY')
})

it('is possible to update the role key', async () => {
Expand Down
2 changes: 0 additions & 2 deletions deployables/app/app/routes/edit-route.$data.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,6 @@ export const clientAction = async ({
const chainId = verifyChainId(getInt(data, 'chainId'))
const providerType = verifyProviderType(getInt(data, 'providerType'))

console.log({ account, chainId, providerType })

return editRoute(
request.url,
updatePilotAddress(
Expand Down
52 changes: 52 additions & 0 deletions deployables/app/e2e/accountHandling/lockedAccount.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import test, { expect } from '@playwright/test'
import { mockWeb3 } from '@zodiac/test-utils/e2e'
import { connectWallet } from '../connectWallet'

test.describe('Locked account', () => {
const account = '0x1000000000000000000000000000000000000000'

test('handles wallet disconnect gracefully', async ({ page }) => {
const { lockWallet } = await mockWeb3(page)

await page.goto('/new-route')

await connectWallet(page)
await lockWallet()

await expect(
page.getByRole('alert', { name: 'Wallet disconnected' }),
).toBeInViewport()
})

test('it is possible to reconnect an account', async ({ page }) => {
const { lockWallet } = await mockWeb3(page, {
accounts: [account],
})

await page.goto('/new-route')

await connectWallet(page, account)
await lockWallet()

await page.getByRole('button', { name: 'Connect', exact: true }).click()

await expect(
page.getByRole('textbox', { name: 'Pilot Account' }),
).toHaveValue(account)
})

test('it is possible to disconnect a locked account', async ({ page }) => {
const { lockWallet } = await mockWeb3(page)

await page.goto('/new-route')

await connectWallet(page)
await lockWallet()

await page.getByRole('button', { name: 'Disconnect' }).click()

await expect(
page.getByRole('button', { name: 'Connect wallet' }),
).toBeInViewport()
})
})
24 changes: 24 additions & 0 deletions deployables/app/e2e/accountHandling/unavailableWallet.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import test, { expect } from '@playwright/test'
import { mockWeb3 } from '@zodiac/test-utils/e2e'
import { connectWallet } from '../connectWallet'

test.describe('Account unavailable', () => {
test('handles unavailable accounts gracefully', async ({ page }) => {
const { loadAccounts } = await mockWeb3(page, {
accounts: ['0x1000000000000000000000000000000000000000'],
})

await page.goto('/new-route')

await connectWallet(page, '0x1000000000000000000000000000000000000000')
await loadAccounts(['0x2000000000000000000000000000000000000000'])

await expect(
page.getByRole('alert', {
name: `Account is not connected`,
}),
).toHaveAccessibleDescription(
'Switch your wallet to this account in order to use Pilot.',
)
})
})
33 changes: 33 additions & 0 deletions deployables/app/e2e/accountHandling/wrongChain.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import test, { expect } from '@playwright/test'
import { mockWeb3 } from '@zodiac/test-utils/e2e'
import { connectWallet } from '../connectWallet'

test.describe('Wrong chain selected', () => {
test('it is possible to switch to the correct chain', async ({ page }) => {
const { switchChain } = await mockWeb3(page)

await page.goto('/new-route')

await connectWallet(page)
await switchChain(10)

await expect(
page.getByRole('alert', { name: 'Chain mismatch' }),
).toBeInViewport()
})

test('it is possible to switch back to the connected chain', async ({
page,
}) => {
const { switchChain } = await mockWeb3(page)

await page.goto('/new-route')

await connectWallet(page)
await switchChain(10)

await expect(
page.getByRole('button', { name: 'Switch wallet to Ethereum' }),
).toBeInViewport()
})
})
13 changes: 13 additions & 0 deletions deployables/app/e2e/connectWallet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { expect, type Page } from '@playwright/test'
import { defaultMockAccount } from '@zodiac/test-utils/e2e'

export const connectWallet = async (
page: Page,
account: `0x${string}` = defaultMockAccount,
) => {
await page.getByRole('button', { name: 'Connect wallet' }).click()
await page.getByRole('button', { name: 'Browser Wallet' }).click()
await expect(
page.getByRole('textbox', { name: 'Pilot Account' }),
).toHaveValue(account)
}
9 changes: 9 additions & 0 deletions deployables/app/e2e/smoketest.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import test, { expect } from '@playwright/test'

test('connection to example app', async ({ page }) => {
await page.goto('/new-route')

await expect(
page.getByRole('heading', { name: 'Route configuration' }),
).toBeInViewport()
})
39 changes: 39 additions & 0 deletions deployables/app/e2e/utils/fixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/* eslint-disable no-empty-pattern, react-hooks/rules-of-hooks */
import { test as base, chromium, type BrowserContext } from '@playwright/test'
import { fileURLToPath } from 'url'

export const test = base.extend<{
context: BrowserContext
extensionId: string
}>({
context: async ({}, use) => {
const pathToExtension = fileURLToPath(
new URL('../../public', import.meta.url),
)

const context = await chromium.launchPersistentContext('', {
headless: false,
args: [
`--disable-extensions-except=${pathToExtension}`,
`--load-extension=${pathToExtension}`,
],
ignoreDefaultArgs: [
'--disable-component-extensions-with-background-pages',
],
})

await use(context)
await context.close()
},

extensionId: async ({ context }, use) => {
let [background] = context.serviceWorkers()
if (!background) background = await context.waitForEvent('serviceworker')

const extensionId = background.url().split('/')[2]

await use(extensionId)
},
})

export const expect = test.expect
16 changes: 16 additions & 0 deletions deployables/app/e2e/utils/getExtensionPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { Page } from '@playwright/test'
import { waitFor } from '@zodiac/test-utils/e2e'

export const getExtensionPage = (page: Page) =>
waitFor(() => {
const extension = page
.context()
.pages()
.find((page) => page.url().startsWith('chrome-extension'))

if (extension == null) {
throw new Error('Extension not found')
}

return extension
})
4 changes: 4 additions & 0 deletions deployables/app/e2e/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export { expect, test } from './fixture'
export { loadExtension } from './loadExtension'
export { defaultMockAccount, mockWeb3 } from './mockWeb3'
export { waitFor } from './waitFor'
10 changes: 10 additions & 0 deletions deployables/app/e2e/utils/loadExtension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { Page } from '@playwright/test'
import { getExtensionPage } from './getExtensionPage'

export const loadExtension = async (page: Page) => {
await page.goto('/')

await page.getByRole('button', { name: 'Open extension' }).click()

return getExtensionPage(page)
}
4 changes: 4 additions & 0 deletions deployables/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
"start": "wrangler dev",
"check-types": "react-router typegen && tsc -b",
"test": "vitest",
"test:e2e": "playwright test --headed",
"test:e2e:ui": "pnpm test:e2e --ui",
"lint": "eslint . --max-warnings=0"
},
"dependencies": {
Expand Down Expand Up @@ -37,6 +39,7 @@
"@cloudflare/workers-types": "4.20250109.0",
"@depay/web3-mock": "^14.19.1",
"@hiogawa/vite-node-miniflare": "0.1.1",
"@playwright/test": "^1.48.1",
"@react-router/dev": "^7.1.1",
"@testing-library/jest-dom": "^6.4.6",
"@testing-library/react": "^16.0.1",
Expand All @@ -50,6 +53,7 @@
"@zodiac/test-utils": "workspace:*",
"@zodiac/typescript-config": "workspace:*",
"autoprefixer": "^10.4.20",
"dotenv": "^16.0.1",
"eslint": "^9.7.0",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.16",
Expand Down
44 changes: 44 additions & 0 deletions deployables/app/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { defineConfig, devices } from '@playwright/test'
import dotenv from 'dotenv'

dotenv.config()

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './e2e',
/* Run tests in files in parallel */
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 ? 2 : 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: process.env.CI ? 'github' : 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: process.env.PLAYWRIGHT_TEST_BASE_URL || 'http://localhost:3040',

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},

/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],

/* Run your local dev server before starting the tests */
// webServer: {
// command: 'npm run start',
// url: 'http://127.0.0.1:3000',
// reuseExistingServer: !process.env.CI,
// },
})
Loading
Loading