diff --git a/.config/dictionaries/project.dic b/.config/dictionaries/project.dic index 4ed180b565..21d5209dfb 100644 --- a/.config/dictionaries/project.dic +++ b/.config/dictionaries/project.dic @@ -271,6 +271,7 @@ tablestats tacho testcov testdocs +testid testplan testunit thiserror @@ -280,6 +281,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 418a486033..32e1d1d80e 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,12 +31,12 @@ 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 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 56b4627a32..e72dcf9dc9 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/pages/homePage.ts b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/pages/homePage.ts index f1afa2208c..3f08cdb773 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 05b2c4220e..0954550296 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 0000000000..cb3df5087e --- /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 02a828eaf1..ad8c3198f5 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 @@ -20,7 +20,7 @@ export default defineConfig({ reporter: [ ['html', { open: 'never', outputFolder: '/results' }], ['junit', { outputFile: '/results/cardano-wallet.junit-report.xml' }]], - timeout: 120 * 1000, + timeout: 60 * 1000, projects: [ { name: 'chromium', 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 0000000000..0f6b358187 --- /dev/null +++ b/catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/wallet-automation/setup.ts @@ -0,0 +1,68 @@ +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 + args: [ + `--disable-extensions-except=${extensionPath}`, + `--load-extension=${extensionPath}`, + ], + }); + 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 60dfaea138..5fa53d8361 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 c5a91dc130..b466bad1da 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,28 +1,35 @@ 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 { test, BrowserContext, expect } from '@playwright/test'; +import { restoreWallet, enableWallet } from '../setup'; +import { WalletListPage } from '../pages/walletListPage'; + +let browser: BrowserContext; 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.skip(walletConfig.extension.Name === 'Lace', 'https://github.com/input-output-hk/catalyst-voices/issues/1190'); + test.beforeAll(async ({}, testInfo) => { + browser = await restoreWallet(walletConfig); + await enableWallet(walletConfig, browser); }); + 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.extension.Name); + await homePage.assertBasicWalletCipData(walletCipData, walletConfig); }); + 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'), @@ -34,6 +41,9 @@ walletConfigs.forEach(( walletConfig ) => { 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'), @@ -45,6 +55,9 @@ walletConfigs.forEach(( walletConfig ) => { 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'), @@ -53,9 +66,12 @@ walletConfigs.forEach(( walletConfig ) => { await signWalletPopup(walletPopup, walletConfig); await homePage.assertModal(ModalName.SignAndSubmitRBACTx); }); - + 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'), @@ -64,11 +80,14 @@ walletConfigs.forEach(( walletConfig ) => { const walletConfigClone = structuredClone(walletConfig); walletConfigClone.password = 'BadPassword'; await signWalletPopup(walletPopup, walletConfigClone, false); - await expect(walletPopup.getByTestId('password-input-error')).toBeVisible(); + 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'), @@ -77,11 +96,14 @@ walletConfigs.forEach(( walletConfig ) => { const walletConfigClone = structuredClone(walletConfig); walletConfigClone.password = 'BadPassword'; await signWalletPopup(walletPopup, walletConfigClone, false); - await expect(walletPopup.getByTestId('password-input-error')).toBeVisible(); + 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'), @@ -90,7 +112,7 @@ walletConfigs.forEach(( walletConfig ) => { const walletConfigClone = structuredClone(walletConfig); walletConfigClone.password = 'BadPassword'; await signWalletPopup(walletPopup, walletConfigClone, false); - await expect(walletPopup.getByTestId('password-input-error')).toBeVisible(); + 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 22e3605a7f..de09abeca3 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 9b7584f5fb..78bee1df96 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 6fc27b33e3..ee099a04de 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 0000000000..2b310fe343 --- /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 792eca2edb..6611a80d5d 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 c7123fd6eb..f912a4ddb4 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') }