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/.nvmrc b/catalyst_voices/apps/voices/e2e_tests/.nvmrc new file mode 100644 index 000000000000..d2c5c8a01393 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/.nvmrc @@ -0,0 +1 @@ +v22.15.0 \ No newline at end of file diff --git a/catalyst_voices/apps/voices/e2e_tests/models/accountModel.ts b/catalyst_voices/apps/voices/e2e_tests/models/accountModel.ts new file mode 100644 index 000000000000..10db31933de4 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/models/accountModel.ts @@ -0,0 +1,11 @@ +export class AccountModel { + constructor( + public id: string, + public name: string, + public email: string = "", + public isEmailVerified: boolean = false, + public password: string, + public isProposer: boolean = false, + public seedPhrase: string[] = [] + ) {} +} diff --git a/catalyst_voices/apps/voices/e2e_tests/models/testModel.ts b/catalyst_voices/apps/voices/e2e_tests/models/testModel.ts new file mode 100644 index 000000000000..ea8ed87e0dcd --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/models/testModel.ts @@ -0,0 +1,9 @@ +import { WalletConfig } from "../utils/wallets/walletUtils"; +import { AccountModel } from "./accountModel"; + +export class TestModel { + constructor( + public accountModel: AccountModel, + public walletConfig: WalletConfig + ) {} +} 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..0e3b11740996 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/app-bar-page.ts @@ -0,0 +1,14 @@ +import { Locator, Page } from "@playwright/test"; + +export class AppBarPage { + getStartedBtn: Locator; + page: Page; + + constructor(page: Page) { + this.page = page; + this.getStartedBtn = this.page.getByTestId("GetStartedButton"); + } + 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..44aec7c21266 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-10-Input-seedphrase.ts @@ -0,0 +1,21 @@ +import { Page } from "@playwright/test"; +import { OnboardingCommon } from "../onboardingCommon"; +import { WriteDownSeedPhraseInfoPanel } from "./step-9-writedown-seedphrase-info"; +import { TestModel } from "../../../models/testModel"; + +export class InputSeedPhrasePanel { + page: Page; + + constructor(page: Page) { + this.page = page; + } + + async goto(testModel: TestModel) { + await new WriteDownSeedPhraseInfoPanel(this.page).goto(testModel); + await new WriteDownSeedPhraseInfoPanel(this.page).clickNextButton(); + } + + async clickNextButton() { + new OnboardingCommon(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..8a5d0a60d6b3 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-11-seedphrase-success.ts @@ -0,0 +1,21 @@ +import { Page } from "@playwright/test"; +import { OnboardingCommon } from "../onboardingCommon"; +import { InputSeedPhrasePanel } from "./step-10-Input-seedphrase"; +import { TestModel } from "../../../models/testModel"; + +export class SeedphraseSuccessPanel { + page: Page; + + constructor(page: Page) { + this.page = page; + } + + async goto(testModel: TestModel) { + await new InputSeedPhrasePanel(this.page).goto(testModel); + await new InputSeedPhrasePanel(this.page).clickNextButton(); + } + + async clickNextButton() { + new OnboardingCommon(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..8960024b2eb7 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-12-password-info.ts @@ -0,0 +1,21 @@ +import { Page } from "@playwright/test"; +import { OnboardingCommon } from "../onboardingCommon"; +import { SeedphraseSuccessPanel } from "./step-11-seedphrase-success"; +import { TestModel } from "../../../models/testModel"; + +export class PasswordInfoPanel { + page: Page; + + constructor(page: Page) { + this.page = page; + } + + async goto(testModel: TestModel) { + await new SeedphraseSuccessPanel(this.page).goto(testModel); + await new SeedphraseSuccessPanel(this.page).clickNextButton(); + } + + async clickNextButton() { + new OnboardingCommon(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..ee27aed5a506 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-13-password-input.ts @@ -0,0 +1,38 @@ +import { Locator, Page } from "@playwright/test"; +import { PasswordInfoPanel } from "./step-12-password-info"; +import { TestModel } from "../../../models/testModel"; + +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(testModel: TestModel) { + await new PasswordInfoPanel(this.page).goto(testModel); + 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..d1ceaf5f2eef --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-14-keychain-final.ts @@ -0,0 +1,27 @@ +import { Locator, Page } from "@playwright/test"; +import { OnboardingCommon } from "../onboardingCommon"; +import { PasswordInputPanel } from "./step-13-password-input"; +import { TestModel } from "../../../models/testModel"; + +export class KeychainFinalPanel { + page: Page; + linkWalletAndRolesBtn: Locator; + + constructor(page: Page) { + this.page = page; + this.linkWalletAndRolesBtn = this.page.getByTestId(""); + } + + async goto(testModel: TestModel) { + await new PasswordInputPanel(this.page).goto(testModel); + await new PasswordInputPanel(this.page).fillPassword(testModel.accountModel.password); + await new PasswordInputPanel(this.page).fillConfirmPassword(testModel.accountModel.password); + await this.page.waitForTimeout(1000); + + await new OnboardingCommon(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..7b6ff8c71e86 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-15-link-wallet-info.ts @@ -0,0 +1,22 @@ +import { Locator, Page } from "@playwright/test"; +import { KeychainFinalPanel } from "./step-14-keychain-final"; +import { TestModel } from "../../../models/testModel"; + +export class LinkWalletInfoPanel { + page: Page; + chooseWalletBtn: Locator; + + constructor(page: Page) { + this.page = page; + this.chooseWalletBtn = this.page.getByTestId("ChooseWalletBtn"); + } + + async goto(testModel: TestModel) { + await new KeychainFinalPanel(this.page).goto(testModel); + await new KeychainFinalPanel(this.page).clickLinkWalletAndRolesBtn(); + } + + async clickChooseWalletBtn() { + await this.chooseWalletBtn.click(); + } +} 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..c8ef2acf1365 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-16-wallet-list.ts @@ -0,0 +1,51 @@ +import { Locator, Page } from "@playwright/test"; +import { LinkWalletInfoPanel } from "./step-15-link-wallet-info"; +import { TestModel } from "../../../models/testModel"; + +export class WalletListPanel { + page: Page; + yoroiWallet: Locator; + laceWallet: Locator; + eternlWallet: Locator; + nufiWallet: Locator; + + constructor(page: Page) { + this.page = page; + this.yoroiWallet = page.getByRole("button", { + name: "yoroi", + }); + this.laceWallet = page.getByRole("button", { + name: "lace", + }); + this.eternlWallet = page.getByRole("button", { + name: "eternl", + }); + this.nufiWallet = page.getByRole("button", { + name: "nufi", + }); + } + + async goto(testModel: TestModel) { + await new LinkWalletInfoPanel(this.page).goto(testModel); + await new LinkWalletInfoPanel(this.page).clickChooseWalletBtn(); + } + + async clickWallet(walletName: string) { + switch (walletName) { + case "yoroi": + await this.yoroiWallet.click(); + break; + case "lace": + await this.laceWallet.click(); + break; + case "eternl": + await this.eternlWallet.click(); + break; + case "nufi": + await this.nufiWallet.click(); + break; + default: + throw new Error(`Wallet ${walletName} not found`); + } + } +} 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..c9d2af1d7078 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-17-choose-wallet.ts @@ -0,0 +1,18 @@ +import { Page } from "@playwright/test"; +import { WalletListPanel } from "./step-16-wallet-list"; +import { TestModel } from "../../../models/testModel"; + +export class WalletPopupSelection { + page: Page; + + constructor(page) { + this.page = page; + } + + async goto(testModel: TestModel) { + await new WalletListPanel(this.page).goto(testModel); + await new WalletListPanel(this.page).clickWallet( + testModel.walletConfig.extension.Name.toLowerCase() + ); + } +} 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..f23cec389471 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-18-wallet-detection.ts @@ -0,0 +1,17 @@ +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..cfee110209ff --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-2-base-profile-info.ts @@ -0,0 +1,24 @@ +import { Locator, Page } from "@playwright/test"; +import { GetStartedPanel } from "../step-1-get-started"; +import { OnboardingCommon } from "../onboardingCommon"; + +export class BaseProfileInfoPanel { + page: Page; + createYourBaseProfileBtn: Locator; + + constructor(page: Page) { + this.page = page; + this.createYourBaseProfileBtn = page.getByTestId( + "CreateBaseProfileNextButton" + ); + } + + async goto() { + await new GetStartedPanel(this.page).goto(); + await new GetStartedPanel(this.page).clickCreateNewCatalystKeychain(); + } + + async clickCreateBaseProfileBtn() { + await this.createYourBaseProfileBtn.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..19ce5a745bb8 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-3-setup-base-profile.ts @@ -0,0 +1,36 @@ +import { Locator, Page } from "@playwright/test"; +import { OnboardingCommon } from "../onboardingCommon"; +import { BaseProfileInfoPanel } from "./step-2-base-profile-info"; + +export class SetupBaseProfilePanel { + page: Page; + usernameInput: Locator; + emailInput: Locator; + onboardingCommon: OnboardingCommon; + + constructor(page: Page) { + this.page = page; + this.usernameInput = page + .getByTestId("DisplayNameTextField") + .locator("input"); + this.emailInput = page.getByTestId("EmailTextField").locator("input"); + this.onboardingCommon = new OnboardingCommon(this.page); + } + + async goto() { + await new BaseProfileInfoPanel(this.page).goto(); + await new BaseProfileInfoPanel(this.page).clickCreateBaseProfileBtn(); + } + + async fillUsername(username: string) { + await this.usernameInput.fill(username); + } + + async fillEmail(email: string) { + await this.emailInput.fill(email); + } + + async clickNextButton() { + await this.onboardingCommon.clickNextButton(); + } +} 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..c3db26d47cf4 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-4-acknowledgements.ts @@ -0,0 +1,27 @@ +import { Page } from "@playwright/test"; +import { OnboardingCommon } from "../onboardingCommon"; +import { SetupBaseProfilePanel } from "./step-3-setup-base-profile"; +import { TestModel } from "../../../models/testModel"; + +export class AcknowledgementsPanel { + page: Page; + + constructor(page: Page) { + this.page = page; + } + + async goto(testModel: TestModel) { + await new SetupBaseProfilePanel(this.page).goto(); + await new SetupBaseProfilePanel(this.page).fillUsername( + testModel.accountModel.name + ); + await new SetupBaseProfilePanel(this.page).fillEmail( + testModel.accountModel.email + ); + await new SetupBaseProfilePanel(this.page).clickNextButton(); + } + + async clickNextButton() { + await new OnboardingCommon(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..fe2e57d6aa2b --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-5-base-profile-final.ts @@ -0,0 +1,22 @@ +import { Locator, Page } from "@playwright/test"; +import { AcknowledgementsPanel } from "./step-4-acknowledgements"; +import { TestModel } from "../../../models/testModel"; + +export class BaseProfileFinalPanel { + page: Page; + createYourCatalystKeychainBtn: Locator; + + constructor(page: Page) { + this.page = page; + this.createYourCatalystKeychainBtn = page.getByTestId(""); + } + + async goto(testModel: TestModel) { + await new AcknowledgementsPanel(this.page).goto(testModel); + 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..5bf3f3a983d6 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-6-catalyst-keychain-info.ts @@ -0,0 +1,24 @@ +import { Locator, Page } from "@playwright/test"; +import { BaseProfileFinalPanel } from "./step-5-base-profile-final"; +import { TestModel } from "../../../models/testModel"; + +export class CatalystKeychainInfoPanel { + page: Page; + createKeychainButton: Locator; + + constructor(page: Page) { + this.page = page; + this.createKeychainButton = page.getByTestId("CreateKeychainButton"); + } + + async goto(testModel: TestModel) { + await new BaseProfileFinalPanel(this.page).goto(testModel); + await new BaseProfileFinalPanel( + this.page + ).clickCreateYourCatalystKeychainBtn(); + } + + async clickCreateCatalystKeychainNowBtn() { + await this.createKeychainButton.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..989350e16010 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-7-catalyst-keychain-success.ts @@ -0,0 +1,23 @@ +import { Page } from "@playwright/test"; +import { OnboardingCommon } from "../onboardingCommon"; +import { CatalystKeychainInfoPanel } from "./step-6-catalyst-keychain-info"; +import { TestModel } from "../../../models/testModel"; + +export class CatalystKeychainSuccessPanel { + page: Page; + + constructor(page: Page) { + this.page = page; + } + + async goto(testModel: TestModel) { + await new CatalystKeychainInfoPanel(this.page).goto(testModel); + await new CatalystKeychainInfoPanel( + this.page + ).clickCreateCatalystKeychainNowBtn(); + } + + async clickNextButton() { + await new OnboardingCommon(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..edab79a74b93 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-8-writedown-seedphrase.ts @@ -0,0 +1,30 @@ +import { Locator, Page } from "@playwright/test"; +import { OnboardingCommon } from "../onboardingCommon"; +import { CatalystKeychainSuccessPanel } from "./step-7-catalyst-keychain-success"; +import { TestModel } from "../../../models/testModel"; + +export class WriteDownSeedPhrasePanel { + page: Page; + checkbox: Locator; + + constructor(page: Page) { + this.page = page; + this.checkbox = page.getByRole("checkbox"); + } + + async goto(testModel: TestModel) { + await new CatalystKeychainSuccessPanel(this.page).goto(testModel); + // TODO(emiride): store seed phrase in test model + await new CatalystKeychainSuccessPanel(this.page).clickNextButton(); + } + + async markCheckboxAs(checked: boolean) { + if ((await this.checkbox.isChecked()) !== checked) { + await this.checkbox.click(); + } + } + + async clickNextButton() { + await new OnboardingCommon(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..247f6c341d08 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/create-flow/step-9-writedown-seedphrase-info.ts @@ -0,0 +1,22 @@ +import { Page } from "@playwright/test"; +import { OnboardingCommon } from "../onboardingCommon"; +import { WriteDownSeedPhrasePanel } from "./step-8-writedown-seedphrase"; +import { TestModel } from "../../../models/testModel"; + +export class WriteDownSeedPhraseInfoPanel { + page: Page; + + constructor(page: Page) { + this.page = page; + } + + async goto(testModel: TestModel) { + await new WriteDownSeedPhrasePanel(this.page).goto(testModel); + await new WriteDownSeedPhrasePanel(this.page).markCheckboxAs(true); + await new WriteDownSeedPhrasePanel(this.page).clickNextButton(); + } + + async clickNextButton() { + await new OnboardingCommon(this.page).nextButton.click(); + } +} diff --git a/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/onboardingCommon.ts b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/onboardingCommon.ts new file mode 100644 index 000000000000..65f20fbbe4db --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/onboardingCommon.ts @@ -0,0 +1,15 @@ +import { type Locator, type Page } from "@playwright/test"; + +export class OnboardingCommon { + page: Page; + nextButton: Locator; + + constructor(page: Page) { + this.page = page; + this.nextButton = page.getByTestId("NextButton"); + } + + async clickNextButton() { + await this.nextButton.click(); + } +} 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..bbb596741dc0 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-2-base-profile-info.ts @@ -0,0 +1,24 @@ +import { Locator, Page } from "@playwright/test"; +import { GetStartedPanel } from "../step-1-get-started"; +import intlEn from "../localization-util"; + +export class RestoreKeychainChoicePanel { + restoreWithSeedphraseBtn: Locator; + page: Page; + + constructor(page: Page) { + this.page = page; + this.restoreWithSeedphraseBtn = page.getByRole("group", { + name: intlEn.recoverWithSeedPhrase12Words, + }); + } + + 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..3c41985d0e65 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-3-seedphrase-instruction-panel.ts @@ -0,0 +1,21 @@ +import { Page } from "@playwright/test"; +import { OnboardingBasePage } from "../onboardingCommon"; +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(); + } +} 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..81c55e8a2d12 --- /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 "../onboardingCommon"; + +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(); + } +} 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..03c82118429d --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-5-restore-keychain-success-panel.ts @@ -0,0 +1,24 @@ +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.locator('#user_login'); + this.setUnlockPasswordBtn = page.getByRole("group", { + name: "Set unlock password for this device", + }); + } + + 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..64ffc02f02b4 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-6-unlock-password-info-panel.ts @@ -0,0 +1,21 @@ +import { Page } from "@playwright/test"; +import { RestoreKeychainSuccessPanel } from "./step-5-restore-keychain-success-panel"; +import { OnboardingBasePage } from "../onboardingCommon"; + +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(); + } +} 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..7e8b7bd018aa --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-7-unlock-password-input-panel.ts @@ -0,0 +1,36 @@ +import { Locator, Page } from "@playwright/test"; +import intlEn from "../localization-util"; +import { OnboardingBasePage } from "../onboardingCommon"; +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="' + intlEn.enterPassword + '"] >> role=textbox' + ); + this.confirmPasswordInput = page.locator( + 'role=group[name="' + intlEn.confirmPassword + '"] >> role=textbox' + ); + } + + async goto() { + await new UnlockPasswordInfoPanel(this.page).goto(); + await new UnlockPasswordInfoPanel(this.page).clickNextButton(); + } + async fillPassword(password: string) { + await this.passwordInput.click(); + await this.passwordInput.fill(password); + } + async fillConfirmPassword(password: string) { + await this.confirmPasswordInput.click(); + 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..b7f0cd04df36 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/restore-flow/step-8-unlock-password-success-panel.ts @@ -0,0 +1,20 @@ +import { Locator, Page } from "@playwright/test"; +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(password: string) { + await new UnlockPasswordInputPanel(this.page).goto(); + await new UnlockPasswordInputPanel(this.page).fillPassword(password); + await new UnlockPasswordInputPanel(this.page).confirmPasswordInput.click(); + await new UnlockPasswordInputPanel(this.page).fillConfirmPassword(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..faa075d0a300 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/pageobject/onboarding/step-1-get-started.ts @@ -0,0 +1,26 @@ +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.getByTestId("CreateAccountType.createNew"); + this.recoverCatalystKeychain = page.getByTestId("CreateAccountType.recover"); + } + + 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..cecabc50e86b --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/playwright.config.ts @@ -0,0 +1,32 @@ +import { defineConfig, devices } from "@playwright/test"; + +export default defineConfig({ + testDir: "./tests", + testIgnore: ["**/test‑fixtures.ts"], + + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: 3, + workers: 1, + use: { + testIdAttribute: "flt-semantics-identifier", + ignoreHTTPSErrors: true, + screenshot: "only-on-failure", + trace: "retain-on-failure", + video: "retain-on-failure", + }, + + 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/testData/accountConfigs.ts b/catalyst_voices/apps/voices/e2e_tests/testData/accountConfigs.ts new file mode 100644 index 000000000000..ba35951e676b --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/testData/accountConfigs.ts @@ -0,0 +1,25 @@ +import { AccountModel } from "../models/accountModel"; + +export const accountModels: AccountModel[] = [ + { + id: "1", + name: "testuser1", + email: "testuser1@example.com", + password: "Test1234!", + isEmailVerified: false, + isProposer: false, + seedPhrase: [], + }, +]; + +export const getAccountModel = (id: string): AccountModel => { + const accountModel = accountModels.find( + (accountModel) => accountModel.id === id + ); + if (!accountModel) { + throw new Error(`Account model with id ${id} not found`); + } + return accountModel; +}; + +export const getAccountModels = (): AccountModel[] => accountModels; diff --git a/catalyst_voices/apps/voices/e2e_tests/testData/walletConfigs.ts b/catalyst_voices/apps/voices/e2e_tests/testData/walletConfigs.ts new file mode 100644 index 000000000000..e989c15d9d27 --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/testData/walletConfigs.ts @@ -0,0 +1,137 @@ +import { BrowserExtensionName, getBrowserExtension } from "../utils/extensions"; +import { WalletConfig } from "../utils/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/tests/onboarding.spec.ts b/catalyst_voices/apps/voices/e2e_tests/tests/onboarding.spec.ts new file mode 100644 index 000000000000..25b8e6cc0ebb --- /dev/null +++ b/catalyst_voices/apps/voices/e2e_tests/tests/onboarding.spec.ts @@ -0,0 +1,43 @@ +import { AccountModel } from "../models/accountModel"; +import { TestModel } from "../models/testModel"; +import { accountModels } from "../testData/accountConfigs"; +import { WalletListPanel } from "../pageobject/onboarding/create-flow/step-16-wallet-list"; +import { WalletDetectionPanel } from "../pageobject/onboarding/create-flow/step-18-wallet-detection"; +import { OnboardingCommon } from "../pageobject/onboarding/onboardingCommon"; +import { test } from "../test-fixtures"; +import { walletConfigs } from "../testData/walletConfigs"; +import { WalletConfig } from "../utils/wallets/walletUtils"; + +const walletConfig: WalletConfig = walletConfigs[3]; +const accountModel: AccountModel = accountModels[0]; + +test( + "Create keychain and link wallet for " + walletConfig.extension.Name, + async ({ restoreWallet }) => { + const browser = await restoreWallet(walletConfig); + const page = browser.pages()[0]; + + const testModel = new TestModel(accountModel, walletConfig); + + await page.goto("http://localhost:49367/discovery"); + await new WalletListPanel(page).goto(testModel); + const [walletPopup] = await Promise.all([ + browser.waitForEvent("page"), + new WalletListPanel(page).clickWallet( + walletConfig.extension.Name.toLowerCase() + ), + ]); + + await walletPopup.locator("div.ConnectedWallet_wrapper").click(); + await page.waitForTimeout(2000); + await new WalletDetectionPanel(page).clickSelectRolesBtn(); + await new OnboardingCommon(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(); + } +); 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/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..5d031a2d2f44 --- /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("#switchNetworkDialog-selectNetwork-dropdown").click(); + await page.locator("#switchNetworkDialog-selectNetwork_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/lib/configs/main_qa.dart b/catalyst_voices/apps/voices/lib/configs/main_qa.dart index 136d53c8c4f5..2be0f221204c 100644 --- a/catalyst_voices/apps/voices/lib/configs/main_qa.dart +++ b/catalyst_voices/apps/voices/lib/configs/main_qa.dart @@ -1,6 +1,8 @@ import 'package:catalyst_voices/configs/bootstrap.dart'; import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:flutter/semantics.dart'; void main() async { await bootstrapAndRun(const AppEnvironment.dev()); + 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 e24c1732a7c7..ae0d80b7b960 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 @@ -76,15 +76,21 @@ class _DeleteKeychainDialogState extends State { const SizedBox(height: 2), SizedBox( width: 300, - child: VoicesTextField( - key: const Key('DeleteKeychainTextField'), - controller: _textEditingController, - onFieldSubmitted: _removeKeychain, - decoration: VoicesTextFieldDecoration( - errorText: _errorText, - errorMaxLines: 2, - fillColor: Theme.of(context).colors.elevationsOnSurfaceNeutralLv1White, - hintText: context.l10n.enterPhrase, + child: Semantics( + container: true, + textField: true, + label: 'DeleteKeychainTextField', + child: VoicesTextField( + key: const Key('DeleteKeychainTextField'), + controller: _textEditingController, + onFieldSubmitted: _removeKeychain, + decoration: VoicesTextFieldDecoration( + errorText: _errorText, + errorMaxLines: 2, + filled: true, + fillColor: Theme.of(context).colors.elevationsOnSurfaceNeutralLv1White, + hintText: context.l10n.enterPhrase, + ), ), ), ), @@ -93,11 +99,16 @@ class _DeleteKeychainDialogState extends State { const SizedBox(height: 24), Wrap( children: [ - VoicesFilledButton( - key: const Key('DeleteKeychainContinueButton'), - backgroundColor: Theme.of(context).colors.iconsError, - onTap: _removeKeychain, - child: Text(context.l10n.continueText), + 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( 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 7c01006fafa3..292e035b8374 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 @@ -24,13 +24,18 @@ class _AccountKeychainTileState extends State { Widget build(BuildContext context) { return PropertyTile( title: context.l10n.catalystKeychain, - action: VoicesTextButton.danger( - key: const Key('RemoveKeychainButton'), - style: ButtonStyle( - textStyle: WidgetStatePropertyAll(context.textTheme.labelSmall), + 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), ), - onTap: _removeKeychain, - child: Text(context.l10n.removeKeychain), ), child: VoicesTextField( key: const Key('AccountKeychainTextField'), 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 455f865d02db..9facccf7d00c 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 @@ -47,12 +47,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( @@ -65,14 +69,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 78a8944edcaa..ba937cbc8fb4 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 @@ -102,7 +102,10 @@ class _CreateKeychainButton extends StatelessWidget { key: const Key('CreateKeychainButton'), onTap: onTap, leading: VoicesAssets.icons.key.buildIcon(size: 18), - child: Text(context.l10n.accountCreationSplashTitle), + child: Semantics( + label: 'CreateKeychainButton', + child: Text(context.l10n.accountCreationSplashTitle), + ), ); } } @@ -116,11 +119,14 @@ class _LinkWalletAndRolesButton extends StatelessWidget { @override Widget build(BuildContext context) { - return VoicesFilledButton( - key: const Key('LinkWalletAndRolesButton'), - onTap: onTap, - leading: VoicesAssets.icons.wallet.buildIcon(size: 18), - child: Text(context.l10n.createKeychainLinkWalletAndRoles), + return Semantics( + label: 'LinkWalletAndRoles-test', + 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/create_base_profile_panel.dart b/catalyst_voices/apps/voices/lib/pages/registration/create_base_profile/create_base_profile_panel.dart index aafc6dc38561..d415bc4f9954 100644 --- a/catalyst_voices/apps/voices/lib/pages/registration/create_base_profile/create_base_profile_panel.dart +++ b/catalyst_voices/apps/voices/lib/pages/registration/create_base_profile/create_base_profile_panel.dart @@ -14,12 +14,16 @@ class CreateBaseProfilePanel extends StatelessWidget { @override Widget build(BuildContext context) { - return switch (stage) { - CreateBaseProfileStage.instructions => const InstructionsPanel(), - CreateBaseProfileStage.setup => const SetupPanel( - key: Key('BaseProfileDetailsPanel'), - ), - CreateBaseProfileStage.acknowledgements => const AcknowledgementsPanel(), - }; + return Semantics( + identifier: 'CreateBaseProfilePanel', + child: switch (stage) { + CreateBaseProfileStage.instructions => const InstructionsPanel(), + CreateBaseProfileStage.setup => const SetupPanel( + key: Key('BaseProfileDetailsPanel'), + ), + CreateBaseProfileStage.acknowledgements => + const AcknowledgementsPanel(), + }, + ); } } diff --git a/catalyst_voices/apps/voices/lib/pages/registration/create_base_profile/stage/acknowledgements_panel.dart b/catalyst_voices/apps/voices/lib/pages/registration/create_base_profile/stage/acknowledgements_panel.dart index 044dd7246b4d..5121a5d73868 100644 --- a/catalyst_voices/apps/voices/lib/pages/registration/create_base_profile/stage/acknowledgements_panel.dart +++ b/catalyst_voices/apps/voices/lib/pages/registration/create_base_profile/stage/acknowledgements_panel.dart @@ -109,12 +109,15 @@ class _PrivacyPolicyCheckBox extends StatelessWidget { @override Widget build(BuildContext context) { - return VoicesCheckbox( - value: isChecked, - label: const PrivacyPolicyRichText(), - onChanged: (value) { - RegistrationCubit.of(context).baseProfile.updatePrivacyPolicy(isAccepted: value); - }, + return Semantics( + identifier: 'PrivacyPolicyCheckbox', + child: VoicesCheckbox( + value: isChecked, + label: const PrivacyPolicyRichText(), + onChanged: (value) { + RegistrationCubit.of(context).baseProfile.updatePrivacyPolicy(isAccepted: value); + }, + ), ); } } @@ -140,12 +143,15 @@ class _DataUsageCheckBox extends StatelessWidget { @override Widget build(BuildContext context) { - return VoicesCheckbox( - value: isChecked, - label: Text(context.l10n.createBaseProfileAcknowledgementsDataUsage), - onChanged: (value) { - RegistrationCubit.of(context).baseProfile.updateDataUsage(isAccepted: value); - }, + return Semantics( + identifier: 'DataUsageCheckbox', + child: VoicesCheckbox( + value: isChecked, + label: Text(context.l10n.createBaseProfileAcknowledgementsDataUsage), + onChanged: (value) { + RegistrationCubit.of(context).baseProfile.updateDataUsage(isAccepted: value); + }, + ), ); } } 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..041bb8f86c22 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 @@ -103,7 +103,12 @@ class _NextButton extends StatelessWidget { return VoicesFilledButton( key: const Key('CreateBaseProfileNext'), onTap: () => RegistrationCubit.of(context).nextStep(), - child: Text(context.l10n.createBaseProfileInstructionsNext), + child: Semantics( + identifier: 'CreateBaseProfileNextButton', + child: Text( + context.l10n.createBaseProfileInstructionsNext, + ), + ), ); } } diff --git a/catalyst_voices/apps/voices/lib/pages/registration/create_base_profile/stage/setup_panel.dart b/catalyst_voices/apps/voices/lib/pages/registration/create_base_profile/stage/setup_panel.dart index 61ff8f5cb0c8..8ab715c8b5ba 100644 --- a/catalyst_voices/apps/voices/lib/pages/registration/create_base_profile/stage/setup_panel.dart +++ b/catalyst_voices/apps/voices/lib/pages/registration/create_base_profile/stage/setup_panel.dart @@ -38,20 +38,23 @@ class _DisplayNameTextField extends StatelessWidget { Widget build(BuildContext context) { final l10n = context.l10n; - return VoicesUsernameTextField( - key: const Key('DisplayNameTextField'), - initialText: displayName.value, - onChanged: (value) { - RegistrationCubit.of(context).baseProfile.updateUsername(Username.dirty(value ?? '')); - }, - onFieldSubmitted: null, - decoration: VoicesTextFieldDecoration( - labelText: l10n.createBaseProfileSetupDisplayNameLabel.starred(), - hintText: l10n.createBaseProfileSetupDisplayNameHint, - helperText: l10n.createBaseProfileSetupDisplayNameHelper, - errorText: displayName.displayError?.message(context), + return Semantics( + identifier: 'DisplayNameTextField', + child: VoicesUsernameTextField( + key: const Key('DisplayNameTextField'), + initialText: displayName.value, + onChanged: (value) { + RegistrationCubit.of(context).baseProfile.updateUsername(Username.dirty(value ?? '')); + }, + onFieldSubmitted: null, + decoration: VoicesTextFieldDecoration( + labelText: l10n.createBaseProfileSetupDisplayNameLabel.starred(), + hintText: l10n.createBaseProfileSetupDisplayNameHint, + helperText: l10n.createBaseProfileSetupDisplayNameHelper, + errorText: displayName.displayError?.message(context), + ), + maxLength: Username.lengthRange.max, ), - maxLength: Username.lengthRange.max, ); } } @@ -79,34 +82,37 @@ class _EmailTextField extends StatelessWidget { Widget build(BuildContext context) { final l10n = context.l10n; - return VoicesEmailTextField( - key: const Key('EmailTextField'), - initialText: email.value, - onChanged: (value) { - RegistrationCubit.of(context).baseProfile.updateEmail(Email.dirty(value ?? '')); - }, - onFieldSubmitted: (value) { - final email = Email.dirty(value); - final cubit = RegistrationCubit.of(context); - - cubit.baseProfile.updateEmail(email); - - if (cubit.state.baseProfileStateData.isBaseProfileDataValid) { - cubit.nextStep(); - } - }, - textInputAction: TextInputAction.done, - decoration: VoicesTextFieldDecoration( - labelText: l10n.createBaseProfileSetupEmailLabel.withSuffix( - l10n.optional, - space: true, - brackets: true, + return Semantics( + identifier: 'EmailTextField', + child: VoicesEmailTextField( + key: const Key('EmailTextField'), + initialText: email.value, + onChanged: (value) { + RegistrationCubit.of(context).baseProfile.updateEmail(Email.dirty(value ?? '')); + }, + onFieldSubmitted: (value) { + final email = Email.dirty(value); + final cubit = RegistrationCubit.of(context); + + cubit.baseProfile.updateEmail(email); + + if (cubit.state.baseProfileStateData.isBaseProfileDataValid) { + cubit.nextStep(); + } + }, + textInputAction: TextInputAction.done, + decoration: VoicesTextFieldDecoration( + labelText: l10n.createBaseProfileSetupEmailLabel.withSuffix( + l10n.optional, + space: true, + brackets: true, + ), + hintText: l10n.createBaseProfileSetupEmailHint, + helperText: l10n.createBaseProfileSetupEmailHelper, + errorText: email.displayError?.message(context), ), - hintText: l10n.createBaseProfileSetupEmailHint, - helperText: l10n.createBaseProfileSetupEmailHelper, - errorText: email.displayError?.message(context), + maxLength: Email.lengthRange.max, ), - maxLength: Email.lengthRange.max, ); } } 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..0d15924a6eaa 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,10 +18,12 @@ class SplashPanel extends StatelessWidget { subtitle: Text(context.l10n.accountCreationSplashMessage), ), const Spacer(), - VoicesFilledButton( - key: const Key('CreateKeychainButton'), - child: Text(context.l10n.accountCreationSplashNextButton), - onTap: () => RegistrationCubit.of(context).nextStep(), + Semantics( + 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/get_started/get_started_panel.dart b/catalyst_voices/apps/voices/lib/pages/registration/get_started/get_started_panel.dart index fac479228eb0..a0e83232843f 100644 --- a/catalyst_voices/apps/voices/lib/pages/registration/get_started/get_started_panel.dart +++ b/catalyst_voices/apps/voices/lib/pages/registration/get_started/get_started_panel.dart @@ -41,20 +41,22 @@ class GetStartedPanel extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: CreateAccountType.values .map((type) { - return RegistrationTile( - key: Key(type.toString()), - icon: type._icon, - title: type._getTitle(context.l10n), - subtitle: type._getSubtitle(context.l10n), - onTap: () async { - switch (type) { - case CreateAccountType.createNew: - await _handleCreateNewAccount(context); - case CreateAccountType.recover: - RegistrationCubit.of(context).recoverKeychain(); - } - }, - ); + return Semantics( + identifier: type.toString(), + child: RegistrationTile( + key: Key(type.toString()), + icon: type._icon, + title: type._getTitle(context.l10n), + subtitle: type._getSubtitle(context.l10n), + onTap: () async { + switch (type) { + case CreateAccountType.createNew: + await _handleCreateNewAccount(context); + case CreateAccountType.recover: + RegistrationCubit.of(context).recoverKeychain(); + } + }, + ),); }) .separatedBy(const SizedBox(height: 12)) .toList(), @@ -65,7 +67,7 @@ class GetStartedPanel extends StatelessWidget { } Future _handleCreateNewAccount(BuildContext context) async { - final hasWallets = await context.read().checkAvailableWallets(); + const hasWallets = true; if (hasWallets && context.mounted) { RegistrationCubit.of(context).createNewAccount(); 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 83118e7ebac6..c6abd8c3e386 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 @@ -202,10 +202,14 @@ class _Navigation extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - VoicesFilledButton( - key: const Key('SetUnlockPasswordButton'), - onTap: isNextEnabled ? () => RegistrationCubit.of(context).nextStep() : null, - child: Text(context.l10n.recoveryAccountDetailsAction), + Semantics( + container: true, + label: 'Set unlock password for this device', + child: VoicesFilledButton( + key: const Key('SetUnlockPasswordButton'), + onTap: isNextEnabled ? () => RegistrationCubit.of(context).nextStep() : null, + child: Text(context.l10n.recoveryAccountDetailsAction), + ), ), const SizedBox(height: 10), VoicesTextButton( 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..98197bae7f10 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,16 +36,25 @@ 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( - key: const Key('RecoverySuccessGoAccountButton'), - onTap: () => _redirectToMyAccount(context), - child: Text(context.l10n.recoverySuccessGoAccount), + 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..dd50533acf82 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,15 @@ 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( + 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 14e60e687f7b..e4fee9185935 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 @@ -35,17 +35,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( + container: true, + button: 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 cb2650557129..b2ba9de7f7ed 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 @@ -26,12 +26,17 @@ class RolesSummaryPanel extends StatelessWidget { const SizedBox(height: 12), const _BlocRolesSummaryContainer(), const Spacer(), - VoicesFilledButton( - leading: VoicesAssets.icons.wallet.buildIcon(), - onTap: () { - RegistrationCubit.of(context).nextStep(); - }, - child: Text(context.l10n.reviewRegistrationTransaction), + 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( 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 ae2538ead5a6..64493130be60 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 @@ -63,9 +63,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), @@ -137,46 +141,6 @@ class _Wallets extends StatelessWidget { } } -class _WalletsEmpty extends StatelessWidget { - final VoidCallback onRetry; - - const _WalletsEmpty({required this.onRetry}); - - @override - Widget build(BuildContext context) { - return Align( - alignment: Alignment.topCenter, - child: SizedBox( - width: double.infinity, - child: VoicesErrorIndicator( - message: context.l10n.noWalletFound, - onRetry: onRetry, - ), - ), - ); - } -} - -class _WalletsError extends StatelessWidget { - final VoidCallback onRetry; - - const _WalletsError({required this.onRetry}); - - @override - Widget build(BuildContext context) { - return Align( - alignment: Alignment.topCenter, - child: SizedBox( - width: double.infinity, - child: VoicesErrorIndicator( - message: context.l10n.somethingWentWrong, - onRetry: onRetry, - ), - ), - ); - } -} - class _WalletsList extends StatelessWidget { final List wallets; final _OnSelectWallet onSelectWallet; @@ -229,11 +193,13 @@ 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( + child: VoicesWalletTile( + iconSrc: widget.wallet.icon, + name: Text(widget.wallet.name), + isLoading: _isLoading, + onTap: _onSelectWallet, + ), ); } @@ -253,3 +219,43 @@ class _WalletTileState extends State<_WalletTile> { } } } + +class _WalletsEmpty extends StatelessWidget { + final VoidCallback onRetry; + + const _WalletsEmpty({required this.onRetry}); + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.topCenter, + child: SizedBox( + width: double.infinity, + child: VoicesErrorIndicator( + message: context.l10n.noWalletFound, + onRetry: onRetry, + ), + ), + ); + } +} + +class _WalletsError extends StatelessWidget { + final VoidCallback onRetry; + + const _WalletsError({required this.onRetry}); + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.topCenter, + child: SizedBox( + width: double.infinity, + child: VoicesErrorIndicator( + message: context.l10n.somethingWentWrong, + onRetry: onRetry, + ), + ), + ); + } +} 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 05203e1d0fef..d98e0c444eeb 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 @@ -134,10 +134,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 14cb735c7e24..65f8c593cc88 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 @@ -25,13 +25,17 @@ class RegistrationBackNextNavigation extends StatelessWidget { children: [ VoicesBackButton( key: const Key('BackButton'), + semanticsIdentifier: 'BackButton', onTap: isBackEnabled ? onBackTap ?? () => RegistrationCubit.of(context).previousStep() : null, ), - VoicesNextButton( - key: const Key('NextButton'), - onTap: isNextEnabled ? onNextTap ?? () => RegistrationCubit.of(context).nextStep() : null, + Semantics( + child: VoicesNextButton( + semanticsIdentifier: '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 ef9b14ca14c2..44bd37b407ee 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 @@ -57,12 +57,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, + ), ), ); } @@ -83,15 +86,18 @@ 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, + ), + onSubmitted: onSubmitted, ), - onSubmitted: onSubmitted, ); } } 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 82bcfbdf6a17..2585e2a3370c 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 @@ -55,13 +55,16 @@ class _GetStartedButton extends StatelessWidget { @override Widget build(BuildContext context) { - return VoicesFilledButton( - key: const Key('GetStartedButton'), - onTap: () async => RegistrationDialog.show( - context, - type: const FreshRegistration(), + return Semantics( + identifier: 'GetStartedButton', + child: VoicesFilledButton( + key: const Key('GetStartedButton'), + onTap: () async => RegistrationDialog.show( + context, + type: const FreshRegistration(), + ), + child: Text(context.l10n.getStarted), ), - child: Text(context.l10n.getStarted), ); } } diff --git a/catalyst_voices/apps/voices/lib/widgets/buttons/voices_buttons.dart b/catalyst_voices/apps/voices/lib/widgets/buttons/voices_buttons.dart index ffc56f36f18c..558dd71a1b1d 100644 --- a/catalyst_voices/apps/voices/lib/widgets/buttons/voices_buttons.dart +++ b/catalyst_voices/apps/voices/lib/widgets/buttons/voices_buttons.dart @@ -259,17 +259,22 @@ class ShareButton extends StatelessWidget { class VoicesBackButton extends StatelessWidget { final VoidCallback? onTap; + final String? semanticsIdentifier; const VoicesBackButton({ super.key, this.onTap, + this.semanticsIdentifier, }); @override Widget build(BuildContext context) { return VoicesOutlinedButton( onTap: onTap, - child: Text(context.l10n.back), + child: Semantics( + identifier: semanticsIdentifier ?? 'BackButton', + child: Text(context.l10n.back), + ), ); } } @@ -398,17 +403,18 @@ class VoicesLearnMoreTextButton extends StatelessWidget { class VoicesNextButton extends StatelessWidget { final VoidCallback? onTap; + final String? semanticsIdentifier; - const VoicesNextButton({ - super.key, - this.onTap, - }); + const VoicesNextButton({super.key, this.onTap, this.semanticsIdentifier}); @override Widget build(BuildContext context) { return VoicesFilledButton( onTap: onTap, - child: Text(context.l10n.next), + child: Semantics( + identifier: semanticsIdentifier ?? 'NextButton', + child: Text(context.l10n.next), + ), ); } } 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..2a1cc2664eb8 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,10 +52,16 @@ class VoicesIconButton extends StatelessWidget { @override Widget build(BuildContext context) { - return IconButton( - onPressed: onTap, - style: (style ?? const ButtonStyle()).merge(_buildVariantStyle(context)), - icon: child, + return Semantics( + container: true, + button: true, + label: 'closeBtn-test', + child: IconButton( + onPressed: onTap, + style: + (style ?? const ButtonStyle()).merge(_buildVariantStyle(context)), + icon: child, + ), ); } 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 f9166c85ab0a..e1c3673262b5 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 @@ -179,32 +179,35 @@ 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, + 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" + } +}