Skip to content

Commit

Permalink
fix: ui tests (#1915)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukaisailovic authored Feb 14, 2024
1 parent 76bd130 commit 334ad12
Show file tree
Hide file tree
Showing 16 changed files with 101 additions and 92 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/ui_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ on:
jobs:
ui_tests:
name: 'Playwright Tests'
runs-on: ubuntu-latest
runs-on:
group: ubuntu-runners
timeout-minutes: 15
steps:
- name: checkout
Expand Down
14 changes: 10 additions & 4 deletions apps/laboratory/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@ import { BASE_URL } from './tests/shared/constants'

import { config } from 'dotenv'
import type { ModalFixture } from './tests/shared/fixtures/w3m-fixture'
import { DEVICES } from './tests/shared/constants/devices'
import { getAvailableDevices } from './tests/shared/utils/device'
config({ path: './.env' })
const availableDevices = getAvailableDevices()

const LIBRARIES = ['wagmi', 'ethers'] as const
const PERMUTATIONS = DEVICES.flatMap(device => LIBRARIES.map(library => ({ device, library })))
const PERMUTATIONS = availableDevices.flatMap(device =>
LIBRARIES.map(library => ({ device, library }))
)

export default defineConfig<ModalFixture>({
testDir: './tests',

fullyParallel: true,
retries: 0,
workers: 1,
retries: 2,
workers: 8,
reporter: process.env['CI']
? [['list'], ['html', { open: 'never' }]]
: [['list'], ['html', { host: '0.0.0.0' }]],
Expand All @@ -28,6 +31,9 @@ export default defineConfig<ModalFixture>({
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: BASE_URL,

/* Take a screenshot when the test fails */
screenshot: 'only-on-failure',

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

Expand Down
4 changes: 2 additions & 2 deletions apps/laboratory/tests/canary.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ testMW.beforeEach(
if (browserName !== 'chromium' || modalPage.library !== 'ethers') {
return
}
await modalPage.copyConnectUriToClipboard()
await walletPage.connect()
const uri = await modalPage.getConnectUri()
await walletPage.connectWithUri(uri)
await walletPage.handleSessionProposal(DEFAULT_SESSION_PARAMS)
await modalValidator.expectConnected()
await walletValidator.expectConnected()
Expand Down
67 changes: 18 additions & 49 deletions apps/laboratory/tests/connect.spec.ts
Original file line number Diff line number Diff line change
@@ -1,71 +1,40 @@
import { DEFAULT_SESSION_PARAMS } from './shared/constants'
import { DEFAULT_CHAIN_NAME, DEFAULT_SESSION_PARAMS } from './shared/constants'
import { testMW } from './shared/fixtures/w3m-wallet-fixture'

testMW.beforeEach(
async ({ modalPage, walletPage, modalValidator, walletValidator, browserName }) => {
// Webkit cannot use clipboard.
if (browserName === 'webkit') {
return
}
await modalPage.copyConnectUriToClipboard()
await walletPage.connect()
await walletPage.handleSessionProposal(DEFAULT_SESSION_PARAMS)
await modalValidator.expectConnected()
await walletValidator.expectConnected()
}
)
testMW.beforeEach(async ({ modalPage, walletPage, modalValidator, walletValidator }) => {
const uri = await modalPage.getConnectUri()
await walletPage.connectWithUri(uri)
await walletPage.handleSessionProposal(DEFAULT_SESSION_PARAMS)
await modalValidator.expectConnected()
await walletValidator.expectConnected()
})

testMW.afterEach(async ({ modalPage, modalValidator, walletValidator, browserName }) => {
// Webkit cannot use clipboard.
if (browserName === 'webkit') {
return
}
testMW.afterEach(async ({ modalPage, modalValidator, walletValidator }) => {
await modalPage.disconnect()
await modalValidator.expectDisconnected()
await walletValidator.expectDisconnected()
})

testMW(
'it should sign',
async ({ modalPage, walletPage, modalValidator, walletValidator, browserName }) => {
// Webkit cannot use clipboard.
if (browserName === 'webkit') {
testMW.skip()

return
}
await modalPage.sign()
await walletValidator.expectReceivedSign({})
await walletPage.handleRequest({ accept: true })
await modalValidator.expectAcceptedSign()
}
)
testMW('it should sign', async ({ modalPage, walletPage, modalValidator, walletValidator }) => {
await modalPage.sign()
await walletValidator.expectReceivedSign({ chainName: DEFAULT_CHAIN_NAME })
await walletPage.handleRequest({ accept: true })
await modalValidator.expectAcceptedSign()
})

testMW(
'it should reject sign',
async ({ modalPage, walletPage, modalValidator, walletValidator, browserName }) => {
// Webkit cannot use clipboard.
if (browserName === 'webkit') {
testMW.skip()

return
}
async ({ modalPage, walletPage, modalValidator, walletValidator }) => {
await modalPage.sign()
await walletValidator.expectReceivedSign({})
await walletValidator.expectReceivedSign({ chainName: DEFAULT_CHAIN_NAME })
await walletPage.handleRequest({ accept: false })
await modalValidator.expectRejectedSign()
}
)

testMW(
'it should switch networks and sign',
async ({ modalPage, walletPage, modalValidator, walletValidator, browserName }) => {
// Webkit cannot use clipboard.
if (browserName === 'webkit') {
testMW.skip()

return
}
async ({ modalPage, walletPage, modalValidator, walletValidator }) => {
let targetChain = 'Polygon'
await modalPage.switchNetwork(targetChain)
await modalPage.sign()
Expand Down
1 change: 1 addition & 0 deletions apps/laboratory/tests/email.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Email } from './shared/utils/email'
const AVAILABLE_MAILSAC_ADDRESSES = 10

testMEmail.beforeEach(async ({ modalPage, context, modalValidator }) => {
testMEmail.skip()
// Skip wagmi as it's not working
if (modalPage.library === 'wagmi') {
return
Expand Down
1 change: 1 addition & 0 deletions apps/laboratory/tests/shared/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export const DEFAULT_SESSION_PARAMS: SessionParams = {
optAccounts: ['1', '2'],
accept: true
}
export const DEFAULT_CHAIN_NAME = process.env['DEFAULT_CHAIN_NAME'] || 'Ethereum'
1 change: 1 addition & 0 deletions apps/laboratory/tests/shared/constants/timeouts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const MAXIMUM_WAIT_CONNECTIONS = 14 * 1000
14 changes: 2 additions & 12 deletions apps/laboratory/tests/shared/fixtures/w3m-wallet-fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,7 @@ interface ModalWalletFixture {

// MW -> test Modal + Wallet
export const testMW = base.extend<ModalWalletFixture>({
walletPage: async ({ context, browserName }, use) => {
// WalletPage needs clipboard permissions with chromium to paste URI
if (browserName === 'chromium') {
await context.grantPermissions(['clipboard-read', 'clipboard-write'])
}

walletPage: async ({ context }, use) => {
// Use a new page, to open alongside the modal
const walletPage = new WalletPage(await context.newPage())
await walletPage.load()
Expand All @@ -27,12 +22,7 @@ export const testMW = base.extend<ModalWalletFixture>({
}
})
export const testMWSiwe = siwe.extend<ModalWalletFixture>({
walletPage: async ({ context, browserName }, use) => {
// WalletPage needs clipboard permissions with chromium to paste URI
if (browserName === 'chromium') {
await context.grantPermissions(['clipboard-read', 'clipboard-write'])
}

walletPage: async ({ context }, use) => {
// Use a new page, to open alongside the modal
const walletPage = new WalletPage(await context.newPage())
await walletPage.load()
Expand Down
7 changes: 4 additions & 3 deletions apps/laboratory/tests/shared/pages/ModalPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@ export class ModalPage {
await this.page.goto(this.url)
}

async copyConnectUriToClipboard() {
async getConnectUri(): Promise<string> {
await this.page.goto(this.url)
await this.connectButton.click()
await this.page.getByTestId('wallet-selector-walletconnect').click()
await this.page.waitForTimeout(2000)
await this.page.getByTestId('copy-wc2-uri').click()
await this.page.waitForTimeout(1500)

return (await this.page.getByTestId('wui-qr-code').getAttribute('uri')) || ''
}

async loginWithEmail(email: string) {
Expand Down
26 changes: 14 additions & 12 deletions apps/laboratory/tests/shared/pages/WalletPage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable no-await-in-loop */
import type { Locator, Page } from '@playwright/test'
import { expect, type Locator, type Page } from '@playwright/test'
import { WALLET_URL } from '../constants'
import type { SessionParams } from '../types'

Expand All @@ -18,18 +18,16 @@ export class WalletPage {
await this.page.goto(this.baseURL)
}

async connect() {
/**
* Connect by inserting provided URI into the input element
*/
async connectWithUri(uri: string) {
const isVercelPreview = (await this.vercelPreview.count()) > 0
if (isVercelPreview) {
await this.vercelPreview.evaluate((iframe: HTMLIFrameElement) => iframe.remove())
}
await this.gotoHome.click()
await this.page.getByTestId('uri-input').click()

// Paste clipboard
const isMac = process.platform === 'darwin'
const modifier = isMac ? 'Meta' : 'Control'
await this.page.keyboard.press(`${modifier}+KeyV`)
await this.page.getByTestId('uri-input').fill(uri)
await this.page.getByTestId('uri-connect-button').click()
}

Expand All @@ -42,16 +40,20 @@ export class WalletPage {
async handleSessionProposal(opts: SessionParams) {
const variant = opts.accept ? `approve` : `reject`
// `.click` doesn't work here, so we use `.focus` and `Space`
await this.page.getByTestId(`session-${variant}-button`).isEnabled()
await this.page.getByTestId(`session-${variant}-button`).focus()
const btn = this.page.getByTestId(`session-${variant}-button`)
await btn.waitFor()
await expect(btn).toBeEnabled()
await btn.focus()
await this.page.keyboard.press('Space')
}

async handleRequest({ accept }: { accept: boolean }) {
const variant = accept ? `approve` : `reject`
// `.click` doesn't work here, so we use `.focus` and `Space`
await this.page.getByTestId(`session-${variant}-button`).isEnabled()
await this.page.getByTestId(`session-${variant}-button`).focus()
const btn = this.page.getByTestId(`session-${variant}-button`)
await btn.waitFor()
await expect(btn).toBeEnabled()
await btn.focus()
await this.page.keyboard.press('Space')
}
}
9 changes: 9 additions & 0 deletions apps/laboratory/tests/shared/utils/device.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { DEVICES } from '../constants/devices'

export function getAvailableDevices(): string[] {
if (!process.env['CI']) {
return DEVICES
}

return DEVICES.filter(d => d !== 'Desktop Safari')
}
9 changes: 9 additions & 0 deletions apps/laboratory/tests/shared/utils/timeouts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { MAXIMUM_WAIT_CONNECTIONS } from '../constants/timeouts'

export function getMaximumWaitConnections(): number {
if (process.env['CI']) {
return MAXIMUM_WAIT_CONNECTIONS
}

return MAXIMUM_WAIT_CONNECTIONS * 2
}
15 changes: 12 additions & 3 deletions apps/laboratory/tests/shared/validators/ModalValidator.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { expect } from '@playwright/test'
import type { Page } from '@playwright/test'
import { ConstantsUtil } from '../../../src/utils/ConstantsUtil'
import { getMaximumWaitConnections } from '../utils/timeouts'

const MAX_WAIT = getMaximumWaitConnections()

export class ModalValidator {
constructor(public readonly page: Page) {}

async expectConnected() {
await expect(this.page.getByTestId('account-button')).toBeVisible()
await expect(this.page.getByTestId('account-button')).toBeVisible({
timeout: MAX_WAIT
})
}

async expectAuthenticated() {
Expand All @@ -24,12 +29,16 @@ export class ModalValidator {
}

async expectDisconnected() {
await expect(this.page.getByTestId('account-button')).not.toBeVisible()
await expect(this.page.getByTestId('account-button')).not.toBeVisible({
timeout: MAX_WAIT
})
}

async expectAcceptedSign() {
// We use Chakra Toast and it's not quite straightforward to set the `data-testid` attribute on the toast element.
await expect(this.page.getByText(ConstantsUtil.SigningSucceededToastTitle)).toBeVisible()
await expect(this.page.getByText(ConstantsUtil.SigningSucceededToastTitle)).toBeVisible({
timeout: 30 * 1000
})
}

async expectRejectedSign() {
Expand Down
17 changes: 13 additions & 4 deletions apps/laboratory/tests/shared/validators/WalletValidator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { expect } from '@playwright/test'
import type { Locator, Page } from '@playwright/test'
import { getMaximumWaitConnections } from '../utils/timeouts'

const MAX_WAIT = getMaximumWaitConnections()

export class WalletValidator {
private readonly gotoSessions: Locator
Expand All @@ -10,16 +13,22 @@ export class WalletValidator {

async expectConnected() {
await this.gotoSessions.click()
await expect(this.page.getByTestId('session-card')).toBeVisible()
await expect(this.page.getByTestId('session-card')).toBeVisible({
timeout: MAX_WAIT
})
}

async expectDisconnected() {
await this.gotoSessions.click()
await expect(this.page.getByTestId('session-card')).not.toBeVisible()
await expect(this.page.getByTestId('session-card')).not.toBeVisible({
timeout: MAX_WAIT
})
}

async expectReceivedSign({ chainName = 'Ethereum' }) {
await expect(this.page.getByTestId('session-approve-button')).toBeVisible()
await expect(this.page.getByTestId('request-details-chain')).toHaveText(chainName)
await expect(this.page.getByTestId('session-approve-button')).toBeVisible({
timeout: MAX_WAIT
})
await expect(this.page.getByTestId('request-details-chain')).toContainText(chainName)
}
}
4 changes: 2 additions & 2 deletions apps/laboratory/tests/siwe.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ testMWSiwe.beforeEach(async ({ modalPage, walletPage, browserName }) => {
if (browserName === 'webkit') {
return
}
await modalPage.copyConnectUriToClipboard()
await walletPage.connect()
const uri = await modalPage.getConnectUri()
await walletPage.connectWithUri(uri)
await walletPage.handleSessionProposal(DEFAULT_SESSION_PARAMS)
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export class W3mConnectingWcQrcode extends W3mConnectingWidget {
uri=${this.uri}
imageSrc=${ifDefined(AssetUtil.getWalletImage(this.wallet))}
alt=${ifDefined(alt)}
data-testid="wui-qr-code"
></wui-qr-code>`
}

Expand Down

0 comments on commit 334ad12

Please sign in to comment.