From 3182bf29133cec207af8e9a5e4e4dbb0d7c9f6d5 Mon Sep 17 00:00:00 2001 From: emiride Date: Wed, 2 Apr 2025 13:01:38 +0200 Subject: [PATCH] test(cat-voices): playwright plays right --- .../apps/voices/e2e_tests/.gitignore | 7 + .../apps/voices/e2e_tests/homePage.ts | 361 ++++++++ .../apps/voices/e2e_tests/modal.ts | 64 ++ .../apps/voices/e2e_tests/package-lock.json | 868 ++++++++++++++++++ .../apps/voices/e2e_tests/package.json | 17 + .../e2e_tests/pageobject/app-bar-page.ts | 16 + .../e2e_tests/pageobject/discovery-page.ts | 10 + .../create-flow/step-10-Input-seedphrase.ts | 20 + .../create-flow/step-11-seedphrase-success.ts | 20 + .../create-flow/step-12-password-info.ts | 20 + .../create-flow/step-13-password-input.ts | 37 + .../create-flow/step-14-keychain-final.ts | 34 + .../create-flow/step-15-link-wallet-info.ts | 23 + .../create-flow/step-16-wallet-list.ts | 23 + .../create-flow/step-17-choose-wallet.ts | 21 + .../create-flow/step-18-wallet-detection.ts | 16 + .../create-flow/step-2-base-profile-info.ts | 23 + .../create-flow/step-3-setup-base-profile.ts | 20 + .../create-flow/step-4-acknowledgements.ts | 20 + .../create-flow/step-5-base-profile-final.ts | 23 + .../step-6-catalyst-keychain-info.ts | 25 + .../step-7-catalyst-keychain-success.ts | 22 + .../step-8-writedown-seedphrase.ts | 28 + .../step-9-writedown-seedphrase-info.ts | 21 + .../onboarding/onboarding-base-page.ts | 22 + .../restore-flow/step-2-base-profile-info.ts | 23 + .../step-3-seedphrase-instruction-panel.ts | 19 + .../step-4-restore-keychain-input-panel.ts | 18 + .../step-5-restore-keychain-success-panel.ts | 23 + .../step-6-unlock-password-info-panel.ts | 20 + .../step-7-unlock-password-input-panel.ts | 31 + .../step-8-unlock-password-success-panel.ts | 25 + .../onboarding/step-1-get-started.ts | 30 + .../voices/e2e_tests/playwright.config.ts | 29 + .../apps/voices/e2e_tests/setup-ext.ts | 68 ++ .../apps/voices/e2e_tests/test-fixtures.ts | 27 + .../voices/e2e_tests/tests/onboarding.spec.ts | 159 ++++ .../e2e_tests/utils/extensionDownloader.ts | 172 ++++ .../apps/voices/e2e_tests/utils/extensions.ts | 54 ++ .../voices/e2e_tests/utils/walletConfigs.ts | 137 +++ .../e2e_tests/utils/wallets/eternlUtils.ts | 28 + .../e2e_tests/utils/wallets/laceUtils.ts | 115 +++ .../e2e_tests/utils/wallets/nufiUtils.ts | 43 + .../e2e_tests/utils/wallets/typhonUtils.ts | 34 + .../e2e_tests/utils/wallets/walletUtils.ts | 120 +++ .../e2e_tests/utils/wallets/yoroiUtils.ts | 58 ++ .../apps/voices/e2e_tests/walletListPage.ts | 22 + .../proposals_page_sidebar_panel.dart | 64 ++ .../proposals_sidebar_branch_classes.dart | 44 + .../suites/proposals_test.dart | 21 +- .../apps/voices/lib/configs/main_qa.dart | 2 + .../pages/account/delete_keychain_dialog.dart | 17 +- .../widgets/account_keychain_tile.dart | 9 +- .../discovery/sections/campaign_hero.dart | 38 +- .../account_create_progress_panel.dart | 23 +- .../stage/instructions_panel.dart | 8 +- .../create_keychain/stage/splash_panel.dart | 8 +- .../recover/recover_method_panel.dart | 28 +- .../seed_phrase/account_details_panel.dart | 7 +- .../recover/seed_phrase/restored_panel.dart | 19 +- .../wallet_link/stage/intro_panel.dart | 19 +- .../stage/rbac_transaction_panel.dart | 27 +- .../stage/roles_summary_panel.dart | 7 +- .../stage/select_wallet_panel.dart | 36 +- .../stage/wallet_details_panel.dart | 13 +- .../registration_stage_navigation.dart | 14 +- .../widgets/unlock_password_form.dart | 32 +- .../spaces/appbar/session_action_header.dart | 16 +- .../widgets/buttons/voices_icon_button.dart | 8 +- .../widgets/text_field/voices_text_field.dart | 56 +- .../xcshareddata/xcschemes/Runner.xcscheme | 1 + package-lock.json | 44 + package.json | 5 + 73 files changed, 3443 insertions(+), 139 deletions(-) create mode 100644 catalyst_voices/apps/voices/e2e_tests/.gitignore create mode 100644 catalyst_voices/apps/voices/e2e_tests/homePage.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/modal.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/package-lock.json create mode 100644 catalyst_voices/apps/voices/e2e_tests/package.json create mode 100644 catalyst_voices/apps/voices/e2e_tests/pageobject/app-bar-page.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/pageobject/discovery-page.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-10-Input-seedphrase.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-11-seedphrase-success.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-12-password-info.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-13-password-input.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-14-keychain-final.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-15-link-wallet-info.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-16-wallet-list.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-17-choose-wallet.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-18-wallet-detection.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-2-base-profile-info.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-3-setup-base-profile.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-4-acknowledgements.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-5-base-profile-final.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-6-catalyst-keychain-info.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-7-catalyst-keychain-success.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-8-writedown-seedphrase.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-9-writedown-seedphrase-info.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/onboarding-base-page.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-2-base-profile-info.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-3-seedphrase-instruction-panel.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-4-restore-keychain-input-panel.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-5-restore-keychain-success-panel.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-6-unlock-password-info-panel.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-7-unlock-password-input-panel.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-8-unlock-password-success-panel.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/step-1-get-started.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/playwright.config.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/setup-ext.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/test-fixtures.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/tests/onboarding.spec.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/utils/extensionDownloader.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/utils/extensions.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/utils/walletConfigs.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/utils/wallets/eternlUtils.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/utils/wallets/laceUtils.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/utils/wallets/nufiUtils.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/utils/wallets/typhonUtils.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/utils/wallets/walletUtils.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/utils/wallets/yoroiUtils.ts create mode 100644 catalyst_voices/apps/voices/e2e_tests/walletListPage.ts create mode 100644 catalyst_voices/apps/voices/integration_test/pageobject/proposals_page_sidebar_panel.dart create mode 100644 catalyst_voices/apps/voices/integration_test/proposal_menu_branch_classes/proposals_sidebar_branch_classes.dart create mode 100644 package-lock.json create mode 100644 package.json diff --git a/catalyst_voices/apps/voices/e2e_tests/.gitignore b/catalyst_voices/apps/voices/e2e_tests/.gitignore new file mode 100644 index 000000000000..58786aac7566 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/.gitignore @@ -0,0 +1,7 @@ + +# Playwright +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/catalyst_voices/apps/voices/e2e_tests/homePage.ts b/catalyst_voices/apps/voices/e2e_tests/homePage.ts new file mode 100644 index 000000000000..84b867968805 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/homePage.ts @@ -0,0 +1,361 @@ +import { Locator, Page, expect } from "@playwright/test"; +import { WalletConfig } from "./utils/wallets/walletUtils"; +import { Modal, ModalName } from "./modal"; + +export interface UTxO { + tx: string; + index: number; + amount: number; +} + +export interface WalletCipData { + balance: number; + extensions: string[]; + networkId: string; + changeAddress: string; + rewardAddresses: string[]; + unusedAddresses: string[]; + usedAddresses: string[]; + utxos: UTxO[]; + publicDRepKey: string; + registeredPublicStakeKeys: string; + unregisteredPublicStakeKeys: string[]; +} + +export class HomePage { + readonly page: Page; + readonly balanceLabel: Locator; + readonly extensionsLabel: Locator; + readonly networkIdLabel: Locator; + readonly changeAddressLabel: Locator; + readonly rewardAddressesLabel: Locator; + readonly unusedAddressesLabel: Locator; + readonly usedAddressesLabel: Locator; + readonly utxosLabel: Locator; + readonly publicDRepKeyLabel: Locator; + readonly registeredPublicStakeKeysLabel: Locator; + readonly unregisteredPublicStakeKeysLabel: Locator; + readonly signDataButton: Locator; + readonly signAndSubmitTxButton: Locator; + readonly signAndSubmitRBACTxButton: Locator; + + // TODO: Add keys to source app and change locators to locate based on keys + constructor(page: Page) { + this.page = page; + this.balanceLabel = page.getByText(/^Balance: Ada \(lovelaces\):/); + this.extensionsLabel = page.getByText(/^Extensions:/); + this.networkIdLabel = page.getByText(/^Network ID:/); + this.changeAddressLabel = page.getByText(/^Change address:/); + this.rewardAddressesLabel = page.getByText(/^Reward addresses:/); + this.unusedAddressesLabel = page.getByText(/^Unused addresses:/); + this.usedAddressesLabel = page.getByText(/^Used addresses:/); + this.utxosLabel = page.getByText(/^UTXOs:/); + this.publicDRepKeyLabel = page.getByText(/^Public DRep Key:/); + this.registeredPublicStakeKeysLabel = page.getByText( + /^Registered Public Stake Keys:/ + ); + this.unregisteredPublicStakeKeysLabel = page.getByText( + /^Unregistered Public Stake Keys:/ + ); + this.signDataButton = page.getByRole("button", { name: "Sign data" }); + this.signAndSubmitTxButton = page.getByRole("button", { + name: "Sign & submit tx", + }); + this.signAndSubmitRBACTxButton = page.getByRole("button", { + name: "Sign & submit RBAC tx", + }); + } + + async getWalletCipData() { + const walletCipData: WalletCipData = { + balance: 0, + extensions: [], + networkId: "", + changeAddress: "", + rewardAddresses: [], + unusedAddresses: [], + usedAddresses: [], + utxos: [], + publicDRepKey: "", + registeredPublicStakeKeys: "", + unregisteredPublicStakeKeys: [], + }; + await this.balanceLabel.waitFor({ state: "visible", timeout: 10000 }); + walletCipData.balance = await this.getBalance(); + walletCipData.extensions = await this.getExtensions(); + walletCipData.networkId = await this.getNetworkId(); + walletCipData.changeAddress = await this.getChangeAddress(); + walletCipData.rewardAddresses = await this.getRewardAddresses(); + walletCipData.unusedAddresses = await this.getUnusedAddresses(); + walletCipData.usedAddresses = await this.getUsedAddresses(); + walletCipData.utxos = await this.getUTXOs(); + walletCipData.publicDRepKey = await this.getPublicDRepKey(); + walletCipData.registeredPublicStakeKeys = + await this.getRegisteredPublicStakeKeys(); + walletCipData.unregisteredPublicStakeKeys = + await this.getUnregisteredPublicStakeKeys(); + return walletCipData; + } + + async assertModal(modalName: ModalName) { + const modal = new Modal(this.page, modalName); + await modal.assertModalIsVisible(); + } + + async getBalance(): Promise { + const isVisible = await this.balanceLabel.isVisible(); + if (!isVisible) { + throw new Error("Balance label is not visible"); + } + const balanceText = await this.balanceLabel.textContent(); + const match = balanceText?.match(/^Balance: Ada \(lovelaces\): (\d+)/); + if (match && match[1]) { + return Number(match[1]); + } else { + throw new Error(`Unable to extract balance from text: ${balanceText}`); + } + } + + async getExtensions(): Promise { + const isVisible = await this.extensionsLabel.isVisible(); + if (!isVisible) { + throw new Error("Extensions label is not visible"); + } + const extensionsText = await this.extensionsLabel.textContent(); + const match = extensionsText?.trim().match(/^Extensions:\s*(.+)$/); + if (match && match[1]) { + 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}` + ); + } + } + + async getNetworkId(): Promise { + const isVisible = await this.networkIdLabel.isVisible(); + if (!isVisible) { + throw new Error("Network ID label is not visible"); + } + const networkIdText = await this.networkIdLabel.textContent(); + const match = networkIdText?.trim().match(/^Network ID:\s*(.+)$/); + if (match && match[1]) { + return match[1].trim(); + } else { + throw new Error( + `Unable to extract network ID from text: ${networkIdText}` + ); + } + } + + async getChangeAddress(): Promise { + const isVisible = await this.changeAddressLabel.isVisible(); + if (!isVisible) { + throw new Error("Change address label is not visible"); + } + const changeAddressText = await this.changeAddressLabel.textContent(); + const match = changeAddressText?.trim().match(/^Change address:\s*(.+)$/s); + if (match && match[1]) { + return match[1].trim(); + } else { + throw new Error( + `Unable to extract change address from text: ${changeAddressText}` + ); + } + } + + async getRewardAddresses(): Promise { + const isVisible = await this.rewardAddressesLabel.isVisible(); + if (!isVisible) { + throw new Error("Reward addresses label is not visible"); + } + const rewardAddressesText = await this.rewardAddressesLabel.textContent(); + const match = rewardAddressesText?.match(/^Reward addresses:\s*(.+)$/s); + if (match && match[1]) { + const addresses = match[1] + .trim() + .split("\n") + .map((addr) => addr.trim()) + .filter((addr) => addr.length > 0); + return addresses; + } else { + throw new Error( + `Unable to extract reward addresses from text: ${rewardAddressesText}` + ); + } + } + + async getUnusedAddresses(): Promise { + const isVisible = await this.unusedAddressesLabel.isVisible(); + if (!isVisible) { + throw new Error("Unused addresses label is not visible"); + } + const unusedAddressesText = await this.unusedAddressesLabel.textContent(); + const match = unusedAddressesText?.match(/^Unused addresses:\s*(.+)$/s); + if (match && match[1]) { + const addresses = match[1] + .trim() + .split("\n") + .map((addr) => addr.trim()) + .filter((addr) => addr.length > 0); + return addresses; + } else { + throw new Error( + `Unable to extract unused addresses from text: ${unusedAddressesText}` + ); + } + } + + async getUsedAddresses(): Promise { + const isVisible = await this.usedAddressesLabel.isVisible(); + if (!isVisible) { + throw new Error("Used addresses label is not visible"); + } + const usedAddressesText = await this.usedAddressesLabel.textContent(); + const match = usedAddressesText?.match(/^Used addresses:\s*(.+)$/s); + if (match && match[1]) { + const addresses = match[1] + .trim() + .split("\n") + .map((addr) => addr.trim()) + .filter((addr) => addr.length > 0); + return addresses; + } else { + throw new Error( + `Unable to extract used addresses from text: ${usedAddressesText}` + ); + } + } + + async getUTXOs(): Promise { + const isVisible = await this.utxosLabel.isVisible(); + if (!isVisible) { + throw new Error("UTXOs label is not visible"); + } + const utxosText = await this.utxosLabel.textContent(); + const match = utxosText?.match(/^UTXOs:\s*(.+)$/s); + if (match && match[1]) { + const utxosData = match[1].trim(); + const utxoEntries = utxosData + .split(/\n\n+/) + .map((entry) => entry.trim()) + .filter((entry) => entry.length > 0); + const utxos: UTxO[] = []; + for (const entry of utxoEntries) { + const txMatch = entry.match(/Tx:\s*([a-fA-F0-9]+)/); + const indexMatch = entry.match(/Index:\s*(\d+)/); + const amountMatch = entry.match(/Amount:\s*Ada \(lovelaces\):\s*(\d+)/); + if (txMatch && indexMatch && amountMatch) { + utxos.push({ + tx: txMatch[1], + index: Number(indexMatch[1]), + amount: Number(amountMatch[1]), + }); + } else { + throw new Error(`Unable to parse UTXO entry: ${entry}`); + } + } + return utxos; + } else { + throw new Error(`Unable to extract UTXOs from text: ${utxosText}`); + } + } + + async getPublicDRepKey(): Promise { + await this.page.waitForTimeout(2000); + const isVisible = await this.publicDRepKeyLabel.isVisible(); + if (!isVisible) { + throw new Error("Public DRep Key label is not visible"); + } + const publicDRepKeyText = await this.publicDRepKeyLabel.textContent(); + const match = publicDRepKeyText + ?.trim() + .match(/^Public DRep Key:\s*([a-fA-F0-9]+)$/); + if (match && match[1]) { + return match[1]; + } else { + throw new Error( + `Unable to extract public DRep key from text: ${publicDRepKeyText}` + ); + } + } + + async getRegisteredPublicStakeKeys(): Promise { + const isVisible = await this.registeredPublicStakeKeysLabel.isVisible(); + if (!isVisible) { + throw new Error("Registered Public Stake Keys label is not visible"); + } + const stakeKeysText = + await this.registeredPublicStakeKeysLabel.textContent(); + const match = stakeKeysText + ?.trim() + .match(/^Registered Public Stake Keys:\s*([a-fA-F0-9]+)$/); + if (match && match[1]) { + return match[1]; + } else { + throw new Error( + `Unable to extract registered public stake keys from text: ${stakeKeysText}` + ); + } + } + + async getUnregisteredPublicStakeKeys(): Promise { + const isVisible = await this.unregisteredPublicStakeKeysLabel.isVisible(); + if (!isVisible) { + throw new Error("Unregistered Public Stake Keys label is not visible"); + } + const keysText = await this.unregisteredPublicStakeKeysLabel.textContent(); + const match = keysText + ?.trim() + .match(/^Unregistered Public Stake Keys:\s*(.*)$/s); + if (match) { + const keysData = match[1].trim(); + if (keysData) { + const keys = keysData + .split("\n") + .map((key) => key.trim()) + .filter((key) => key.length > 0); + return keys; + } else { + return []; + } + } else { + throw new Error( + `Unable to extract unregistered public stake keys from text: ${keysText}` + ); + } + } + + async assertBasicWalletCipData( + actualWalletCipData: WalletCipData, + walletConfig: WalletConfig + ) { + expect(actualWalletCipData.balance).toBeGreaterThan(500000000); + await this.assertExtensions( + actualWalletCipData.extensions, + walletConfig.cipBridge + ); + expect(actualWalletCipData.networkId).not.toBeNaN(); + expect(actualWalletCipData.changeAddress).not.toBeNaN(); + expect(actualWalletCipData.rewardAddresses.length).toBeGreaterThan(0); + //expect(actualWalletCipData.unusedAddresses.length).toBeGreaterThan(0); + expect(actualWalletCipData.usedAddresses.length).toBeGreaterThan(0); + 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/apps/voices/e2e_tests/modal.ts b/catalyst_voices/apps/voices/e2e_tests/modal.ts new file mode 100644 index 000000000000..f31d447ebb6d --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/modal.ts @@ -0,0 +1,64 @@ +import { expect, Locator, Page } from "@playwright/test"; + +export enum ModalName { + SignData = "SignData", + SignAndSubmitTx = "SignAndSubmitTx", + SignAndSubmitRBACTx = "SignAndSubmitRBACTx", + SignDataUserDeclined = "UserDeclined", + SignTxUserDeclined = "SignTxUserDeclined", + SignRBACTxUserDeclined = "SignRBACTxUserDeclined", +} + +export interface ModalContent { + header: string; + unchangingText: string; +} + +export const modalContents: { [key in ModalName]: ModalContent } = { + [ModalName.SignData]: { + header: "Sign data", + unchangingText: "Signature:", + }, + [ModalName.SignAndSubmitTx]: { + header: "Sign & submit tx", + unchangingText: "Tx hash:", + }, + [ModalName.SignAndSubmitRBACTx]: { + header: "Sign & submit RBAC tx", + unchangingText: "Tx hash:", + }, + [ModalName.SignDataUserDeclined]: { + header: "Sign data", + unchangingText: "WalletApiException", + }, + [ModalName.SignTxUserDeclined]: { + header: "Sign & submit tx", + unchangingText: "WalletApiException", + }, + [ModalName.SignRBACTxUserDeclined]: { + header: "Sign & submit RBAC tx", + unchangingText: "WalletApiException", + }, +}; + +export class Modal { + readonly page: Page; + readonly content: ModalContent; + readonly modalHeader: Locator; + readonly modalBody: Locator; + + constructor(page: Page, modalName: ModalName) { + this.page = page; + this.content = modalContents[modalName]; + this.modalHeader = this.page.getByText(this.content.header, { + exact: true, + }); + this.modalBody = this.page.getByText(this.content.unchangingText); + } + + async assertModalIsVisible() { + await this.page.waitForTimeout(2000); + await expect(this.modalHeader).toBeVisible(); + await expect(this.modalBody).toBeVisible(); + } +} diff --git a/catalyst_voices/apps/voices/e2e_tests/package-lock.json b/catalyst_voices/apps/voices/e2e_tests/package-lock.json new file mode 100644 index 000000000000..1746858a5dca --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/package-lock.json @@ -0,0 +1,868 @@ +{ + "name": "e2e_tests", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "e2e_tests", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.51.1", + "@tomjs/unzip-crx": "^1.1.3", + "@types/node": "^22.13.14", + "@types/node-fetch": "^2.6.11", + "node-fetch": "^2.6.7" + } + }, + "node_modules/@playwright/test": { + "version": "1.51.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.51.1.tgz", + "integrity": "sha512-nM+kEaTSAoVlXmMPH10017vn3FSiFqr/bh4fKg9vmAdMfd9SDqRZNvPSiAHADc/itWak+qPvMPZQOPwCBW7k7Q==", + "dev": true, + "dependencies": { + "playwright": "1.51.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@tomjs/unzip-crx": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@tomjs/unzip-crx/-/unzip-crx-1.1.3.tgz", + "integrity": "sha512-uqolp78TcG5q2ZBOZ57Nf7m7o3kaKAz1E9uFf4FCSO/nCI11HaDWpw7PaGUk1MImeIjNradiLpT2b9kTKSs4uw==", + "dev": true, + "dependencies": { + "jszip": "^3.10.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@types/node": { + "version": "22.13.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.14.tgz", + "integrity": "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w==", + "dev": true, + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "dev": true + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dev": true, + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "node_modules/playwright": { + "version": "1.51.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.51.1.tgz", + "integrity": "sha512-kkx+MB2KQRkyxjYPc3a0wLZZoDczmppyGJIvQ43l+aZihkaVvmu/21kiyaHeHjiFxjxNNFnUncKmcGIyOojsaw==", + "dev": true, + "dependencies": { + "playwright-core": "1.51.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.51.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.51.1.tgz", + "integrity": "sha512-/crRMj8+j/Nq5s8QcvegseuyeZPxpQCZb6HNk3Sos3BlZyAknRjoyJPFWkpNn8v0+P3WiwqFF8P+zQo4eqiNuw==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + }, + "dependencies": { + "@playwright/test": { + "version": "1.51.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.51.1.tgz", + "integrity": "sha512-nM+kEaTSAoVlXmMPH10017vn3FSiFqr/bh4fKg9vmAdMfd9SDqRZNvPSiAHADc/itWak+qPvMPZQOPwCBW7k7Q==", + "dev": true, + "requires": { + "playwright": "1.51.1" + } + }, + "@tomjs/unzip-crx": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@tomjs/unzip-crx/-/unzip-crx-1.1.3.tgz", + "integrity": "sha512-uqolp78TcG5q2ZBOZ57Nf7m7o3kaKAz1E9uFf4FCSO/nCI11HaDWpw7PaGUk1MImeIjNradiLpT2b9kTKSs4uw==", + "dev": true, + "requires": { + "jszip": "^3.10.1" + } + }, + "@types/node": { + "version": "22.13.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.14.tgz", + "integrity": "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w==", + "dev": true, + "requires": { + "undici-types": "~6.20.0" + } + }, + "@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "dev": true, + "requires": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + } + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true + }, + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + } + }, + "es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true + }, + "es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "requires": { + "es-errors": "^1.3.0" + } + }, + "es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + } + }, + "form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + } + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true + }, + "get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + } + }, + "get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "requires": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + } + }, + "gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true + }, + "has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true + }, + "has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.3" + } + }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "requires": { + "function-bind": "^1.1.2" + } + }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "dev": true + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dev": true, + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "requires": { + "immediate": "~3.0.5" + } + }, + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "requires": { + "mime-db": "1.52.0" + } + }, + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "playwright": { + "version": "1.51.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.51.1.tgz", + "integrity": "sha512-kkx+MB2KQRkyxjYPc3a0wLZZoDczmppyGJIvQ43l+aZihkaVvmu/21kiyaHeHjiFxjxNNFnUncKmcGIyOojsaw==", + "dev": true, + "requires": { + "fsevents": "2.3.2", + "playwright-core": "1.51.1" + } + }, + "playwright-core": { + "version": "1.51.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.51.1.tgz", + "integrity": "sha512-/crRMj8+j/Nq5s8QcvegseuyeZPxpQCZb6HNk3Sos3BlZyAknRjoyJPFWkpNn8v0+P3WiwqFF8P+zQo4eqiNuw==", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } +} diff --git a/catalyst_voices/apps/voices/e2e_tests/package.json b/catalyst_voices/apps/voices/e2e_tests/package.json new file mode 100644 index 000000000000..1b37528b565d --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/package.json @@ -0,0 +1,17 @@ +{ + "name": "e2e_tests", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": {}, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.51.1", + "@types/node": "^22.13.14", + "node-fetch": "^2.6.7", + "@tomjs/unzip-crx": "^1.1.3", + "@types/node-fetch": "^2.6.11" + } +} diff --git a/catalyst_voices/apps/voices/e2e_tests/pageobject/app-bar-page.ts b/catalyst_voices/apps/voices/e2e_tests/pageobject/app-bar-page.ts new file mode 100644 index 000000000000..03ca90dbb041 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/app-bar-page.ts @@ -0,0 +1,16 @@ +import { Locator, Page } from "@playwright/test"; + +export class AppBarPage { + getStartedBtn: Locator; + page: Page; + + constructor(page: Page) { + this.page = page; + this.getStartedBtn = page.getByRole("button", { + name: "GetStartedButton-test", + }); + } + async GetStartedBtnClick() { + await this.getStartedBtn.click(); + } +} diff --git a/catalyst_voices/apps/voices/e2e_tests/pageobject/discovery-page.ts b/catalyst_voices/apps/voices/e2e_tests/pageobject/discovery-page.ts new file mode 100644 index 000000000000..baffddfa4efe --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/discovery-page.ts @@ -0,0 +1,10 @@ +import { Page } from "@playwright/test"; + +export class DiscoveryPage { + page: Page; + + constructor(page: Page) { + this.page = page; + + } +} \ No newline at end of file diff --git a/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-10-Input-seedphrase.ts b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-10-Input-seedphrase.ts new file mode 100644 index 000000000000..48b2413ac1f4 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-10-Input-seedphrase.ts @@ -0,0 +1,20 @@ +import { Page } from "@playwright/test"; +import { OnboardingBasePage } from "../onboarding-base-page"; +import { WriteDownSeedPhraseInfoPanel } from "./step-9-writedown-seedphrase-info"; + +export class InputSeedPhrasePanel { + page: Page; + + constructor(page: Page) { + this.page = page; + } + + async goto() { + await new WriteDownSeedPhraseInfoPanel(this.page).goto(); + await new WriteDownSeedPhraseInfoPanel(this.page).clickNextButton(); + } + + async clickNextButton() { + new OnboardingBasePage(this.page).nextButton.click(); + } +} diff --git a/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-11-seedphrase-success.ts b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-11-seedphrase-success.ts new file mode 100644 index 000000000000..a9f86f5734b2 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-11-seedphrase-success.ts @@ -0,0 +1,20 @@ +import { Page } from "@playwright/test"; +import { OnboardingBasePage } from "../onboarding-base-page"; +import { InputSeedPhrasePanel } from "./step-10-Input-seedphrase"; + +export class SeedphraseSuccessPanel { + page: Page; + + constructor(page: Page) { + this.page = page; + } + + async goto() { + await new InputSeedPhrasePanel(this.page).goto(); + await new InputSeedPhrasePanel(this.page).clickNextButton(); + } + + async clickNextButton() { + new OnboardingBasePage(this.page).nextButton.click(); + } +} diff --git a/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-12-password-info.ts b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-12-password-info.ts new file mode 100644 index 000000000000..3ddc8cdbd399 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-12-password-info.ts @@ -0,0 +1,20 @@ +import { Page } from "@playwright/test"; +import { OnboardingBasePage } from "../onboarding-base-page"; +import { SeedphraseSuccessPanel } from "./step-11-seedphrase-success"; + +export class PasswordInfoPanel { + page: Page; + + constructor(page: Page) { + this.page = page; + } + + async goto() { + await new SeedphraseSuccessPanel(this.page).goto(); + await new SeedphraseSuccessPanel(this.page).clickNextButton(); + } + + async clickNextButton() { + new OnboardingBasePage(this.page).nextButton.click(); + } +} diff --git a/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-13-password-input.ts b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-13-password-input.ts new file mode 100644 index 000000000000..f41c97f7bbed --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-13-password-input.ts @@ -0,0 +1,37 @@ +import { Locator, Page } from "@playwright/test"; +import { PasswordInfoPanel } from "./step-12-password-info"; + +export class PasswordInputPanel { + page: Page; + passwordInput: Locator; + confirmPasswordInput: Locator; + + constructor(page: Page) { + this.page = page; + this.passwordInput = page.locator( + 'role=group[name="Enter password"] >> role=textbox' + ); + this.confirmPasswordInput = page.locator( + 'role=group[name="Confirm password"] >> role=textbox' + ); + } + + async goto() { + await new PasswordInfoPanel(this.page).goto(); + await new PasswordInfoPanel(this.page).clickNextButton(); + } + + async fillPassword(password: string) { + await this.passwordInput.click(); + await this.page.waitForTimeout(1000); + + await this.passwordInput.fill(password); + } + + async fillConfirmPassword(password: string) { + await this.confirmPasswordInput.click(); + await this.page.waitForTimeout(1000); + + await this.confirmPasswordInput.fill(password); + } +} diff --git a/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-14-keychain-final.ts b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-14-keychain-final.ts new file mode 100644 index 000000000000..6b4eb6c41397 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-14-keychain-final.ts @@ -0,0 +1,34 @@ +import { Locator, Page } from "@playwright/test"; +import { OnboardingBasePage } from "../onboarding-base-page"; +import { PasswordInputPanel } from "./step-13-password-input"; + +export class KaychainFinalPanel { + page: Page; + linkWalletAndRolesBtn: Locator; + + constructor(page: Page) { + this.page = page; + this.linkWalletAndRolesBtn = this.page.getByRole("button", { + name: "LinkWalletAndRoles-test", + }); + } + + async goto() { + await new PasswordInputPanel(this.page).goto(); + + await new PasswordInputPanel(this.page).fillPassword( + new OnboardingBasePage(this.page).password + ); + + await new PasswordInputPanel(this.page).fillConfirmPassword( + new OnboardingBasePage(this.page).password + ); + await this.page.waitForTimeout(1000); + + await new OnboardingBasePage(this.page).nextButton.click(); + } + + async clickLinkWalletAndRolesBtn() { + await this.linkWalletAndRolesBtn.click(); + } +} diff --git a/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-15-link-wallet-info.ts b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-15-link-wallet-info.ts new file mode 100644 index 000000000000..6ba2b234b116 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-15-link-wallet-info.ts @@ -0,0 +1,23 @@ +import { Locator, Page } from "@playwright/test"; +import { KaychainFinalPanel } from "./step-14-keychain-final"; + +export class LinkWalletInfoPanel { + page: Page + chooseWalletBtn: Locator + + constructor(page: Page) { + this.page = page; + this.chooseWalletBtn = this.page.getByRole("button", { + name: "ChooseCardanoWalletButton", + }); + } + + async goto() { + await new KaychainFinalPanel(this.page).goto(); + await new KaychainFinalPanel(this.page).clickLinkWalletAndRolesBtn(); + } + + async clickChooseWalletBtn() { + await this.chooseWalletBtn.click(); + } +} \ No newline at end of file diff --git a/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-16-wallet-list.ts b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-16-wallet-list.ts new file mode 100644 index 000000000000..8693ea33cec3 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-16-wallet-list.ts @@ -0,0 +1,23 @@ +import { Locator, Page } from "@playwright/test"; +import { LinkWalletInfoPanel } from "./step-15-link-wallet-info"; + +export class WalletListPanel { + page: Page; + yoroiWallet: Locator; + + constructor(page: Page) { + this.page = page; + this.yoroiWallet = page.locator( + 'role=group[name="Wallets"] >> role=button' + ); + } + + async goto() { + await new LinkWalletInfoPanel(this.page).goto(); + await new LinkWalletInfoPanel(this.page).clickChooseWalletBtn(); + } + + async clickYoroiWallet() { + await this.yoroiWallet.click(); + } +} diff --git a/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-17-choose-wallet.ts b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-17-choose-wallet.ts new file mode 100644 index 000000000000..04be43d02920 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-17-choose-wallet.ts @@ -0,0 +1,21 @@ +import { Page } from "@playwright/test"; +import { WalletListPanel } from "./step-16-wallet-list"; + +export class WalletPopupSelection { + page: Page + + constructor(page) { + this.page = page; + } + + async goto() { + await new WalletListPanel(this.page).goto(); + await new WalletListPanel(this.page).clickYoroiWallet(); + } + async clickWalletPopup() { + const [walletPopup] = await Promise.all([ + browser.waitForEvent("page"), + new WalletListPanel(page).clickYoroiWallet(), + ]); + } +} \ No newline at end of file diff --git a/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-18-wallet-detection.ts b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-18-wallet-detection.ts new file mode 100644 index 000000000000..7ddf993861b9 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-18-wallet-detection.ts @@ -0,0 +1,16 @@ +import { Locator, Page } from "@playwright/test"; + +export class WalletDetectionPanel { + page: Page + selectRolesBtn: Locator + + constructor(page : Page) { + this.page = page; + this.selectRolesBtn = page.getByRole("button", { name: "selectRolesBtn" }); + } + + async clickSelectRolesBtn() { + + await this.selectRolesBtn.click(); + } +} \ No newline at end of file diff --git a/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-2-base-profile-info.ts b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-2-base-profile-info.ts new file mode 100644 index 000000000000..1b4a60dac39c --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-2-base-profile-info.ts @@ -0,0 +1,23 @@ +import { Locator, Page } from "@playwright/test"; +import { GetStartedPanel } from "../step-1-get-started"; + +export class BaseProfileInfoPanel { + page: Page; + ceateYourBaseProfilebtn: Locator; + + constructor(page: Page) { + this.page = page; + this.ceateYourBaseProfilebtn = page.getByRole("button", { + name: "CreateBaseProfileNext-test", + }); + } + + async goto() { + await new GetStartedPanel(this.page).goto(); + await new GetStartedPanel(this.page).clickCreateNewCatalystKeychain(); + } + + async clickCreateBaseProfileBtn() { + await this.ceateYourBaseProfilebtn.click(); + } +} diff --git a/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-3-setup-base-profile.ts b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-3-setup-base-profile.ts new file mode 100644 index 000000000000..0f35d49dd429 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-3-setup-base-profile.ts @@ -0,0 +1,20 @@ +import { Page } from "@playwright/test"; +import { OnboardingBasePage } from "../onboarding-base-page"; +import { BaseProfileInfoPanel } from "./step-2-base-profile-info"; + +export class SetupBaseProfilePanel { + page: Page; + + constructor(page: Page) { + this.page = page; + } + + async goto() { + await new BaseProfileInfoPanel(this.page).goto(); + await new BaseProfileInfoPanel(this.page).clickCreateBaseProfileBtn(); + } + + async clickNextButton() { + await new OnboardingBasePage(this.page).nextButton.click(); + } +} diff --git a/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-4-acknowledgements.ts b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-4-acknowledgements.ts new file mode 100644 index 000000000000..497d98d1d68c --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-4-acknowledgements.ts @@ -0,0 +1,20 @@ +import { Page } from "@playwright/test"; +import { OnboardingBasePage } from "../onboarding-base-page"; +import { SetupBaseProfilePanel } from "./step-3-setup-base-profile"; + +export class AcknowledgementsPanel { + page: Page; + + constructor(page: Page) { + this.page = page; + } + + async goto() { + await new SetupBaseProfilePanel(this.page).goto(); + await new SetupBaseProfilePanel(this.page).clickNextButton(); + } + + async clickNextButton() { + await new OnboardingBasePage(this.page).nextButton.click(); + } +} diff --git a/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-5-base-profile-final.ts b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-5-base-profile-final.ts new file mode 100644 index 000000000000..9fc059a9fdcb --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-5-base-profile-final.ts @@ -0,0 +1,23 @@ +import { Locator, Page } from "@playwright/test"; +import { AcknowledgementsPanel } from "./step-4-acknowledgements"; + +export class BaseProfileFinalPanel { + page: Page; + createYourCatalystKeychainbtn: Locator; + + constructor(page: Page) { + this.page = page; + this.createYourCatalystKeychainbtn = page.getByRole("button", { + name: "CreateKeychain-test", + }); + } + + async goto() { + await new AcknowledgementsPanel(this.page).goto(); + await new AcknowledgementsPanel(this.page).clickNextButton(); + } + + async clickCreateYourCatalystKeychainbtn() { + await this.createYourCatalystKeychainbtn.click(); + } +} diff --git a/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-6-catalyst-keychain-info.ts b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-6-catalyst-keychain-info.ts new file mode 100644 index 000000000000..1554fc5aa455 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-6-catalyst-keychain-info.ts @@ -0,0 +1,25 @@ +import { Locator, Page } from "@playwright/test"; +import { BaseProfileFinalPanel } from "./step-5-base-profile-final"; + +export class CatalystKeychainInfoPanel { + page: Page; + createCatalystKeychainNowBtn: Locator; + + constructor(page: Page) { + this.page = page; + this.createCatalystKeychainNowBtn = page.getByRole("button", { + name: "CreateKeychainNow-test", + }); + } + + async goto() { + await new BaseProfileFinalPanel(this.page).goto(); + await new BaseProfileFinalPanel( + this.page + ).clickCreateYourCatalystKeychainbtn(); + } + + async clickCreateCatalystKeychainNowBtn() { + await this.createCatalystKeychainNowBtn.click(); + } +} diff --git a/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-7-catalyst-keychain-success.ts b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-7-catalyst-keychain-success.ts new file mode 100644 index 000000000000..39a4679ab6c1 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-7-catalyst-keychain-success.ts @@ -0,0 +1,22 @@ +import { Page } from "@playwright/test"; +import { OnboardingBasePage } from "../onboarding-base-page"; +import { CatalystKeychainInfoPanel } from "./step-6-catalyst-keychain-info"; + +export class CatalystKeychainSuccessPanel { + page: Page; + + constructor(page: Page) { + this.page = page; + } + + async goto() { + await new CatalystKeychainInfoPanel(this.page).goto(); + await new CatalystKeychainInfoPanel( + this.page + ).clickCreateCatalystKeychainNowBtn(); + } + + async clickNextButton() { + await new OnboardingBasePage(this.page).nextButton.click(); + } +} diff --git a/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-8-writedown-seedphrase.ts b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-8-writedown-seedphrase.ts new file mode 100644 index 000000000000..23e531806aef --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-8-writedown-seedphrase.ts @@ -0,0 +1,28 @@ +import { Locator, Page } from "@playwright/test"; +import { OnboardingBasePage } from "../onboarding-base-page"; +import { CatalystKeychainSuccessPanel } from "./step-7-catalyst-keychain-success"; + +export class WriteDownSeedPhrasePanel { + page: Page; + checkbox: Locator; + + constructor(page: Page) { + this.page = page; + this.checkbox = page.getByRole("checkbox"); + } + + async goto() { + await new CatalystKeychainSuccessPanel(this.page).goto(); + await new CatalystKeychainSuccessPanel(this.page).clickNextButton(); + } + + async markCheckboxAs(checked: boolean) { + if ((await this.checkbox.isChecked()) !== checked) { + await this.checkbox.click(); + } + } + + async clickNextButton() { + await new OnboardingBasePage(this.page).nextButton.click(); + } +} diff --git a/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-9-writedown-seedphrase-info.ts b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-9-writedown-seedphrase-info.ts new file mode 100644 index 000000000000..d3d9347096ac --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-9-writedown-seedphrase-info.ts @@ -0,0 +1,21 @@ +import { Page } from "@playwright/test"; +import { OnboardingBasePage } from "../onboarding-base-page"; +import { WriteDownSeedPhrasePanel } from "./step-8-writedown-seedphrase"; + +export class WriteDownSeedPhraseInfoPanel { + page: Page; + + constructor(page: Page) { + this.page = page; + } + + async goto() { + await new WriteDownSeedPhrasePanel(this.page).goto(); + await new WriteDownSeedPhrasePanel(this.page).markCheckboxAs(true); + await new WriteDownSeedPhrasePanel(this.page).clickNextButton(); + } + + async clickNextButton() { + await new OnboardingBasePage(this.page).nextButton.click(); + } +} diff --git a/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/onboarding-base-page.ts b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/onboarding-base-page.ts new file mode 100644 index 000000000000..5349e2d95315 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/onboarding-base-page.ts @@ -0,0 +1,22 @@ +import { test, expect, type Locator, type Page } from "@playwright/test"; + +export class OnboardingBasePage { + page: Page; + nextBtnGroup: Locator; + nextButton: Locator; + setUnlockPasswordGroup: Locator; + setUnlockPasswordBtn: Locator; + password: string; + + constructor(page:Page) { + this.page = page; + this.nextBtnGroup = page.getByRole("group", { name: "NextButton-test" }); + this.nextButton = this.nextBtnGroup.locator('role=button[name="Next"]'); + this.setUnlockPasswordGroup = page.getByRole("group", { + name: "SetUnlockPasswordButton", + }); + this.setUnlockPasswordBtn = this.setUnlockPasswordGroup.locator("role=button"); + this.password = "bera1234"; + } + +} \ No newline at end of file diff --git a/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-2-base-profile-info.ts b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-2-base-profile-info.ts new file mode 100644 index 000000000000..9affd72b820d --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-2-base-profile-info.ts @@ -0,0 +1,23 @@ +import { Locator, Page } from "@playwright/test"; +import { GetStartedPanel } from "../step-1-get-started"; + +export class RestoreKeychainChoicePanel { + restoreWithSeedphraseBtn: Locator; + page: Page; + + constructor(page: Page) { + this.page = page; + this.restoreWithSeedphraseBtn = page.getByRole("group", { + name: "Restore seedphrase Restore/Upload with 12-word seed phrase", + }); + } + + async goto() { + await new GetStartedPanel(this.page).goto(); + await new GetStartedPanel(this.page).clickRecoverCatalystKeychain(); + } + + async clickRestoreWithSeedphrase() { + await this.restoreWithSeedphraseBtn.click(); + } +} diff --git a/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-3-seedphrase-instruction-panel.ts b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-3-seedphrase-instruction-panel.ts new file mode 100644 index 000000000000..6aa1aad60d3e --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-3-seedphrase-instruction-panel.ts @@ -0,0 +1,19 @@ +import { Page } from "@playwright/test"; +import { OnboardingBasePage } from "../onboarding-base-page"; +import { RestoreKeychainChoicePanel } from "./step-2-base-profile-info"; + +export class SeedPhraseInstructionsPanel { + page: Page + + constructor(page: Page) { + this.page = page; + } + + async goto() { + await new RestoreKeychainChoicePanel(this.page).goto(); + await new RestoreKeychainChoicePanel(this.page).clickRestoreWithSeedphrase(); + } + async clickNextButton() { + await new OnboardingBasePage(this.page).nextButton.click(); + } +} \ No newline at end of file diff --git a/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-4-restore-keychain-input-panel.ts b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-4-restore-keychain-input-panel.ts new file mode 100644 index 000000000000..29e820d8c29a --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-4-restore-keychain-input-panel.ts @@ -0,0 +1,18 @@ +import { Page } from "@playwright/test"; +import { SeedPhraseInstructionsPanel } from "./step-3-seedphrase-instruction-panel"; +import { OnboardingBasePage } from "../onboarding-base-page"; + +export class RestoreKeychainInputPanel { + page: Page + constructor(page: Page) { + this.page = page; + } + + async goto() { + await new SeedPhraseInstructionsPanel(this.page).goto(); + await new SeedPhraseInstructionsPanel(this.page).clickNextButton(); + } + async clickNextButton() { + await new OnboardingBasePage(this.page).nextButton.click(); + } +} \ No newline at end of file diff --git a/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-5-restore-keychain-success-panel.ts b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-5-restore-keychain-success-panel.ts new file mode 100644 index 000000000000..0730d318d6e8 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-5-restore-keychain-success-panel.ts @@ -0,0 +1,23 @@ +import { Locator, Page } from "@playwright/test"; +import { RestoreKeychainInputPanel } from "./step-4-restore-keychain-input-panel"; + +export class RestoreKeychainSuccessPanel { + page: Page; + setUnlockPasswordBtn: Locator; + + constructor(page: Page) { + this.page = page; + this.setUnlockPasswordBtn = page.getByRole("group", { + name: "SetUnlockPasswordButton", + }); + } + + async goto() { + await new RestoreKeychainInputPanel(this.page).goto(); + await new RestoreKeychainInputPanel(this.page).clickNextButton(); + } + async clickSetUnlockPasswordBtn() { + await this.page.waitForTimeout(300); + await this.setUnlockPasswordBtn.click(); + } +} diff --git a/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-6-unlock-password-info-panel.ts b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-6-unlock-password-info-panel.ts new file mode 100644 index 000000000000..3e11c889640d --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-6-unlock-password-info-panel.ts @@ -0,0 +1,20 @@ +import { Page } from "@playwright/test"; +import { RestoreKeychainSuccessPanel } from "./step-5-restore-keychain-success-panel"; +import { OnboardingBasePage } from "../onboarding-base-page"; + +export class UnlockPasswordInfoPanel { + page: Page + + constructor(page : Page) { + this.page = page; + } + + async goto() { + await new RestoreKeychainSuccessPanel(this.page).goto(); + await new RestoreKeychainSuccessPanel(this.page).clickSetUnlockPasswordBtn(); + } + async clickNextButton() { + await new OnboardingBasePage(this.page).nextButton.click(); + } + +} \ No newline at end of file diff --git a/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-7-unlock-password-input-panel.ts b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-7-unlock-password-input-panel.ts new file mode 100644 index 000000000000..9689b61ae83e --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-7-unlock-password-input-panel.ts @@ -0,0 +1,31 @@ +import { Locator, Page } from "@playwright/test"; +import { OnboardingBasePage } from "../onboarding-base-page"; +import { UnlockPasswordInfoPanel } from "./step-6-unlock-password-info-panel"; + +export class UnlockPasswordInputPanel { + page: Page; + passwordInput: Locator; + confirmPasswordInput: Locator; + + constructor(page: Page) { + this.page = page; + this.passwordInput = page + .locator('role=group[name="Enter password"] >> role=textbox'); + this.confirmPasswordInput = page + .locator('role=group[name="Confirm password"] >> role=textbox'); + } + + async goto() { + await new UnlockPasswordInfoPanel(this.page).goto(); + await new UnlockPasswordInfoPanel(this.page).clickNextButton(); + } + async fillPassword(password: string) { + await this.passwordInput.fill(password); + } + async fillConfirmPassword(password: string) { + await this.confirmPasswordInput.fill(password); + } + async clickNextButton() { + await new OnboardingBasePage(this.page).nextButton.click(); + } +} diff --git a/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-8-unlock-password-success-panel.ts b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-8-unlock-password-success-panel.ts new file mode 100644 index 000000000000..56bf89af9d95 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-8-unlock-password-success-panel.ts @@ -0,0 +1,25 @@ +import { Locator, Page } from "@playwright/test"; +import { OnboardingBasePage } from "../onboarding-base-page"; +import { UnlockPasswordInputPanel } from "./step-7-unlock-password-input-panel"; + +export class UnlockPasswordSuccessPanel { + page: Page; + jumpToDiscoveryButton: Locator; + checkMyAccountButton: Locator; + + constructor(page: Page) { + this.page = page; + } + + async goto() { + await new UnlockPasswordInputPanel(this.page).goto(); + await new UnlockPasswordInputPanel(this.page).fillPassword( + new OnboardingBasePage(this.page).password + ); + await new UnlockPasswordInputPanel(this.page).confirmPasswordInput.click(); + await new UnlockPasswordInputPanel(this.page).fillConfirmPassword( + new OnboardingBasePage(this.page).password + ); + await new UnlockPasswordInputPanel(this.page).clickNextButton(); + } +} diff --git a/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/step-1-get-started.ts b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/step-1-get-started.ts new file mode 100644 index 000000000000..b5f36f696fb8 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/step-1-get-started.ts @@ -0,0 +1,30 @@ +import { Locator, Page } from "@playwright/test"; +import { AppBarPage } from "../app-bar-page"; + +export class GetStartedPanel { + createNewCatalystKeychain: Locator; + recoverCatalystKeychain: Locator; + page: Page; + + constructor(page: Page) { + this.page = page; + this.createNewCatalystKeychain = page.getByRole("group", { + name: "Create a new Catalyst Keychain On this device", + }); + this.recoverCatalystKeychain = page.getByRole("group", { + name: "Recover your Catalyst Keychain On this device", + }); + } + + async goto() { + await new AppBarPage(this.page).GetStartedBtnClick(); + } + + async clickCreateNewCatalystKeychain() { + await this.createNewCatalystKeychain.click(); + } + + async clickRecoverCatalystKeychain() { + await this.recoverCatalystKeychain.click(); + } +} diff --git a/catalyst_voices/apps/voices/e2e_tests/playwright.config.ts b/catalyst_voices/apps/voices/e2e_tests/playwright.config.ts new file mode 100644 index 000000000000..badc7bb27012 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/playwright.config.ts @@ -0,0 +1,29 @@ +import { defineConfig, devices } from "@playwright/test"; + +export default defineConfig({ + testDir: "./tests", + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: 3, + workers: 1, + use: { + ignoreHTTPSErrors: true, + screenshot: "only-on-failure", + trace: "on", + video: "on", + }, + + reporter: "html", + timeout: 120 * 1000, + projects: [ + { + name: "chrome", + use: { + ...devices["Desktop Chrome"], + launchOptions: { + channel: "chrome", + }, + }, + }, + ], +}); diff --git a/catalyst_voices/apps/voices/e2e_tests/setup-ext.ts b/catalyst_voices/apps/voices/e2e_tests/setup-ext.ts new file mode 100644 index 000000000000..fda3971211b4 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/setup-ext.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"; + +export 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}`, + ], + }); + 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.goto("/"); + await page.waitForTimeout(4000); + const [walletPopup] = await Promise.all([ + browser.waitForEvent("page"), + page.locator('//*[text()="Enable wallet"]').first().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 + **/ +export 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/apps/voices/e2e_tests/test-fixtures.ts b/catalyst_voices/apps/voices/e2e_tests/test-fixtures.ts new file mode 100644 index 000000000000..dcc1ec0f5d06 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/test-fixtures.ts @@ -0,0 +1,27 @@ +import { test as base, BrowserContext } from "@playwright/test"; + +import { getDynamicUrlInChrome, installExtension } from "./setup-ext"; +import { onboardWallet, WalletConfig } from "./utils/wallets/walletUtils"; + +type MyFixtures = { + restoreWallet: (walletConfig: WalletConfig) => Promise; +}; + +export const test = base.extend({ + restoreWallet: async ({}, use) => { + const restoreWalletFn = 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; + }; + + // Provide the function to the test + await use(restoreWalletFn); + }, +}); diff --git a/catalyst_voices/apps/voices/e2e_tests/tests/onboarding.spec.ts b/catalyst_voices/apps/voices/e2e_tests/tests/onboarding.spec.ts new file mode 100644 index 000000000000..01e661b44d9f --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/tests/onboarding.spec.ts @@ -0,0 +1,159 @@ +import { WalletListPanel } from "../pageobject/onboarding/create-flow/step-16-wallet-list"; +import { WalletDetectionPanel } from "../pageobject/onboarding/create-flow/step-18-wallet-detection"; +import { OnboardingBasePage } from "../pageobject/onboarding/onboarding-base-page"; +import { UnlockPasswordSuccessPanel } from "../pageobject/onboarding/restore-flow/step-8-unlock-password-success-panel"; +import { test } from "../test-fixtures"; +import { walletConfigs } from "../utils/walletConfigs"; +import { WalletConfig } from "../utils/wallets/walletUtils"; + +const walletConfig: WalletConfig = walletConfigs[3]; + +// do some non-happy flows (wrong seed phrase, cancel connection, no wallet, etc) +test( + "Create keychain and link wallet for " + walletConfig.extension.Name, + async ({ restoreWallet }) => { + const browser = await restoreWallet(walletConfig); + const page = browser.pages()[0]; + + // await new WalletListPage(page).clickEnableWallet(walletConfig.extension.Name); + await page.goto("http://localhost:52199/m4/discovery"); + await new WalletListPanel(page).goto(); + const [walletPopup] = await Promise.all([ + browser.waitForEvent("page"), + new WalletListPanel(page).clickYoroiWallet(), + ]); + await walletPopup.locator("div.ConnectedWallet_wrapper").click(); + await page.waitForTimeout(2000); + await new WalletDetectionPanel(page).clickSelectRolesBtn(); + await new OnboardingBasePage(page).nextButton.click(); + await page.locator('role=button[name="reviewRegTransaction"]').click(); + const [signPopup] = await Promise.all([ + browser.waitForEvent("page"), + page.locator('role=button[name="SignBtn"]').click({ delay: 100 }), + ]); + await signPopup.locator("#walletPassword").fill(walletConfig.password); + await signPopup.locator("#confirmButton").click(); + } +); + +test("Refactored restore flow demo", async ({ page }) => { + await new UnlockPasswordSuccessPanel(page).goto(); +}); +test("Refactored create flow demo", async ({ page }) => { + await new WalletListPanel(page).goto(); +}); + +// test("Restore flow demo", async ({ page }) => { +// const nextBtnGroup = page.getByRole("group", { name: "NextButton-test" }); +// const nextButton = nextBtnGroup.locator('role=button[name="Next"]'); +// const setUnlockPasswordGroup = page.getByRole("group", { +// name: "SetUnlockPasswordButton", +// }); +// const setUnlockPasswordBtn = setUnlockPasswordGroup.locator("role=button"); +// const password = "bera1234"; + +// await page.getByRole("button", { name: "GetStartedButton-test" }).click(); +// await page +// .getByRole("group", { +// name: "Recover your Catalyst Keychain On this device", +// }) +// .click(); +// await page +// .getByRole("group", { +// name: "Restore seedphrase Restore/Upload with 12-word seed phrase", +// }) +// .click(); +// await nextButton.click(); +// await nextButton.click(); + +// //Wait for the set unlock password button to be enabled +// const setUnlockPasswordBtnIsDisabled = +// await setUnlockPasswordBtn.getAttribute("aria-disabled"); +// if (setUnlockPasswordBtnIsDisabled === "true") { +// await setUnlockPasswordBtn.waitFor({ +// state: "visible", +// timeout: 5000, +// }); +// } +// await setUnlockPasswordBtn.click(); +// await nextButton.click(); + +// await page +// .locator('role=group[name="Enter password"] >> role=textbox') +// .fill(password); +// await page +// .locator('role=group[name="Confirm password"] >> role=textbox') +// .click(); +// await page +// .locator('role=group[name="Confirm password"] >> role=textbox') +// .fill(password); + +// //Wait for the next button to be enabled +// const nextBtnIsDisabled = await nextButton.getAttribute("aria-disabled"); +// if (nextBtnIsDisabled === "true") { +// await nextButton.waitFor({ +// state: "visible", +// timeout: 5000, +// }); +// } +// await nextButton.click(); +// await page.locator('role=button[name="Go to account"]').click(); +// await page.locator('role=button[name="RemoveKeychainButton"]').click(); +// await page.getByLabel("VoicesTextField Enter phrase").fill("Remove Keychain"); +// await page.waitForTimeout(200); +// await page +// .getByRole("button", { name: "DeleteKeychainContinueButton" }) +// .click(); +// await page.getByRole("button", { name: "closebtn-test" }).click(); +// }); + +// test("Create flow demo", async ({ page }) => { +// const nextBtnGroup = page.getByRole("group", { name: "NextButton-test" }); +// const nextButton = nextBtnGroup.locator('role=button[name="Next"]'); +// const checkbox = page.getByRole("checkbox"); +// const setUnlockPasswordGroup = page.getByRole("group", { +// name: "SetUnlockPasswordButton", +// }); +// const setUnlockPasswordBtn = setUnlockPasswordGroup.locator("role=button"); +// const password = "bera1234"; + +// await page.getByRole("button", { name: "GetStartedButton-test" }).click(); +// await page +// .getByRole("group", { +// name: "Create a new Catalyst Keychain On this device", +// }) +// .click(); +// await page +// .getByRole("button", { +// name: "CreateBaseProfileNext-test", +// }) +// .click(); +// await nextButton.click(); +// await nextButton.click(); +// await page.getByRole("button", { name: "CreateKeychain-test" }).click(); +// await page.getByRole("button", { name: "CreateKeychainNow-test" }).click(); +// await nextButton.click(); +// const checkboxIsChecked = await checkbox.getAttribute("aria-checked"); +// if (checkboxIsChecked === "false") { +// await checkbox.click(); +// } +// await nextButton.click(); +// await nextButton.click(); +// await nextButton.click(); +// await nextButton.click(); +// await nextButton.click(); + +// await page +// .locator('role=group[name="Enter password"] >> role=textbox') +// .fill(password); +// await page.waitForTimeout(1200); +// await page +// .locator('role=group[name="Confirm password"] >> role=textbox') +// .click(); +// await page +// .locator('role=group[name="Confirm password"] >> role=textbox') +// .fill(password); +// await nextButton.click(); +// await page.getByRole("button", { name: "LinkWalletAndRoles-test" }).click(); +// await page.getByRole("button", { name: "closebtn-test" }).click(); +// }); diff --git a/catalyst_voices/apps/voices/e2e_tests/utils/extensionDownloader.ts b/catalyst_voices/apps/voices/e2e_tests/utils/extensionDownloader.ts new file mode 100644 index 000000000000..d448035c5373 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/utils/extensionDownloader.ts @@ -0,0 +1,172 @@ +/* cspell:disable */ +import unzip from "@tomjs/unzip-crx"; +import fs, { promises as fsPromises } from "fs"; +import nodeFetch from "node-fetch"; +import * as os from "os"; +import path from "path"; +import { pipeline } from "stream/promises"; +import { BrowserExtensionName, getBrowserExtension } from "./extensions"; + +interface PlatformInfo { + os: string; + arch: string; + nacl_arch: string; +} + +export class ExtensionDownloader { + private extensionsDir: string; + + constructor() { + 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 { + const extensionId = getBrowserExtension(extensionName).Id; + const extensionPath = path.join(this.extensionsDir, extensionId); + + // Check if the extension has already been downloaded + if (fs.existsSync(extensionPath)) { + console.log(`Extension already exists at: ${extensionPath}`); + return extensionPath; + } + + // Download the extension + if (extensionName === BrowserExtensionName.Nufi) { + const zipPath = await this.downloadNufiExtension(); + await this.extractExtension(zipPath, extensionPath); + } else { + const crxPath = await this.downloadExtension(extensionName); + await this.extractExtension(crxPath, extensionPath); + } + // Extract the extension + + return extensionPath; + } + + private async downloadNufiExtension(): Promise { + const url = + "https://assets.nu.fi/extension/testnet/nufi-cwe-testnet-latest.zip"; + const filePath = path.join( + this.extensionsDir, + "nufi-cwe-testnet-latest.zip" + ); + + // Ensure the download directory exists + await fsPromises.mkdir(this.extensionsDir, { recursive: true }); + + // Fetch the extension + const res = await nodeFetch(url); + if (!res.ok) { + throw new Error(`Failed to download extension: ${res.statusText}`); + } + + // Stream the response directly to a file + const fileStream = fs.createWriteStream(filePath); + await pipeline(res.body, fileStream); + + console.log(`Extension has been downloaded to: ${filePath}`); + return filePath; + } + + private async extractExtension( + extensionPath: string, + extractPath: string + ): Promise { + // Ensure the extraction directory exists + await fsPromises.mkdir(extractPath, { recursive: true }); + + // Use unzip-crx to extract the CRX file + try { + await unzip(extensionPath, extractPath); + console.log(`Extension has been extracted to: ${extractPath}`); + } catch (error) { + console.error(`Failed to extract extension: ${(error as Error).message}`); + throw error; + } + } + + private async downloadExtension( + extensionName: BrowserExtensionName + ): Promise { + const extensionId = getBrowserExtension(extensionName).Id; + const url = this.getCrxUrl(extensionName); + + // Ensure the download directory exists + await fsPromises.mkdir(this.extensionsDir, { recursive: true }); + + const filePath = path.join(this.extensionsDir, `${extensionId}.crx`); + + // Fetch the extension + const res = await nodeFetch(url); + if (!res.ok) { + throw new Error(`Failed to download extension: ${res.statusText}`); + } + + // Stream the response directly to a file + const fileStream = fs.createWriteStream(filePath); + await pipeline(res.body, fileStream); + + console.log(`Extension has been downloaded to: ${filePath}`); + return filePath; + } + + private getCrxUrl(extensionName: BrowserExtensionName): string { + 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"; + 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"; + } + + // 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"; + + return { os: osName, arch: archName, os_arch, nacl_arch: naclArch }; + } +} diff --git a/catalyst_voices/apps/voices/e2e_tests/utils/extensions.ts b/catalyst_voices/apps/voices/e2e_tests/utils/extensions.ts new file mode 100644 index 000000000000..7c9b6a35337c --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/utils/extensions.ts @@ -0,0 +1,54 @@ +export interface BrowserExtension { + Name: BrowserExtensionName; + Id: string; + HomeUrl: string; +} + +export enum BrowserExtensionName { + Lace = "Lace", + Typhon = "Typhon", + Eternl = "Eternl", + Yoroi = "Yoroi", + Nufi = "Nufi", +} +/* cspell: disable */ +export const browserExtensions: BrowserExtension[] = [ + { + Name: BrowserExtensionName.Lace, + Id: "gafhhkghbfjjkeiendhlofajokpaflmk", + HomeUrl: "app.html#/setup", + }, + { + Name: BrowserExtensionName.Typhon, + Id: "kfdniefadaanbjodldohaedphafoffoh", + HomeUrl: "tab.html#/wallet/access/", + }, + { + Name: BrowserExtensionName.Eternl, + Id: "kmhcihpebfmpgmihbkipmjlmmioameka", + HomeUrl: "index.html#/app/preprod/welcome", + }, + { + Name: BrowserExtensionName.Yoroi, + Id: "poonlenmfdfbjfeeballhiibknlknepo", + HomeUrl: "main_window.html#", + }, + { + Name: BrowserExtensionName.Nufi, + Id: "hbklpdnlgiadjhdadfnfmemmklbopbcm", + HomeUrl: "/index.html#", + }, +]; +/* cspell: enable */ + +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; +}; diff --git a/catalyst_voices/apps/voices/e2e_tests/utils/walletConfigs.ts b/catalyst_voices/apps/voices/e2e_tests/utils/walletConfigs.ts new file mode 100644 index 000000000000..ce1e11a8f04e --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/utils/walletConfigs.ts @@ -0,0 +1,137 @@ +import { BrowserExtensionName, getBrowserExtension } from "./extensions"; +import { WalletConfig } from "./wallets/walletUtils"; + +export const walletConfigs: WalletConfig[] = [ + { + id: "1", + extension: getBrowserExtension(BrowserExtensionName.Lace), + seed: [ + "stomach", + "horn", + "rail", + "afraid", + "flip", + "also", + "abandon", + "speed", + "chaos", + "daring", + "soon", + "soft", + "okay", + "online", + "benefit", + ], + username: "test123", + password: "test12345678@", + cipBridge: ["cip-95"], + }, + { + id: "2", + extension: getBrowserExtension(BrowserExtensionName.Typhon), + seed: [ + "stomach", + "horn", + "rail", + "afraid", + "flip", + "also", + "abandon", + "speed", + "chaos", + "daring", + "soon", + "soft", + "okay", + "online", + "benefit", + ], + 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"], + }, + { + id: "4", + extension: getBrowserExtension(BrowserExtensionName.Yoroi), + seed: [ + "stomach", + "horn", + "rail", + "afraid", + "flip", + "also", + "abandon", + "speed", + "chaos", + "daring", + "soon", + "soft", + "okay", + "online", + "benefit", + ], + username: "test123", + password: "test12345678@!!", + cipBridge: ["cip-95"], + }, + { + id: "5", + extension: getBrowserExtension(BrowserExtensionName.Nufi), + seed: [ + "stomach", + "horn", + "rail", + "afraid", + "flip", + "also", + "abandon", + "speed", + "chaos", + "daring", + "soon", + "soft", + "okay", + "online", + "benefit", + ], + username: "test123", + password: "test12345678@!!", + cipBridge: ["cip-95"], + }, +]; + +export const getWalletConfig = (id: string): WalletConfig => { + const walletConfig = walletConfigs.find( + (walletConfig) => walletConfig.id === id + ); + if (!walletConfig) { + throw new Error(`Wallet config with id ${id} not found`); + } + return walletConfig; +}; + +export const getWalletConfigs = (): WalletConfig[] => walletConfigs; diff --git a/catalyst_voices/apps/voices/e2e_tests/utils/wallets/eternlUtils.ts b/catalyst_voices/apps/voices/e2e_tests/utils/wallets/eternlUtils.ts new file mode 100644 index 000000000000..2b310fe343bb --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/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/apps/voices/e2e_tests/utils/wallets/laceUtils.ts b/catalyst_voices/apps/voices/e2e_tests/utils/wallets/laceUtils.ts new file mode 100644 index 000000000000..39318aab1fd4 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/utils/wallets/laceUtils.ts @@ -0,0 +1,115 @@ +import { expect, Page } from "@playwright/test"; +import { WalletConfig } from "./walletUtils"; + +const clickRestoreWalletButton = async (page: Page): Promise => { + const maxAttempts = 3; + + // Selector for the restore wallet button + const restoreWalletButtonSelector = '[data-testid="restore-wallet-button"]'; + + // Selector for an element that exists only on the next page + const nextPageSelector = '[data-testid="wallet-setup-step-btn-next"]'; + + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + try { + // Wait for the restore wallet button to be visible and enabled + const restoreWalletButton = page.locator(restoreWalletButtonSelector); + await restoreWalletButton.waitFor({ state: "visible", timeout: 5000 }); + await expect(restoreWalletButton).toBeEnabled(); + + // Click the restore wallet button and wait for the next page to load + await Promise.all([ + page.waitForSelector(nextPageSelector, { timeout: 10000 }), + restoreWalletButton.click(), + ]); + + // Verify that the next page has loaded by checking for a unique element + const nextPageElement = page.locator(nextPageSelector); + await nextPageElement.waitFor({ state: "visible", timeout: 5000 }); + + // If the next page is detected, exit the function + console.log("Successfully navigated to the next page."); + return; + } catch (error) { + if (attempt === maxAttempts) { + // If it's the last attempt, rethrow the error + throw new Error( + `Failed to click 'restore-wallet-button' after ${maxAttempts} attempts: ${error}` + ); + } else { + // Log the attempt and retry + console.warn( + `Attempt ${attempt} to click 'restore-wallet-button' failed. Retrying...` + ); + // Optionally, you can add a short delay before retrying + await page.waitForTimeout(1000); + } + } + } +}; + +/* + * This handles the situation where after clicking restore Lace sometimes leads directly to recovery phrase page + * and sometimes leads to a page where the user has to click on the recovery phrase button to get to the recovery phrase page + */ +const handleNextPage = async (page: Page): Promise => { + const title = await page.getByTestId("wallet-setup-step-title").textContent(); + if (title === "Choose recovery method") { + await page.locator('[data-testid="wallet-setup-step-btn-next"]').click(); + } else { + return; + } +}; + +export const onboardLaceWallet = async ( + page: Page, + walletConfig: WalletConfig +): Promise => { + await page.locator('[data-testid="analytics-accept-button"]').click(); + await clickRestoreWalletButton(page); + await handleNextPage(page); + await page.getByTestId("recovery-phrase-15").click(); + const seedPhrase = walletConfig.seed; + for (let i = 0; i < seedPhrase.length; i++) { + const ftSeedPhraseSelector = `//*[@id="mnemonic-word-${i + 1}"]`; + await page.locator(ftSeedPhraseSelector).fill(seedPhrase[i]); + } + await page.getByRole("button", { name: "Next" }).click(); + await page.getByTestId("wallet-name-input").fill(walletConfig.username); + await page + .getByTestId("wallet-password-verification-input") + .fill(walletConfig.password); + await page + .getByTestId("wallet-password-confirmation-input") + .fill(walletConfig.password); + await page.getByRole("button", { name: "Open wallet" }).click(); + //Lace is very slow at loading + await page + .getByTestId("profile-dropdown-trigger-menu") + .click({ timeout: 300000 }); + await page + .getByTestId("header-menu") + .getByTestId("header-menu-network-choice-container") + .click(); + await page + .getByTestId("header-menu") + .getByTestId("network-preprod-radio-button") + .click(); + await page.waitForTimeout(4000); +}; + +export const signLaceData = async ( + page: Page, + password: string, + isCorrectPassword: boolean +): Promise => { + await page.getByRole("button", { name: "Confirm" }).click(); + await page.getByTestId("password-input").fill(password); + await page.getByTestId("sign-transaction-confirm").click(); + if (!isCorrectPassword) { + await page.close(); + return; + } + await page.waitForTimeout(2000); + await page.close(); +}; diff --git a/catalyst_voices/apps/voices/e2e_tests/utils/wallets/nufiUtils.ts b/catalyst_voices/apps/voices/e2e_tests/utils/wallets/nufiUtils.ts new file mode 100644 index 000000000000..fd1c058b179a --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/utils/wallets/nufiUtils.ts @@ -0,0 +1,43 @@ +import { Page } from "playwright"; +import { WalletConfig } from "./walletUtils"; + +export const onboardNufiWallet = async ( + page: Page, + walletConfig: WalletConfig +): Promise => { + await page.locator("//*[@data-testid='RestorePageIcon']").click(); + const seedPhrase = walletConfig.seed; + for (let i = 0; i < 15; i++) { + await page + .locator(`//div[@rtl-data-test-id='mnemonic-field-input-${i}']//input`) + .fill(seedPhrase[i]); + } + await page + .locator("//span[@data-test-id='terms-and-conditions-checkbox']/input") + .check(); + await page.locator("button:has-text('Continue')").click(); + await page + .locator("//input[@rtl-data-test-id='wallet-name-field']") + .fill(walletConfig.username); + await page.locator("//input[@id=':rg:']").fill(walletConfig.password); + await page.locator("//input[@id=':rh:']").fill(walletConfig.password); + await page.locator("button:has-text('Continue')").click(); + await page.locator("button:has-text('Recover')").click(); + await page.locator("button:has-text('Go to Wallet')").click(); +}; + +export const connectWalletPopup = async (page: Page): Promise => { + await page.locator("button:has-text('Connect')").click(); +}; + +export const signNufiData = async ( + page: Page, + password: string, + isCorrectPassword: boolean +): Promise => { + if (!isCorrectPassword) { + await page.locator("button:has-text('Reject')").click(); + return; + } + await page.locator("button:has-text('Sign')").click(); +}; diff --git a/catalyst_voices/apps/voices/e2e_tests/utils/wallets/typhonUtils.ts b/catalyst_voices/apps/voices/e2e_tests/utils/wallets/typhonUtils.ts new file mode 100644 index 000000000000..416ad09ff3c3 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/utils/wallets/typhonUtils.ts @@ -0,0 +1,34 @@ +import { Page } from "playwright"; +import { WalletConfig } from "./walletUtils"; + +export const onboardTyphonWallet = async (page: Page, walletConfig: WalletConfig): Promise => { + //switch to preprod network + await page.locator('button#headlessui-menu-button-1').click(); + await page.locator('button#headlessui-menu-item-6').click(); + //import wallet + await page.getByRole('button', { name: 'Import' }).click(); + await page.getByPlaceholder('Wallet Name').fill(walletConfig.username); + await page.getByPlaceholder('Password', { exact: true }).fill(walletConfig.password); + await page.getByPlaceholder('Confirm Password', { exact: true }).fill(walletConfig.password); + await page.locator('input#termsAndConditions').click(); + await page.getByRole('button', { name: 'Continue' }).click(); + + // Input seed phrase + const seedPhrase = walletConfig.seed; + for (let i = 0; i < seedPhrase.length; i++) { + const ftSeedPhraseSelector = `(//input[@type='text'])[${i + 1}]`; + await page.locator(ftSeedPhraseSelector).fill(seedPhrase[i]); + } + + await page.locator('//*[@id="app"]/div/div/div[3]/div/div[2]/div/div/div/div[1]/div[1]/div[1]/span[1]').click(); + await page.getByRole('button', { name: 'Unlock Wallet' }).click(); +}; + +export const signTyphonData = async (signTab: Page, password: string, isCorrectPassword: boolean): Promise => { + await signTab.getByRole('button', { name: 'Sign' }).click(); + await signTab.getByPlaceholder('Password', { exact: true }).fill(password); + if (!isCorrectPassword) { + return + } + await signTab.getByRole('button', { name: 'confirm' }).click(); +} \ No newline at end of file diff --git a/catalyst_voices/apps/voices/e2e_tests/utils/wallets/walletUtils.ts b/catalyst_voices/apps/voices/e2e_tests/utils/wallets/walletUtils.ts new file mode 100644 index 000000000000..d2601dd37dad --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/utils/wallets/walletUtils.ts @@ -0,0 +1,120 @@ +import { BrowserContext, Locator, Page } from "@playwright/test"; +import { BrowserExtension, BrowserExtensionName } from "../extensions"; +import { onboardEternlWallet, signEternlData } from "./eternlUtils"; +import { onboardLaceWallet, signLaceData } from "./laceUtils"; +import { onboardNufiWallet, signNufiData } from "./nufiUtils"; +import { onboardTyphonWallet, signTyphonData } from "./typhonUtils"; +import { onboardYoroiWallet, signYoroiData } from "./yoroiUtils"; + +export interface WalletConfig { + id: string; + extension: BrowserExtension; + seed: string[]; + username: string; + password: string; + cipBridge: string[]; +} + +export const onboardWallet = async ( + page: Page, + walletConfig: WalletConfig +): Promise => { + switch (walletConfig.extension.Name) { + case BrowserExtensionName.Typhon: + await onboardTyphonWallet(page, walletConfig); + break; + case BrowserExtensionName.Lace: + await onboardLaceWallet(page, walletConfig); + break; + case BrowserExtensionName.Eternl: + await onboardEternlWallet(page, walletConfig); + break; + case BrowserExtensionName.Yoroi: + await onboardYoroiWallet(page, walletConfig); + break; + case BrowserExtensionName.Nufi: + await onboardNufiWallet(page, walletConfig); + break; + default: + throw new Error("Wallet not in use"); + } + await page.waitForTimeout(2000); +}; + +//TODO: move specific cases to specific utils for wallets +export const allowExtension = async ( + tab: Page, + wallet: string +): Promise => { + switch (wallet) { + case "Typhon": + await tab.getByRole("button", { name: "Allow" }).click(); + break; + case "Lace": + 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; + case "Yoroi": + await tab.locator("button:has(#connectedWalletName)").click(); + break; + case "Nufi": + await tab.locator("//input[@type='password']").fill("test12345678@!!"); + await tab.locator("button:has-text('Connect')").click(); + await tab.locator("button:has-text('Connect')").click(); + break; + default: + throw new Error("Wallet not in use"); + } +}; + +const getWalletPopup = async ( + browser: BrowserContext, + triggerLocatorCLick: Locator +): Promise => { + if (browser.pages().length > 1) { + await triggerLocatorCLick.click(); + await browser.pages()[0].waitForTimeout(2000); + await browser + .pages() + [browser.pages().length - 1].waitForLoadState("domcontentloaded"); + return browser.pages()[browser.pages().length - 1]; + } else { + const [page] = await Promise.all([ + browser.waitForEvent("page"), + triggerLocatorCLick.click(), + ]); + await page.waitForLoadState("domcontentloaded"); + return page; + } +}; + +export const signWalletPopup = async ( + browser: BrowserContext, + walletConfig: WalletConfig, + locatorTrigger: Locator, + isCorrectPassword = true +): Promise => { + const page = await getWalletPopup(browser, locatorTrigger); + switch (walletConfig.extension.Name) { + case BrowserExtensionName.Typhon: + await signTyphonData(page, walletConfig.password, isCorrectPassword); + break; + case BrowserExtensionName.Lace: + await signLaceData(page, walletConfig.password, isCorrectPassword); + break; + case BrowserExtensionName.Eternl: + await signEternlData(page, walletConfig.password, isCorrectPassword); + break; + case BrowserExtensionName.Yoroi: + await signYoroiData(page, walletConfig.password, isCorrectPassword); + break; + case BrowserExtensionName.Nufi: + await signNufiData(page, walletConfig.password, isCorrectPassword); + break; + default: + throw new Error("Wallet not in use"); + } +}; diff --git a/catalyst_voices/apps/voices/e2e_tests/utils/wallets/yoroiUtils.ts b/catalyst_voices/apps/voices/e2e_tests/utils/wallets/yoroiUtils.ts new file mode 100644 index 000000000000..110f3d0f4516 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/utils/wallets/yoroiUtils.ts @@ -0,0 +1,58 @@ +import { Page } from "playwright"; +import { WalletConfig } from "./walletUtils"; + +export const onboardYoroiWallet = async ( + page: Page, + walletConfig: WalletConfig +): Promise => { + /* cspell: disable */ + await page.locator("#initialPage-tosAgreement-checkbox").check(); + await page.locator("#initialPage-continue-button").click(); + await page.locator("#startupAnalytics-accept-button").click(); + await page.locator("#somewhere-checkbox").check(); + await page.locator('button:has-text("Continue")').click(); + + await page + .locator(".UriPromptForm_buttonsWrapper button.MuiButton-secondary") + .click(); + + await page.locator("#restoreWalletButton").click(); + await page.locator("#fifteenWordsButton").click(); + const seedPhrase = walletConfig.seed; + for (let i = 0; i < seedPhrase.length; i++) { + const ftSeedPhraseSelector = `#downshift-${i}-input`; + await page.locator(ftSeedPhraseSelector).fill(seedPhrase[i]); + } + // await page.locator('button:has-text("Cardano Preprod Testnet")').click(); + + await page.locator(`#downshift-${seedPhrase.length - 1}-input`).blur(); + await page.locator("#primaryButton").click(); + await page.locator("#infoDialogContinueButton").click(); + await page.locator("#walletNameInput-label").fill(walletConfig.username); + await page.locator("#walletPasswordInput-label").fill(walletConfig.password); + await page.locator("#repeatPasswordInput-label").fill(walletConfig.password); + await page.locator("#primaryButton").click(); + await page.locator("#dialog-gotothewallet-button").click(); + await page.locator('xpath=//*[@id="sidebar.settings"]').click(); + await page.locator('button:has-text("I Understand")').click(); + await page.locator('button:has-text("SWITCH NETWORK")').click(); + await page.locator('xpath=//*[@id="selectedNetwork--55"]').click(); + await page.locator("#selectNetwork-network_250-menuItem").click(); + await page.locator("#switchNetworkDialog-apply-button").click(); + await page.locator("#somewhere-checkbox").click(); + await page.locator('button:has-text("Continue")').click(); +}; +/* cspell: enable */ + +export const signYoroiData = async ( + signTab: Page, + password: string, + isCorrectPassword: boolean +): Promise => { + await signTab.locator("#walletPassword").fill(password); + await signTab.locator("#confirmButton").click(); + if (!isCorrectPassword) { + await signTab.locator("#cancelButton").click(); + return; + } +}; diff --git a/catalyst_voices/apps/voices/e2e_tests/walletListPage.ts b/catalyst_voices/apps/voices/e2e_tests/walletListPage.ts new file mode 100644 index 000000000000..6cb38463f183 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/walletListPage.ts @@ -0,0 +1,22 @@ +import { 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 { + if (walletName === BrowserExtensionName.Nufi) { + const [walletPopup] = await Promise.all([ + this.page.context().waitForEvent("page"), + await this.page.locator('//*[text()="Enable wallet"]').first().click(), + ]); + await walletPopup.locator("button:has-text('Connect')").click(); + } else { + // await this.page.locator('//*[text()="Enable wallet"]').first().click(); + } + await this.page.waitForTimeout(2000); + } +} diff --git a/catalyst_voices/apps/voices/integration_test/pageobject/proposals_page_sidebar_panel.dart b/catalyst_voices/apps/voices/integration_test/pageobject/proposals_page_sidebar_panel.dart new file mode 100644 index 000000000000..c9d04409f884 --- /dev/null +++ b/catalyst_voices/apps/voices/integration_test/pageobject/proposals_page_sidebar_panel.dart @@ -0,0 +1,64 @@ +import 'package:catalyst_voices/widgets/common/simple_tree_view.dart'; +import 'package:flutter/material.dart'; +import 'package:patrol_finders/patrol_finders.dart'; + +import '../proposal_menu_branch_classes/proposals_sidebar_branch_classes.dart'; +class ProposalsPageSidebarPanel { + ProposalsPageSidebarPanel(this.$); + late PatrolTester $; + late final overview = Overview($); + + final proposalSetupBranch = const ValueKey('Segment[1]NodeMenu'); + final proposalSummaryBranch = const ValueKey('Segment[2]NodeMenu'); + final campaignCategoryBranch = const ValueKey('Segment[3]NodeMenu'); + final themeSelectionBranch = const ValueKey('Segment[4]NodeMenu'); + final yourProjectAndSolutionBranch = const ValueKey('Segment[5]NodeMenu'); + final milestonesBranch = const ValueKey('Segment[6]NodeMenu'); + final finalPitchBranch = const ValueKey('Segment[7]NodeMenu'); + final acknowledgementsBranch = const ValueKey('Segment[8]NodeMenu'); + final commentsBranch = const ValueKey('Segment[9]NodeMenu'); + final metadata=const ValueKey('NodeMenuoverview.metadataRowKey'); + final proposalTitle = const ValueKey('NodeMenusetup.titleRowKey'); + final proposer=const ValueKey('NodeMenusetup.proposerRowKey'); + + + + Future clickProposalSetupBranch() async { + await $(proposalSetupBranch).$(SimpleTreeViewRootRow).tap(); + } + + Future clickProposalSummaryBranch() async { + await $(proposalSummaryBranch).$(SimpleTreeViewRootRow).tap(); + } + + Future clickCampaignCategoryBranch() async { + await $(campaignCategoryBranch).$(SimpleTreeViewRootRow).tap(); + } + + Future clickThemeSelectionBranch() async { + await $(themeSelectionBranch).$(SimpleTreeViewRootRow).tap(); + } + + Future clickYourProjectAndSolutionBranch() async { + await $(yourProjectAndSolutionBranch).$(SimpleTreeViewRootRow).tap(); + } + + Future clickMilestonesBranch() async { + await $(milestonesBranch).$(SimpleTreeViewRootRow).tap(); + } + + Future clickFinalPitchBranch() async { + await $(finalPitchBranch).$(SimpleTreeViewRootRow).tap(); + } + + Future clickAcknowledgementsBranch() async { + await $(acknowledgementsBranch).$(SimpleTreeViewRootRow).tap(); + } + + Future clickCommentsBranch() async { + await $(commentsBranch).$(SimpleTreeViewRootRow).tap(); + } + + + +} diff --git a/catalyst_voices/apps/voices/integration_test/proposal_menu_branch_classes/proposals_sidebar_branch_classes.dart b/catalyst_voices/apps/voices/integration_test/proposal_menu_branch_classes/proposals_sidebar_branch_classes.dart new file mode 100644 index 000000000000..49795eb64c99 --- /dev/null +++ b/catalyst_voices/apps/voices/integration_test/proposal_menu_branch_classes/proposals_sidebar_branch_classes.dart @@ -0,0 +1,44 @@ +import 'package:catalyst_voices/widgets/common/simple_tree_view.dart'; +import 'package:catalyst_voices_assets/catalyst_voices_assets.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:patrol_finders/patrol_finders.dart'; + +class Overview { + Overview(this.$); + late PatrolTester $; + + late final metadata = Metadata($); + final overviewBranch = const ValueKey('Segment[0]NodeMenu'); + + Future tap() async { + await $(overviewBranch).$(SimpleTreeViewRootRow).tap(); + } + + bool isColapsed() { + try { + return $(checkSvgBytesLoaderPath('assets/icons/node-open.svg')).exists; + } catch (_) { + return false; + } + } + + Finder checkSvgBytesLoaderPath(String path) { + return find.byWidgetPredicate( + (widget) => + widget is CatalystSvgPicture && + (widget.bytesLoader as dynamic).assetName ==path, + ); + } + +} + +class Metadata extends Overview{ + Metadata(super.$); + final metadataKey = const ValueKey('NodeMenuoverview.metadataRowKey'); + + @override + Future tap() async { + await $(metadataKey).$(SimpleTreeViewRootRow).tap(); + } +} diff --git a/catalyst_voices/apps/voices/integration_test/suites/proposals_test.dart b/catalyst_voices/apps/voices/integration_test/suites/proposals_test.dart index 49817a67f9c9..0b88b2622661 100644 --- a/catalyst_voices/apps/voices/integration_test/suites/proposals_test.dart +++ b/catalyst_voices/apps/voices/integration_test/suites/proposals_test.dart @@ -8,6 +8,7 @@ import 'package:patrol_finders/patrol_finders.dart'; import '../pageobject/discovery/campaign_hero_section_page.dart'; import '../pageobject/proposals_page.dart'; +import '../pageobject/proposals_page_sidebar_panel.dart'; void main() async { late final GoRouter router; @@ -18,9 +19,9 @@ void main() async { setUp(() async { await registerDependencies(config: const AppConfig()); - router.go(const ProposalsRoute().location); + router.go('const ProposalsRoute().location'); }); - +//http://localhost:54319/m4/proposal/067dae82-5c96-76f0-8000-70739e06c8d0?version=067dae82-5c96-76f0-8000-70739e06c8d0 tearDown(() async { await restartDependencies(); }); @@ -151,5 +152,21 @@ void main() async { await CampaignHeroSection($).campaignBriefTitleIsRenderedCorrectly(); }, ); + },skip: true,); + + group('Proposals sidebar -', () { + patrolWidgetTest( + 'overview selection tap', + (PatrolTester $) async { + final proposalsPageSidebarPanel = ProposalsPageSidebarPanel($); + await $.pumpWidgetAndSettle(App(routerConfig: router)); + await proposalsPageSidebarPanel.overview.tap(); + await proposalsPageSidebarPanel.overview.tap(); + await proposalsPageSidebarPanel.overview.tap(); + print(proposalsPageSidebarPanel.overview.isColapsed()); + + }, + ); }); + } diff --git a/catalyst_voices/apps/voices/lib/configs/main_qa.dart b/catalyst_voices/apps/voices/lib/configs/main_qa.dart index 49d48e6de413..910467cec689 100644 --- a/catalyst_voices/apps/voices/lib/configs/main_qa.dart +++ b/catalyst_voices/apps/voices/lib/configs/main_qa.dart @@ -1,5 +1,7 @@ import 'package:catalyst_voices/configs/bootstrap.dart'; +import 'package:flutter/semantics.dart'; void main() async { await bootstrapAndRun(); + SemanticsBinding.instance.ensureSemantics(); } diff --git a/catalyst_voices/apps/voices/lib/pages/account/delete_keychain_dialog.dart b/catalyst_voices/apps/voices/lib/pages/account/delete_keychain_dialog.dart index de69d0bb9956..371e5058a183 100644 --- a/catalyst_voices/apps/voices/lib/pages/account/delete_keychain_dialog.dart +++ b/catalyst_voices/apps/voices/lib/pages/account/delete_keychain_dialog.dart @@ -77,7 +77,12 @@ class _DeleteKeychainDialogState extends State { const SizedBox(height: 2), SizedBox( width: 300, - child: VoicesTextField( + child: + Semantics( + container: true, + textField: true, + label: 'DeleteKeychainTextField', + child: VoicesTextField( key: const Key('DeleteKeychainTextField'), controller: _textEditingController, onFieldSubmitted: _removeKeychain, @@ -90,19 +95,23 @@ class _DeleteKeychainDialogState extends State { .elevationsOnSurfaceNeutralLv1White, hintText: context.l10n.enterPhrase, ), - ), + )), ), ], ), const SizedBox(height: 24), Wrap( children: [ - VoicesFilledButton( + Semantics( + container: true, + button: true, + label: 'DeleteKeychainContinueButton', + child: VoicesFilledButton( key: const Key('DeleteKeychainContinueButton'), backgroundColor: Theme.of(context).colors.iconsError, onTap: _removeKeychain, child: Text(context.l10n.continueText), - ), + ),), const SizedBox(width: 8), VoicesTextButton.danger( onTap: () => Navigator.of(context).pop(), diff --git a/catalyst_voices/apps/voices/lib/pages/account/widgets/account_keychain_tile.dart b/catalyst_voices/apps/voices/lib/pages/account/widgets/account_keychain_tile.dart index 739c699733b2..813dcf13cf46 100644 --- a/catalyst_voices/apps/voices/lib/pages/account/widgets/account_keychain_tile.dart +++ b/catalyst_voices/apps/voices/lib/pages/account/widgets/account_keychain_tile.dart @@ -26,14 +26,19 @@ class _AccountKeychainTileState extends State { Widget build(BuildContext context) { return PropertyTile( title: context.l10n.catalystKeychain, - action: VoicesTextButton.danger( + action: + Semantics( + container: true, + button: true, + label: 'RemoveKeychainButton', + child: VoicesTextButton.danger( key: const Key('RemoveKeychainButton'), style: ButtonStyle( textStyle: WidgetStatePropertyAll(context.textTheme.labelSmall), ), onTap: _removeKeychain, child: Text(context.l10n.removeKeychain), - ), + )), child: VoicesTextField( key: const Key('AccountKeychainTextField'), controller: _controller, diff --git a/catalyst_voices/apps/voices/lib/pages/discovery/sections/campaign_hero.dart b/catalyst_voices/apps/voices/lib/pages/discovery/sections/campaign_hero.dart index 853bcd106f33..e04106ef0710 100644 --- a/catalyst_voices/apps/voices/lib/pages/discovery/sections/campaign_hero.dart +++ b/catalyst_voices/apps/voices/lib/pages/discovery/sections/campaign_hero.dart @@ -48,12 +48,16 @@ class _CampaignBrief extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - key: const Key('CampaignBriefTitle'), - context.l10n.heroSectionTitle, - style: Theme.of(context).textTheme.displaySmall?.copyWith( - color: ThemeBuilder.buildTheme().colorScheme.primary, - ), + Semantics( + label: context.l10n.projectCatalystDescription, + identifier: 'CampaignBriefTitle', + child: Text( + key: const Key('CampaignBriefTitle'), + context.l10n.heroSectionTitle, + style: Theme.of(context).textTheme.displaySmall?.copyWith( + color: ThemeBuilder.buildTheme().colorScheme.primary, + ), + ), ), const SizedBox(height: 32), Text( @@ -66,14 +70,20 @@ class _CampaignBrief extends StatelessWidget { const SizedBox(height: 32), Row( children: [ - VoicesFilledButton( - key: const Key('ViewProposalsBtn'), - onTap: () { - const ProposalsRoute().go(context); - }, - backgroundColor: ThemeBuilder.buildTheme().colorScheme.primary, - foregroundColor: ThemeBuilder.buildTheme().colorScheme.onPrimary, - child: Text(context.l10n.viewProposals), + Semantics( + container: true, + label: context.l10n.viewProposals, + identifier: 'ViewProposalsBtn', + child: VoicesFilledButton( + key: const Key('ViewProposalsBtn'), + onTap: () { + const ProposalsRoute().go(context); + }, + backgroundColor: ThemeBuilder.buildTheme().colorScheme.primary, + foregroundColor: + ThemeBuilder.buildTheme().colorScheme.onPrimary, + child: Text(context.l10n.viewProposals), + ), ), const SizedBox(width: 8), const _DiscoveryMyProposalsButton(), diff --git a/catalyst_voices/apps/voices/lib/pages/registration/create_account_progress/account_create_progress_panel.dart b/catalyst_voices/apps/voices/lib/pages/registration/create_account_progress/account_create_progress_panel.dart index 20ddf65579bc..861c9694ecf7 100644 --- a/catalyst_voices/apps/voices/lib/pages/registration/create_account_progress/account_create_progress_panel.dart +++ b/catalyst_voices/apps/voices/lib/pages/registration/create_account_progress/account_create_progress_panel.dart @@ -99,11 +99,16 @@ class _CreateKeychainButton extends StatelessWidget { @override Widget build(BuildContext context) { - return VoicesFilledButton( - key: const Key('CreateKeychainButton'), - onTap: onTap, - leading: VoicesAssets.icons.key.buildIcon(size: 18), - child: Text(context.l10n.accountCreationSplashTitle), + return Semantics( + container: true, + button: true, + label: 'CreateKeychain-test', // Stable test identifier + child: VoicesFilledButton( + key: const Key('CreateKeychainButton'), + onTap: onTap, + leading: VoicesAssets.icons.key.buildIcon(size: 18), + child: Text(context.l10n.accountCreationSplashTitle), + ), ); } } @@ -117,12 +122,16 @@ class _LinkWalletAndRolesButton extends StatelessWidget { @override Widget build(BuildContext context) { - return VoicesFilledButton( + return Semantics( + container: true, + button: true, + label: 'LinkWalletAndRoles-test', // Stable test identifier + child: VoicesFilledButton( key: const Key('LinkWalletAndRolesButton'), onTap: onTap, leading: VoicesAssets.icons.wallet.buildIcon(size: 18), child: Text(context.l10n.createKeychainLinkWalletAndRoles), - ); + ),); } } diff --git a/catalyst_voices/apps/voices/lib/pages/registration/create_base_profile/stage/instructions_panel.dart b/catalyst_voices/apps/voices/lib/pages/registration/create_base_profile/stage/instructions_panel.dart index 7c25affce5ec..9149433f6e6e 100644 --- a/catalyst_voices/apps/voices/lib/pages/registration/create_base_profile/stage/instructions_panel.dart +++ b/catalyst_voices/apps/voices/lib/pages/registration/create_base_profile/stage/instructions_panel.dart @@ -100,10 +100,14 @@ class _NextButton extends StatelessWidget { @override Widget build(BuildContext context) { - return VoicesFilledButton( + return Semantics( + container: true, + button: true, + label: 'CreateBaseProfileNext-test', // Stable test identifier + child: VoicesFilledButton( key: const Key('CreateBaseProfileNext'), onTap: () => RegistrationCubit.of(context).nextStep(), child: Text(context.l10n.createBaseProfileInstructionsNext), - ); + ),); } } diff --git a/catalyst_voices/apps/voices/lib/pages/registration/create_keychain/stage/splash_panel.dart b/catalyst_voices/apps/voices/lib/pages/registration/create_keychain/stage/splash_panel.dart index 905c0b5d7652..482286c8db67 100644 --- a/catalyst_voices/apps/voices/lib/pages/registration/create_keychain/stage/splash_panel.dart +++ b/catalyst_voices/apps/voices/lib/pages/registration/create_keychain/stage/splash_panel.dart @@ -18,11 +18,15 @@ class SplashPanel extends StatelessWidget { subtitle: Text(context.l10n.accountCreationSplashMessage), ), const Spacer(), - VoicesFilledButton( + Semantics( + container: true, + button: true, + label: 'CreateKeychainNow-test', // Stable test identifier + child: VoicesFilledButton( key: const Key('CreateKeychainButton'), child: Text(context.l10n.accountCreationSplashNextButton), onTap: () => RegistrationCubit.of(context).nextStep(), - ), + )), ], ); } diff --git a/catalyst_voices/apps/voices/lib/pages/registration/recover/recover_method_panel.dart b/catalyst_voices/apps/voices/lib/pages/registration/recover/recover_method_panel.dart index 42f8ac084dcc..27ae3ac060b0 100644 --- a/catalyst_voices/apps/voices/lib/pages/registration/recover/recover_method_panel.dart +++ b/catalyst_voices/apps/voices/lib/pages/registration/recover/recover_method_panel.dart @@ -48,18 +48,22 @@ class RecoverMethodPanel extends StatelessWidget { children: RegistrationRecoverMethod.values .map( (method) { - return RegistrationTile( - key: ValueKey(method), - icon: method._icon, - title: method._getTitle(context.l10n), - subtitle: method._getSubtitle(context.l10n), - onTap: () { - switch (method) { - case RegistrationRecoverMethod.seedPhrase: - RegistrationCubit.of(context) - .recoverWithSeedPhrase(); - } - }, + return Semantics( + container: true, + label: 'Restore seedphrase', + child: RegistrationTile( + key: ValueKey(method), + icon: method._icon, + title: method._getTitle(context.l10n), + subtitle: method._getSubtitle(context.l10n), + onTap: () { + switch (method) { + case RegistrationRecoverMethod.seedPhrase: + RegistrationCubit.of(context) + .recoverWithSeedPhrase(); + } + }, + ), ); }, ) diff --git a/catalyst_voices/apps/voices/lib/pages/registration/recover/seed_phrase/account_details_panel.dart b/catalyst_voices/apps/voices/lib/pages/registration/recover/seed_phrase/account_details_panel.dart index c3ad9e02affa..20f94611cdb9 100644 --- a/catalyst_voices/apps/voices/lib/pages/registration/recover/seed_phrase/account_details_panel.dart +++ b/catalyst_voices/apps/voices/lib/pages/registration/recover/seed_phrase/account_details_panel.dart @@ -165,13 +165,16 @@ class _Navigation extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - VoicesFilledButton( + Semantics( + container: true, + label: 'SetUnlockPasswordButton', + child: VoicesFilledButton( key: const Key('SetUnlockPasswordButton'), onTap: isNextEnabled ? () => RegistrationCubit.of(context).nextStep() : null, child: Text(context.l10n.recoveryAccountDetailsAction), - ), + ),), const SizedBox(height: 10), VoicesTextButton( key: const Key('RecoverDifferentKeychainButton'), diff --git a/catalyst_voices/apps/voices/lib/pages/registration/recover/seed_phrase/restored_panel.dart b/catalyst_voices/apps/voices/lib/pages/registration/recover/seed_phrase/restored_panel.dart index 6174b6d0a89f..993ecbe63380 100644 --- a/catalyst_voices/apps/voices/lib/pages/registration/recover/seed_phrase/restored_panel.dart +++ b/catalyst_voices/apps/voices/lib/pages/registration/recover/seed_phrase/restored_panel.dart @@ -36,17 +36,24 @@ class RestoredPanel extends StatelessWidget { ), const Spacer(), const SizedBox(height: 10), - VoicesFilledButton( - key: const Key('RecoverySuccessGoToDashboardButton'), - onTap: () => _redirectToDashboard(context), - child: Text(context.l10n.recoverySuccessGoToDashboard), + Semantics(container: true, + label: 'Go to dashboard', + child: VoicesFilledButton( + key: const Key('RecoverySuccessGoToDashboardButton'), + onTap: () => _redirectToDashboard(context), + child: Text(context.l10n.recoverySuccessGoToDashboard), + ), ), const SizedBox(height: 10), - VoicesTextButton( + Semantics( + container: true, + button: true, + label: 'Go to account', + child: VoicesTextButton( key: const Key('RecoverySuccessGoAccountButton'), onTap: () => _redirectToMyAccount(context), child: Text(context.l10n.recoverySuccessGoAccount), - ), + )), ], ); } diff --git a/catalyst_voices/apps/voices/lib/pages/registration/wallet_link/stage/intro_panel.dart b/catalyst_voices/apps/voices/lib/pages/registration/wallet_link/stage/intro_panel.dart index 512fe5f4b80e..61f52c62bd76 100644 --- a/catalyst_voices/apps/voices/lib/pages/registration/wallet_link/stage/intro_panel.dart +++ b/catalyst_voices/apps/voices/lib/pages/registration/wallet_link/stage/intro_panel.dart @@ -19,13 +19,18 @@ class IntroPanel extends StatelessWidget { subtitle: Text(context.l10n.walletLinkIntroContent), ), const Spacer(), - VoicesFilledButton( - key: const Key('ChooseCardanoWalletButton'), - leading: VoicesAssets.icons.wallet.buildIcon(), - onTap: () { - RegistrationCubit.of(context).nextStep(); - }, - child: Text(context.l10n.chooseCardanoWallet), + Semantics( + container: true, + button: true, + label: 'ChooseCardanoWalletButton', // Stable test identifier + child: VoicesFilledButton( + key: const Key('ChooseCardanoWalletButton'), + leading: VoicesAssets.icons.wallet.buildIcon(), + onTap: () { + RegistrationCubit.of(context).nextStep(); + }, + child: Text(context.l10n.chooseCardanoWallet), + ), ), ], ); diff --git a/catalyst_voices/apps/voices/lib/pages/registration/wallet_link/stage/rbac_transaction_panel.dart b/catalyst_voices/apps/voices/lib/pages/registration/wallet_link/stage/rbac_transaction_panel.dart index faa8bf593b17..2ef455e7a29e 100644 --- a/catalyst_voices/apps/voices/lib/pages/registration/wallet_link/stage/rbac_transaction_panel.dart +++ b/catalyst_voices/apps/voices/lib/pages/registration/wallet_link/stage/rbac_transaction_panel.dart @@ -338,17 +338,22 @@ class _BlocSubmitTxButton extends StatelessWidget { canSubmitTx: state.canSubmitTx?.isSuccess ?? false, ), builder: (context, state) { - return VoicesFilledButton( - leading: VoicesAssets.icons.wallet.buildIcon(), - onTap: state.canSubmitTx ? onSubmit : null, - trailing: state.isLoading - ? const SizedBox( - width: 16, - height: 16, - child: VoicesCircularProgressIndicator(), - ) - : null, - child: Text(context.l10n.walletLinkTransactionSign), + return Semantics( + button: true, + container: true, + label: 'SignBtn', + child: VoicesFilledButton( + leading: VoicesAssets.icons.wallet.buildIcon(), + onTap: state.canSubmitTx ? onSubmit : null, + trailing: state.isLoading + ? const SizedBox( + width: 16, + height: 16, + child: VoicesCircularProgressIndicator(), + ) + : null, + child: Text(context.l10n.walletLinkTransactionSign), + ), ); }, ); diff --git a/catalyst_voices/apps/voices/lib/pages/registration/wallet_link/stage/roles_summary_panel.dart b/catalyst_voices/apps/voices/lib/pages/registration/wallet_link/stage/roles_summary_panel.dart index c98065c61cbb..318e4c45d3ab 100644 --- a/catalyst_voices/apps/voices/lib/pages/registration/wallet_link/stage/roles_summary_panel.dart +++ b/catalyst_voices/apps/voices/lib/pages/registration/wallet_link/stage/roles_summary_panel.dart @@ -25,13 +25,16 @@ class RolesSummaryPanel extends StatelessWidget { const SizedBox(height: 12), const _BlocRolesSummaryContainer(), const Spacer(), - VoicesFilledButton( + Semantics( container: true, + label: 'reviewRegTransaction', + button: true, + child :VoicesFilledButton( leading: VoicesAssets.icons.wallet.buildIcon(), onTap: () { RegistrationCubit.of(context).nextStep(); }, child: Text(context.l10n.reviewRegistrationTransaction), - ), + ),), const SizedBox(height: 10), VoicesTextButton( onTap: () { diff --git a/catalyst_voices/apps/voices/lib/pages/registration/wallet_link/stage/select_wallet_panel.dart b/catalyst_voices/apps/voices/lib/pages/registration/wallet_link/stage/select_wallet_panel.dart index 02ce2bbdbd45..0e9cdeb7a588 100644 --- a/catalyst_voices/apps/voices/lib/pages/registration/wallet_link/stage/select_wallet_panel.dart +++ b/catalyst_voices/apps/voices/lib/pages/registration/wallet_link/stage/select_wallet_panel.dart @@ -44,9 +44,13 @@ class _SelectWalletPanelState extends State { ), const SizedBox(height: 40), Expanded( - child: _BlocWallets( - onRefreshTap: _refreshWallets, - onSelectWallet: _onSelectWallet, + child: Semantics( + container: true, + label: 'WalletsList', + child: _BlocWallets( + onRefreshTap: _refreshWallets, + onSelectWallet: _onSelectWallet, + ), ), ), const SizedBox(height: 24), @@ -103,10 +107,14 @@ class _BlocWallets extends StatelessWidget { key: const Key('WalletsLinkBuilder'), selector: (state) => state.wallets, builder: (context, state) { - return _Wallets( - result: state, - onRefreshTap: onRefreshTap, - onSelectWallet: onSelectWallet, + return Semantics( + container: true, + label: 'Wallets', + child: _Wallets( + result: state, + onRefreshTap: onRefreshTap, + onSelectWallet: onSelectWallet, + ), ); }, ); @@ -178,11 +186,15 @@ class _WalletTileState extends State<_WalletTile> { @override Widget build(BuildContext context) { - return VoicesWalletTile( - iconSrc: widget.wallet.icon, - name: Text(widget.wallet.name), - isLoading: _isLoading, - onTap: _onSelectWallet, + return Semantics( + container: true, + label: 'WalletTile', + child: VoicesWalletTile( + iconSrc: widget.wallet.icon, + name: Text(widget.wallet.name), + isLoading: _isLoading, + onTap: _onSelectWallet, + ), ); } diff --git a/catalyst_voices/apps/voices/lib/pages/registration/wallet_link/stage/wallet_details_panel.dart b/catalyst_voices/apps/voices/lib/pages/registration/wallet_link/stage/wallet_details_panel.dart index 898c6506b3c9..4003953d886a 100644 --- a/catalyst_voices/apps/voices/lib/pages/registration/wallet_link/stage/wallet_details_panel.dart +++ b/catalyst_voices/apps/voices/lib/pages/registration/wallet_link/stage/wallet_details_panel.dart @@ -133,10 +133,15 @@ class _RegistrationTextBackNextNavigation extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - VoicesFilledButton( - onTap: () => RegistrationCubit.of(context).nextStep(), - leading: VoicesAssets.icons.users.buildIcon(), - child: Text(context.l10n.walletLinkRolesSubheader), + Semantics( + container: true, + button: true, + label: 'selectRolesBtn', + child: VoicesFilledButton( + onTap: () => RegistrationCubit.of(context).nextStep(), + leading: VoicesAssets.icons.users.buildIcon(), + child: Text(context.l10n.walletLinkRolesSubheader), + ), ), const SizedBox(height: 10), VoicesOutlinedButton( diff --git a/catalyst_voices/apps/voices/lib/pages/registration/widgets/registration_stage_navigation.dart b/catalyst_voices/apps/voices/lib/pages/registration/widgets/registration_stage_navigation.dart index 917f2382db95..fb33896e81f8 100644 --- a/catalyst_voices/apps/voices/lib/pages/registration/widgets/registration_stage_navigation.dart +++ b/catalyst_voices/apps/voices/lib/pages/registration/widgets/registration_stage_navigation.dart @@ -29,11 +29,15 @@ class RegistrationBackNextNavigation extends StatelessWidget { ? onBackTap ?? () => RegistrationCubit.of(context).previousStep() : null, ), - VoicesNextButton( - key: const Key('NextButton'), - onTap: isNextEnabled - ? onNextTap ?? () => RegistrationCubit.of(context).nextStep() - : null, + Semantics( + container: true, + label: 'NextButton-test', // Stable test identifier + child: VoicesNextButton( + key: const Key('NextButton'), + onTap: isNextEnabled + ? onNextTap ?? () => RegistrationCubit.of(context).nextStep() + : null, + ), ), ], ); diff --git a/catalyst_voices/apps/voices/lib/pages/registration/widgets/unlock_password_form.dart b/catalyst_voices/apps/voices/lib/pages/registration/widgets/unlock_password_form.dart index 5fb20445671e..6a33a6a530f5 100644 --- a/catalyst_voices/apps/voices/lib/pages/registration/widgets/unlock_password_form.dart +++ b/catalyst_voices/apps/voices/lib/pages/registration/widgets/unlock_password_form.dart @@ -54,12 +54,15 @@ class _UnlockPasswordTextField extends StatelessWidget { @override Widget build(BuildContext context) { - return VoicesPasswordTextField( - key: const Key('PasswordInputField'), - controller: controller, - textInputAction: TextInputAction.next, - decoration: VoicesTextFieldDecoration( - labelText: context.l10n.enterPassword, + return Semantics( + container: true, + child: VoicesPasswordTextField( + key: const Key('PasswordInputField'), + controller: controller, + textInputAction: TextInputAction.next, + decoration: VoicesTextFieldDecoration( + labelText: context.l10n.enterPassword, + ), ), ); } @@ -78,13 +81,16 @@ class _ConfirmUnlockPasswordTextField extends StatelessWidget { @override Widget build(BuildContext context) { - return VoicesPasswordTextField( - key: const Key('PasswordConfirmInputField'), - controller: controller, - decoration: VoicesTextFieldDecoration( - labelText: context.l10n.confirmPassword, - helperText: context.l10n.xCharactersMinimum(minimumLength), - errorText: showError ? context.l10n.passwordDoNotMatch : null, + return Semantics( + container: true, + child: VoicesPasswordTextField( + key: const Key('PasswordConfirmInputField'), + controller: controller, + decoration: VoicesTextFieldDecoration( + labelText: context.l10n.confirmPassword, + helperText: context.l10n.xCharactersMinimum(minimumLength), + errorText: showError ? context.l10n.passwordDoNotMatch : null, + ), ), ); } diff --git a/catalyst_voices/apps/voices/lib/pages/spaces/appbar/session_action_header.dart b/catalyst_voices/apps/voices/lib/pages/spaces/appbar/session_action_header.dart index e11ba9f4cd29..f63ba6e381a0 100644 --- a/catalyst_voices/apps/voices/lib/pages/spaces/appbar/session_action_header.dart +++ b/catalyst_voices/apps/voices/lib/pages/spaces/appbar/session_action_header.dart @@ -41,10 +41,18 @@ class _GetStartedButton extends StatelessWidget { @override Widget build(BuildContext context) { - return VoicesFilledButton( - key: const Key('GetStartedButton'), - onTap: isEnabled ? () async => RegistrationDialog.show(context) : null, - child: Text(context.l10n.getStarted), + return Semantics( + button: true, + label: 'GetStartedButton-test', // Stable test identifier + container: true, + child: VoicesFilledButton( + + key: const Key('GetStartedButton'), + onTap: isEnabled ? () async => RegistrationDialog.show(context) : null, + child: ExcludeSemantics( + child: Text(context.l10n.getStarted), + ), + ), ); } } diff --git a/catalyst_voices/apps/voices/lib/widgets/buttons/voices_icon_button.dart b/catalyst_voices/apps/voices/lib/widgets/buttons/voices_icon_button.dart index e1ee79eb71a3..273f24d3a545 100644 --- a/catalyst_voices/apps/voices/lib/widgets/buttons/voices_icon_button.dart +++ b/catalyst_voices/apps/voices/lib/widgets/buttons/voices_icon_button.dart @@ -52,11 +52,15 @@ class VoicesIconButton extends StatelessWidget { @override Widget build(BuildContext context) { - return IconButton( + return Semantics( + container: true, + button: true, + label: 'closebtn-test', // Stable test identifier + child: IconButton( onPressed: onTap, style: (style ?? const ButtonStyle()).merge(_buildVariantStyle(context)), icon: child, - ); + ),); } /// Majority of configuration takes takes places in theme builder diff --git a/catalyst_voices/apps/voices/lib/widgets/text_field/voices_text_field.dart b/catalyst_voices/apps/voices/lib/widgets/text_field/voices_text_field.dart index 77dbd6f773e1..126139713ff7 100644 --- a/catalyst_voices/apps/voices/lib/widgets/text_field/voices_text_field.dart +++ b/catalyst_voices/apps/voices/lib/widgets/text_field/voices_text_field.dart @@ -177,32 +177,36 @@ class VoicesTextField extends VoicesFormField { resizableVertically: resizableVertically, minHeight: maxLines == null ? 65 : 48, iconBottomSpacing: maxLines == null ? 18 : 0, - child: TextField( - key: const Key('VoicesTextField'), - textAlignVertical: TextAlignVertical.top, - autofocus: autofocus, - expands: resizableVertically, - controller: state._obtainController(), - statesController: statesController, - focusNode: focusNode, - onSubmitted: onFieldSubmitted, - onEditingComplete: onEditingComplete, - inputFormatters: inputFormatters, - decoration: state._buildDecoration(), - keyboardType: keyboardType, - textInputAction: textInputAction, - textCapitalization: textCapitalization, - style: style, - obscureText: obscureText, - maxLines: maxLines, - minLines: minLines, - maxLength: maxLength, - maxLengthEnforcement: maxLengthEnforcement, - enabled: enabled, - readOnly: readOnly, - ignorePointers: ignorePointers, - onChanged: onChangedHandler, - mouseCursor: mouseCursor, + child: Semantics( + container: true, + label: 'VoicesTextField', + child: TextField( + key: const Key('VoicesTextField'), + textAlignVertical: TextAlignVertical.top, + autofocus: autofocus, + expands: resizableVertically, + controller: state._obtainController(), + statesController: statesController, + focusNode: focusNode, + onSubmitted: onFieldSubmitted, + onEditingComplete: onEditingComplete, + inputFormatters: inputFormatters, + decoration: state._buildDecoration(), + keyboardType: keyboardType, + textInputAction: textInputAction, + textCapitalization: textCapitalization, + style: style, + obscureText: obscureText, + maxLines: maxLines, + minLines: minLines, + maxLength: maxLength, + maxLengthEnforcement: maxLengthEnforcement, + enabled: enabled, + readOnly: readOnly, + ignorePointers: ignorePointers, + onChanged: onChangedHandler, + mouseCursor: mouseCursor, + ), ), ), ], diff --git a/catalyst_voices/apps/voices/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/catalyst_voices/apps/voices/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 25a1dcde5851..f8fea40dcc8a 100644 --- a/catalyst_voices/apps/voices/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/catalyst_voices/apps/voices/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -59,6 +59,7 @@ ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" + enableGPUValidationMode = "1" allowLocationSimulation = "YES"> diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000000..76a7234a3129 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,44 @@ +{ + "name": "catalyst-voices", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "devDependencies": { + "@types/node": "^22.13.14" + } + }, + "node_modules/@types/node": { + "version": "22.13.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.14.tgz", + "integrity": "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w==", + "dev": true, + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true + } + }, + "dependencies": { + "@types/node": { + "version": "22.13.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.14.tgz", + "integrity": "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w==", + "dev": true, + "requires": { + "undici-types": "~6.20.0" + } + }, + "undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 000000000000..b020bd1c0c6c --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "@types/node": "^22.13.14" + } +}