From 663184c537ffc484b160c335b6d33285caca008c Mon Sep 17 00:00:00 2001 From: Derek Date: Thu, 18 Jan 2024 08:12:37 -0500 Subject: [PATCH] feat(ui-tests): scaffolds basic test (#388) --- .github/workflows/playwright.yml | 29 ------- .github/workflows/ui_test.yml | 44 +++++++++++ package.json | 4 +- playwright.config.ts | 41 ++-------- src/Modals.tsx | 3 +- .../PrivacySettings/PrivacySettings.scss | 27 ------- .../settings/PrivacySettings/index.tsx | 79 ------------------- src/routes.tsx | 1 - src/utils/env.ts | 1 + src/utils/sentry.ts | 2 +- tests/README.md | 20 +++++ tests/shared/constants/index.ts | 10 +++ tests/shared/fixtures/fixture.ts | 24 ++++++ tests/shared/fixtures/wallet-fixture.ts | 28 +++++++ tests/shared/pages/DeviceRegistrationPage.ts | 16 ++++ tests/shared/pages/InboxPage.ts | 68 ++++++++++++++++ tests/shared/pages/WalletPage.ts | 58 ++++++++++++++ tests/shared/types/index.ts | 5 ++ tests/shared/validators/ModalValidator.ts | 38 +++++++++ tests/shared/validators/WalletValidator.ts | 25 ++++++ tests/subscribe.spec.ts | 36 +++++++++ 21 files changed, 385 insertions(+), 174 deletions(-) delete mode 100644 .github/workflows/playwright.yml create mode 100644 .github/workflows/ui_test.yml delete mode 100644 src/components/settings/PrivacySettings/PrivacySettings.scss delete mode 100644 src/components/settings/PrivacySettings/index.tsx create mode 100644 src/utils/env.ts create mode 100644 tests/README.md create mode 100644 tests/shared/constants/index.ts create mode 100644 tests/shared/fixtures/fixture.ts create mode 100644 tests/shared/fixtures/wallet-fixture.ts create mode 100644 tests/shared/pages/DeviceRegistrationPage.ts create mode 100644 tests/shared/pages/InboxPage.ts create mode 100644 tests/shared/pages/WalletPage.ts create mode 100644 tests/shared/types/index.ts create mode 100644 tests/shared/validators/ModalValidator.ts create mode 100644 tests/shared/validators/WalletValidator.ts create mode 100644 tests/subscribe.spec.ts diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml deleted file mode 100644 index e18764d9..00000000 --- a/.github/workflows/playwright.yml +++ /dev/null @@ -1,29 +0,0 @@ -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/.github/workflows/ui_test.yml b/.github/workflows/ui_test.yml new file mode 100644 index 00000000..da8f8501 --- /dev/null +++ b/.github/workflows/ui_test.yml @@ -0,0 +1,44 @@ +name: Playwright Tests +on: + push: + branches: [main] + pull_request: + branches: [main] +concurrency: + # Support push/pr as event types with different behaviors each: + # 1. push: queue up builds + # 2. pr: only allow one run per PR + group: ${{ github.workflow }}-${{ github.event.type }}${{ github.event.pull_request.number }} + # If there is already a workflow running for the same pull request, cancel it + cancel-in-progress: ${{ github.event_name == 'pull_request' }} +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 }} + VITE_EXPLORER_API_URL: ${{ secrets.VITE_EXPLORER_API_URL }} + VITE_CI: true + - uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 7 + - uses: actions/upload-artifact@v3 + if: always() + with: + name: test-results + path: test-results/ + retention-days: 7 diff --git a/package.json b/package.json index 152a559f..bd8be5ef 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,9 @@ "prettier": "prettier --check '**/*.{js,ts,jsx,tsx,scss}'", "prettier:write": "prettier --write '**/*.{js,ts,jsx,tsx,scss}'", "playwright:install": "playwright install --with-deps", - "playwright:test": "playwright test" + "playwright:test": "playwright test", + "playwright:start": "VITE_CI=true yarn dev", + "playwright:debug": "playwright test --debug" }, "dependencies": { "@sentry/react": "^7.93.0", diff --git a/playwright.config.ts b/playwright.config.ts index 121b6501..901a8c2a 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,11 +1,5 @@ import { defineConfig, devices } from '@playwright/test' -/** - * Read environment variables from file. - * https://github.com/motdotla/dotenv - */ -// require('dotenv').config(); - const baseURL = 'http://localhost:5173' /** @@ -18,20 +12,19 @@ export default defineConfig({ /* 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, + retries: 0, + /* Parallel tests currently blocked. */ + workers: 1, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: '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, - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', - - screenshot: 'only-on-failure' + screenshot: 'only-on-failure', + video: 'retain-on-failure' }, /* Configure projects for major browsers */ @@ -40,41 +33,19 @@ export default defineConfig({ name: 'chromium', use: { ...devices['Desktop Chrome'] } }, - { name: 'firefox', use: { ...devices['Desktop Firefox'] } }, - { name: 'webkit', use: { ...devices['Desktop Safari'] } } - - /* Test against mobile viewports. */ - // { - // name: 'Mobile Chrome', - // use: { ...devices['Pixel 5'] }, - // }, - // { - // name: 'Mobile Safari', - // use: { ...devices['iPhone 12'] }, - // }, - - /* Test against branded browsers. */ - // { - // name: 'Microsoft Edge', - // use: { ...devices['Desktop Edge'], channel: 'msedge' }, - // }, - // { - // name: 'Google Chrome', - // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, - // }, ], /* Run your local dev server before starting the tests */ webServer: { - command: 'yarn dev', + command: 'yarn playwright:start', url: baseURL, reuseExistingServer: !process.env.CI } diff --git a/src/Modals.tsx b/src/Modals.tsx index 6dc6c529..e49fc2be 100644 --- a/src/Modals.tsx +++ b/src/Modals.tsx @@ -10,6 +10,7 @@ import NotificationPwaModal from '@/components/utils/NotificationPwaModal' import PwaModal from '@/components/utils/PwaModal' import W3iContext from '@/contexts/W3iContext/context' import { SignatureModal } from '@/pages/Login/SignatureModal' +import { isCI } from '@/utils/env' import { useModals } from '@/utils/hooks' import { useNotificationPermissionState } from '@/utils/hooks/notificationHooks' import { @@ -96,7 +97,7 @@ export const Modals = () => { {shouldShowPWAModal && } - {isNotificationPwaModalOpen && } + {!isCI && isNotificationPwaModalOpen && } {shouldShowChangeBrowserModal && } diff --git a/src/components/settings/PrivacySettings/PrivacySettings.scss b/src/components/settings/PrivacySettings/PrivacySettings.scss deleted file mode 100644 index 2b4171ef..00000000 --- a/src/components/settings/PrivacySettings/PrivacySettings.scss +++ /dev/null @@ -1,27 +0,0 @@ -.PrivacySettings { - &__wrapper { - display: flex; - flex-direction: column; - - .SettingsItem__content { - width: 100%; - } - } - - &__radios { - padding: 0.625rem 1.75rem; - display: flex; - flex-direction: column; - gap: 1.25rem; - - @media only screen and (max-width: 768px) { - padding: 0.625rem 0rem; - } - - &__subtitle { - display: block; - color: #788181; - width: 75%; - } - } -} diff --git a/src/components/settings/PrivacySettings/index.tsx b/src/components/settings/PrivacySettings/index.tsx deleted file mode 100644 index e183df40..00000000 --- a/src/components/settings/PrivacySettings/index.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import React from 'react' - -import MessageCheckmarkIcon from '@/components/general/Icon/MessageCheckmarkIcon' -import ProfileCheckIcon from '@/components/general/Icon/ProfileCheckIcon' -import ProfileDeclineIcon from '@/components/general/Icon/ProfileDeclineIcon' -import ProfileLoadingIcon from '@/components/general/Icon/ProfileLoadingIcon' -import Radio from '@/components/general/Radio' -import Text from '@/components/general/Text' -import MobileHeader from '@/components/layout/MobileHeader' - -import SettingsHeader from '../SettingsHeader' -import SettingsItem from '../SettingsItem' -import SettingsToggle from '../SettingsToggle/Index' - -import './PrivacySettings.scss' - -const radios = [ - { - id: 'require-invite', - label: 'Require new contacts to send me a chat invite', - description: 'You will get notified about new contact requests and can accept or deny them.', - icon: - }, - { - id: 'reject-new', - label: 'Decline all chat invites from new contacts', - description: 'New contacts will be added immediately and can send you messages right away.', - icon: - }, - { - id: 'accept-new', - label: 'Accept all chat invites from new contacts', - description: 'Only you can invite others. Choose this option if you receive too many requests.', - icon: - } -] - -const PrivacySettings: React.FC = () => { - return ( -
- - -
- - {/* } - title="Send read receipts" - subtitle="You must enable read receipts to see when others have read your messages." - active={true} - /> */} - - -
- {radios.map(({ id, label, icon, description }) => ( - {}} - /> - ))} -
-
-
-
- ) -} - -export default PrivacySettings diff --git a/src/routes.tsx b/src/routes.tsx index b1de83c1..f1b6f0a7 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -45,7 +45,6 @@ const ConfiguredRoutes: React.FC = () => { } /> } /> } /> - {/* } /> */} ) : null} diff --git a/src/utils/env.ts b/src/utils/env.ts new file mode 100644 index 00000000..3ba58151 --- /dev/null +++ b/src/utils/env.ts @@ -0,0 +1 @@ +export const isCI = import.meta.env.VITE_CI === 'true' diff --git a/src/utils/sentry.ts b/src/utils/sentry.ts index 220ae68f..ca88abf7 100644 --- a/src/utils/sentry.ts +++ b/src/utils/sentry.ts @@ -7,7 +7,7 @@ export const initSentry = () => { new Sentry.BrowserTracing({ tracePropagationTargets: ['https://web3inbox-dev-hidden.vercel.app'] }), - new Sentry.Replay() + new Sentry.Replay(), ], tracesSampleRate: 0.2, replaysSessionSampleRate: 0.1, diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 00000000..dc5bae11 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,20 @@ +# Functional Tests + +We use Playwright as our functional test runner. It's configured to try multiple permutations: + +- Browsers: Chromium/Firefox + +## Running Tests + +- `npx playwright test` to run in default mode (make sure your `.env` is set up) +- `npm run playwright:debug` to step by step see what the tests are doing + +## Debugging + +For scenarios when tests pass locally but not remotely you can `await this.page.screenshot({ path: './screenshots/wallet.png' })` and the screenshot will be uploaded to GitHub Actions. + +## Running from GitHub Actions + +These tests can be run from GitHub Actions both from this and other repositories. + +You can tweak what's under test by setting the `BASE_URL` and `WALLET_URL` (default 'https://react-wallet.walletconnect.com/') environment variables. diff --git a/tests/shared/constants/index.ts b/tests/shared/constants/index.ts new file mode 100644 index 00000000..27c19328 --- /dev/null +++ b/tests/shared/constants/index.ts @@ -0,0 +1,10 @@ +import type { SessionParams } from '../types' + +// Allow localhost +export const BASE_URL = process.env['BASE_URL'] || 'http://localhost:5173/' +export const WALLET_URL = process.env['WALLET_URL'] || 'https://react-wallet.walletconnect.com/' +export const DEFAULT_SESSION_PARAMS: SessionParams = { + reqAccounts: ['1', '2'], + optAccounts: ['1', '2'], + accept: true +} diff --git a/tests/shared/fixtures/fixture.ts b/tests/shared/fixtures/fixture.ts new file mode 100644 index 00000000..08f28131 --- /dev/null +++ b/tests/shared/fixtures/fixture.ts @@ -0,0 +1,24 @@ +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({ + modalPage: async ({ page }, 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/wallet-fixture.ts b/tests/shared/fixtures/wallet-fixture.ts new file mode 100644 index 00000000..885be665 --- /dev/null +++ b/tests/shared/fixtures/wallet-fixture.ts @@ -0,0 +1,28 @@ +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 { + walletPage: WalletPage + walletValidator: WalletValidator +} + +export const testWallet = 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 { expect } from '@playwright/test' diff --git a/tests/shared/pages/DeviceRegistrationPage.ts b/tests/shared/pages/DeviceRegistrationPage.ts new file mode 100644 index 00000000..6afd0b48 --- /dev/null +++ b/tests/shared/pages/DeviceRegistrationPage.ts @@ -0,0 +1,16 @@ +import type { Page } from '@playwright/test' + +export class DeviceRegistrationPage { + constructor( + public readonly page: Page, + public readonly url: string + ) {} + + async load() { + await this.page.goto(this.url) + } + + async approveDevice() { + await this.page.getByRole('button', { name: 'Approve' }).click() + } +} diff --git a/tests/shared/pages/InboxPage.ts b/tests/shared/pages/InboxPage.ts new file mode 100644 index 00000000..fbff74f3 --- /dev/null +++ b/tests/shared/pages/InboxPage.ts @@ -0,0 +1,68 @@ +import { type Locator, type Page, expect } 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 rejectNotifications() { + // Allow for the modal to pop up + await this.page.waitForTimeout(4000) + const isVisible = (await this.page.locator('.NotificationPwaModal__close-button').count()) > 0 + if (!isVisible) return + await this.page.locator('.NotificationPwaModal__close-button').first().click() + } + + async subscribe(nth: number) { + await this.page.locator('.AppCard__body > .AppCard__body__subscribe').nth(nth).click() + await this.page.getByText('Subscribed to', { exact: false }).isVisible() + } + + async unsubscribe(nth: number) { + await this.page.getByRole('button', { name: 'Subscribed' }).nth(nth).click() + await this.page.getByRole('button', { name: 'Subscribed' }).nth(nth).isHidden() + await this.page.locator('.AppNotificationsHeader__wrapper > .Dropdown').click() + await this.page.getByRole('button', { name: 'Unsubscribe' }).click() + await this.page.getByRole('button', { name: 'Unsubscribe' }).nth(1).click() + await this.page.getByText('Unsubscribed from', { exact: false }).isVisible() + 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/WalletPage.ts b/tests/shared/pages/WalletPage.ts new file mode 100644 index 00000000..ba1c0360 --- /dev/null +++ b/tests/shared/pages/WalletPage.ts @@ -0,0 +1,58 @@ +/* eslint-disable no-await-in-loop */ +import type { Locator, Page } from '@playwright/test' + +import { WALLET_URL } from '../constants' +import type { SessionParams } from '../types' + +export class WalletPage { + private readonly baseURL = WALLET_URL + + private readonly gotoHome: Locator + private readonly vercelPreview: Locator + + constructor(public readonly page: Page) { + this.gotoHome = this.page.getByTestId('wc-connect') + this.vercelPreview = this.page.locator('css=vercel-live-feedback') + } + + async load() { + await this.page.goto(this.baseURL) + } + + async connect() { + 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-connect-button').click() + } + + /** + * Handle a session proposal event in the wallet + * @param reqAccounts - required account numbers to select ex/ ['1', '2'] + * @param optAccounts - optional account numbers to select ex/ ['1', '2'] + * @param accept - accept or reject the session + */ + 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() + 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() + await this.page.keyboard.press('Space') + } +} diff --git a/tests/shared/types/index.ts b/tests/shared/types/index.ts new file mode 100644 index 00000000..d9188ff7 --- /dev/null +++ b/tests/shared/types/index.ts @@ -0,0 +1,5 @@ +export interface SessionParams { + reqAccounts: string[] + optAccounts: string[] + accept: boolean +} diff --git a/tests/shared/validators/ModalValidator.ts b/tests/shared/validators/ModalValidator.ts new file mode 100644 index 00000000..6405b8bc --- /dev/null +++ b/tests/shared/validators/ModalValidator.ts @@ -0,0 +1,38 @@ +import { expect } from '@playwright/test' +import type { Page } from '@playwright/test' + +export class ModalValidator { + constructor(public readonly page: Page) {} + + async expectConnected() { + await expect(this.page.getByTestId('account-button')).toBeVisible() + } + + async expectAuthenticated() { + await expect(this.page.getByTestId('w3m-authentication-status')).toContainText('authenticated') + } + + async expectUnauthenticated() { + await expect(this.page.getByTestId('w3m-authentication-status')).toContainText( + 'unauthenticated' + ) + } + + async expectSignatureDeclined() { + await expect(this.page.getByText('Signature declined')).toBeVisible() + } + + async expectDisconnected() { + await expect(this.page.getByTestId('account-button')).not.toBeVisible() + } + + 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('abc')).toBeVisible() + } + + async expectRejectedSign() { + // 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('abc')).toBeVisible() + } +} diff --git a/tests/shared/validators/WalletValidator.ts b/tests/shared/validators/WalletValidator.ts new file mode 100644 index 00000000..2f7a0305 --- /dev/null +++ b/tests/shared/validators/WalletValidator.ts @@ -0,0 +1,25 @@ +import { expect } from '@playwright/test' +import type { Locator, Page } from '@playwright/test' + +export class WalletValidator { + private readonly gotoSessions: Locator + + constructor(public readonly page: Page) { + this.gotoSessions = this.page.getByTestId('sessions') + } + + async expectConnected() { + await this.gotoSessions.click() + await expect(this.page.getByTestId('session-card')).toBeVisible() + } + + async expectDisconnected() { + await this.gotoSessions.click() + await expect(this.page.getByTestId('session-card')).not.toBeVisible() + } + + async expectReceivedSign({ chainName = 'Ethereum' }) { + await expect(this.page.getByTestId('session-approve-button')).toBeVisible() + await expect(this.page.getByTestId('request-details-chain')).toHaveText(chainName) + } +} diff --git a/tests/subscribe.spec.ts b/tests/subscribe.spec.ts new file mode 100644 index 00000000..f0c9097e --- /dev/null +++ b/tests/subscribe.spec.ts @@ -0,0 +1,36 @@ +import { DEFAULT_SESSION_PARAMS } from './shared/constants' +import { testWallet as test } from './shared/fixtures/wallet-fixture' + +test.beforeEach(async ({ modalPage, walletPage, browserName }) => { + if (browserName === 'webkit') { + // Clipboard doesn't work here. Remove this when we moved away from Clipboard in favor of links + test.skip() + } + test.skip() + await modalPage.copyConnectUriToClipboard() + await walletPage.connect() + await walletPage.handleSessionProposal(DEFAULT_SESSION_PARAMS) +}) + +test.afterEach(async ({ modalValidator, walletValidator }) => { + await modalValidator.expectDisconnected() + await walletValidator.expectDisconnected() +}) + +test('it should subscribe and unsubscribe', async ({ + modalPage, + walletPage, + walletValidator, + browserName +}) => { + if (browserName === 'webkit') { + // Clipboard doesn't work here. Remove this when we moved away from Clipboard in favor of links + test.skip() + } + await modalPage.promptSiwe() + await walletValidator.expectReceivedSign({}) + await walletPage.handleRequest({ accept: true }) + await modalPage.rejectNotifications() + await modalPage.subscribe(0) + await modalPage.unsubscribe(0) +})