From 84868efe7acc6e301ea2f57efde919763fba33f8 Mon Sep 17 00:00:00 2001 From: emiride Date: Wed, 13 Nov 2024 09:54:52 +0100 Subject: [PATCH] test(cat-voices): playwright refactoring --- .config/dictionaries/project.dic | 2 + .../wallet-automation/Earthfile | 14 +- .../wallet-automation/README.md | 47 ++-- .../wallet-automation/nginx.conf | 40 +++ .../wallet-automation/pages/homePage.ts | 23 +- .../wallet-automation/pages/modal.ts | 19 +- .../wallet-automation/pages/walletListPage.ts | 18 ++ .../wallet-automation/playwright.config.ts | 39 +-- .../wallet-automation/setup.ts | 70 ++++++ .../wallet-automation/test-fixtures.ts | 78 +++--- .../wallet-automation/tests/wallets.spec.ts | 236 +++++++++++------- .../utils/extensionDownloader.ts | 94 +++---- .../wallet-automation/utils/extensions.ts | 42 ++-- .../wallet-automation/utils/walletConfigs.ts | 26 ++ .../utils/wallets/eternlUtils.ts | 28 +++ .../utils/wallets/laceUtils.ts | 3 +- .../utils/wallets/walletUtils.ts | 11 + 17 files changed, 536 insertions(+), 254 deletions(-) create mode 100644 catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/nginx.conf create mode 100644 catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/pages/walletListPage.ts create mode 100644 catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/setup.ts create mode 100644 catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/utils/wallets/eternlUtils.ts diff --git a/.config/dictionaries/project.dic b/.config/dictionaries/project.dic index a61c56ea5a4..0625b217959 100644 --- a/.config/dictionaries/project.dic +++ b/.config/dictionaries/project.dic @@ -273,6 +273,7 @@ tablestats tacho testcov testdocs +testid testplan testunit thiserror @@ -282,6 +283,7 @@ Toastify todos toggleable tojunit +tomjs Traceback traefik trailings diff --git a/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/Earthfile b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/Earthfile index 9bb534a260b..abfda491cbb 100644 --- a/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/Earthfile +++ b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/Earthfile @@ -18,11 +18,11 @@ src: COPY --dir utils ./utils COPY playwright.config.ts . COPY test-fixtures.ts . + COPY setup.ts . package-test: FROM +src ENV APP_URL http://test-app:80 - ENV APP_URL http://test-app:80 RUN mkdir /results VOLUME /results ENTRYPOINT ["/bin/sh", "-c", "/usr/bin/xvfb-run --auto-servernum npm test", ""] @@ -31,19 +31,21 @@ package-test: builder: DO flutter-ci+SETUP - COPY ../../../../+repo-catalyst-voices-all/repo . + COPY ../../../../../+repo-catalyst-voices/repo . DO flutter-ci+BOOTSTRAP build-web: FROM +builder - ARG WORKDIR=/frontend/catalyst_voices_packages/catalyst_cardano/catalyst_cardano/example + ARG WORKDIR=/frontend/packages/libs/catalyst_cardano/catalyst_cardano/example DO flutter-ci+BUILD_WEB --TARGET=lib/main.dart --WORKDIR=$WORKDIR SAVE ARTIFACT web package-app: FROM nginx:alpine3.20-slim ARG tag='latest' - COPY +build-web/web /usr/share/nginx/html/ + COPY +build-web/web /app + COPY ./nginx.conf /etc/nginx/nginx.conf + EXPOSE 80 SAVE IMAGE test-app:$tag nightly-test: @@ -55,10 +57,8 @@ nightly-test: --load test:latest=(+package-test) RUN docker run --network=default_default --name=test test:latest ; \ - docker cp test:/results/cardano-wallet.junit-report.xml cardano-wallet.junit-report.xml ; \ - docker cp test:/results/ /playwright-report + docker cp test:/results/cardano-wallet.junit-report.xml cardano-wallet.junit-report.xml ; END WAIT SAVE ARTIFACT cardano-wallet.junit-report.xml AS LOCAL cardano-wallet.junit-report.xml - SAVE ARTIFACT ./playwright-report/* AS LOCAL ./playwright-report/ END \ No newline at end of file diff --git a/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/README.md b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/README.md index 56b4627a324..e72dcf9dc98 100644 --- a/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/README.md +++ b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/README.md @@ -1,44 +1,49 @@ +# Wallet Automation + +Welcome to wallet automation, a testing package in Playwright that tests wallet integration for Catalyst Voices. + ## Introduction -Wallet automation is a testing package in Playwright that automates the wallet creation process for the Catalyst project. +Wallet automation is a testing package in Playwright that automates the wallet creation process for the Catalyst project. It is a part of the Catalyst Voices ecosystem. ## Getting Started 1. Clone this repository: -```sh -git clone -cd catalyst-voices -``` + ```sh + git clone + cd catalyst-voices + ``` 2. Install Flutter and Dart: -```sh -brew install flutter -``` + ```sh + brew install flutter + ``` 3. Bootstrap the project: -```sh -melos bootstrap -``` + ```sh + melos bootstrap + ``` 4. Execute earthly command from this directory: -```sh -earthly +package-app -``` + ```sh + earthly +package-app + ``` 5. Use docker compose to run the app: -```sh -docker compose up -``` -The app should be running on `localhost:8000`. + ```sh + docker compose up + ``` + + The app should be running on `localhost:8000`. 6. You can now run tests with the following command: -```sh -npx playwright test -``` \ No newline at end of file + ```sh + npx playwright test + ``` diff --git a/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/nginx.conf b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/nginx.conf new file mode 100644 index 00000000000..7fcc05e6bbd --- /dev/null +++ b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/nginx.conf @@ -0,0 +1,40 @@ +user nginx; +worker_processes 1; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; +events { + worker_connections 1024; +} +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + access_log /var/log/nginx/access.log main; + sendfile on; + keepalive_timeout 65; + server { + listen 80; + server_name localhost; + + # https://cjycode.com/flutter_rust_bridge/manual/miscellaneous/web-cross-origin#background + add_header Cross-Origin-Opener-Policy "same-origin"; + add_header Cross-Origin-Embedder-Policy "require-corp"; + + location / { + root /app; + index index.html; + try_files $uri $uri/ /index.html; + } + # Ensure that /m4 (and any other SPA path) serves index.html + location /m4 { + root /app; + try_files $uri $uri/ /index.html; + } + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } + } +} \ No newline at end of file diff --git a/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/pages/homePage.ts b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/pages/homePage.ts index f1afa2208c9..3f08cdb7738 100644 --- a/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/pages/homePage.ts +++ b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/pages/homePage.ts @@ -1,6 +1,6 @@ import { Page, Locator, expect } from '@playwright/test'; -import { BrowserExtensionName } from '../utils/extensions'; import { Modal, ModalName } from './modal'; +import { WalletConfig } from '../utils/wallets/walletUtils'; export interface UTxO { tx: string; @@ -10,7 +10,7 @@ export interface UTxO { export interface WalletCipData { balance: number; - extensions: string; + extensions: string[]; networkId: string; changeAddress: string; rewardAddresses: string[]; @@ -61,7 +61,7 @@ export class HomePage { async getWalletCipData() { const walletCipData: WalletCipData = { balance: 0, - extensions: '', + extensions: [], networkId: '', changeAddress: '', rewardAddresses: [], @@ -106,7 +106,7 @@ export class HomePage { } } - async getExtensions(): Promise { + async getExtensions(): Promise { const isVisible = await this.extensionsLabel.isVisible(); if (!isVisible) { throw new Error('Extensions label is not visible'); @@ -114,7 +114,8 @@ export class HomePage { const extensionsText = await this.extensionsLabel.textContent(); const match = extensionsText?.trim().match(/^Extensions:\s*(.+)$/); if (match && match[1]) { - return match[1].trim(); + const trimmedText = match[1].trim(); + return trimmedText.split(',').map(ext => ext.trim()).filter(ext => ext.length > 0); } else { throw new Error(`Unable to extract extensions from text: ${extensionsText}`); } @@ -272,9 +273,9 @@ export class HomePage { } } - async assertBasicWalletCipData(actualWalletCipData: WalletCipData, extensionName: BrowserExtensionName) { + async assertBasicWalletCipData(actualWalletCipData: WalletCipData, walletConfig: WalletConfig) { expect(actualWalletCipData.balance).toBeGreaterThan(500000000); - expect(actualWalletCipData.extensions).toBe(extensionName === BrowserExtensionName.Typhon ? 'cip-30' : 'cip-95'); + await this.assertExtensions(actualWalletCipData.extensions, walletConfig.cipBridge); expect(actualWalletCipData.networkId).not.toBeNaN(); expect(actualWalletCipData.changeAddress).not.toBeNaN(); expect(actualWalletCipData.rewardAddresses.length).toBeGreaterThan(0); @@ -283,6 +284,12 @@ export class HomePage { expect(actualWalletCipData.utxos.length).toBeGreaterThan(0); expect(actualWalletCipData.publicDRepKey).not.toBeNaN(); expect(actualWalletCipData.registeredPublicStakeKeys).not.toBeNaN(); - + } + + //Check if expected extensions are present in actual extensions + async assertExtensions(actualExtensions: string[], expectedExtensions: string[]) { + for (const ext of expectedExtensions) { + expect(actualExtensions).toContain(ext); + } } } diff --git a/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/pages/modal.ts b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/pages/modal.ts index 05b2c4220e8..09545502963 100644 --- a/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/pages/modal.ts +++ b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/pages/modal.ts @@ -4,6 +4,9 @@ export enum ModalName { SignData = 'SignData', SignAndSubmitTx = 'SignAndSubmitTx', SignAndSubmitRBACTx = 'SignAndSubmitRBACTx', + SignDataUserDeclined = 'UserDeclined', + SignTxUserDeclined = 'SignTxUserDeclined', + SignRBACTxUserDeclined = 'SignRBACTxUserDeclined', } export interface ModalContent { @@ -24,6 +27,18 @@ export const modalContents: { [key in ModalName]: ModalContent } = { header: 'Sign & submit RBAC tx', unchangingText: 'Tx hash:', }, + [ModalName.SignDataUserDeclined]: { + header: 'Sign data', + unchangingText: 'user declined sign data', + }, + [ModalName.SignTxUserDeclined]: { + header: 'Sign & submit tx', + unchangingText: 'user declined sign tx', + }, + [ModalName.SignRBACTxUserDeclined]: { + header: 'Sign & submit RBAC tx', + unchangingText: 'user declined sign tx', + }, }; export class Modal { @@ -35,8 +50,8 @@ export class Modal { constructor(page: Page, modalName: ModalName) { this.page = page; this.content = modalContents[modalName]; - this.modalHeader = this.page.getByText(this.content.header) - this.modalBody = this.page.getByText(this.content.unchangingText) + this.modalHeader = this.page.getByText(this.content.header, { exact: true }); + this.modalBody = this.page.getByText(this.content.unchangingText) } async assertModalIsVisible() { diff --git a/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/pages/walletListPage.ts b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/pages/walletListPage.ts new file mode 100644 index 00000000000..cb3df5087e5 --- /dev/null +++ b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/pages/walletListPage.ts @@ -0,0 +1,18 @@ +import { Locator, Page } from '@playwright/test'; +import { BrowserExtensionName } from '../utils/extensions'; + + +export class WalletListPage { + readonly page: Page; + + constructor(page: Page) { + this.page = page; + } + async clickEnableWallet(walletName: BrowserExtensionName): Promise { + const enableButton = (walletName: BrowserExtensionName) => this.page.locator( + `flt-semantics:has(flt-semantics-img[aria-label*="Name: ${walletName.toLowerCase()}"]) ` + + `flt-semantics[role="button"]:has-text("Enable wallet")` + ); + await enableButton(walletName).click(); + } +} \ No newline at end of file diff --git a/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/playwright.config.ts b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/playwright.config.ts index 02a828eaf1d..6571ff0b0d6 100644 --- a/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/playwright.config.ts +++ b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/playwright.config.ts @@ -1,30 +1,37 @@ -import { defineConfig, devices } from '@playwright/test'; -import dotenv from 'dotenv'; -import path from 'path'; +import { defineConfig, devices } from "@playwright/test"; +import dotenv from "dotenv"; +import path from "path"; -dotenv.config({ path: path.resolve(__dirname, '.env') }); +dotenv.config({ path: path.resolve(__dirname, ".env") }); export default defineConfig({ - testDir: './tests', + testDir: "./tests", fullyParallel: false, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 1, workers: process.env.CI ? 1 : 1, use: { - baseURL: process.env.APP_URL || 'http://localhost:8000', - screenshot: 'only-on-failure', - trace: 'on', - video: 'on', - + baseURL: process.env.APP_URL || "http://localhost:8000", + ignoreHTTPSErrors: true, + screenshot: "only-on-failure", + trace: "on", + video: "on", }, reporter: [ - ['html', { open: 'never', outputFolder: '/results' }], - ['junit', { outputFile: '/results/cardano-wallet.junit-report.xml' }]], - timeout: 120 * 1000, + ["junit", { outputFile: "/results/cardano-wallet.junit-report.xml" }], + ], + timeout: 60 * 1000, projects: [ { - name: 'chromium', - use: { ...devices['Desktop Chrome'] }, + name: "chromium", + use: { + ...devices["Desktop Chrome"], + launchOptions: { + args: [ + "--unsafely-treat-insecure-origin-as-secure=http://test-app:80", + ], + }, + }, }, - ] + ], }); diff --git a/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/setup.ts b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/setup.ts new file mode 100644 index 00000000000..841cc05d0f8 --- /dev/null +++ b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/setup.ts @@ -0,0 +1,70 @@ +import { BrowserContext, chromium, Page } from "@playwright/test"; +import { ExtensionDownloader } from "./utils/extensionDownloader"; +import { BrowserExtensionName } from "./utils/extensions"; +import { + allowExtension, + onboardWallet, + WalletConfig, +} from "./utils/wallets/walletUtils"; + +const installExtension = async (extensionName: BrowserExtensionName) => { + const extensionPath = await new ExtensionDownloader().getExtension( + extensionName + ); + const browser = await chromium.launchPersistentContext("", { + headless: false, // extensions only work in headful mode + ignoreHTTPSErrors: true, + args: [ + `--disable-extensions-except=${extensionPath}`, + `--load-extension=${extensionPath}`, + "--unsafely-treat-insecure-origin-as-secure=http://test-app:80", + ], + }); + let [background] = browser.serviceWorkers(); + if (!background) background = await browser.waitForEvent("serviceworker"); + return browser; +}; + +export const restoreWallet = async (walletConfig: WalletConfig) => { + const browser = await installExtension(walletConfig.extension.Name); + const extensionTab = browser.pages()[0]; + walletConfig.extension.HomeUrl = await getDynamicUrlInChrome( + extensionTab, + walletConfig + ); + await extensionTab.goto(walletConfig.extension.HomeUrl); + await onboardWallet(extensionTab, walletConfig); + return browser; +}; + +export const enableWallet = async ( + walletConfig: WalletConfig, + browser: BrowserContext +) => { + const page = browser.pages()[0]; + await page.reload(); + await page.goto("/"); + await page.waitForTimeout(4000); + const [walletPopup] = await Promise.all([ + browser.waitForEvent("page"), + page.locator('//*[text()="Enable wallet"]').click(), + ]); + await walletPopup.waitForTimeout(2000); + await allowExtension(walletPopup, walletConfig.extension.Name); + await page.waitForTimeout(2000); + return browser; +}; + +/** + * We need this because some extensions have dynamic URLs + **/ +const getDynamicUrlInChrome = async ( + extensionTab: Page, + walletConfig: WalletConfig +): Promise => { + await extensionTab.goto("chrome://extensions/"); + const extensionId = await extensionTab + .locator("extensions-item") + .getAttribute("id"); + return `chrome-extension://${extensionId}/${walletConfig.extension.HomeUrl}`; +}; diff --git a/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/test-fixtures.ts b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/test-fixtures.ts index 60dfaea138e..5fa53d8361d 100644 --- a/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/test-fixtures.ts +++ b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/test-fixtures.ts @@ -1,54 +1,32 @@ -import { test as base, BrowserContext, chromium, Page } from '@playwright/test'; -import { allowExtension, onboardWallet, WalletConfig } from './utils/wallets/walletUtils'; -import { BrowserExtensionName } from './utils/extensions'; -import { ExtensionDownloader } from './utils/extensionDownloader'; -import { HomePage } from './pages/homePage'; +// import { test as base, BrowserContext, chromium, Page } from '@playwright/test'; +// import { HomePage } from './pages/homePage'; +// import { WalletListPage } from './pages/walletListPage'; -type MyFixtures = { - enableWallet: (walletConfig: WalletConfig) => Promise; - installExtension: (extensionName: BrowserExtensionName) => Promise; -}; -export const test = base.extend({ - enableWallet: async ({ installExtension }, use) => { - let browser: BrowserContext | null = null; +// // import { allowExtension, onboardWallet, WalletConfig } from './utils/wallets/walletUtils'; - const enableWalletFn = async (walletConfig: WalletConfig) => { - browser = await installExtension(walletConfig.extension.Name); - const extensionTab = browser.pages()[0]; - await extensionTab.goto(walletConfig.extension.HomeUrl); - await onboardWallet(extensionTab, walletConfig); - await extensionTab.goto('/'); - await extensionTab.waitForTimeout(4000); - const [walletPopup] = await Promise.all([ - browser.waitForEvent('page'), - extensionTab.locator('//*[text()="Enable wallet"]').click(), - ]); - await walletPopup.waitForTimeout(2000); - await allowExtension(walletPopup, walletConfig.extension.Name); - await extensionTab.waitForTimeout(2000); - //await new HomePage(extensionTab).balanceLabel.waitFor({ state: 'visible' }); - return browser; - }; - - // Provide the function to the test - await use(enableWalletFn); - }, +// // type MyFixtures = { +// // enableWallet: (walletConfig: WalletConfig, browser: BrowserContext) => Promise; +// // }; - installExtension: async ({ }, use) => { - await use(async (extensionName: BrowserExtensionName) => { - const extensionPath = await new ExtensionDownloader().getExtension(extensionName); - const browser = await chromium.launchPersistentContext('', { - headless: false, // extensions only work in headfull mode - args: [ - `--disable-extensions-except=${extensionPath}`, - `--load-extension=${extensionPath}`, - ], - }); - let [background] = browser.serviceWorkers(); - if (!background) - background = await browser.waitForEvent('serviceworker'); - return browser; - }); - } -}); \ No newline at end of file +// // export const test = base.extend({ +// // enableWallet: async ({ }, use) => { +// // const enableWalletFn = async (walletConfig: WalletConfig, browser: BrowserContext) => { +// // const page = browser.pages()[0]; +// // await page.reload(); +// // await page.goto('/'); +// // await page.waitForTimeout(4000); +// // const [walletPopup] = await Promise.all([ +// // browser.waitForEvent('page'), +// // page.locator('//*[text()="Enable wallet"]').click(), +// // ]); +// // await walletPopup.waitForTimeout(2000); +// // await allowExtension(walletPopup, walletConfig.extension.Name); +// // await page.waitForTimeout(2000); +// // return browser; +// // }; + +// // // Provide the function to the test +// // await use(enableWalletFn); +// // }, +// // }); \ No newline at end of file diff --git a/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/tests/wallets.spec.ts b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/tests/wallets.spec.ts index c5a91dc1308..66a3b998045 100644 --- a/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/tests/wallets.spec.ts +++ b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/tests/wallets.spec.ts @@ -1,96 +1,152 @@ -import { HomePage } from '../pages/homePage'; -import { test } from '../test-fixtures'; -import { walletConfigs } from '../utils/walletConfigs'; -import { signWalletPopup } from '../utils/wallets/walletUtils'; -import { ModalName } from '../pages/modal'; -import { BrowserContext, expect } from '@playwright/test'; +import { BrowserContext, test } from "@playwright/test"; +import { HomePage } from "../pages/homePage"; +import { ModalName } from "../pages/modal"; +import { WalletListPage } from "../pages/walletListPage"; +import { enableWallet, restoreWallet } from "../setup"; +import { walletConfigs } from "../utils/walletConfigs"; +import { signWalletPopup } from "../utils/wallets/walletUtils"; -walletConfigs.forEach(( walletConfig ) => { - let browser: BrowserContext; - test.describe(`Testing with ${walletConfig.extension.Name}`, () => { - test.skip(walletConfig.extension.Name === 'Typhon', 'https://github.com/input-output-hk/catalyst-voices/issues/753'); - test.beforeAll(async ({ enableWallet }) => { - browser = await enableWallet(walletConfig); - }); - test.afterAll(async () => { - await browser.close(); - }); - test('Get wallet details for ' + walletConfig.extension.Name, async () => { - const page = browser.pages()[0]; - const homePage = new HomePage(page); - const walletCipData = await homePage.getWalletCipData(); - await homePage.assertBasicWalletCipData(walletCipData, walletConfig.extension.Name); - }); - test('Sign data with ' + walletConfig.extension.Name, async () => { - const page = browser.pages()[0]; - const homePage = new HomePage(page); - const [walletPopup] = await Promise.all([ - browser.waitForEvent('page'), - homePage.signDataButton.click() - ]); - await signWalletPopup(walletPopup, walletConfig); - await homePage.assertModal(ModalName.SignData); - }); +let browser: BrowserContext; +walletConfigs.forEach((walletConfig) => { + test.describe(`Testing with ${walletConfig.extension.Name}`, () => { + test.skip( + walletConfig.extension.Name === "Typhon", + "https://github.com/input-output-hk/catalyst-voices/issues/753" + ); + test.skip( + walletConfig.extension.Name === "Lace", + "https://github.com/input-output-hk/catalyst-voices/issues/1190" + ); + test.beforeAll(async () => { + browser = await restoreWallet(walletConfig); + await enableWallet(walletConfig, browser); + }); - test('Sign and submit tx with ' + walletConfig.extension.Name, async () => { - const page = browser.pages()[0]; - const homePage = new HomePage(page); - const [walletPopup] = await Promise.all([ - browser.waitForEvent('page'), - homePage.signAndSubmitTxButton.click() - ]); - await signWalletPopup(walletPopup, walletConfig); - await homePage.assertModal(ModalName.SignAndSubmitTx); - }); + test("Get wallet details for " + walletConfig.extension.Name, async () => { + const page = browser.pages()[0]; + await page.reload(); + await new WalletListPage(page).clickEnableWallet( + walletConfig.extension.Name + ); + const homePage = new HomePage(page); + const walletCipData = await homePage.getWalletCipData(); + await homePage.assertBasicWalletCipData(walletCipData, walletConfig); + }); - test('Sign and submit RBAC tx with ' + walletConfig.extension.Name, async () => { - const page = browser.pages()[0]; - const homePage = new HomePage(page); - const [walletPopup] = await Promise.all([ - browser.waitForEvent('page'), - homePage.signAndSubmitRBACTxButton.click() - ]); - await signWalletPopup(walletPopup, walletConfig); - await homePage.assertModal(ModalName.SignAndSubmitRBACTx); - }); + test("Sign data with " + walletConfig.extension.Name, async () => { + const page = browser.pages()[0]; + await page.reload(); + await new WalletListPage(page).clickEnableWallet( + walletConfig.extension.Name + ); + const homePage = new HomePage(page); + const [walletPopup] = await Promise.all([ + browser.waitForEvent("page"), + homePage.signDataButton.click(), + ]); + await signWalletPopup(walletPopup, walletConfig); + await homePage.assertModal(ModalName.SignData); + }); - test('Fail to Sign data with incorrect password ' + walletConfig.extension.Name, async () => { - const page = browser.pages()[0]; - const homePage = new HomePage(page); - const [walletPopup] = await Promise.all([ - browser.waitForEvent('page'), - homePage.signDataButton.click() - ]); - const walletConfigClone = structuredClone(walletConfig); - walletConfigClone.password = 'BadPassword'; - await signWalletPopup(walletPopup, walletConfigClone, false); - await expect(walletPopup.getByTestId('password-input-error')).toBeVisible(); - }); + test("Sign and submit tx with " + walletConfig.extension.Name, async () => { + const page = browser.pages()[0]; + await page.waitForTimeout(2000); + await page.reload(); + await new WalletListPage(page).clickEnableWallet( + walletConfig.extension.Name + ); + const homePage = new HomePage(page); + const [walletPopup] = await Promise.all([ + browser.waitForEvent("page"), + homePage.signAndSubmitTxButton.click(), + ]); + await signWalletPopup(walletPopup, walletConfig); + await homePage.assertModal(ModalName.SignAndSubmitTx); + }); - test('Fail to Sign & submit tx with incorrect password' + walletConfig.extension.Name, async () => { - const page = browser.pages()[0]; - const homePage = new HomePage(page); - const [walletPopup] = await Promise.all([ - browser.waitForEvent('page'), - homePage.signAndSubmitTxButton.click() - ]); - const walletConfigClone = structuredClone(walletConfig); - walletConfigClone.password = 'BadPassword'; - await signWalletPopup(walletPopup, walletConfigClone, false); - await expect(walletPopup.getByTestId('password-input-error')).toBeVisible(); - }); + test( + "Sign and submit RBAC tx with " + walletConfig.extension.Name, + async () => { + const page = browser.pages()[0]; + await page.waitForTimeout(2000); + await page.reload(); + await new WalletListPage(page).clickEnableWallet( + walletConfig.extension.Name + ); + const homePage = new HomePage(page); + const [walletPopup] = await Promise.all([ + browser.waitForEvent("page"), + homePage.signAndSubmitRBACTxButton.click(), + ]); + await signWalletPopup(walletPopup, walletConfig); + await homePage.assertModal(ModalName.SignAndSubmitRBACTx); + } + ); - test('Fail to Sign & submit RBAC tx with incorrect password' + walletConfig.extension.Name, async () => { - const page = browser.pages()[0]; - const homePage = new HomePage(page); - const [walletPopup] = await Promise.all([ - browser.waitForEvent('page'), - homePage.signAndSubmitRBACTxButton.click() - ]); - const walletConfigClone = structuredClone(walletConfig); - walletConfigClone.password = 'BadPassword'; - await signWalletPopup(walletPopup, walletConfigClone, false); - await expect(walletPopup.getByTestId('password-input-error')).toBeVisible(); - }); - }); -}) + test( + "Fail to Sign data with incorrect password " + + walletConfig.extension.Name, + async () => { + const page = browser.pages()[0]; + await page.waitForTimeout(2000); + await page.reload(); + await new WalletListPage(page).clickEnableWallet( + walletConfig.extension.Name + ); + const homePage = new HomePage(page); + const [walletPopup] = await Promise.all([ + browser.waitForEvent("page"), + homePage.signDataButton.click(), + ]); + const walletConfigClone = structuredClone(walletConfig); + walletConfigClone.password = "BadPassword"; + await signWalletPopup(walletPopup, walletConfigClone, false); + await homePage.assertModal(ModalName.SignDataUserDeclined); + } + ); + + test( + "Fail to Sign & submit tx with incorrect password" + + walletConfig.extension.Name, + async () => { + const page = browser.pages()[0]; + await page.waitForTimeout(2000); + await page.reload(); + await new WalletListPage(page).clickEnableWallet( + walletConfig.extension.Name + ); + const homePage = new HomePage(page); + const [walletPopup] = await Promise.all([ + browser.waitForEvent("page"), + homePage.signAndSubmitTxButton.click(), + ]); + const walletConfigClone = structuredClone(walletConfig); + walletConfigClone.password = "BadPassword"; + await signWalletPopup(walletPopup, walletConfigClone, false); + await homePage.assertModal(ModalName.SignTxUserDeclined); + } + ); + + test( + "Fail to Sign & submit RBAC tx with incorrect password" + + walletConfig.extension.Name, + async () => { + const page = browser.pages()[0]; + await page.waitForTimeout(2000); + await page.reload(); + await new WalletListPage(page).clickEnableWallet( + walletConfig.extension.Name + ); + const homePage = new HomePage(page); + const [walletPopup] = await Promise.all([ + browser.waitForEvent("page"), + homePage.signAndSubmitRBACTxButton.click(), + ]); + const walletConfigClone = structuredClone(walletConfig); + walletConfigClone.password = "BadPassword"; + await signWalletPopup(walletPopup, walletConfigClone, false); + await homePage.assertModal(ModalName.SignRBACTxUserDeclined); + } + ); + }); +}); diff --git a/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/utils/extensionDownloader.ts b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/utils/extensionDownloader.ts index 22e3605a7f2..de09abeca3a 100644 --- a/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/utils/extensionDownloader.ts +++ b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/utils/extensionDownloader.ts @@ -1,11 +1,11 @@ -import * as os from 'os'; -import { promises as fsPromises } from 'fs'; -import { pipeline } from 'stream/promises'; -import unzip from '@tomjs/unzip-crx'; +/* cspell:disable */ +import unzip from "@tomjs/unzip-crx"; +import fs, { promises as fsPromises } from "fs"; import nodeFetch from "node-fetch"; -import fs from 'fs'; +import * as os from "os"; +import path from "path"; +import { pipeline } from "stream/promises"; import { BrowserExtensionName, getBrowserExtension } from "./extensions"; -import path from 'path'; interface PlatformInfo { os: string; @@ -17,21 +17,23 @@ export class ExtensionDownloader { private extensionsDir: string; constructor() { - this.extensionsDir = path.resolve(__dirname, '..', 'extensions'); + this.extensionsDir = path.resolve(__dirname, "..", "extensions"); } /** * Downloads and extracts the specified browser extension. * @param extensionName The name of the extension to download. * @returns The path to the extracted extension. - * + * * @example * const extensionPath = await new ExtensionDownloader().getExtension(BrowserExtensionName.Lace); * console.log(extensionPath); * Output: /path/to/extension - * - */ - public async getExtension(extensionName: BrowserExtensionName): Promise { + * + */ + public async getExtension( + extensionName: BrowserExtensionName + ): Promise { const extensionId = getBrowserExtension(extensionName).Id; const extensionPath = path.join(this.extensionsDir, extensionId); @@ -50,7 +52,10 @@ export class ExtensionDownloader { return extensionPath; } - private async extractExtension(extensionPath: string, extractPath: string): Promise { + private async extractExtension( + extensionPath: string, + extractPath: string + ): Promise { // Ensure the extraction directory exists await fsPromises.mkdir(extractPath, { recursive: true }); @@ -63,8 +68,10 @@ export class ExtensionDownloader { throw error; } } - - private async downloadExtension(extensionName: BrowserExtensionName): Promise { + + private async downloadExtension( + extensionName: BrowserExtensionName + ): Promise { const extensionId = getBrowserExtension(extensionName).Id; const url = this.getCrxUrl(extensionName); @@ -91,45 +98,46 @@ export class ExtensionDownloader { const extensionId = getBrowserExtension(extensionName).Id; const platformInfo = this.getPlatformInfo(); - const productId = 'chromecrx'; - const productChannel = 'unknown'; - let productVersion = '9999.0.9999.0'; - - let url = 'https://clients2.google.com/service/update2/crx?response=redirect'; - url += '&os=' + platformInfo.os; - url += '&arch=' + platformInfo.arch; - url += '&os_arch=' + platformInfo.os_arch; - url += '&nacl_arch=' + platformInfo.nacl_arch; - url += '&prod=' + productId; - url += '&prodchannel=' + productChannel; - url += '&prodversion=' + productVersion; - url += '&lang=en' - url += '&acceptformat=crx3'; - url += '&x=id%3D' + extensionId + '%26installsource%3Dondemand%26uc'; + const productId = "chromecrx"; + const productChannel = "unknown"; + let productVersion = "9999.0.9999.0"; + + let url = + "https://clients2.google.com/service/update2/crx?response=redirect"; + url += "&os=" + platformInfo.os; + url += "&arch=" + platformInfo.arch; + url += "&os_arch=" + platformInfo.os_arch; + url += "&nacl_arch=" + platformInfo.nacl_arch; + url += "&prod=" + productId; + url += "&prodchannel=" + productChannel; + url += "&prodversion=" + productVersion; + url += "&lang=en"; + url += "&acceptformat=crx3"; + url += "&x=id%3D" + extensionId + "%26installsource%3Dondemand%26uc"; return url; } private getPlatformInfo(): PlatformInfo & { os_arch: string } { // Determine OS let osType = os.type().toLowerCase(); - let osName = 'win'; - if (osType.includes('darwin')) { - osName = 'mac'; - } else if (osType.includes('linux')) { - osName = 'linux'; - } else if (osType.includes('win')) { - osName = 'win'; - } else if (osType.includes('cros')) { - osName = 'cros'; + let osName = "win"; + if (osType.includes("darwin")) { + osName = "mac"; + } else if (osType.includes("linux")) { + osName = "linux"; + } else if (osType.includes("win")) { + osName = "win"; + } else if (osType.includes("cros")) { + osName = "cros"; } // Determine architecture const arch = os.arch(); // Returns 'x64', 'arm', 'ia32', etc. - const is64Bit = arch === 'x64' || arch === 'arm64'; - const archName = is64Bit ? 'x64' : 'x86'; - const os_arch = is64Bit ? 'x86_64' : 'x86'; - const naclArch = is64Bit ? 'x86-64' : 'x86-32'; + const is64Bit = arch === "x64" || arch === "arm64"; + const archName = is64Bit ? "x64" : "x86"; + const os_arch = is64Bit ? "x86_64" : "x86"; + const naclArch = is64Bit ? "x86-64" : "x86-32"; return { os: osName, arch: archName, os_arch, nacl_arch: naclArch }; -} + } } diff --git a/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/utils/extensions.ts b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/utils/extensions.ts index 9b7584f5fb8..78bee1df967 100644 --- a/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/utils/extensions.ts +++ b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/utils/extensions.ts @@ -1,32 +1,42 @@ export interface BrowserExtension { - Name: BrowserExtensionName, - Id: string - HomeUrl: string + Name: BrowserExtensionName; + Id: string; + HomeUrl: string; } export enum BrowserExtensionName { - Lace = 'Lace', - Typhon = 'Typhon' + Lace = "Lace", + Typhon = "Typhon", + Eternl = "Eternl", } - +/* cspell: disable */ export const browserExtensions: BrowserExtension[] = [ - { + { Name: BrowserExtensionName.Lace, - Id: 'gafhhkghbfjjkeiendhlofajokpaflmk', - HomeUrl: 'chrome-extension://gafhhkghbfjjkeiendhlofajokpaflmk/app.html#/setup' - + Id: "gafhhkghbfjjkeiendhlofajokpaflmk", + HomeUrl: "app.html#/setup", }, { Name: BrowserExtensionName.Typhon, - Id: 'kfdniefadaanbjodldohaedphafoffoh', - HomeUrl: 'chrome-extension://kfdniefadaanbjodldohaedphafoffoh/tab.html#/wallet/access/' - } + Id: "kfdniefadaanbjodldohaedphafoffoh", + HomeUrl: "tab.html#/wallet/access/", + }, + { + Name: BrowserExtensionName.Eternl, + Id: "kmhcihpebfmpgmihbkipmjlmmioameka", + HomeUrl: "index.html#/app/preprod/welcome", + }, ]; +/* cspell: enable */ -export const getBrowserExtension = (name: BrowserExtensionName): BrowserExtension => { - const extension = browserExtensions.find(extension => extension.Name === name); +export const getBrowserExtension = ( + name: BrowserExtensionName +): BrowserExtension => { + const extension = browserExtensions.find( + (extension) => extension.Name === name + ); if (!extension) { throw new Error(`Browser extension with name ${name} not found`); } return extension; -} \ No newline at end of file +}; diff --git a/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/utils/walletConfigs.ts b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/utils/walletConfigs.ts index 6fc27b33e36..ee099a04de1 100644 --- a/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/utils/walletConfigs.ts +++ b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/utils/walletConfigs.ts @@ -24,6 +24,7 @@ export const walletConfigs: WalletConfig[] = [ ], username: 'test123', password: 'test12345678@', + cipBridge: ['cip-95'] }, { id: '2', @@ -47,7 +48,32 @@ export const walletConfigs: WalletConfig[] = [ ], username: 'test123', password: 'test12345678@', + cipBridge: ['cip-30'] }, + { + id: '3', + extension: getBrowserExtension(BrowserExtensionName.Eternl), + seed: [ + 'stomach', + 'horn', + 'rail', + 'afraid', + 'flip', + 'also', + 'abandon', + 'speed', + 'chaos', + 'daring', + 'soon', + 'soft', + 'okay', + 'online', + 'benefit', + ], + username: 'test123', + password: 'test12345678@!!', + cipBridge: ['cip-30', 'cip-95'] + } ]; export const getWalletConfig = (id: string): WalletConfig => { diff --git a/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/utils/wallets/eternlUtils.ts b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/utils/wallets/eternlUtils.ts new file mode 100644 index 00000000000..2b310fe343b --- /dev/null +++ b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/utils/wallets/eternlUtils.ts @@ -0,0 +1,28 @@ +import { expect, Page } from "@playwright/test"; +import { WalletConfig } from "./walletUtils"; + +export const onboardEternlWallet = async (page: Page, walletConfig: WalletConfig): Promise => { + await page.locator('button:has-text("Add Wallet")').click(); + await page.locator('button:has-text("Restore wallet")').click(); + await page.locator('button:has-text("15 words")').click(); + await page.locator('button.cc-btn-primary:has-text("next")').click(); + await page.locator('#wordInput').fill(walletConfig.seed.join(' ')); + await page.locator('button:has-text("continue")').click(); + await page.locator('#inputWalletName').fill(walletConfig.username); + await page.locator('#password').fill(walletConfig.password); + await page.locator('#repeatPassword').fill(walletConfig.password); + await page.locator('button:has-text("save")').click(); + await page.locator('button:has-text("save")').click(); + await page.locator('div.flex.flex-row.justify-center.items-center.cursor-pointer.cc-area-light-1').click(); +}; + +export const signEternlData = async (signTab: Page, password: string, isCorrectPassword: boolean): Promise => { + + await signTab.locator('input#password').fill(password); + await signTab.locator('//button[.//span[text()="sign"]]').click(); + if (!isCorrectPassword) { + expect(await signTab.locator('//div[contains(text(), "try again")]').isVisible()).toBeTruthy(); + await signTab.locator('//button[.//span[text()="cancel"]]').click(); + return + } +} \ No newline at end of file diff --git a/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/utils/wallets/laceUtils.ts b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/utils/wallets/laceUtils.ts index 792eca2edb0..6611a80d5da 100644 --- a/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/utils/wallets/laceUtils.ts +++ b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/utils/wallets/laceUtils.ts @@ -84,7 +84,8 @@ export const signLaceData = async (page: Page, password: string, isCorrectPasswo await page.getByTestId('password-input').fill(password); await page.getByRole('button', { name: 'Confirm' }).click(); if (!isCorrectPassword) { + await page.getByRole('button', { name: 'Close' }).click(); return; } - await page.getByRole('button', { name: 'Close' }).click(); + await page.waitForTimeout(2000); } \ No newline at end of file diff --git a/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/utils/wallets/walletUtils.ts b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/utils/wallets/walletUtils.ts index c7123fd6eb4..f912a4ddb45 100644 --- a/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/utils/wallets/walletUtils.ts +++ b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/utils/wallets/walletUtils.ts @@ -2,6 +2,7 @@ import { Page } from "@playwright/test"; import { BrowserExtension, BrowserExtensionName } from "../extensions"; import { onboardTyphonWallet, signTyphonData } from "./typhonUtils"; import { onboardLaceWallet, signLaceData } from "./laceUtils"; +import { onboardEternlWallet, signEternlData } from "./eternlUtils"; export interface WalletConfig { id: string; @@ -9,6 +10,7 @@ export interface WalletConfig { seed: string[]; username: string; password: string; + cipBridge: string[]; } export const onboardWallet = async (page: Page, walletConfig: WalletConfig): Promise => { @@ -19,6 +21,9 @@ export const onboardWallet = async (page: Page, walletConfig: WalletConfig): Pro case BrowserExtensionName.Lace: await onboardLaceWallet(page, walletConfig); break; + case BrowserExtensionName.Eternl: + await onboardEternlWallet(page, walletConfig); + break; default: throw new Error('Wallet not in use') } @@ -34,6 +39,9 @@ export const allowExtension = async (tab: Page, wallet: string): Promise = await tab.getByTestId('connect-authorize-button').click(); await tab.getByRole('button', { name: 'Always' }).click(); break; + case 'Eternl': + await tab.locator('button:has-text("Grant Access")').click(); + break; default: throw new Error('Wallet not in use') } @@ -47,6 +55,9 @@ export const signWalletPopup = async (page: Page, walletConfig: WalletConfig, is case BrowserExtensionName.Lace: await signLaceData(page, walletConfig.password, isCorrectPassword); break; + case BrowserExtensionName.Eternl: + await signEternlData(page, walletConfig.password, isCorrectPassword); + break; default: throw new Error('Wallet not in use') }