Skip to content

Commit

Permalink
chore(pilot-app): e2e for companion app (#571)
Browse files Browse the repository at this point in the history
* start migrating existing e2e tests to companion app

* globally ignore playwright output

* make some adjustments to tests

* run app e2e tests

* adjust commands

* change default working directory

* add type import

* hacky way to get the deployment url

* use correct output field

* use explicit string

* reduce test flakiness

* fix yaml.. maybe...

* maybe fix regex

* try newer action

* simplify regex

* use match

* remove console log

* fix wrong assertion
  • Loading branch information
frontendphil authored Jan 17, 2025
1 parent 0841ce1 commit 6b941e7
Show file tree
Hide file tree
Showing 23 changed files with 469 additions and 15 deletions.
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

0 comments on commit 6b941e7

Please sign in to comment.