Skip to content

Commit

Permalink
chore: refactor email tests (#2012)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukaisailovic authored Mar 1, 2024
1 parent 221cb7e commit 91f1254
Show file tree
Hide file tree
Showing 15 changed files with 241 additions and 125 deletions.
1 change: 0 additions & 1 deletion apps/laboratory/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ config({ path: './.env.local' })

export default defineConfig<ModalFixture>({
testDir: './tests',
testIgnore: 'email.spec.ts',
fullyParallel: true,
retries: getValue(2, 1),
workers: getValue(8, 4),
Expand Down
76 changes: 34 additions & 42 deletions apps/laboratory/tests/email.spec.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,44 @@
import { testMEmail } from './shared/fixtures/w3m-fixture'
import { DeviceRegistrationPage } from './shared/pages/DeviceRegistrationPage'
import { Email } from './shared/utils/email'
import { expect } from '@playwright/test'
import { testMEmail } from './shared/fixtures/w3m-email-fixture'
import { SECURE_WEBSITE_URL } from './shared/constants'

// Prevent collissions by using a semi-random reserved Mailsac email
const AVAILABLE_MAILSAC_ADDRESSES = 10

testMEmail.beforeEach(async ({ modalPage, context, modalValidator }) => {
// This is prone to collissions and will be improved later
const tempEmail = `web3modal${Math.floor(
Math.random() * AVAILABLE_MAILSAC_ADDRESSES
)}@mailsac.com`
const mailsacApiKey = process.env['MAILSAC_API_KEY']
if (!mailsacApiKey) {
throw new Error('MAILSAC_API_KEY is not set')
}
const email = new Email(mailsacApiKey)
await email.deleteAllMessages(tempEmail)
await modalPage.loginWithEmail(tempEmail)

let latestMessage = await email.getNewMessage(tempEmail)
let messageId = latestMessage._id
testMEmail.beforeEach(async ({ modalValidator }) => {
await modalValidator.expectConnected()
})

if (!messageId) {
throw new Error('No messageId found')
}
testMEmail('it should sign', async ({ modalPage, modalValidator }) => {
await modalPage.sign()
await modalPage.approveSign()
await modalValidator.expectAcceptedSign()
})

let otp = await email.getCodeFromEmail(tempEmail, messageId)
testMEmail('it should upgrade wallet', async ({ modalPage, context }) => {
const page = await modalPage.clickWalletUpgradeCard(context)
expect(page.url()).toContain(SECURE_WEBSITE_URL)
await page.close()
})

if (otp.length !== 6) {
// We got a device registration link so let's register first
const drp = new DeviceRegistrationPage(await context.newPage(), otp)
drp.load()
await drp.approveDevice()
testMEmail('it should reject sign', async ({ modalPage, modalValidator }) => {
await modalPage.sign()
await modalPage.rejectSign()
await modalValidator.expectRejectedSign()
})

latestMessage = await email.getNewMessage(tempEmail)
messageId = latestMessage._id
if (!messageId) {
throw new Error('No messageId found')
}
otp = await email.getCodeFromEmail(tempEmail, messageId)
}
testMEmail('it should switch network and sign', async ({ modalPage, modalValidator }) => {
let targetChain = 'Polygon'
await modalPage.switchNetwork(targetChain)
await modalValidator.expectNetwork(targetChain)
await modalPage.page.waitForTimeout(1500)
await modalPage.sign()
await modalPage.approveSign()
await modalValidator.expectAcceptedSign()

await modalPage.enterOTP(otp)
await modalValidator.expectConnected()
})
await modalPage.page.waitForTimeout(2000)

testMEmail('it should sign', async ({ modalPage, modalValidator }) => {
testMEmail.skip(modalPage.library === 'wagmi', 'Tests are flaky on wagmi')
targetChain = 'Ethereum'
await modalPage.switchNetwork(targetChain)
await modalValidator.expectNetwork(targetChain)
await modalPage.page.waitForTimeout(1500)
await modalPage.sign()
await modalPage.approveSign()
await modalValidator.expectAcceptedSign()
Expand Down
2 changes: 1 addition & 1 deletion apps/laboratory/tests/shared/constants/devices.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const DEVICES = ['Desktop Chrome', 'Desktop Brave', 'Desktop Firefox']
export const DEVICES = ['Desktop Firefox', 'Desktop Brave', 'Desktop Chrome']
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,4 +8,5 @@ export const DEFAULT_SESSION_PARAMS: SessionParams = {
optAccounts: ['1', '2'],
accept: true
}
export const SECURE_WEBSITE_URL = 'https://secure.walletconnect.com'
export const DEFAULT_CHAIN_NAME = process.env['DEFAULT_CHAIN_NAME'] || 'Ethereum'
61 changes: 61 additions & 0 deletions apps/laboratory/tests/shared/fixtures/w3m-email-fixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { test as base } from '@playwright/test'
import type { ModalFixture } from './w3m-fixture'
import { ModalPage } from '../pages/ModalPage'
import { ModalValidator } from '../validators/ModalValidator'
import { DeviceRegistrationPage } from '../pages/DeviceRegistrationPage'
import { Email } from '../utils/email'

const mailsacApiKey = process.env['MAILSAC_API_KEY']
if (!mailsacApiKey) {
throw new Error('MAILSAC_API_KEY is not set')
}

export const testMEmail = base.extend<ModalFixture>({
library: ['wagmi', { option: true }],
modalPage: async ({ page, library, context }, use, testInfo) => {
const modalPage = new ModalPage(page, library, 'email')
await modalPage.load()

const email = new Email(mailsacApiKey)

const tempEmail = email.getEmailAddressToUse(testInfo.parallelIndex)

await email.deleteAllMessages(tempEmail)
await modalPage.loginWithEmail(tempEmail)

let messageId = await email.getLatestMessageId(tempEmail)

if (!messageId) {
throw new Error('No messageId found')
}
let emailBody = await email.getEmailBody(tempEmail, messageId)
let otp = ''
if (email.isApproveEmail(emailBody)) {
const url = email.getApproveUrlFromBody(emailBody)

await email.deleteAllMessages(tempEmail)

const drp = new DeviceRegistrationPage(await context.newPage(), url)
drp.load()
await drp.approveDevice()
await drp.close()

messageId = await email.getLatestMessageId(tempEmail)

emailBody = await email.getEmailBody(tempEmail, messageId)
if (!email.isApproveEmail(emailBody)) {
otp = email.getOtpCodeFromBody(emailBody)
}
}
if (otp.length !== 6) {
otp = email.getOtpCodeFromBody(emailBody)
}
await modalPage.enterOTP(otp)

await use(modalPage)
},
modalValidator: async ({ modalPage }, use) => {
const modalValidator = new ModalValidator(modalPage.page)
await use(modalValidator)
}
})
13 changes: 1 addition & 12 deletions apps/laboratory/tests/shared/fixtures/w3m-fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,5 @@ export const testMSiwe = base.extend<ModalFixture>({
await use(modalValidator)
}
})
export const testMEmail = base.extend<ModalFixture>({
library: ['wagmi', { option: true }],
modalPage: async ({ page, library }, use) => {
const modalPage = new ModalPage(page, library, 'email')
await modalPage.load()
await use(modalPage)
},
modalValidator: async ({ modalPage }, use) => {
const modalValidator = new ModalValidator(modalPage.page)
await use(modalValidator)
}
})

export { expect } from '@playwright/test'
17 changes: 2 additions & 15 deletions apps/laboratory/tests/shared/fixtures/w3m-wallet-fixture.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { testM as base, testMSiwe as siwe } from './w3m-fixture'
import { WalletPage } from '../pages/WalletPage'
import { WalletValidator } from '../validators/WalletValidator'
import type { BrowserContext, Page } from '@playwright/test'

import { DEFAULT_SESSION_PARAMS } from '../constants'
import { doActionAndWaitForNewPage } from '../utils/actions'

// Declare the types of fixtures to use
interface ModalWalletFixture {
Expand Down Expand Up @@ -35,18 +36,4 @@ export const testMWSiwe = siwe.extend<ModalWalletFixture>({
}
})

export async function doActionAndWaitForNewPage(
action: Promise<void>,
context: BrowserContext
): Promise<Page> {
if (!context) {
throw new Error('Browser Context is undefined')
}
const pagePromise = context.waitForEvent('page')
await action
const newPage = await pagePromise

return newPage
}

export { expect } from '@playwright/test'
9 changes: 8 additions & 1 deletion apps/laboratory/tests/shared/pages/DeviceRegistrationPage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { Page } from '@playwright/test'
import { expect, type Page } from '@playwright/test'

const LOGIN_APPROVED_SUCCESS_TEXT = 'Login Approved'

export class DeviceRegistrationPage {
constructor(
Expand All @@ -8,9 +10,14 @@ export class DeviceRegistrationPage {

async load() {
await this.page.goto(this.url)
await this.page.waitForLoadState()
}

async approveDevice() {
await this.page.getByRole('button', { name: 'Approve' }).click()
await expect(this.page.getByText(LOGIN_APPROVED_SUCCESS_TEXT)).toBeVisible()
}
async close() {
await this.page.close()
}
}
56 changes: 45 additions & 11 deletions apps/laboratory/tests/shared/pages/ModalPage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { Locator, Page } from '@playwright/test'
/* eslint-disable no-await-in-loop */
import type { BrowserContext, Locator, Page } from '@playwright/test'
import { expect } from '@playwright/test'
import { BASE_URL } from '../constants'
import { doActionAndWaitForNewPage } from '../utils/actions'

export type ModalFlavor = 'default' | 'siwe' | 'email'

Expand Down Expand Up @@ -64,16 +66,25 @@ export class ModalPage {

async enterOTP(otp: string) {
const splitted = otp.split('')
// Remove empy space in OTP code 111 111
splitted.splice(3, 1)

// eslint-disable-next-line no-plusplus
for (let i = 0; i < splitted.length; i++) {
const digit = splitted[i]
if (!digit) {
throw new Error('Invalid OTP')
}
/* eslint-disable no-await-in-loop */
await this.page.getByTestId('wui-otp-input').locator('input').nth(i).focus()
/* eslint-disable no-await-in-loop */
await this.page.getByTestId('wui-otp-input').locator('input').nth(i).fill(digit)
const otpInput = this.page.getByTestId('wui-otp-input')
const wrapper = otpInput.locator('wui-input-numeric').nth(i)
await expect(wrapper, `Wrapper element for input ${i} should be visible`).toBeVisible({
timeout: 5000
})
const input = wrapper.locator('input')
await expect(input, `Input ${i} should be enabled`).toBeEnabled({
timeout: 5000
})
await input.fill(digit)
}

await expect(this.page.getByText('Confirm Email')).not.toBeVisible()
Expand All @@ -94,16 +105,39 @@ export class ModalPage {
await this.page.getByTestId('sign-message-button').click()
}

async approveSign() {
async signatureRequestFrameShouldVisible() {
await expect(
this.page.frameLocator('#w3m-iframe').getByText('requests a signature'),
'Web3Modal iframe should be visible'
).toBeVisible()
).toBeVisible({
timeout: 10000
})
await this.page.waitForTimeout(2000)
await this.page
.frameLocator('#w3m-iframe')
.getByRole('button', { name: 'Sign', exact: true })
.click()
}
async clickSignatureRequestButton(name: string) {
await this.page.frameLocator('#w3m-iframe').getByRole('button', { name, exact: true }).click()
}

async approveSign() {
await this.signatureRequestFrameShouldVisible()
await this.clickSignatureRequestButton('Sign')
}

async rejectSign() {
await this.signatureRequestFrameShouldVisible()
await this.clickSignatureRequestButton('Cancel')
}

async clickWalletUpgradeCard(context: BrowserContext) {
await this.page.getByTestId('account-button').click()
await this.page.getByTestId('w3m-wallet-upgrade-card').click()

const page = await doActionAndWaitForNewPage(
this.page.getByTestId('w3m-secure-website-button').click(),
context
)

return page
}

async promptSiwe() {
Expand Down
15 changes: 15 additions & 0 deletions apps/laboratory/tests/shared/utils/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { BrowserContext, Page } from '@playwright/test'

export async function doActionAndWaitForNewPage(
action: Promise<void>,
context: BrowserContext
): Promise<Page> {
if (!context) {
throw new Error('Browser Context is undefined')
}
const pagePromise = context.waitForEvent('page')
await action
const newPage = await pagePromise

return newPage
}
Loading

0 comments on commit 91f1254

Please sign in to comment.