diff --git a/.github/workflows/ui_tests.yml b/.github/workflows/ui_tests.yml index bdc50085ed..2c64e46634 100644 --- a/.github/workflows/ui_tests.yml +++ b/.github/workflows/ui_tests.yml @@ -55,6 +55,7 @@ jobs: env: NEXT_PUBLIC_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_PROJECT_ID }} NEXTAUTH_SECRET: ${{ secrets.TESTS_NEXTAUTH_SECRET }} + MAILSEC_API_KEY: ${{ secrets.TESTS_MAILSEC_API_KEY }} CI: true working-directory: ./apps/laboratory/ run: npm run playwright:test diff --git a/apps/laboratory/.env.example b/apps/laboratory/.env.example index 3a79f1b520..4c3a1d823e 100644 --- a/apps/laboratory/.env.example +++ b/apps/laboratory/.env.example @@ -1,3 +1,4 @@ # Obtain a project ID from https://cloud.walletconnect.com NEXT_PUBLIC_PROJECT_ID="" NEXTAUTH_SECRET="" +MAILSEC_API_KEY="" diff --git a/apps/laboratory/package.json b/apps/laboratory/package.json index 71031509a4..e6d6302014 100644 --- a/apps/laboratory/package.json +++ b/apps/laboratory/package.json @@ -30,6 +30,7 @@ "devDependencies": { "@playwright/test": "1.40.1", "dotenv": "16.3.1", - "ethers": "6.9.0" + "ethers": "6.9.0", + "@mailsac/api": "1.0.5" } } diff --git a/apps/laboratory/tests/email.spec.ts b/apps/laboratory/tests/email.spec.ts new file mode 100644 index 0000000000..d24a367058 --- /dev/null +++ b/apps/laboratory/tests/email.spec.ts @@ -0,0 +1,20 @@ +import { testMEmail } from './shared/fixtures/w3m-fixture' +import { Email } from './shared/utils/email' + +testMEmail.beforeEach(async ({ modalPage }) => { + const tempEmail = `web3modal@mailsac.com` // TODO: we pay per user so need to improve this + const email = new Email(process.env['MAILSAC_API_KEY']!) + await email.deleteAllMessages(tempEmail) + await modalPage.loginWithEmail(tempEmail) + + const latestMessage: any = await email.getNewMessage(tempEmail) + const messageId = latestMessage._id + const otp = await email.getCodeFromEmail(tempEmail, messageId) + + await modalPage.enterOTP(otp) +}) + +testMEmail('it should sign', async ({ modalValidator }) => { + await modalValidator.expectConnected() + // TODO Implement sign +}) diff --git a/apps/laboratory/tests/shared/fixtures/w3m-fixture.ts b/apps/laboratory/tests/shared/fixtures/w3m-fixture.ts index 68e49bb8a2..0604268021 100644 --- a/apps/laboratory/tests/shared/fixtures/w3m-fixture.ts +++ b/apps/laboratory/tests/shared/fixtures/w3m-fixture.ts @@ -34,4 +34,16 @@ export const testMSiwe = base.extend({ await use(modalValidator) } }) +export const testMEmail = base.extend({ + 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' diff --git a/apps/laboratory/tests/shared/fixtures/w3m-wallet-fixture.ts b/apps/laboratory/tests/shared/fixtures/w3m-wallet-fixture.ts index e8fab9c619..36ab7b3a7c 100644 --- a/apps/laboratory/tests/shared/fixtures/w3m-wallet-fixture.ts +++ b/apps/laboratory/tests/shared/fixtures/w3m-wallet-fixture.ts @@ -1,4 +1,4 @@ -import { testM as base, testMSiwe as siwe } from './w3m-fixture' +import { testM as base, testMSiwe as siwe, testMEmail as email } from './w3m-fixture' import { WalletPage } from '../pages/WalletPage' import { WalletValidator } from '../validators/WalletValidator' diff --git a/apps/laboratory/tests/shared/pages/ModalPage.ts b/apps/laboratory/tests/shared/pages/ModalPage.ts index 10a94e3f03..e09af7c48a 100644 --- a/apps/laboratory/tests/shared/pages/ModalPage.ts +++ b/apps/laboratory/tests/shared/pages/ModalPage.ts @@ -1,7 +1,7 @@ import type { Locator, Page } from '@playwright/test' import { BASE_URL } from '../constants' -export type ModalFlavor = 'default' | 'siwe' +export type ModalFlavor = 'default' | 'siwe' | 'email' export class ModalPage { private readonly baseURL = BASE_URL @@ -16,8 +16,8 @@ export class ModalPage { ) { this.connectButton = this.page.getByTestId('connect-button') this.url = - flavor === 'siwe' - ? `${this.baseURL}library/${this.library}-siwe/` + flavor !== 'default' + ? `${this.baseURL}library/${this.library}-${this.flavor}/` : `${this.baseURL}library/${this.library}/` } @@ -33,6 +33,22 @@ export class ModalPage { await this.page.getByTestId('copy-wc2-uri').click() } + async loginWithEmail(email: string) { + await this.page.goto(this.url) + await this.connectButton.click() + await this.page.getByTestId('wui-email-input').locator('input').focus() + await this.page.getByTestId('wui-email-input').locator('input').fill(email) + await this.page.getByTestId('wui-email-input').locator('input').press('Enter') + } + + async enterOTP(otp: string) { + const splitted = otp.split('') + for (let i = 0; i < splitted.length; i++) { + await this.page.getByTestId('wui-otp-input').locator('input').nth(i).focus() + await this.page.getByTestId('wui-otp-input').locator('input').nth(i).fill(splitted[i]!) + } + } + async disconnect() { await this.page.getByTestId('account-button').click() await this.page.getByTestId('disconnect-button').click() diff --git a/apps/laboratory/tests/shared/utils/email.ts b/apps/laboratory/tests/shared/utils/email.ts new file mode 100644 index 0000000000..0fc49273fc --- /dev/null +++ b/apps/laboratory/tests/shared/utils/email.ts @@ -0,0 +1,48 @@ +import { Mailsac, type EmailMessage } from '@mailsac/api' + +export class Email { + private readonly mailsac: Mailsac + private messageCount: any + constructor(public readonly apiKey: string) { + this.mailsac = new Mailsac({ headers: { "Mailsac-Key": apiKey } }) + this.messageCount = undefined + } + + async deleteAllMessages(email: string) { + this.messageCount = 0 + return await this.mailsac.messages.deleteAllMessages(email) + } + + async getNewMessage(email: string) { + const timeout = new Promise((_, reject) => { + setTimeout(() => { + reject('timeout') + }, 25000) + }) + + const messagePoll = new Promise((resolve) => { + const interval = setInterval(async () => { + const messages = await this.mailsac.messages.listMessages(email) + if (messages.data.length > this.messageCount) { + clearInterval(interval) + this.messageCount = messages.data.length + const message = messages.data[messages.data.length - 1] as EmailMessage + return resolve(message) + } + }, 500) + }) + + return Promise.any([timeout, messagePoll]) + } + + async getCodeFromEmail(email: string, messageId: string) { + const result = await this.mailsac.messages.getBodyPlainText(email, messageId) + const regex = /\d{3}\s?\d{3}/ + const match = result.data.match(regex) + if (match) { + return match[0].replace(/\s/g, '') + } else { + throw new Error('No code found in email: ' + result.data) + } + } +} diff --git a/package-lock.json b/package-lock.json index 593253245a..409119883b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -82,6 +82,7 @@ "@chakra-ui/react": "2.8.2", "@emotion/react": "11.11.1", "@emotion/styled": "11.11.0", + "@mailsac/api": "^1.0.5", "@sentry/browser": "7.89.0", "@sentry/react": "7.89.0", "@web3modal/ethers": "3.5.3", @@ -5785,6 +5786,14 @@ "@lit-labs/ssr-dom-shim": "^1.1.2" } }, + "node_modules/@mailsac/api": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@mailsac/api/-/api-1.0.5.tgz", + "integrity": "sha512-EbqJun6pMCMlDpEY5VmsAlhMJ/ZjofQBUM2TX4p6IKFGaJ3oMBXSbFURu07gXCRRGE97CDUULixww7MoREpx9A==", + "dependencies": { + "axios": "^1.6.0" + } + }, "node_modules/@mdx-js/react": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-2.3.0.tgz", @@ -13056,7 +13065,6 @@ "version": "1.6.3", "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.3.tgz", "integrity": "sha512-fWyNdeawGam70jXSVlKl+SUNVcL6j6W79CuSIPfi6HnDUmSCH6gyUys/HrqHeA/wU0Az41rRgean494d0Jb+ww==", - "dev": true, "dependencies": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -16260,7 +16268,6 @@ "version": "1.15.3", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", - "dev": true, "funding": [ { "type": "individual", diff --git a/packages/ui/src/composites/wui-email-input/index.ts b/packages/ui/src/composites/wui-email-input/index.ts index ed22f99875..6254ed05eb 100644 --- a/packages/ui/src/composites/wui-email-input/index.ts +++ b/packages/ui/src/composites/wui-email-input/index.ts @@ -27,6 +27,7 @@ export class WuiEmailInput extends LitElement { size="md" .disabled=${this.disabled} .value=${this.value} + data-testid="wui-email-input" > ${this.templateError()} ` diff --git a/packages/ui/src/composites/wui-otp/index.ts b/packages/ui/src/composites/wui-otp/index.ts index 3f1f302a37..227e78d05f 100644 --- a/packages/ui/src/composites/wui-otp/index.ts +++ b/packages/ui/src/composites/wui-otp/index.ts @@ -29,7 +29,7 @@ export class WuiOtp extends LitElement { // -- Render -------------------------------------------- // public override render() { return html` - + ${Array.from({ length: this.length }).map( (_, index: number) => html`