diff --git a/.github/workflows/ui_test.yml b/.github/workflows/ui_test.yml new file mode 100644 index 000000000..e18764d93 --- /dev/null +++ b/.github/workflows/ui_test.yml @@ -0,0 +1,29 @@ +name: Playwright Tests +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] +jobs: + test: + timeout-minutes: 10 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Install dependencies + run: yarn + - name: Install Playwright Browsers + run: yarn playwright:install + - name: Run Playwright tests + run: yarn playwright:test + env: + VITE_PROJECT_ID: ${{ secrets.VITE_DEV_PROJECT_ID }} + - uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/playwright.config.ts b/playwright.config.ts index 766a10ffc..466bdcf52 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,8 +1,9 @@ import { defineConfig, devices } from '@playwright/test' +import { config } from 'dotenv' + import { BASE_URL } from './tests/shared/constants' +import type { ModalFixture } from './tests/shared/fixtures/fixture' -import { config } from 'dotenv' -import type { ModalFixture } from './tests/shared/fixtures/w3m-fixture' config({ path: './.env' }) export default defineConfig({ @@ -32,22 +33,12 @@ export default defineConfig({ projects: [ { name: 'chromium/wagmi', - use: { ...devices['Desktop Chrome'], library: 'wagmi' } + use: { ...devices['Desktop Chrome'] } }, { name: 'firefox/wagmi', - use: { ...devices['Desktop Firefox'], library: 'wagmi' } - }, - - { - name: 'chromium/ethers', - use: { ...devices['Desktop Chrome'], library: 'ethers' } - }, - - { - name: 'firefox/ethers', - use: { ...devices['Desktop Firefox'], library: 'ethers' } + use: { ...devices['Desktop Firefox'] } } ], diff --git a/src/constants/projects.ts b/src/constants/projects.ts index 6d5a95bf0..6d8f2dd75 100644 --- a/src/constants/projects.ts +++ b/src/constants/projects.ts @@ -35,7 +35,8 @@ export const COMING_SOON_PROJECTS: Array = [ { id: 'chainspot', name: 'Chainspot', - description: 'Bridge&swap across 27 chains at the best rates and research data about 100+ Web3 products.', + description: + 'Bridge&swap across 27 chains at the best rates and research data about 100+ Web3 products.', url: 'https://app.chainspot.io/', isComingSoon: true, isVerified: false, diff --git a/src/contexts/W3iContext/hooks/notifyHooks.ts b/src/contexts/W3iContext/hooks/notifyHooks.ts index 18f6656ef..eef682f56 100644 --- a/src/contexts/W3iContext/hooks/notifyHooks.ts +++ b/src/contexts/W3iContext/hooks/notifyHooks.ts @@ -51,15 +51,15 @@ export const useNotifyState = (w3iProxy: Web3InboxProxy, proxyReady: boolean) => * load in progress state using interval until it is */ useEffect(() => { - if(watchSubscriptionsComplete) { - return noop; + if (watchSubscriptionsComplete) { + return noop } // Account for sync init const intervalId = setInterval(() => { - if (notifyClient?.hasFinishedInitialLoad()) { - setWatchSubscriptionsComplete(true) - return noop; - } + if (notifyClient?.hasFinishedInitialLoad()) { + setWatchSubscriptionsComplete(true) + return noop + } refreshNotifyState() }, 100) diff --git a/tests/shared/fixtures/fixture.ts b/tests/shared/fixtures/fixture.ts new file mode 100644 index 000000000..a1673b87a --- /dev/null +++ b/tests/shared/fixtures/fixture.ts @@ -0,0 +1,25 @@ +import { test as base } from '@playwright/test' + +import { ModalPage } from '../pages/InboxPage' +import { ModalValidator } from '../validators/ModalValidator' + +// Declare the types of fixtures to use +export interface ModalFixture { + modalPage: ModalPage + modalValidator: ModalValidator + library: string +} + +export const test = base.extend({ + library: ['wagmi', { option: true }], + modalPage: async ({ page, library }, use) => { + const modalPage = new ModalPage(page) + 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/tests/shared/fixtures/w3m-fixture.ts b/tests/shared/fixtures/w3m-fixture.ts deleted file mode 100644 index 060426802..000000000 --- a/tests/shared/fixtures/w3m-fixture.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { test as base } from '@playwright/test' -import { ModalPage } from '../pages/ModalPage' -import { ModalValidator } from '../validators/ModalValidator' - -// Declare the types of fixtures to use -export interface ModalFixture { - modalPage: ModalPage - modalValidator: ModalValidator - library: string -} - -// M -> test Modal -export const testM = base.extend({ - library: ['wagmi', { option: true }], - modalPage: async ({ page, library }, use) => { - const modalPage = new ModalPage(page, library, 'default') - await modalPage.load() - await use(modalPage) - }, - modalValidator: async ({ modalPage }, use) => { - const modalValidator = new ModalValidator(modalPage.page) - await use(modalValidator) - } -}) -export const testMSiwe = base.extend({ - library: ['wagmi', { option: true }], - modalPage: async ({ page, library }, use) => { - const modalPage = new ModalPage(page, library, 'siwe') - await modalPage.load() - await use(modalPage) - }, - modalValidator: async ({ modalPage }, use) => { - const modalValidator = new ModalValidator(modalPage.page) - 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/tests/shared/fixtures/w3m-wallet-fixture.ts b/tests/shared/fixtures/wallet-fixture.ts similarity index 52% rename from tests/shared/fixtures/w3m-wallet-fixture.ts rename to tests/shared/fixtures/wallet-fixture.ts index e8fab9c61..885be6656 100644 --- a/tests/shared/fixtures/w3m-wallet-fixture.ts +++ b/tests/shared/fixtures/wallet-fixture.ts @@ -1,6 +1,6 @@ -import { testM as base, testMSiwe as siwe } from './w3m-fixture' import { WalletPage } from '../pages/WalletPage' import { WalletValidator } from '../validators/WalletValidator' +import { test as base } from './fixture' // Declare the types of fixtures to use interface ModalWalletFixture { @@ -8,25 +8,7 @@ interface ModalWalletFixture { walletValidator: WalletValidator } -// MW -> test Modal + Wallet -export const testMW = base.extend({ - walletPage: async ({ context, browserName }, use) => { - // WalletPage needs clipboard permissions with chromium to paste URI - if (browserName === 'chromium') { - await context.grantPermissions(['clipboard-read', 'clipboard-write']) - } - - // Use a new page, to open alongside the modal - const walletPage = new WalletPage(await context.newPage()) - await walletPage.load() - await use(walletPage) - }, - walletValidator: async ({ walletPage }, use) => { - const walletValidator = new WalletValidator(walletPage.page) - await use(walletValidator) - } -}) -export const testMWSiwe = siwe.extend({ +export const testWallet = base.extend({ walletPage: async ({ context, browserName }, use) => { // WalletPage needs clipboard permissions with chromium to paste URI if (browserName === 'chromium') { diff --git a/tests/shared/pages/InboxPage.ts b/tests/shared/pages/InboxPage.ts new file mode 100644 index 000000000..ca1f26606 --- /dev/null +++ b/tests/shared/pages/InboxPage.ts @@ -0,0 +1,58 @@ +import type { Locator, Page } from '@playwright/test' + +import { BASE_URL } from '../constants' + +export class ModalPage { + private readonly baseURL = BASE_URL + + private readonly connectButton: Locator + + constructor(public readonly page: Page) { + this.connectButton = this.page.getByRole('button', { name: 'Connect Wallet' }) + } + + async load() { + await this.page.goto(this.baseURL) + } + + async copyConnectUriToClipboard() { + await this.page.goto(this.baseURL) + 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() + } + + async disconnect() { + await this.page.getByTestId('account-button').click() + await this.page.getByTestId('disconnect-button').click() + } + + async promptSiwe() { + await this.page.getByRole('button', { name: 'Sign in with wallet' }).click() + } + + async subscribe(nth: number) { + await this.page.locator('.AppCard__body > .AppCard__body__subscribe').nth(nth).click() + } + + async unsubscribe() { + await this.page.getByRole('button', { name: 'Subscribed' }).click() + await this.page.getByRole('button', { name: 'Subscribed' }).isHidden() + await this.page.getByRole('button').first().click() + await this.page.getByRole('button', { name: 'Unsubscribe' }).click() + await this.page.getByRole('button', { name: 'Unsubscribe' }).nth(1).click() + await this.page.waitForTimeout(2000) + } + + async cancelSiwe() { + await this.page.getByTestId('w3m-connecting-siwe-cancel').click() + } + + async switchNetwork(network: string) { + await this.page.getByTestId('account-button').click() + await this.page.getByTestId('w3m-account-select-network').click() + await this.page.getByTestId(`w3m-network-switch-${network}`).click() + await this.page.getByTestId(`w3m-header-close`).click() + } +} diff --git a/tests/shared/pages/ModalPage.ts b/tests/shared/pages/ModalPage.ts deleted file mode 100644 index 52ec2ef24..000000000 --- a/tests/shared/pages/ModalPage.ts +++ /dev/null @@ -1,113 +0,0 @@ -import type { Locator, Page } from '@playwright/test' -import { expect } from '@playwright/test' -import { BASE_URL } from '../constants' - -export type ModalFlavor = 'default' | 'siwe' | 'email' - -export class ModalPage { - private readonly baseURL = BASE_URL - - private readonly connectButton: Locator - private readonly url: string - - constructor( - public readonly page: Page, - public readonly library: string, - public readonly flavor: ModalFlavor - ) { - this.connectButton = this.page.getByRole('button', { name: 'Connect Wallet' }) - this.url = - flavor === 'default' - ? `${this.baseURL}library/${this.library}/` - : `${this.baseURL}library/${this.library}-${this.flavor}/` - } - - async load() { - await this.page.goto(this.url) - } - - async copyConnectUriToClipboard() { - 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() - } - - async loginWithEmail(email: string) { - await this.page.goto(this.url) - // Connect Button doesn't have a proper `disabled` attribute so we need to wait for the button to change the text - await this.page - .getByTestId('connect-button') - .getByRole('button', { name: 'Connect Wallet' }) - .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('') - // 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) - } - - await expect(this.page.getByText('Confirm Email')).not.toBeVisible() - } - - async disconnect() { - await this.page.getByTestId('account-button').click() - await this.page.getByTestId('disconnect-button').click() - } - - async sign() { - await this.page.getByTestId('sign-message-button').click() - } - - async approveSign() { - await expect( - this.page.frameLocator('#w3m-iframe').getByText('requests a signature') - ).toBeVisible() - await this.page.waitForTimeout(2000) - await this.page - .frameLocator('#w3m-iframe') - .getByRole('button', { name: 'Sign', exact: true }) - .click() - } - - async promptSiwe() { - await this.page.getByRole('button', { name: 'Sign in with wallet' }).click() - } - - async subscribe(nth: number) { - await this.page.locator('.AppCard__body > .AppCard__body__subscribe').nth(nth).click() - } - - async unsubscribe() { - await this.page.getByRole('button', { name: 'Subscribed' }).click() - await this.page.getByRole('button', { name: 'Subscribed' }).isHidden() - await this.page.getByRole('button').first().click() - await this.page.getByRole('button', { name: 'Unsubscribe' }).click() - await this.page.getByRole('button', { name: 'Unsubscribe' }).nth(1).click() - await this.page.waitForTimeout(8000) - } - - async cancelSiwe() { - await this.page.getByTestId('w3m-connecting-siwe-cancel').click() - } - - async switchNetwork(network: string) { - await this.page.getByTestId('account-button').click() - await this.page.getByTestId('w3m-account-select-network').click() - await this.page.getByTestId(`w3m-network-switch-${network}`).click() - await this.page.getByTestId(`w3m-header-close`).click() - } -} diff --git a/tests/shared/pages/WalletPage.ts b/tests/shared/pages/WalletPage.ts index 619748645..ba1c03605 100644 --- a/tests/shared/pages/WalletPage.ts +++ b/tests/shared/pages/WalletPage.ts @@ -1,5 +1,6 @@ /* eslint-disable no-await-in-loop */ import type { Locator, Page } from '@playwright/test' + import { WALLET_URL } from '../constants' import type { SessionParams } from '../types' diff --git a/tests/subscribe.spec.ts b/tests/subscribe.spec.ts index 7e2b4d98a..03b4278d7 100644 --- a/tests/subscribe.spec.ts +++ b/tests/subscribe.spec.ts @@ -1,27 +1,21 @@ import { DEFAULT_SESSION_PARAMS } from './shared/constants' -import { testMWSiwe } from './shared/fixtures/w3m-wallet-fixture' +import { testWallet as test } from './shared/fixtures/wallet-fixture' -testMWSiwe.beforeEach(async ({ modalPage, walletPage }) => { +test.beforeEach(async ({ modalPage, walletPage }) => { await modalPage.copyConnectUriToClipboard() await walletPage.connect() await walletPage.handleSessionProposal(DEFAULT_SESSION_PARAMS) }) -testMWSiwe.afterEach(async ({ modalValidator, walletValidator }) => { +test.afterEach(async ({ modalValidator, walletValidator }) => { await modalValidator.expectDisconnected() await walletValidator.expectDisconnected() }) -testMWSiwe( - 'it should sign in with ethereum', - async ({ modalPage, walletPage, modalValidator, walletValidator }) => { - await modalPage.promptSiwe() - await modalPage.promptSiwe() - await walletValidator.expectReceivedSign({}) - await walletPage.handleRequest({ accept: true }) - while (true) { - await modalPage.subscribe(0) - await modalPage.unsubscribe() - } - } -) +test('it should subscribe and unsubscribe', async ({ modalPage, walletPage, walletValidator }) => { + await modalPage.promptSiwe() + await walletValidator.expectReceivedSign({}) + await walletPage.handleRequest({ accept: true }) + await modalPage.subscribe(0) + await modalPage.unsubscribe() +})