From 23f7eb3d772ea10eff7cd7391115009422830c43 Mon Sep 17 00:00:00 2001 From: Cesar Date: Mon, 25 Nov 2024 17:21:01 +0000 Subject: [PATCH] test:webwallet --- .github/workflows/pull-request.yml | 5 +- e2e/{src => }/config.ts | 3 +- e2e/node_modules/@types/fs-extra | 1 + e2e/node_modules/@types/imap-simple | 1 + e2e/node_modules/@types/mailparser | 1 + e2e/node_modules/@types/node | 2 +- e2e/node_modules/@types/nodemailer | 1 + e2e/node_modules/axios | 1 + e2e/node_modules/fs-extra | 1 + e2e/node_modules/imap-simple | 1 + e2e/node_modules/mailparser | 1 + e2e/node_modules/nodemailer | 1 + e2e/package.json | 16 +- e2e/playwright.config.ts | 21 +- e2e/src/fixtures.ts | 11 - e2e/src/languages/ILanguage.ts | 3 - e2e/src/languages/en/index.ts | 162 --- e2e/src/languages/index.ts | 8 - e2e/src/page-objects/Account.ts | 930 ------------------ e2e/src/page-objects/Activity.ts | 74 -- e2e/src/page-objects/AddressBook.ts | 83 -- e2e/src/page-objects/Dapps.ts | 157 --- e2e/src/page-objects/DeveloperSettings.ts | 72 -- e2e/src/page-objects/ExtensionPage.ts | 486 --------- e2e/src/page-objects/Messages.ts | 17 - e2e/src/page-objects/Navigation.ts | 140 --- e2e/src/page-objects/Network.ts | 91 -- e2e/src/page-objects/Nfts.ts | 21 - e2e/src/page-objects/Preferences.ts | 40 - e2e/src/page-objects/Settings.ts | 142 --- e2e/src/page-objects/Swap.ts | 95 -- e2e/src/page-objects/TokenDetails.ts | 94 -- e2e/src/page-objects/Wallet.ts | 152 --- e2e/src/specs/dapps.spec.ts | 47 - e2e/src/test.ts | 183 ---- e2e/src/utils/Clipboard.ts | 46 - e2e/src/utils/common.ts | 30 - e2e/src/utils/downloadGitHubRelease.ts | 64 -- e2e/src/utils/getBranchVersion.sh | 24 - e2e/src/utils/getBranchVersion.ts | 22 - e2e/src/utils/global.teardown.ts | 16 - e2e/src/utils/index.ts | 19 - e2e/src/utils/unzip.sh | 58 -- e2e/src/utils/unzip.ts | 36 - e2e/src/webwallet/config.ts | 30 + e2e/src/webwallet/fixtures.ts | 8 + e2e/src/webwallet/page-objects/Dapps.ts | 104 ++ e2e/src/webwallet/page-objects/Login.ts | 88 ++ e2e/src/webwallet/page-objects/Navigation.ts | 44 + e2e/src/webwallet/page-objects/WalletHome.ts | 90 ++ .../page-objects/WalletHomeSubpages.ts | 124 +++ .../webwallet/page-objects/WebWalletPage.ts | 35 + .../webwallet/shared/cfg/global.teardown.ts | 16 + e2e/src/webwallet/shared/cfg/test.ts | 75 ++ e2e/src/webwallet/shared/config.ts | 27 + e2e/src/webwallet/shared/src/Utils.ts | 21 + .../{utils => webwallet/shared/src}/assets.ts | 29 +- e2e/src/webwallet/shared/src/common.ts | 8 + e2e/src/webwallet/shared/src/emailClient.ts | 139 +++ e2e/src/webwallet/specs/dapps.spec.ts | 17 + e2e/src/webwallet/test.ts | 94 ++ e2e/tsconfig.json | 16 +- package.json | 7 +- pnpm-lock.yaml | 783 +++++++++++---- 64 files changed, 1585 insertions(+), 3549 deletions(-) rename e2e/{src => }/config.ts (98%) create mode 120000 e2e/node_modules/@types/fs-extra create mode 120000 e2e/node_modules/@types/imap-simple create mode 120000 e2e/node_modules/@types/mailparser create mode 120000 e2e/node_modules/@types/nodemailer create mode 120000 e2e/node_modules/axios create mode 120000 e2e/node_modules/fs-extra create mode 120000 e2e/node_modules/imap-simple create mode 120000 e2e/node_modules/mailparser create mode 120000 e2e/node_modules/nodemailer delete mode 100644 e2e/src/fixtures.ts delete mode 100644 e2e/src/languages/ILanguage.ts delete mode 100644 e2e/src/languages/en/index.ts delete mode 100644 e2e/src/languages/index.ts delete mode 100644 e2e/src/page-objects/Account.ts delete mode 100644 e2e/src/page-objects/Activity.ts delete mode 100644 e2e/src/page-objects/AddressBook.ts delete mode 100644 e2e/src/page-objects/Dapps.ts delete mode 100644 e2e/src/page-objects/DeveloperSettings.ts delete mode 100644 e2e/src/page-objects/ExtensionPage.ts delete mode 100644 e2e/src/page-objects/Messages.ts delete mode 100644 e2e/src/page-objects/Navigation.ts delete mode 100644 e2e/src/page-objects/Network.ts delete mode 100644 e2e/src/page-objects/Nfts.ts delete mode 100644 e2e/src/page-objects/Preferences.ts delete mode 100644 e2e/src/page-objects/Settings.ts delete mode 100644 e2e/src/page-objects/Swap.ts delete mode 100644 e2e/src/page-objects/TokenDetails.ts delete mode 100644 e2e/src/page-objects/Wallet.ts delete mode 100644 e2e/src/specs/dapps.spec.ts delete mode 100644 e2e/src/test.ts delete mode 100644 e2e/src/utils/Clipboard.ts delete mode 100644 e2e/src/utils/common.ts delete mode 100644 e2e/src/utils/downloadGitHubRelease.ts delete mode 100755 e2e/src/utils/getBranchVersion.sh delete mode 100644 e2e/src/utils/getBranchVersion.ts delete mode 100644 e2e/src/utils/global.teardown.ts delete mode 100644 e2e/src/utils/index.ts delete mode 100755 e2e/src/utils/unzip.sh delete mode 100644 e2e/src/utils/unzip.ts create mode 100644 e2e/src/webwallet/config.ts create mode 100644 e2e/src/webwallet/fixtures.ts create mode 100644 e2e/src/webwallet/page-objects/Dapps.ts create mode 100644 e2e/src/webwallet/page-objects/Login.ts create mode 100644 e2e/src/webwallet/page-objects/Navigation.ts create mode 100644 e2e/src/webwallet/page-objects/WalletHome.ts create mode 100644 e2e/src/webwallet/page-objects/WalletHomeSubpages.ts create mode 100644 e2e/src/webwallet/page-objects/WebWalletPage.ts create mode 100644 e2e/src/webwallet/shared/cfg/global.teardown.ts create mode 100644 e2e/src/webwallet/shared/cfg/test.ts create mode 100644 e2e/src/webwallet/shared/config.ts create mode 100644 e2e/src/webwallet/shared/src/Utils.ts rename e2e/src/{utils => webwallet/shared/src}/assets.ts (93%) create mode 100644 e2e/src/webwallet/shared/src/common.ts create mode 100644 e2e/src/webwallet/shared/src/emailClient.ts create mode 100644 e2e/src/webwallet/specs/dapps.spec.ts create mode 100644 e2e/src/webwallet/test.ts diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index b4814f9..5fc846c 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -17,6 +17,9 @@ jobs: E2E_REPO_TOKEN: ${{ secrets.E2E_REPO_TOKEN }} E2E_REPO_OWNER: ${{ secrets.E2E_REPO_OWNER }} E2E_REPO_RELEASE_NAME: ${{ secrets.E2E_REPO_RELEASE_NAME }} + WW_EMAIL: ${{ secrets.WW_EMAIL }} + WW_LOGIN_PASSWORD: ${{ secrets.WW_LOGIN_PASSWORD }} + EMAIL_PASSWORD: ${{ secrets.EMAIL_PASSWORD }} steps: - name: Checkout code @@ -62,7 +65,7 @@ jobs: fi sleep 1 done - xvfb-run --auto-servernum pnpm test + xvfb-run --auto-servernum pnpm test:webwallet - name: Upload artifacts uses: actions/upload-artifact@v4 if: always() diff --git a/e2e/src/config.ts b/e2e/config.ts similarity index 98% rename from e2e/src/config.ts rename to e2e/config.ts index 4deb5df..85abf2f 100644 --- a/e2e/src/config.ts +++ b/e2e/config.ts @@ -2,11 +2,10 @@ import path from "path" import dotenv from "dotenv" import fs from "fs" -const envPath = path.resolve(__dirname || "", "../.env") +const envPath = path.resolve(__dirname || "", ".env") if (fs.existsSync(envPath)) { dotenv.config({ path: envPath }) } - const commonConfig = { isProdTesting: process.env.ARGENT_X_ENVIRONMENT === "prod" ? true : false || "", password: "MyP@ss3!", diff --git a/e2e/node_modules/@types/fs-extra b/e2e/node_modules/@types/fs-extra new file mode 120000 index 0000000..056c5a5 --- /dev/null +++ b/e2e/node_modules/@types/fs-extra @@ -0,0 +1 @@ +../../../node_modules/.pnpm/@types+fs-extra@11.0.4/node_modules/@types/fs-extra \ No newline at end of file diff --git a/e2e/node_modules/@types/imap-simple b/e2e/node_modules/@types/imap-simple new file mode 120000 index 0000000..e36fd63 --- /dev/null +++ b/e2e/node_modules/@types/imap-simple @@ -0,0 +1 @@ +../../../node_modules/.pnpm/@types+imap-simple@4.2.9/node_modules/@types/imap-simple \ No newline at end of file diff --git a/e2e/node_modules/@types/mailparser b/e2e/node_modules/@types/mailparser new file mode 120000 index 0000000..1dece4c --- /dev/null +++ b/e2e/node_modules/@types/mailparser @@ -0,0 +1 @@ +../../../node_modules/.pnpm/@types+mailparser@3.4.5/node_modules/@types/mailparser \ No newline at end of file diff --git a/e2e/node_modules/@types/node b/e2e/node_modules/@types/node index c93d2e8..b564bae 120000 --- a/e2e/node_modules/@types/node +++ b/e2e/node_modules/@types/node @@ -1 +1 @@ -../../../node_modules/.pnpm/@types+node@22.9.0/node_modules/@types/node \ No newline at end of file +../../../node_modules/.pnpm/@types+node@22.9.3/node_modules/@types/node \ No newline at end of file diff --git a/e2e/node_modules/@types/nodemailer b/e2e/node_modules/@types/nodemailer new file mode 120000 index 0000000..a5ebce0 --- /dev/null +++ b/e2e/node_modules/@types/nodemailer @@ -0,0 +1 @@ +../../../node_modules/.pnpm/@types+nodemailer@6.4.17/node_modules/@types/nodemailer \ No newline at end of file diff --git a/e2e/node_modules/axios b/e2e/node_modules/axios new file mode 120000 index 0000000..3836960 --- /dev/null +++ b/e2e/node_modules/axios @@ -0,0 +1 @@ +../../node_modules/.pnpm/axios@1.7.7/node_modules/axios \ No newline at end of file diff --git a/e2e/node_modules/fs-extra b/e2e/node_modules/fs-extra new file mode 120000 index 0000000..1ab2b07 --- /dev/null +++ b/e2e/node_modules/fs-extra @@ -0,0 +1 @@ +../../node_modules/.pnpm/fs-extra@11.2.0/node_modules/fs-extra \ No newline at end of file diff --git a/e2e/node_modules/imap-simple b/e2e/node_modules/imap-simple new file mode 120000 index 0000000..a106ea2 --- /dev/null +++ b/e2e/node_modules/imap-simple @@ -0,0 +1 @@ +../../node_modules/.pnpm/imap-simple@5.1.0/node_modules/imap-simple \ No newline at end of file diff --git a/e2e/node_modules/mailparser b/e2e/node_modules/mailparser new file mode 120000 index 0000000..5ac761b --- /dev/null +++ b/e2e/node_modules/mailparser @@ -0,0 +1 @@ +../../node_modules/.pnpm/mailparser@3.7.1/node_modules/mailparser \ No newline at end of file diff --git a/e2e/node_modules/nodemailer b/e2e/node_modules/nodemailer new file mode 120000 index 0000000..e7738a7 --- /dev/null +++ b/e2e/node_modules/nodemailer @@ -0,0 +1 @@ +../../node_modules/.pnpm/nodemailer@6.9.16/node_modules/nodemailer \ No newline at end of file diff --git a/e2e/package.json b/e2e/package.json index ca6c0a0..f478608 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -7,6 +7,8 @@ "peerDependencies": { "@scure/base": "^1.1.1", "@scure/bip39": "^1.2.1", + "axios": "^1.7.7", + "fs-extra": "^11.2.0", "lodash-es": "^4.17.21", "object-hash": "^3.0.0", "react": "^18.0.0", @@ -15,15 +17,25 @@ "zod": "^3.23.8" }, "devDependencies": { - "@types/axios": "^0.14.0", "@playwright/test": "^1.48.1", + "@types/axios": "^0.14.0", + "@types/fs-extra": "^11.0.4", + "@types/imap-simple": "^4.2.9", + "@types/mailparser": "^3.4.5", "@types/node": "^22.0.0", + "@types/nodemailer": "^6.4.17", "@types/uuid": "^10.0.0", "dotenv": "^16.3.1", "starknet": "6.11.0", "uuid": "^11.0.0" }, "scripts": { - "test": "pnpm playwright test " + "test:argentx": "pnpm playwright test --project=ArgentX", + "test:webwallet": "pnpm playwright test --project=WebWallet" + }, + "dependencies": { + "imap-simple": "^5.1.0", + "mailparser": "^3.7.1", + "nodemailer": "^6.9.16" } } \ No newline at end of file diff --git a/e2e/playwright.config.ts b/e2e/playwright.config.ts index 2565b9f..eba8c75 100644 --- a/e2e/playwright.config.ts +++ b/e2e/playwright.config.ts @@ -1,5 +1,5 @@ import type { PlaywrightTestConfig } from "@playwright/test" -import config from "./src/config" +import config from "./config" const playwrightConfig: PlaywrightTestConfig = { projects: [ @@ -13,7 +13,22 @@ const playwrightConfig: PlaywrightTestConfig = { }, timeout: config.isCI ? 5 * 60e3 : 1 * 60e3, expect: { timeout: 2 * 60e3 }, // 2 minute - testDir: "./src/specs", + testDir: "./src/argent-x/specs", + testMatch: /\.spec.ts$/, + retries: config.isCI ? 1 : 0, + outputDir: config.artifactsDir, + }, + { + name: "WebWallet", + use: { + trace: "retain-on-failure", + actionTimeout: 120 * 1000, // 2 minute + permissions: ["clipboard-read", "clipboard-write"], + screenshot: "only-on-failure", + }, + timeout: config.isCI ? 5 * 60e3 : 1 * 60e3, + expect: { timeout: 2 * 60e3 }, // 2 minute + testDir: "./src/webwallet/specs", testMatch: /\.spec.ts$/, retries: config.isCI ? 1 : 0, outputDir: config.artifactsDir, @@ -29,7 +44,7 @@ const playwrightConfig: PlaywrightTestConfig = { forbidOnly: config.isCI, outputDir: config.artifactsDir, preserveOutput: "failures-only", - globalTeardown: "./src/utils/global.teardown.ts", + // globalTeardown: "./src/utils/global.teardown.ts", } export default playwrightConfig diff --git a/e2e/src/fixtures.ts b/e2e/src/fixtures.ts deleted file mode 100644 index 24e3870..0000000 --- a/e2e/src/fixtures.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ChromiumBrowserContext } from "@playwright/test" - -import type ExtensionPage from "./page-objects/ExtensionPage" - -export interface TestExtensions { - extension: ExtensionPage - secondExtension: ExtensionPage - thirdExtension: ExtensionPage - browserContext: ChromiumBrowserContext - upgradeExtension: ExtensionPage -} diff --git a/e2e/src/languages/ILanguage.ts b/e2e/src/languages/ILanguage.ts deleted file mode 100644 index c96f1e4..0000000 --- a/e2e/src/languages/ILanguage.ts +++ /dev/null @@ -1,3 +0,0 @@ -import texts from "./en" - -export type ILanguage = typeof texts diff --git a/e2e/src/languages/en/index.ts b/e2e/src/languages/en/index.ts deleted file mode 100644 index 7737812..0000000 --- a/e2e/src/languages/en/index.ts +++ /dev/null @@ -1,162 +0,0 @@ -const texts = { - common: { - back: "Back", - close: "Close", - confirm: "Confirm", - done: "Done", - next: "Next", - continue: "Continue", - yes: "Yes", - no: "No", - unlock: "Unlock", - showSettings: "Show settings", - reset: "Reset", - confirmReset: "Reset", - save: "Save", - create: "Create", - cancel: "Cancel", - privacyStatement: - "GDPR statement for browser extension wallet: Argent takes the privacy and security of individuals very seriously and takes every reasonable measure and precaution to protect and secure the personal data that we process. The browser extension wallet does not collect any personal information nor does it correlate any of your personal information with anonymous data processed as part of its services. On top of this Argent has robust information security policies and procedures in place to make sure any processing complies with applicable laws. If you would like to know more or have any questions then please visit our website at https://www.argent.xyz/", - approve: "Approve", - addArgentShield: "Add Argent Shield", - changeAccountType: "Change", - accountUpgraded: "Account upgraded", - changedToStandardAccount: "Changed to Standard Account", - dismiss: "Dismiss", - reviewSend: "Review send", - hide: "Hide account", - copy: "Copy", - beforeYouContinue: "Before you continue...", - seedWarning: - "Please save your recovery phrase. This is the only way you will be able to recover your Argent X accounts", - revealSeedPhrase: "Click to reveal recovery phrase", - copied: "Copied", - confirmRecovery: - "I have saved my recovery phrase and understand I should never share it with anyone else", - remove: "Remove", - upgrade: "Upgrade", - }, - account: { - noAccounts: "You have no accounts on", - createAccount: "Create account", - fund: "Fund", - fundsFromStarkNet: "From another Starknet wallet", - fullAccountAddress: "Full account address", - send: "Send", - export: "Export", - accountRecovery: "Save your recovery phrase", - showAccountRecovery: "Show recovery phrase", - saveTheRecoveryPhrase: "Save the recovery phrase", - confirmTheSeedPhrase: - "I have saved my recovery phrase and understand I should never share it with anyone else", - pendingTransactions: "Pending", - recipientAddress: "Recipient's address", - saveAddress: "Save address", - deployFirst: - "You must deploy this account before upgrading to a Smart Account", - wrongPassword: "Incorrect password", - invalidStarkIdError: " not found", - shortAddressError: "Address must be 66 characters long", - invalidCheckSumError: "Invalid address (checksum error)", - invalidAddress: "Invalid address", - createMultisig: "Create multisig", - activateAccount: "Activate Account", - notEnoughFoundsFee: "Insufficient funds to pay fee", - newToken: "New token", - argentShield: { - wrongCode: "Looks like the wrong code. Please try again.", - failedCode: - "You have reached the maximum number of attempts. Please wait 30 minutes and request a new code.", - codeNotRequested: - "You have not requested a verification code. Please request a new one.", - emailInUse: - /This address is associated with accounts from another seedphrase[.,]?\s*Please enter another email address to continue[.,]?/, - }, - removedFromMultisig: "You were removed from this multisig", - copyAddress: "Copy address", - }, - wallet: { - //first screen - banner1: "Welcome to Argent X", - desc1: "Enjoy the security of Ethereum with the scale of Starknet", - createButton: "Create a new wallet", - restoreButton: "Restore an existing wallet", - //second screen - banner2: "Disclaimer", - desc2: - "Starknet is in Alpha and may experience technical issues or introduce breaking changes from time to time. Please accept this before continuing.", - lossOfFunds: - "I understand that Starknet will introduce changes (e.g. Cairo 1.0) that will affect my existing account(s) (e.g. rendering unusable) if I do not complete account upgrades.", - alphaVersion: - "I understand that Starknet may experience performance issues and my transactions may fail for various reasons.", - //third screen - banner3: "Create a password", - desc3: "This is used to protect and unlock your wallet", - password: "Password", - repeatPassword: "Repeat password", - createWallet: "Create wallet", - //fourth screen - banner4: /Your (smart )?account is ready!/, - download: "Download the mobile app", - twitter: "Follow us on X", - dapps: "Explore Starknet apps", - finish: "Finish", - }, - settings: { - account: { - manageOwners: { - manageOwners: "Manage owners", - removeOwner: "Remove owner", - replaceOwner: "Replace owner", - }, - setConfirmations: "Set confirmations", - viewOnStarkScan: "View on StarkScan", - viewOnVoyager: "View on Voyager", - hideAccount: "Hide account", - deployAccount: "Deploy account", - authorisedDapps: { - authorisedDapps: "Authorised dapps", - connect: "Connect", - reject: "Reject", - disconnectAll: "Disconnect all", - noAuthorisedDapps: "No authorised dapps", - }, - exportPrivateKey: "Export private key", - }, - preferences: { - preferences: "Preferences", - hideTokens: "Hidden and spam tokens", - hiddenAccounts: "Hidden accounts", - defaultBlockExplorer: "Default block explorer", - defaultNFTMarket: "Default NFT marketplace", - emailNotifications: "Email notifications", - }, - securityPrivacy: { - securityPrivacy: "Security & privacy", - autoLockTimer: "Auto lock timer", - recoveryPhase: "Recovery phrase", - automaticErrorReporting: "Automatic Error Reporting", - shareAnonymousData: "Share anonymous data", - }, - addressBook: { - addressBook: "Address book", - nameRequired: "Contact Name is required", - addressRequired: "Address is required", - removeAddress: "Remove from address book", - delete: "Delete", - }, - advancedSettings: { - advancedSettings: "Advanced settings", - manageNetworks: { - manageNetworks: "Manage networks", - restoreDefaultNetworks: "Restore default networks", - }, - smartContractDevelopment: "Smart Contract Development", - experimental: "Experimental", - }, - extendedView: "Extended view", - lockWallet: "Lock wallet", - }, -} as const - -export default texts diff --git a/e2e/src/languages/index.ts b/e2e/src/languages/index.ts deleted file mode 100644 index 183e1b9..0000000 --- a/e2e/src/languages/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import path from "node:path" - -import type { ILanguage } from "./ILanguage" - -// eslint-disable-next-line @typescript-eslint/no-var-requires -export const lang: ILanguage = require( - path.join(__dirname, `${process.env.LANGUAGE ?? "en"}`), -).default diff --git a/e2e/src/page-objects/Account.ts b/e2e/src/page-objects/Account.ts deleted file mode 100644 index 88b2f56..0000000 --- a/e2e/src/page-objects/Account.ts +++ /dev/null @@ -1,930 +0,0 @@ -import { Page, expect } from "@playwright/test" - -import { lang } from "../languages" -import Activity from "./Activity" -import { FeeTokens, TokenSymbol, logInfo, sleep } from "../utils" -import config from "../config" - -export interface IAsset { - name: string - balance: number - unit: string -} - -export default class Account extends Activity { - upgradeTest: boolean - constructor(page: Page, upgradeTest: boolean = false) { - super(page) - this.upgradeTest = upgradeTest - } - accountName1 = "Account 1" - accountName2 = "Account 2" - accountName3 = "Account 3" - accountNameMulti1 = "Multisig 1" - accountNameMulti2 = "Multisig 2" - accountNameMulti3 = "Multisig 3" - accountNameMulti4 = "Multisig 4" - accountNameMulti5 = "Multisig 5" - accountNameMulti6 = "Multisig 6" - - importedAccountName1 = "Imported Account 1" - importedAccountName2 = "Imported Account 2" - get noAccountBanner() { - return this.page.locator(`div h4:has-text("${lang.account.noAccounts}")`) - } - - get createAccount() { - return this.page.locator('[data-testid="create-account-button"]') - } - - get fundMenu() { - return this.page.getByRole("button", { name: "Fund" }) - } - - get addFundsFromStartNet() { - return this.page.locator(`a :text-is("${lang.account.fundsFromStarkNet}")`) - } - - get accountAddress() { - return this.page.locator( - `[aria-label="${lang.account.fullAccountAddress}"]`, - ) - } - - get accountAddressFromAssetsView() { - return this.page.locator('[data-testid="address-copy-button"]').first() - } - - get send() { - return this.page.locator(`button:has-text("${lang.account.send}")`) - } - - get sendToHeader() { - return this.page.getByRole("heading", { name: "Send to" }) - } - - get deployAccount() { - return this.page.locator( - `button :text-is("${lang.settings.account.deployAccount}")`, - ) - } - - get selectTokenButton() { - return this.page.getByTestId("select-token-button") - } - - async accountNames() { - await expect( - this.page.locator('[data-testid="account-name"]').first(), - ).toBeVisible() - return await this.page - .locator('[data-testid="account-name"]') - .all() - .then( - async (els) => - await Promise.all(els.map(async (el) => await el.textContent())), - ) - } - - token(tkn: TokenSymbol) { - return this.page.locator(`[data-testid="${tkn}"]`) - } - - get accountListSelector() { - return this.page.locator(`[aria-label="Show account list"]`) - } - - get addANewAccountFromAccountList() { - return this.page.getByRole("button", { name: "Add account" }) - } - - get addStandardAccountFromNewAccountScreen() { - return this.page.locator('[aria-label="Standard Account"]') - } - - get importAccountFromNewAccountScreen() { - return this.page.locator('[aria-label="Import from private key"]') - } - - get importAccountAddressLoc() { - return this.page.locator('[name="address"]') - } - - get importPKLoc() { - return this.page.locator('[name="pk"]') - } - - get importSubmitLoc() { - return this.page.locator('button:text-is("Import")') - } - - get addMultisigAccountFromNewAccountScreen() { - return this.page.locator('[aria-label="Multisig Account"]') - } - - get createWithArgent() { - return this.page.locator('[aria-label="Create with Argent"]') - } - - get createNewMultisig() { - return this.page.locator('[aria-label="Create new multisig"]') - } - - get joinExistingMultisig() { - return this.page.locator('[aria-label="Join existing multisig"]') - } - - get joinWithArgent() { - return this.page.locator('[aria-label="Join with Argent"]') - } - - get assetsList() { - return this.page.locator('button[role="alert"] ~ button') - } - - get amount() { - return this.page.locator('[name="amount"]') - } - - get sendMax() { - return this.page.locator('button:text-is("Max")') - } - - get recipientAddressQuery() { - return this.page.locator('[data-testid="recipient-input"]') - } - - account(accountName: string) { - return this.page.locator(`button[aria-label^="Select ${accountName}"]`) - } - - accountNameBtnLoc(accountName: string) { - return this.page.locator(`button[aria-label="Select ${accountName}"]`) - } - - get balance() { - return this.page.locator('[data-testid="tokenBalance"]') - } - - currentBalance(tkn: TokenSymbol) { - return this.page.locator(`[data-testid="${tkn}-balance"]`) - } - - currentBalanceDevNet(tkn: "ETH") { - return this.page.locator(`//button//h6[contains(text(), '${tkn}')]`) - } - - get accountName() { - return this.page.locator('[data-testid="account-tokens"] h2') - } - - invalidStarkIdError(id: string) { - return this.page.locator( - `form label:has-text('${id}${lang.account.invalidStarkIdError}')`, - ) - } - - get shortAddressError() { - return this.page.locator( - `form label:has-text('${lang.account.shortAddressError}')`, - ) - } - - get invalidCheckSumError() { - return this.page.locator( - `form label:has-text('${lang.account.invalidCheckSumError}')`, - ) - } - - get invalidAddress() { - return this.page.locator( - `form label:has-text('${lang.account.invalidAddress}')`, - ) - } - - get failPredict() { - return this.page.locator('[data-testid="tx-error"]') - } - - accountGroup( - group: string = "my-accounts" || - "multisig - accounts" || - "imported-accounts", - ) { - return this.page.locator(`[data-testid="${group}"]`) - } - - async addAccountMainnet({ firstAccount = true }: { firstAccount?: boolean }) { - if (firstAccount) { - await this.createAccount.click() - } else { - await this.accountListSelector.click() - await this.addANewAccountFromAccountList.click() - } - await this.addStandardAccountFromNewAccountScreen.click() - await this.continueLocator.click() - await this.account("").last().click() - await expect(this.accountListSelector).toBeVisible() - } - - async dismissAccountRecoveryBanner() { - await this.showAccountRecovery.click() - await this.confirmTheSeedPhrase.click() - await this.doneLocator.click() - } - - async addAccount({ firstAccount = true }: { firstAccount?: boolean }) { - if (firstAccount) { - await this.createAccount.click() - } else { - await this.accountListSelector.click() - await this.page.getByRole("button", { name: "Add account" }).click() - } - await this.addStandardAccountFromNewAccountScreen.click() - await this.continueLocator.click() - await expect(this.account("").last()).toBeVisible() - const accountsName = await this.account("").allInnerTexts() - const accountLoc = this.page.locator( - `[data-testid="Account ${accountsName.length}"]`, - ) - await expect(accountLoc).toBeVisible() - await this.account(`Account ${accountsName.length}`).hover() - await expect( - accountLoc.locator('[data-testid="goto-settings"]'), - ).toBeVisible() - await accountLoc.click() - //todo check why this is needed, click twice - await sleep(1000) - if (await accountLoc.isVisible()) { - await accountLoc.click() - } - await expect(this.accountListSelector).toBeVisible() - await this.fundMenu.click() - await this.addFundsFromStartNet.click() - const accountAddress = await this.accountAddress - .textContent() - .then((v) => v?.replaceAll(" ", "")) - await this.closeLocator.last().click() - const accountName = await this.accountListSelector.textContent() - return { accountName, accountAddress } - } - - async selectAccount(accountName: string) { - await this.accountListSelector.click() - await this.account(accountName).click() - } - - async ensureSelectedAccount(accountName: string) { - const currentAccount = await this.accountListSelector.textContent() - if (currentAccount != accountName) { - await this.selectAccount(accountName) - } - await expect(this.accountListSelector).toContainText(accountName) - } - - async assets(accountName: string) { - await this.ensureSelectedAccount(accountName) - - const assetsList: IAsset[] = [] - for (const asset of await this.assetsList.all()) { - const row = (await asset.innerText()).split(/\r?\n| /) - assetsList.push({ - name: row[0], - balance: parseFloat(row[1]), - unit: row[2], - } as IAsset) - } - return assetsList - } - - async ensureAsset( - accountName: string, - name: TokenSymbol = "ETH", - value: string, - ) { - await this.ensureSelectedAccount(accountName) - await expect(this.currentBalance(name)).toContainText(value) - } - - async getTotalFeeValue() { - const fee = await this.page - .locator('[aria-label="Show Fee Estimate details"] p') - .first() - .textContent() - if (!fee) { - throw new Error("Error! Fee not available") - } - - return parseFloat(fee.split(" ")[0]) - } - async txValidations(feAmount: string) { - const trxAmountHeader = await this.page - .locator(`//*[starts-with(text(),'Send ')]`) - .textContent() - .then((v) => v?.split(" ")[1]) - - const sendAmountFEText = await this.page - .locator("[data-fe-value]") - .getAttribute("data-fe-value") - const sendAmountTXText = await this.page - .locator("[data-tx-value]") - .getAttribute("data-tx-value") - const sendAmountFE = sendAmountFEText!.split(" ")[0] - const sendAmountTX = parseInt(sendAmountTXText!) - logInfo({ sendAmountFE, sendAmountTX }) - expect(sendAmountFE).toBe(`${trxAmountHeader}`) - - if (feAmount != "MAX") { - expect(feAmount).toBe(trxAmountHeader) - } - return { sendAmountTX, sendAmountFE } - } - - async fillRecipientAddress({ - recipientAddress, - fillRecipientAddress = "paste", - validAddress = true, - }: { - recipientAddress: string - fillRecipientAddress?: "typing" | "paste" - validAddress?: boolean - }) { - if (fillRecipientAddress === "paste") { - await this.setClipboardText(recipientAddress) - await this.recipientAddressQuery.focus() - await this.paste() - } else { - await this.recipientAddressQuery.type(recipientAddress) - await this.page.keyboard.press("Enter") - } - if (validAddress) { - if (recipientAddress.endsWith("stark")) { - await this.page.click(`button:has-text("${recipientAddress}")`) - } - } - } - - async confirmTransaction() { - await Promise.race([ - expect(this.confirmLocator) - .toBeEnabled() - .then((_) => this.confirmLocator.click()), - expect(this.failPredict).toBeVisible(), - ]) - if (await this.failPredict.isVisible()) { - await this.failPredict.click() - console.error("failPredict", this.paste) - } - } - - async transfer({ - originAccountName, - recipientAddress, - token, - amount, - fillRecipientAddress = "paste", - submit = true, - feeToken = "ETH", - }: { - originAccountName: string - recipientAddress: string - token: TokenSymbol - amount: number | "MAX" - fillRecipientAddress?: "typing" | "paste" - submit?: boolean - feeToken?: FeeTokens - }) { - await this.ensureSelectedAccount(originAccountName) - await this.send.click() - await this.fillRecipientAddress({ recipientAddress, fillRecipientAddress }) - await this.selectTokenButton.click() - await this.token(token).click() - if (amount === "MAX") { - await expect(this.balance).toBeVisible() - await expect(this.sendMax).toBeVisible() - await this.sendMax.click() - } else { - await this.amount.fill(amount.toString()) - } - - await this.reviewSendLocator.click() - - if (submit) { - if (feeToken) { - await this.selectFeeToken(feeToken) - } - await this.confirmTransaction() - } - const { sendAmountFE, sendAmountTX } = await this.txValidations( - amount.toString(), - ) - try { - await expect(this.failPredict) - .toBeVisible({ timeout: 1000 * 3 }) - .then(async (_) => { - await this.failPredict.click() - await this.page.locator('[data-testid="copy-error"]').click() - await this.setClipboard() - console.error( - "Error message copied to clipboard", - await this.getClipboard(), - ) - throw new Error("Transaction failed") - }) - } catch { - /* empty */ - } - return { sendAmountTX, sendAmountFE } - } - - async ensureTokenBalance({ - accountName, - token, - balance, - }: { - accountName: string - token: TokenSymbol - balance: number - }) { - await this.ensureSelectedAccount(accountName) - await this.token(token).click() - await expect(this.page.locator('[data-testid="tokenBalance"]')).toHaveText( - balance.toString(), - ) - await this.backLocator.click() - } - - get password() { - return this.page.locator('input[name="password"]').first() - } - - get exportPrivateKey() { - return this.page.locator(`button:text-is("${lang.account.export}")`) - } - - get setUpAccountRecovery() { - return this.page.locator( - `button:text-is("${lang.account.accountRecovery}")`, - ) - } - - get showAccountRecovery() { - return this.page.locator( - `button:text-is("${lang.account.showAccountRecovery}")`, - ) - } - - get confirmTheSeedPhrase() { - return this.page.locator( - `p:text-is("${lang.account.confirmTheSeedPhrase}")`, - ) - } - - // account recovery modal - get saveTheRecoveryPhrase() { - return this.page.locator( - `//a//*[text()="${lang.account.saveTheRecoveryPhrase}"]`, - ) - } - - get recipientAddress() { - return this.page.locator('[data-testid="recipient-input"]') - } - - get saveAddress() { - return this.page.locator(`button:text-is("${lang.account.saveAddress}")`) - } - - get copyAddress() { - return this.page.locator('[data-testid="address-copy-button"]').first() - } - - get copyAddressFromFundMenu() { - return this.page.locator(`button:text-is("${lang.account.copyAddress}")`) - } - - contact(label: string) { - return this.page.locator(`div h5:text-is("${label}")`) - } - - get avnuBanner() { - return this.page.locator('p:text-is("Swap with AVNU")') - } - - get ekuboBanner() { - return this.page.locator('p:text-is("Provide liquidity on Ekubo")') - } - - get avnuBannerClose() { - return this.page.locator('[data-testid="close-banner"]') - } - - async saveRecoveryPhrase() { - const nextModal = await this.nextLocator.isVisible({ timeout: 60 }) - if (nextModal) { - await this.nextLocator.click() - } - await this.page - .locator(`span:has-text("${lang.common.revealSeedPhrase}")`) - .click() - const pos = Array.from({ length: 12 }, (_, i) => i + 1) - const seed = await Promise.all( - pos.map(async (index) => { - return this.page - .locator(`//*[normalize-space() = '${index}']/parent::*`) - .textContent() - .then((text) => text?.replace(/[0-9]/g, "")) - }), - ).then((result) => result.join(" ")) - - await Promise.all([ - this.page.locator(`button:has-text("${lang.common.copy}")`).click(), - expect( - this.page.locator(`button:has-text("${lang.common.copied}")`), - ).toBeVisible(), - ]) - await this.setClipboard() - const seedPhraseCopied = await this.getClipboard() - await expect(this.doneLocator).toBeDisabled() - await this.page - .locator(`p:has-text("${lang.common.confirmRecovery}")`) - .click() - await expect(this.page.getByTestId("recovery-phrase-checked")).toBeVisible() - await expect(this.doneLocator).toBeEnabled() - await this.doneLocator.click({ force: true }) - expect(seed).toBe(seedPhraseCopied) - return String(seedPhraseCopied) - } - - // Smart Account - get email() { - return this.page.locator('input[name="email"]') - } - - get pinLocator() { - return this.page.locator('[aria-label="Please enter your pin code"]') - } - - async fillPin(pin: string = "111111") { - //avoid BE error PIN not requested - await sleep(2000) - await expect(this.pinLocator).toHaveCount(6) - await this.pinLocator.first().click() - await this.pinLocator.first().fill(pin) - } - - async setupRecovery() { - //ensure modal is loaded - await expect( - this.page.locator('[data-testid="account-tokens"]'), - ).toBeVisible() - await expect( - this.page.locator('[data-testid="address-copy-button"]'), - ).toBeVisible() - if (config.isProdTesting) { - await this.showAccountRecovery.click() - } else { - await this.accountAddressFromAssetsView.click() - } - return this.saveRecoveryPhrase().then((adr) => String(adr)) - } - - get accountUpgraded() { - return this.page.getByRole("heading", { - name: lang.common.accountUpgraded, - }) - } - - get changedToStandardAccountLabel() { - return this.page.getByRole("heading", { - name: lang.common.changedToStandardAccount, - }) - } - - // Multisig - get deployNeededWarning() { - return this.page.locator(`p:has-text("${lang.account.deployFirst}")`) - } - - get increaseThreshold() { - return this.page.locator(`[data-testid="increase-threshold"]`) - } - - get decreaseThreshold() { - return this.page.locator(`[data-testid="decrease-threshold"]`) - } - - get setConfirmationsLocator() { - return this.page.locator(`button:has-text("Set confirmations")`) - } - - async addMultisigAccount({ - signers = [], - confirmations = 1, - }: { - signers?: string[] - confirmations?: number - }) { - await this.accountListSelector.click() - await this.addANewAccountFromAccountList.click() - await this.addMultisigAccountFromNewAccountScreen.click() - await this.continueLocator.click() - await this.createNewMultisig.click() - - const [pages] = await Promise.all([ - this.page.context().waitForEvent("page"), - this.createWithArgent.click(), - ]) - - const tabs = pages.context().pages() - await tabs[1].waitForLoadState("load") - await expect(tabs[1].locator('[name^="signerKeys.0.key"]')).toHaveCount(1) - - if (signers.length > 0) { - for (let index = 0; index < signers.length; index++) { - await tabs[1] - .locator(`[name="signerKeys\\.${index}\\.key"]`) - .isVisible() - .then(async (visible) => { - if (!visible) { - await tabs[1].locator('[data-testid="addOwnerButton"]').click() - } - }) - await tabs[1] - .locator(`[name="signerKeys.${index}.key"]`) - .fill(signers[index]) - } - } - //remove empty inputs - const locs = await tabs[1].locator('[data-testid^="signerContainer"]').all() - if (locs.length > signers.length) { - for (let index = locs.length; index > signers.length; index--) { - await tabs[1] - .locator(`[data-testid="closeButton.${index - 1}"]`) - .click() - } - } - - await tabs[1].locator('button:text-is("Next")').click() - const currentThreshold = await tabs[1] - .locator('[data-testid="threshold"]') - .innerText() - .then((v) => parseInt(v!)) - - //set confirmations - if (confirmations > currentThreshold) { - for (let i = currentThreshold; i < confirmations; i++) { - await tabs[1].locator('[data-testid="increase-threshold"]').click() - } - } - - await tabs[1] - .locator(`button:text-is("${lang.account.createMultisig}")`) - .click() - await tabs[1].locator(`button:text-is("${lang.wallet.finish}")`).click() - } - - async joinMultisig() { - await this.accountListSelector.click() - await this.addANewAccountFromAccountList.click() - await this.addMultisigAccountFromNewAccountScreen.click() - await this.continueLocator.click() - - await this.joinExistingMultisig.click() - await this.joinWithArgent.click() - await this.page.locator('[data-testid="copy-pubkey"]').click() - await this.setClipboard() - await this.page.locator('[data-testid="button-done"]').click() - return String(await this.getClipboard()) - } - - async addOwnerToMultisig({ - accountName, - pubKey, - confirmations = 1, - }: { - accountName: string - pubKey: string - confirmations?: number - }) { - await this.showSettingsLocator.click() - await this.account(accountName).click() - await this.manageOwners.click() - await this.page.locator('[data-testid="add-owners"]').click() - //hydrogen build will always have 2 inputs - const locs = await this.page.locator('[data-testid^="closeButton."]').all() - for (let index = 0; locs.length - 1 > index; index++) { - await this.page.locator(`[data-testid^="closeButton.${index}"]`).click() - } - await this.page.locator('[name^="signerKeys.0.key"]').fill(pubKey) - - await this.nextLocator.click() - - const currentThreshold = await this.page - .locator('[data-testid="threshold"]') - .innerText() - .then((v) => parseInt(v!)) - //set confirmations - if (confirmations > currentThreshold) { - for (let i = currentThreshold; i < confirmations; i++) { - await this.page.locator('[data-testid="increase-threshold"]').click() - } - } - await this.nextLocator.click() - await this.confirmLocator.click() - } - - ensureMultisigActivated() { - return Promise.all([ - expect(this.page.locator("label:has-text('Not activated')")).toBeHidden(), - expect( - this.page.locator('[data-testid="activating-multisig"]'), - ).toBeHidden(), - ]) - } - - accountListConfirmations(accountName: string) { - return this.page.locator( - `[aria-label="Select ${accountName}"] [data-testid="confirmations"]`, - ) - } - - get accountViewConfirmations() { - return this.page.locator('[data-testid="confirmations"]').first() - } - - async acceptTx(tx: string) { - await this.menuActivityLocator.click() - await this.page.locator(`[data-tx-hash="${tx}"]`).click() - await this.confirmTransaction() - } - - async setConfirmations(accountName: string, confirmations: number) { - await this.ensureSelectedAccount(accountName) - await this.showSettingsLocator.click() - await this.account(accountName).click() - await this.setConfirmationsLocator.click() - - const currentThreshold = await this.page - .locator('[data-testid="threshold"]') - .innerText() - .then((v) => parseInt(v!)) - if (confirmations > currentThreshold) { - for (let i = currentThreshold; i < confirmations; i++) { - await this.increaseThreshold.click() - } - } else if (confirmations < currentThreshold) { - for (let i = currentThreshold; i > confirmations; i--) { - await this.decreaseThreshold.click() - } - } - await this.page.locator('[data-testid="update-confirmations"]').click() - await this.confirmTransaction() - await Promise.all([ - expect(this.confirmLocator).toBeHidden(), - expect(this.menuActivityLocator).toBeVisible(), - ]) - } - - async ensureSmartAccountNotEnabled(accountName: string) { - await this.selectAccount(accountName) - await Promise.all([ - expect(this.menuPendingTransactionsIndicatorLocator).toBeHidden(), - expect( - this.page.locator('[data-testid="smart-account-on-account-view"]'), - ).toBeHidden(), - ]) - await this.showSettingsLocator.click() - await Promise.all([ - expect( - this.page.locator('[data-testid="smart-account-on-settings"]'), - ).toBeHidden(), - expect( - this.page.locator('[data-testid="smart-account-not-activated"]'), - ).toBeVisible(), - ]) - await this.account(accountName).click() - await expect( - this.page.locator( - '[data-testid="smart-account-button"]:has-text("Upgrade to Smart Account")', - ), - ).toBeVisible() - } - - editOwnerLocator(owner: string) { - return this.page.locator(`[data-testid="edit-${owner}"]`) - } - get manageOwners() { - return this.page.locator( - `//button//*[text()="${lang.settings.account.manageOwners.manageOwners}"]`, - ) - } - - get removeOwnerLocator() { - return this.page.locator( - `//button[text()="${lang.settings.account.manageOwners.removeOwner}"]`, - ) - } - - get removedFromMultisigLocator() { - return this.page.getByText(lang.account.removedFromMultisig) - } - - async removeMultiSigOwner(accountName: string, owner: string) { - await this.showSettingsLocator.click() - await this.account(accountName).click() - await this.manageOwners.click() - await this.editOwnerLocator(owner).click() - await this.removeOwnerLocator.click() - await this.removeLocator.click() - await this.nextLocator.click() - await this.confirmTransaction() - } - - //TX v3 - get feeTokenPickerLoc() { - return this.page.locator('[data-testid="fee-token-picker"]') - } - - feeTokenLoc(token: FeeTokens) { - return this.page.locator(`[data-testid="fee-token-${token}"]`) - } - - feeTokenBalanceLoc(token: FeeTokens) { - return this.page.locator(`[data-testid="fee-token-${token}-balance"]`) - } - - selectedFeeTokenLoc(token: FeeTokens) { - return this.feeTokenPickerLoc.locator(`img[alt=${token}]`) - } - - async selectFeeToken(token: FeeTokens) { - //wait for locator to be visible - await Promise.race([ - expect(this.selectedFeeTokenLoc("ETH")).toBeVisible(), - expect(this.selectedFeeTokenLoc("STRK")).toBeVisible(), - ]) - const tokenAlreadySelected = - await this.selectedFeeTokenLoc(token).isVisible() - if (!tokenAlreadySelected) { - await this.feeTokenPickerLoc.click() - await this.feeTokenLoc(token).click() - await expect(this.selectedFeeTokenLoc(token)).toBeVisible() - } - } - - async gotoSettingsFromAccountList(accountName: string) { - await expect(this.accountNameBtnLoc(accountName)).toBeVisible() - await this.accountNameBtnLoc(accountName).hover() - await expect( - this.accountNameBtnLoc(accountName).locator( - '[data-testid="token-value"]', - ), - ).toBeHidden() - await expect( - this.accountNameBtnLoc(accountName).locator( - '[data-testid="goto-settings"]', - ), - ).toBeVisible() - await expect( - this.accountNameBtnLoc(accountName).locator( - '[data-testid="goto-settings"]', - ), - ).toHaveCount(1) - //todo: remove sleep - await sleep(1000) - await this.accountNameBtnLoc(accountName) - .locator('[data-testid="goto-settings"]') - .click() - await expect( - this.page.locator( - `[data-testid="account-settings-${accountName.replaceAll(/ /g, "")}"]`, - ), - ).toBeVisible() - } - - async importAccount({ - address, - privateKey, - validPK = true, - }: { - address: string - privateKey: string - validPK?: boolean - }) { - await this.accountListSelector.click() - await this.addANewAccountFromAccountList.click() - await this.importAccountFromNewAccountScreen.click() - - await this.continueLocator.click() - await this.importAccountAddressLoc.fill(address) - await this.importPKLoc.fill(privateKey) - await this.importSubmitLoc.click() - if (!validPK) { - await Promise.all([ - expect(this.page.getByText("The private key is invalid")).toBeVisible(), - expect(this.page.getByRole("button", { name: "Ok" })).toBeVisible(), - ]) - } - } -} diff --git a/e2e/src/page-objects/Activity.ts b/e2e/src/page-objects/Activity.ts deleted file mode 100644 index 4ad4146..0000000 --- a/e2e/src/page-objects/Activity.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { Page, expect } from "@playwright/test" - -import { lang } from "../languages" -import Navigation from "./Navigation" - -export default class Activity extends Navigation { - constructor(page: Page) { - super(page) - } - - ensurePendingTransactions(nbr: number) { - return expect( - this.page.locator( - `//p[contains(text(),'Pending')]/following-sibling::div[contains(text(),'${nbr}')]`, - ), - ).toBeVisible() - } - - ensureNoPendingTransactions() { - return expect( - this.page.locator( - `h6 div:text-is("${lang.account.pendingTransactions}") >> div`, - ), - ).not.toBeVisible() - } - - activityByDestination(destination: string) { - return this.page.locator( - `//button//p[contains(text()[1], 'To: ') and contains(text()[2], '${destination}')]`, - ) - } - - checkActivity(nbr: number) { - return Promise.all([ - this.menuPendingTransactionsIndicatorLocator.click(), - this.ensurePendingTransactions(nbr), - ]) - } - - async activityTxHashs() { - await expect( - this.page.locator("button[data-tx-hash]").first(), - ).toBeVisible() - const loc = await this.page.locator("button[data-tx-hash]").all() - return Promise.all(loc.map((el) => el.getAttribute("data-tx-hash"))) - } - - async getLastTxHash() { - await this.menuActivityActiveLocator.isVisible().then(async (visible) => { - if (!visible) { - await this.menuActivityLocator.click() - } - }) - expect(this.historyButton) - .toBeVisible({ timeout: 1000 }) - .then(async () => { - await this.historyButton.click() - }) - .catch(async () => { - null - }) - - const txHashs = await this.activityTxHashs() - return txHashs[0] - } - - get historyButton() { - return this.page.locator("button:text-is('History')") - } - - get queueButton() { - return this.page.locator("button:text-is('Queue')") - } -} diff --git a/e2e/src/page-objects/AddressBook.ts b/e2e/src/page-objects/AddressBook.ts deleted file mode 100644 index e98c399..0000000 --- a/e2e/src/page-objects/AddressBook.ts +++ /dev/null @@ -1,83 +0,0 @@ -import type { Page } from "@playwright/test" - -import { lang } from "../languages" -import Navigation from "./Navigation" - -export default class AddressBook extends Navigation { - constructor(page: Page) { - super(page) - } - - get add() { - return this.page.locator('button[aria-label="add"]') - } - - get name() { - return this.page.locator('input[name="name"]') - } - - get address() { - return this.page.locator('textarea[name="address"]') - } - - get network() { - return this.page.locator('[aria-label="network-selector"]') - } - - get saveLocator() { - return this.page.locator(`button:text-is("${lang.common.save}")`) - } - - get cancelLocator() { - return this.page.locator(`button:text-is("${lang.common.cancel}")`) - } - - networkOption(name: "Localhost 5050" | "Sepolia" | "Mainnet") { - return this.page.locator(`button[role="menuitem"]:text-is("${name}")`) - } - - get nameRequired() { - return this.page.locator( - `//input[@name="name"]/following::label[contains(text(), '${lang.settings.addressBook.nameRequired}')]`, - ) - } - - get addressRequired() { - return this.page.locator( - `//textarea[@name="address"]/following::label[contains(text(), '${lang.settings.addressBook.addressRequired}')]`, - ) - } - - addressByName(name: string) { - return this.page.locator( - `//button/following::*[contains(text(),'${name}')]`, - ) - } - - get deleteAddress() { - return this.page.locator( - `button[aria-label="${lang.settings.addressBook.removeAddress}"]`, - ) - } - - get delete() { - return this.page.locator( - `button:text-is("${lang.settings.addressBook.delete}")`, - ) - } - - get addressBook() { - return this.page.locator( - `button:text-is("${lang.settings.addressBook.addressBook}")`, - ) - } - - async editAddress(name: string) { - await this.page.locator(`[data-testid="${name}"]`).first().click() - await this.page.locator(`[data-testid="${name}"]`).first().click() - await this.page.locator(`[data-testid="${name}"]`).first().hover() - await this.page - .locator(`[data-testid="${name}"] [data-testid^="edit-contact"]`) - .click() - } -} diff --git a/e2e/src/page-objects/Dapps.ts b/e2e/src/page-objects/Dapps.ts deleted file mode 100644 index e300272..0000000 --- a/e2e/src/page-objects/Dapps.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { ChromiumBrowserContext, Page, expect } from "@playwright/test" - -import { lang } from "../languages" -import Navigation from "./Navigation" -import config from "../config" - -const dappUrl = "http://localhost:3000" -const dappName = 'localhost' -export default class Dapps extends Navigation { - constructor(page: Page) { - super(page) - } - - account(accountName: string) { - return this.page.locator(`[data-testid="${accountName}"]`).first() - } - - connectedDapps(accountName: string, nbrConnectedDapps: number) { - return nbrConnectedDapps > 1 - ? this.page.locator( - `[data-testid="${accountName}"]:has-text("${nbrConnectedDapps} dapps connected")`, - ) - : this.page.locator( - `[data-testid="${accountName}"]:has-text("${nbrConnectedDapps} dapp connected")`, - ) - } - - get noConnectedDapps() { - return this.page.locator( - `text=${lang.settings.account.authorisedDapps.noAuthorisedDapps}`, - ) - } - - connected() { - return this.page.locator(`//div/*[contains(text(),'${dappName}')]`) - } - - disconnect() { - return this.page.locator( - `//div/*[contains(text(),'${dappName}')]/following::button[1]`, - ) - } - - disconnectAll() { - return this.page.locator( - `p:text-is("${lang.settings.account.authorisedDapps.disconnectAll}")`, - ) - } - - get accept() { - return this.page.locator( - `button:text-is("${lang.settings.account.authorisedDapps.connect}")`, - ) - } - - get reject() { - return this.page.locator( - `button:text-is("${lang.settings.account.authorisedDapps.reject}")`, - ) - } - - get knownDappButton() { - return this.page.locator('[data-testid="KnownDappButton"]') - } - - async ensureKnowDappText() { - return Promise.all([ - expect(this.page.locator('h4:text-is("Known Dapp")')).toBeVisible(), - expect( - this.page.locator('p:text-is("This dapp is listed on Dappland")'), - ).toBeVisible(), - ]) - } - async requestConnectionFromDapp( - { browserContext, - starknetKitModal = false }: - { - browserContext: ChromiumBrowserContext, - starknetKitModal?: boolean - } - ) { - //open dapp page - const dapp = await browserContext.newPage() - await dapp.setViewportSize({ width: 1080, height: 720 }) - await dapp.goto("chrome://inspect/#extensions") - await dapp.waitForTimeout(1000) - await dapp.goto(dappUrl) - - await dapp.getByRole('button', { name: 'Connection' }).click() - if (starknetKitModal) { - await dapp.getByRole('button', { name: 'Starknetkit Modal' }).click() - await dapp.locator('#starknetkit-modal-container').getByRole('button', { name: 'Argent X Argent X' }).click() - } else { - await expect(dapp.locator('button :text-is("Argent X")')).toBeVisible() - } - await dapp.locator('button :text-is("Argent X")').click() - return dapp - } - - async claimSpok(browserContext: ChromiumBrowserContext) { - const spokCampaignUrl = config.spokCampaignUrl! || '' - //open dapp page - const dapp = await browserContext.newPage() - await dapp.setViewportSize({ width: 1080, height: 720 }) - await dapp.goto("chrome://inspect/#extensions") - await dapp.waitForTimeout(1000) - await dapp.goto(spokCampaignUrl) - await dapp.getByRole("button", { name: "Check eligibility" }).click() - await expect(dapp.locator("text=Argent X")).toBeVisible() - await dapp.locator("text=Argent X").click() - return dapp - } - - checkCriticalRiskConnectionScreen() { - return Promise.all([ - expect( - this.page.locator( - `//span[text()="Critical risk"]/following-sibling::label[text()="Use of a blacklisted domain"]`, - ), - ).toBeVisible(), - expect( - this.page.locator( - `//p[@data-testid="review-footer" and text()="Please review warnings before continuing"]`, - ), - ).toBeVisible(), - expect(this.page.getByRole("button", { name: "Connect" })).toBeDisabled(), - ]) - } - - async acceptCriticalRiskConnection() { - await this.page.getByRole("button", { name: "Review" }).click() - await Promise.all([ - expect( - this.page.locator(`//header[@title="1 risk identified"]`), - ).toBeVisible(), - expect( - this.page.locator( - '//label[text()="We strongly recommend you do not proceed with this transaction"]', - ), - ).toBeVisible(), - expect( - this.page.locator( - '//span[text()="Critical risk"]/following-sibling::span[text()="Use of a blacklisted domain"]/following-sibling::p[text()="You are currently on an unsafe domain. Be aware of the risks."]', - ), - ).toBeVisible(), - ]) - await this.page.getByRole("button", { name: "Accept risk" }).click() - } - - async connectedDappsTooltip(dappUrl: string) { - await this.showSettingsLocator.click() - await this.page.hover('[data-testid="connected-dapp"]') - await expect( - this.page.locator('[data-testid="connected-dapp"]'), - ).toHaveText(`Connected to ${dappUrl}`) - } -} diff --git a/e2e/src/page-objects/DeveloperSettings.ts b/e2e/src/page-objects/DeveloperSettings.ts deleted file mode 100644 index 3ca6937..0000000 --- a/e2e/src/page-objects/DeveloperSettings.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { Page } from "@playwright/test" - -import { lang } from "../languages" - -export default class DeveloperSettings { - constructor(private page: Page) {} - - get manageNetworks() { - return this.page.locator( - `//a//*[text()="${lang.settings.advancedSettings.manageNetworks.manageNetworks}"]`, - ) - } - - get blockExplorer() { - return this.page.locator( - `//a//*[text()="${lang.settings.preferences.defaultBlockExplorer}"]`, - ) - } - - get smartCOntractDevelopment() { - return this.page.locator( - `//a//*[text()="${lang.settings.advancedSettings.smartContractDevelopment}"]`, - ) - } - - get experimental() { - return this.page.locator( - `//a//*[text()="${lang.settings.advancedSettings.experimental}"]`, - ) - } - - // Manage networks - get addNetwork() { - return this.page.locator('button[aria-label="add"]') - } - - get networkName() { - return this.page.locator('[name="name"]') - } - - get chainId() { - return this.page.locator('[name="chainId"]') - } - - get sequencerUrl() { - return this.page.locator('[name="sequencerUrl"]') - } - - get rpcUrl() { - return this.page.locator('[name="rpcUrl"]') - } - - get create() { - return this.page.locator('button[type="submit"]') - } - - get restoreDefaultNetworks() { - return this.page.locator( - `button:has-text("${lang.settings.advancedSettings.manageNetworks.restoreDefaultNetworks}")`, - ) - } - - networkByName(name: string) { - return this.page.locator(`h5:has-text("${name}")`) - } - - deleteNetworkByName(name: string) { - return this.page.locator( - `//div/*[contains(text(),'${name}')]/following::button[1]`, - ) - } -} diff --git a/e2e/src/page-objects/ExtensionPage.ts b/e2e/src/page-objects/ExtensionPage.ts deleted file mode 100644 index ef5ee2d..0000000 --- a/e2e/src/page-objects/ExtensionPage.ts +++ /dev/null @@ -1,486 +0,0 @@ -import { expect, type Page } from "@playwright/test" -import fs from "fs-extra" - -import Messages from "./Messages" -import Account from "./Account" -import Activity from "./Activity" -import AddressBook from "./AddressBook" -import Dapps from "./Dapps" -import DeveloperSettings from "./DeveloperSettings" -import Navigation from "./Navigation" -import Network from "./Network" -import Settings from "./Settings" -import Wallet from "./Wallet" -import config from "../config" -import Nfts from "./Nfts" -import Preferences from "./Preferences" -import Swap from "./Swap" -import TokenDetails from "./TokenDetails" - -import { - transferTokens, - AccountsToSetup, - validateTx, - isScientific, - convertScientificToDecimal, - FeeTokens, - logInfo, - Clipboard, - unzip, -} from "../utils" - -export default class ExtensionPage { - page: Page - wallet: Wallet - network: Network - account: Account - messages: Messages - activity: Activity - settings: Settings - navigation: Navigation - developerSettings: DeveloperSettings - addressBook: AddressBook - dapps: Dapps - nfts: Nfts - preferences: Preferences - clipboard: Clipboard - swap: Swap - tokenDetails: TokenDetails - - upgradeTest: boolean = false - - constructor( - page: Page, - private extensionUrl: string, - upgradeTest: boolean = false, - ) { - this.page = page - this.wallet = new Wallet(page, upgradeTest) - this.network = new Network(page) - this.account = new Account(page, upgradeTest) - this.extensionUrl = extensionUrl - this.messages = new Messages(page) - this.activity = new Activity(page) - this.settings = new Settings(page) - this.navigation = new Navigation(page) - this.developerSettings = new DeveloperSettings(page) - this.addressBook = new AddressBook(page) - this.dapps = new Dapps(page) - this.nfts = new Nfts(page) - this.preferences = new Preferences(page) - this.clipboard = new Clipboard(page) - this.swap = new Swap(page) - this.tokenDetails = new TokenDetails(page) - this.upgradeTest = upgradeTest - } - - async open() { - await this.page.setViewportSize(config.viewportSize) - await this.page.goto(this.extensionUrl) - } - - async resetExtension() { - await this.navigation.showSettingsLocator.click() - await this.navigation.lockWalletLocator.click() - await this.navigation.resetLocator.click() - await this.page.locator('[name="validationString"]').fill("RESET WALLET") - await this.page.locator('label[type="checkbox"]').click({ force: true }) - await this.navigation.confirmResetLocator.click() - } - - async pasteSeed() { - await this.page.locator('[data-testid="seed-input-0"]').focus() - await this.clipboard.paste() - } - - async recoverWallet(seed: string, password?: string) { - await this.page.setViewportSize({ width: 1080, height: 720 }) - - await this.wallet.restoreExistingWallet.click() - await this.wallet.agreeLoc.click() - await this.clipboard.setClipboardText(seed) - await this.pasteSeed() - await this.navigation.continueLocator.click() - - await this.wallet.password.fill(password ?? config.password) - await this.wallet.repeatPassword.fill(password ?? config.password) - - await this.navigation.continueLocator.click() - await Promise.race([ - expect(this.wallet.finish).toBeVisible(), - expect(this.page.getByText("Your account is ready!")).toBeVisible(), - expect(this.page.getByText("Your smart account is ready!")).toBeVisible(), - ]) - - await this.open() - await expect(this.network.networkSelector).toBeVisible() - } - - async addAccount() { - await this.account.addAccount({ firstAccount: false }) - await this.account.copyAddress.click() - await this.clipboard.setClipboard() - const accountAddress = await this.clipboard.getClipboard() - expect(accountAddress).toMatch(/^0x0/) - return accountAddress - } - - async deployAccount(accountName: string, feeToken?: FeeTokens) { - if (accountName) { - await this.account.ensureSelectedAccount(accountName) - } - await this.navigation.showSettingsLocator.click() - await this.page.locator(`[data-testid="${accountName}"]`).click() - await this.settings.deployAccount.click() - if (feeToken) { - await this.account.selectFeeToken(feeToken) - } - await this.account.confirmTransaction() - await this.navigation.backLocator.click() - await this.navigation.closeLocator.click() - if (await this.page.getByRole("heading", { name: "Activity" }).isHidden()) { - await this.navigation.menuActivityLocator.click() - } - await expect( - this.page.getByText(/(Account created and transfer|Account activation)/), - ).toBeVisible() - - await this.navigation.showSettingsLocator.click() - await expect(this.page.getByText("Deploying")).toBeHidden() - await this.navigation.closeLocator.click() - await this.navigation.menuTokensLocator.click() - } - - async activateSmartAccount({ - accountName, - email, - pin = "111111", - validSession = false, - }: { - accountName: string - email?: string - pin?: string - validSession?: boolean - }) { - await this.account.ensureSelectedAccount(accountName) - await this.navigation.showSettingsLocator.click() - await this.settings.account(accountName).click() - await this.settings.smartAccountButton.click() - await this.navigation.nextLocator.click() - if (!validSession) { - await this.account.email.fill(email!) - await this.navigation.nextLocator.first().click() - await this.account.fillPin(pin) - } - await this.navigation.upgradeLocator.click() - await this.account.confirmTransaction() - await expect(this.account.accountUpgraded).toBeVisible() - await this.navigation.doneLocator.click() - await this.navigation.backLocator.click() - await this.navigation.closeLocator.click() - await Promise.all([ - expect( - this.activity.menuPendingTransactionsIndicatorLocator, - ).toBeHidden(), - expect( - this.page.locator('[data-testid="smart-account-on-account-view"]'), - ).toBeVisible(), - ]) - await this.navigation.showSettingsLocator.click() - await expect( - this.page.locator('[data-testid="smart-account-on-settings"]'), - ).toBeVisible() - await this.settings.account(accountName).click() - await expect( - this.page.locator( - '[data-testid="smart-account-button"]:has-text("Change to Standard Account")', - ), - ).toBeEnabled() - await this.navigation.backLocator.click() - await this.navigation.closeLocator.click() - } - - async changeToStandardAccount({ - accountName, - email, - pin = "111111", - validSession = false, - }: { - accountName: string - email: string - pin?: string - validSession?: boolean - }) { - await this.account.ensureSelectedAccount(accountName) - await this.navigation.showSettingsLocator.click() - await this.settings.account(accountName).click() - await this.settings.changeToStandardAccountButton.click() - //await this.navigation.nextLocator.click() - if (!validSession) { - await this.account.email.fill(email) - await this.navigation.nextLocator.first().click() - await this.account.fillPin(pin) - } - - await this.navigation.confirmChangeAccountTypeLocator.click() - await this.account.confirmTransaction() - await expect(this.account.changedToStandardAccountLabel).toBeVisible() - await this.navigation.doneLocator.click() - await this.navigation.backLocator.click() - await this.navigation.closeLocator.click() - await this.account.ensureSmartAccountNotEnabled(accountName) - } - - async fundAccount( - acc: AccountsToSetup, - accountAddress: string, - accIndex: number, - ) { - let expectedTokenValue - for (const [assetIndex, asset] of acc.assets.entries()) { - logInfo({ - op: "fundAccount", - assetIndex, - asset, - isProdTesting: config.isProdTesting, - }) - if (asset.balance > 0) { - await transferTokens( - asset.balance, - accountAddress, // receiver wallet address - asset.token, - ) - - if (isScientific(asset.balance)) { - expectedTokenValue = `${convertScientificToDecimal(asset.balance)}` - } else { - expectedTokenValue = `${asset.balance}` - } - if (!expectedTokenValue.includes(".")) { - expectedTokenValue += ".0" - } - expectedTokenValue += ` ${asset.token}` - await this.account.ensureAsset( - `Account ${accIndex + 1}`, - asset.token, - expectedTokenValue, - ) - } - } - - if (acc.deploy) { - await this.deployAccount(`Account ${accIndex + 1}`, acc.feeToken) - } - } - - async setupWallet({ - accountsToSetup, - email, - pin = "111111", - success = true, - }: { - accountsToSetup: AccountsToSetup[] - email?: string - success?: boolean - pin?: string - }) { - await this.wallet.newWalletOnboarding(email, pin, success) - if (!success) { - return { accountAddresses: [], seed: "" } - } - await this.open() - const seed = await this.account.setupRecovery() - //await this.network.selectDefaultNetwork() - const noAccount = await this.account.noAccountBanner.isVisible({ - timeout: 1000, - }) - const accountAddresses: string[] = [] - for (const [accIndex, acc] of accountsToSetup.entries()) { - if (noAccount) { - await this.account.addAccount({ firstAccount: true }) - } else if (accIndex !== 0) { - await this.account.addAccount({ firstAccount: false }) - } - await this.account.copyAddress.click() - await this.clipboard.setClipboard() - const accountAddress = await this.clipboard - .getClipboard() - .then((adr) => String(adr)) - expect(accountAddress).toMatch(/^0x0/) - accountAddresses.push(accountAddress) - if (acc.assets[0].balance > 0) { - await this.fundAccount(acc, accountAddress, accIndex) - } - } - logInfo({ - op: "setupWallet", - accountsNbr: accountAddresses.length, - accountAddresses, - seed, - }) - return { accountAddresses, seed } - } - - async validateTx({ - txHash, - receiver, - sendAmountFE, - sendAmountTX, - uniqLocator, - txType = "token", - }: { - txHash: string - receiver: string - sendAmountFE?: string - sendAmountTX?: number - uniqLocator?: boolean - txType?: "token" | "nft" - }) { - logInfo({ - op: "validateTx", - txHash, - receiver, - sendAmountFE, - sendAmountTX, - uniqLocator, - }) - await this.navigation.menuActivityActiveLocator - .isVisible() - .then(async (visible: boolean) => { - if (!visible) { - await this.navigation.menuActivityLocator.click() - } - }) - if (sendAmountFE) { - const activityAmountLocator = this.page.locator( - `button[data-tx-hash$="${txHash.substring(3)}"] [data-value]`, - ) - let activityAmountElement = activityAmountLocator - if (uniqLocator) { - activityAmountElement = activityAmountLocator.first() - } - expect(this.activity.historyButton) - .toBeVisible({ timeout: 1000 }) - .then(async () => { - await this.activity.historyButton.click() - }) - .catch(async () => { - return null - }) - const activityAmount = await activityAmountElement - .textContent() - .then((text) => text?.match(/[\d|.]+/)![0]) - if (sendAmountFE.toString().length > 6) { - expect(activityAmount).toBe( - parseFloat(sendAmountFE.toString()) - .toFixed(4) - .toString() - .match(/[\d\\.]+[^0]+/)?.[0], - ) - } else { - expect(activityAmount).toBe( - parseFloat(sendAmountFE.toString()).toString(), - ) - } - } - await this.activity.ensureNoPendingTransactions() - await validateTx({ txHash, receiver, amount: sendAmountTX, txType }) - } - - async fundMultisigAccount({ - accountName, - balance, - }: { - accountName: string - balance: number - }) { - await this.account.ensureSelectedAccount(accountName) - await this.account.copyAddress.click() - await this.clipboard.setClipboard() - const accountAddress = await this.clipboard - .getClipboard() - .then((adr) => String(adr)) - await transferTokens( - balance, - accountAddress, // receiver wallet address - ) - await this.account.ensureAsset(accountName, "ETH", `${balance} ETH`) - } - - async activateMultisig(accountName: string) { - await this.account.ensureSelectedAccount(accountName) - await expect( - this.page.locator("label:has-text('Add ETH or STRK and activate')"), - ).toBeVisible() - await this.page.locator('[data-testid="activate-multisig"]').click() - await this.account.confirmTransaction() - await expect( - this.page.locator('[data-testid="activating-multisig"]'), - ).toBeVisible() - await Promise.all([ - expect( - this.page.locator("label:has-text('Add ETH or STRK and activate')"), - ).toBeHidden(), - expect( - this.page.locator('[data-testid="activating-multisig"]'), - ).toBeHidden(), - ]) - } - - async removeMultisigOwner(accountName: string) { - await this.account.ensureSelectedAccount(accountName) - await this.navigation.showSettingsLocator.click() - await this.settings.account(accountName).click() - } - - async setExtensionVersion(version: string) { - //used to decrease the version of the extension, release version: 6.3.4, extension version 5.3.4 - function decreaseMajorVersion(version: string): string { - const [major, minor, patch] = version.split(".").map(Number) - const newMajor = major - 1 - return `${newMajor}.${minor}.${patch}` - } - const currentVersionDir = await unzip(version) - await fs.remove(config.migVersionDir) - await fs.copy(currentVersionDir, config.migVersionDir) - // Reload Extension - await this.page.goto("chrome://extensions") - // Find and click the reload button for our extension - const reloadButton = this.page - .locator("extensions-item") - .filter({ hasText: "Argent X" }) - .locator('cr-icon-button[title="Reload"]') - await reloadButton.click() - await this.page.waitForTimeout(1000) - - //open extension - await this.open() - await expect(this.version).resolves.toBe(decreaseMajorVersion(version)) - } - - async restoreExtensionVersion() { - await fs.remove(config.migVersionDir) - await fs.copy(config.distDir, config.migVersionDir) - // Reload Extension - await this.page.goto("chrome://extensions") - // Find and click the reload button for our extension - const reloadButton = this.page - .locator("extensions-item") - .filter({ hasText: "Argent X" }) - .locator('cr-icon-button[title="Reload"]') - await reloadButton.click() - await this.page.waitForTimeout(1000) - - //open extension - await this.open() - } - - async upgradeExtension(newVersion: string) { - await this.restoreExtensionVersion() - //unlock extension - await this.account.password.fill(config.password) - await this.navigation.unlockLocator.click() - //check extension version - await expect(this.version).resolves.toBe(newVersion) - } -} diff --git a/e2e/src/page-objects/Messages.ts b/e2e/src/page-objects/Messages.ts deleted file mode 100644 index 9e940bc..0000000 --- a/e2e/src/page-objects/Messages.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { Page } from "@playwright/test" - -export default class Messages { - constructor(private page: Page) {} - - sendMessage = (message: any) => - this.page.evaluate(`window.sendMessage(${JSON.stringify(message)})`) - waitForMessage = (message: string) => - this.page.evaluate(`window.waitForMessage(${JSON.stringify(message)})`) - - resetExtension() { - return Promise.all([ - this.sendMessage({ type: "RESET_ALL" }), - this.waitForMessage("DISCONNECT_ACCOUNT"), - ]) - } -} diff --git a/e2e/src/page-objects/Navigation.ts b/e2e/src/page-objects/Navigation.ts deleted file mode 100644 index 3be34e1..0000000 --- a/e2e/src/page-objects/Navigation.ts +++ /dev/null @@ -1,140 +0,0 @@ -import type { Page } from "@playwright/test" - -import { lang } from "../languages" -import Clipboard from "../utils/Clipboard" - -export default class Navigation extends Clipboard { - constructor(page: Page) { - super(page) - } - - get backLocator() { - return this.page.getByLabel(`${lang.common.back}`).first() - } - - get closeLocator() { - return this.page.locator(`[aria-label="${lang.common.close}"]`) - } - - get closeButtonLocator() { - return this.page.getByLabel("close") - } - - get closeButtonDappInfoLocator() { - return this.page.getByTestId("close-button") - } - - get confirmLocator() { - return this.page.locator(`button:text-is("${lang.common.confirm}")`) - } - - get nextLocator() { - return this.page.locator(`button:text-is("${lang.common.next}")`) - } - - get reviewSendLocator() { - return this.page.locator(`button:text-is("${lang.common.reviewSend}")`) - } - - get doneLocator() { - return this.page.locator(`button:text-is("${lang.common.done}")`) - } - - get continueLocator() { - return this.page - .locator(`button:text-is("${lang.common.continue}")`) - .first() - } - - get yesLocator() { - return this.page.locator(`button:text-is("${lang.common.yes}")`) - } - - get noLocator() { - return this.page.locator(`button:text-is("${lang.common.no}")`) - } - - get unlockLocator() { - return this.page.locator(`button:text-is("${lang.common.unlock}")`).first() - } - - get showSettingsLocator() { - return this.page.locator('[aria-label="Show settings"]') - } - - get lockWalletLocator() { - return this.page.locator( - `//button//*[text()="${lang.settings.lockWallet}"]`, - ) - } - - get resetLocator() { - return this.page.getByText("Reset").first() - } - - get confirmResetLocator() { - return this.page.locator(`button:text-is("${lang.common.confirmReset}")`) - } - - get menuPendingTransactionsIndicatorLocator() { - return this.page.locator('[aria-label="Pending transactions"]') - } - - get menuTokensLocator() { - return this.page.locator('[aria-label="Tokens"]') - } - - get menuNTFsLocator() { - return this.page.locator('[aria-label="NFTs"]') - } - - get menuSwapsLocator() { - return this.page.locator('[aria-label="Swap"]') - } - - get menuActivityLocator() { - return this.page.locator('[aria-label="Activity"]') - } - - get menuActivityActiveLocator() { - return this.page.locator('[aria-label="Activity"][class*="active"]') - } - - get saveLocator() { - return this.page.locator(`button:text-is("${lang.common.save}")`) - } - - get createLocator() { - return this.page.locator(`button:text-is("${lang.common.create}")`) - } - - get cancelLocator() { - return this.page.locator(`button:text-is("${lang.common.cancel}")`) - } - - get approveLocator() { - return this.page.locator(`button:text-is("${lang.common.approve}")`) - } - - get addArgentShieldLocator() { - return this.page.locator(`button:text-is("${lang.common.addArgentShield}")`) - } - - get confirmChangeAccountTypeLocator() { - return this.page.locator( - `button:text-is("${lang.common.changeAccountType}")`, - ) - } - - get dismissLocator() { - return this.page.locator(`button:text-is("${lang.common.dismiss}")`) - } - - get removeLocator() { - return this.page.locator(`button:text-is("${lang.common.remove}")`) - } - - get upgradeLocator() { - return this.page.locator(`button:text-is("${lang.common.upgrade}")`) - } -} diff --git a/e2e/src/page-objects/Network.ts b/e2e/src/page-objects/Network.ts deleted file mode 100644 index a5ba2ce..0000000 --- a/e2e/src/page-objects/Network.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { Page, expect } from "@playwright/test" -import Navigation from "./Navigation" - -type NetworkName = "Devnet" | "Sepolia" | "Mainnet" | "My Network" - -export function getDefaultNetwork() { - const argentXEnv = process.env.ARGENT_X_ENVIRONMENT - - if (!argentXEnv) { - throw new Error("ARGENT_X_ENVIRONMENT not set") - } - let defaultNetworkId: string - switch (argentXEnv.toLowerCase()) { - case "prod": - case "staging": - defaultNetworkId = "mainnet-alpha" - break - - case "hydrogen": - case "test": - defaultNetworkId = "sepolia-alpha" - break - - default: - throw new Error(`Unknown ARGENTX_ENVIRONMENT: ${argentXEnv}`) - } - - return defaultNetworkId -} -export default class Network extends Navigation { - // Change 'private' to 'protected' or 'public' to match the base class - constructor(page: Page) { - super(page) - } - - get networkSelector() { - return this.page.getByLabel("Show account list") - } - - networkOption(name: string) { - return this.page.locator(`button[role="menuitem"] span:text-is("${name}")`) - } - - async selectNetwork(networkName: NetworkName) { - await this.networkSelector.click() - await this.page.locator('[data-testid="network-switcher-button"]').click() - await this.networkOption(networkName).click() - } - - async selectDefaultNetwork() { - const networkName = this.getDefaultNetworkName() - await this.networkSelector.click() - await this.page.locator('[data-testid="network-switcher-button"]').click() - await this.networkOption(networkName).click() - const accounts = await this.page - .locator('[aria-label^="Select A"]') - .allInnerTexts() - if (accounts.length > 0) { - await this.page.locator('[aria-label^="Select A"]').first().click() - } else { - await this.closeButtonLocator.click() - } - } - - async ensureAvailableNetworks(networks: string[]) { - await this.networkSelector.click() - await this.page.locator('[data-testid="network-switcher-button"]').click() - const availableNetworks = await this.page - .locator('[role="menu"] button') - .allInnerTexts() - return expect(availableNetworks).toEqual(networks) - } - - getDefaultNetworkName() { - const defaultNetworkId = getDefaultNetwork() - switch (defaultNetworkId.toLowerCase()) { - case "mainnet-alpha": - return "Mainnet" - case "sepolia-alpha": - return "Sepolia" - case "goerli-alpha": - return "Goerli" - default: - throw new Error(`Unknown ARGENTX_Network: ${defaultNetworkId}`) - } - } - - ensureSelectedNetwork(networkName: NetworkName) { - return expect(this.networkSelector).toContainText(networkName) - } -} diff --git a/e2e/src/page-objects/Nfts.ts b/e2e/src/page-objects/Nfts.ts deleted file mode 100644 index 15a56cc..0000000 --- a/e2e/src/page-objects/Nfts.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Page } from "@playwright/test" - -import Navigation from "./Navigation" - -export default class Nfts extends Navigation { - constructor(page: Page) { - super(page) - } - - collection(name: string) { - return this.page.locator(`h5:text-is("${name}")`) - } - - ntf(name: string) { - return this.page.getByRole("group", { name }).getByRole("img") - } - - nftByPosition(position: number = 0) { - return this.page.locator('[data-testid="nft-item-name"]').nth(position) - } -} diff --git a/e2e/src/page-objects/Preferences.ts b/e2e/src/page-objects/Preferences.ts deleted file mode 100644 index 96330d2..0000000 --- a/e2e/src/page-objects/Preferences.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Page } from "@playwright/test" - -import { lang } from "../languages" -import Navigation from "./Navigation" - -export default class Preferences extends Navigation { - constructor(page: Page) { - super(page) - } - - get hiddenAndSpamTokens() { - return this.page.locator( - `//p[contains(text(),'${lang.settings.preferences.hideTokens}')]`, - ) - } - - get hideTokensStatus() { - return this.page.locator( - `//p[contains(text(),'${lang.settings.preferences.hideTokens}')]/following::input`, - ) - } - - get defaultBlockExplorer() { - return this.page.locator( - `//p[contains(text(),'${lang.settings.preferences.defaultBlockExplorer}')]`, - ) - } - - get defaultNFTMarket() { - return this.page.locator( - `//p[contains(text(),'${lang.settings.preferences.defaultNFTMarket}')]`, - ) - } - - get emailNotifications() { - return this.page.locator( - `//p[contains(text(),'${lang.settings.preferences.emailNotifications}')]`, - ) - } -} diff --git a/e2e/src/page-objects/Settings.ts b/e2e/src/page-objects/Settings.ts deleted file mode 100644 index eaa9ef5..0000000 --- a/e2e/src/page-objects/Settings.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { expect, type Page } from "@playwright/test" - -import { lang } from "../languages" -import { sleep } from "../utils" -import Navigation from "./Navigation" - -export default class Settings extends Navigation { - constructor(page: Page) { - super(page) - } - - get extendedView() { - return this.page.locator(`[aria-label="${lang.settings.extendedView}"]`) - } - - get addressBook() { - return this.page.locator( - `//a//*[text()="${lang.settings.addressBook.addressBook}"]`, - ) - } - - get authorizedDapps() { - return this.page.locator( - `//a//*[text()="${lang.settings.account.authorisedDapps.authorisedDapps}"]`, - ) - } - - get advancedSettings() { - return this.page.locator( - `//a//*[text()="${lang.settings.advancedSettings.advancedSettings}"]`, - ) - } - - get preferences() { - return this.page.locator( - `//a//*[text()="${lang.settings.preferences.preferences}"]`, - ) - } - - // account settings - get accountName() { - return this.page.locator('input[placeholder="Account name"]') - } - - get exportPrivateKey() { - return this.page.getByRole("button", { name: "Export private key" }) - } - - get deployAccount() { - return this.page.locator( - `//button//*[text()="${lang.settings.account.deployAccount}"]`, - ) - } - - get hideAccount() { - return this.page.getByRole("button", { name: "Hide account" }) - } - - account(accountName: string) { - return this.page.locator(`[aria-label="Select ${accountName}"]`) - } - - async setAccountName(newAccountName: string) { - await this.accountName.click() - await this.accountName.fill(newAccountName) - await this.page.locator("form button").click() - } - - get confirmHide() { - return this.page.locator(`button:text-is("${lang.common.hide}")`) - } - get hiddenAccounts() { - return this.page.locator( - `p:text-is("${lang.settings.preferences.hiddenAccounts}")`, - ) - } - - unhideAccount(accountName: string) { - return this.page.locator(`button :text-is("${accountName}")`) - } - - get smartAccountButton() { - return this.page.locator('[data-testid="smart-account-button"]') - } - - get changeToStandardAccountButton() { - return this.page.locator( - '[data-testid="smart-account-button"]:has-text("Change to Standard Account")', - ) - } - - get privateKey() { - return this.page.locator('[aria-label="Private key"]') - } - - get copy() { - return this.page.locator(`button:text-is("${lang.common.copy}")`) - } - - get help() { - return this.page.getByRole("link", { name: "Help" }) - } - - get discord() { - return this.page.getByRole("link", { name: "Discord" }) - } - - get github() { - return this.page.getByRole("link", { name: "GitHub" }) - } - - get viewOnStarkScanLocator() { - return this.page.getByRole("button", { - name: lang.settings.account.viewOnStarkScan, - }) - } - - get viewOnVoyagerLocator() { - return this.page.getByRole("button", { - name: lang.settings.account.viewOnVoyager, - }) - } - - get pinLocator() { - return this.page.locator('[aria-label="Please enter your pin code"]') - } - - async signIn(email: string, pin: string = "111111") { - await this.page.getByRole("button", { name: "Sign in to Argent" }).click() - await this.page.getByTestId("email-input").fill(email) - await this.nextLocator.click() - //avoid BE error PIN not requested - await sleep(2000) - await expect(this.pinLocator).toHaveCount(6) - await this.pinLocator.first().click() - await this.pinLocator.first().fill(pin) - await expect( - this.page.getByRole("button", { name: "Logout" }), - ).toBeVisible() - await this.closeLocator.click() - } -} diff --git a/e2e/src/page-objects/Swap.ts b/e2e/src/page-objects/Swap.ts deleted file mode 100644 index 7065f28..0000000 --- a/e2e/src/page-objects/Swap.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { Page, expect } from "@playwright/test" - -import Navigation from "./Navigation" -import { TokenSymbol } from "../utils" - -export default class Swap extends Navigation { - constructor(page: Page) { - super(page) - } - - get swapHeader() { - return this.page.getByRole("heading", { name: "Swap" }) - } - - get valueLoc() { - return this.page.locator('[data-testid="swap-input-pay-panel"]') - } - - get switchInOutLoc() { - return this.page.locator('[aria-label="Switch input and output"]') - } - - get maxLoc() { - return this.page.locator('label:has-text("Max")') - } - - get payTokenLoc() { - return this.page.locator('[data-testid="swap-token-button"]').nth(0) - } - - get receiveTokenLoc() { - return this.page.locator('[data-testid="swap-token-button"]').nth(1) - } - - get reviewSwapLoc() { - return this.page.locator('[data-testid="review-swap-button"]') - } - - get deployFeeLoc() { - return this.page.locator('[data-testid="deploy-fee"]') - } - - get useMaxLoc() { - return this.page.locator('[data-testid="use-max-button"]') - } - - async setPayToken(token: string) { - await this.payTokenLoc.click() - await this.page.locator(`p:text-is("${token}")`).click() - } - - async setReceiveToken(token: string) { - await this.receiveTokenLoc.click() - await this.page.locator(`p:text-is("${token}")`).click() - } - - async swapTokens({ - payToken, - receiveToken, - amount, - alreadyDeployed = true, - }: { - payToken: TokenSymbol - receiveToken: TokenSymbol - amount: number | "MAX" - alreadyDeployed: boolean - }) { - await this.setPayToken(payToken) - await this.setReceiveToken(receiveToken) - if (amount === "MAX") { - await this.maxLoc.click() - await this.useMaxLoc.click() - } else { - await this.valueLoc.fill(amount.toString()) - } - await this.reviewSwapLoc.click() - //raise an error if Transaction fail predict, to avoid waiting test timeout - const failPredict = this.page.getByText("Transaction fail") - await expect(failPredict) - .toBeVisible({ timeout: 1000 * 5 }) - .then(async (_) => { - throw new Error("Transaction failure predicted") - }) - .catch((_) => null) - if (!alreadyDeployed) { - await expect(this.deployFeeLoc).toBeVisible() - } - const sendAmountFEText = await this.page - .locator("[data-fe-value]") - .nth(1) - .getAttribute("data-fe-value") - await this.confirmLocator.click() - return sendAmountFEText - } -} diff --git a/e2e/src/page-objects/TokenDetails.ts b/e2e/src/page-objects/TokenDetails.ts deleted file mode 100644 index 8b96aff..0000000 --- a/e2e/src/page-objects/TokenDetails.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { expect, Page } from "@playwright/test" - -import Navigation from "./Navigation" - -export default class TokenDetails extends Navigation { - constructor(page: Page) { - super(page) - } - - openTokenDetails(token: string) { - return this.page.getByTestId(`${token}-balance`) - } - - get swapButtonLoc() { - return this.page.locator('button[aria-label="Swap"]') - } - - get buyButtonLoc() { - return this.page.locator('button[aria-label="Buy"]') - } - - get sendButtonLoc() { - return this.page.locator('button[aria-label="Send"]') - } - - graphTimeFrameLoc(frame: "1D" | "1W" | "1M" | "1Y" | "All") { - return this.page.locator(`button:text-is('${frame}')`) - } - - get activityButtonLoc() { - return this.page.locator(`button:text-is('Activity')`) - } - - get aboutButtonLoc() { - return this.page.locator(`button:text-is('About')`) - } - - get menuButtonLoc() { - return this.page.locator('[id^="menu-button"]') - } - - get menuCopyTokenAddressLoc() { - return this.page.getByText("Copy token address") - } - - get menuViewOnVoyagerLoc() { - return this.page.getByRole("menuitem", { name: "View on Voyager" }) - } - - get newTokenButtonLoc() { - return this.page.getByText("New token") - } - - get addTokenButtonLoc() { - return this.page.getByRole("button", { name: "Add token" }) - } - - fillTokenAddress(tokenAddress: string) { - return this.page.locator("[name='address']").fill(tokenAddress) - } - - async addNewToken(tokenAddress: string, tokenSymbol: string) { - await this.newTokenButtonLoc.click() - await this.fillTokenAddress(tokenAddress) - await expect(this.page.locator('[name="symbol"]')).toHaveValue(tokenSymbol) - await Promise.race([ - this.addTokenButtonLoc.click(), - this.addThisToken.click(), - ]) - } - - token(tokenName: string) { - return this.page.locator(`h5:text-is('${tokenName}')`) - } - - showToken(tokenSymbol: string) { - return this.page.locator(`[data-testid="show-token-button-${tokenSymbol}"]`) - } - - hideToken(tokenSymbol: string) { - return this.page.locator(`[data-testid="hide-token-button-${tokenSymbol}"]`) - } - - get spamTokensList() { - return this.page.getByRole("button", { name: "Spam" }) - } - - get tokensList() { - return this.page.getByRole("button", { name: "Tokens" }) - } - get addThisToken() { - return this.page.getByRole("button", { name: "Add this token" }) - } -} diff --git a/e2e/src/page-objects/Wallet.ts b/e2e/src/page-objects/Wallet.ts deleted file mode 100644 index a717a8b..0000000 --- a/e2e/src/page-objects/Wallet.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { Page, expect } from "@playwright/test" - -import config from "../config" -import { lang } from "../languages" -import Navigation from "./Navigation" -import { sleep } from "../utils" - -export default class Wallet extends Navigation { - upgradeTest: boolean - constructor(page: Page, upgradeTest: boolean = false) { - super(page) - this.upgradeTest = upgradeTest - } - get banner() { - return this.page.locator(`div h1:text-is("${lang.wallet.banner1}")`) - } - get description() { - return this.page.locator(`div p:text-is("${lang.wallet.desc1}")`) - } - get createNewWallet() { - return this.page.locator(`button:text-is("${lang.wallet.createButton}")`) - } - get restoreExistingWallet() { - return this.page.locator(`button:text-is("${lang.wallet.restoreButton}")`) - } - - //second screen - get banner2() { - return this.page.locator(`div h1:text-is("${lang.wallet.banner2}")`) - } - get description2() { - return this.page.locator(`div p:text-is("${lang.wallet.desc2}")`) - } - - get disclaimerLostOfFunds() { - return this.page.locator( - `//input[@value="lossOfFunds"]/following::p[contains(text(),'${lang.wallet.lossOfFunds}')]`, - ) - } - get disclaimerAlphaVersion() { - return this.page.locator( - `//input[@value="alphaVersion"]/following::p[contains(text(),'${lang.wallet.alphaVersion}')]`, - ) - } - - get privacyPolicyLink() { - return this.page.getByRole("link", { name: "Privacy Policy" }) - } - - //third screen - get banner3() { - return this.page.locator(`div h1:text-is("${lang.wallet.banner3}")`) - } - get description3() { - return this.page.locator(`div p:text-is("${lang.wallet.desc3}")`) - } - get password() { - return this.page.locator( - `input[name="password"][placeholder="${lang.wallet.password}"]`, - ) - } - get repeatPassword() { - return this.page.locator( - `input[name="repeatPassword"][placeholder="${lang.wallet.repeatPassword}"]`, - ) - } - get createWallet() { - return this.page.locator(`button:text-is("${lang.wallet.createWallet}")`) - } - - //fourth screen - get banner4() { - return this.page.locator("div h1", { - hasText: lang.wallet.banner4, - }) - } - - get download() { - return this.page.locator(`a:has-text("${lang.wallet.download}")`) - } - - get twitter() { - return this.page.locator(`a:has-text("${lang.wallet.twitter}")`) - } - - get dapps() { - return this.page.locator(`a:has-text("${lang.wallet.dapps}")`) - } - - get finish() { - return this.page.locator(`button:text-is("${lang.wallet.finish}")`) - } - - get agreeLoc() { - return this.page.locator('[data-testid="agree-button"]') - } - - get addStandardAccountFromNewAccountScreen() { - return this.page.locator('[aria-label="Standard Account"]') - } - - get addSmartAccountFromNewAccountScreen() { - return this.page.locator('[aria-label="Smart Account"]') - } - - get pinLocator() { - return this.page.locator('[aria-label="Please enter your pin code"]') - } - - fillEmail(email: string) { - return this.page.locator('[data-testid="email-input"]').fill(email) - } - - async fillPin(pin: string) { - //avoid BE error PIN not requested - await sleep(2000) - await expect(this.pinLocator).toHaveCount(6) - await this.pinLocator.first().click() - await this.pinLocator.first().fill(pin) - } - async newWalletOnboarding( - email?: string, - pin: string = "111111", - success: boolean = true, - ) { - await this.createNewWallet.click() - await this.agreeLoc.click() - await this.password.fill(config.password) - await this.repeatPassword.fill(config.password) - await this.continueLocator.click() - if (!email) { - await this.addStandardAccountFromNewAccountScreen.click() - await this.continueLocator.click() - } else { - await this.addSmartAccountFromNewAccountScreen.click() - await this.continueLocator.click() - await this.fillEmail(email) - await this.continueLocator.click() - await this.fillPin(pin) - if (!success) { - await expect( - this.page.getByText(lang.account.argentShield.emailInUse), - ).toBeVisible() - } - } - if (success) { - await expect( - this.page.getByRole("heading", { name: "Your wallet is ready!" }), - ).toBeVisible() - } - } -} diff --git a/e2e/src/specs/dapps.spec.ts b/e2e/src/specs/dapps.spec.ts deleted file mode 100644 index 990f49f..0000000 --- a/e2e/src/specs/dapps.spec.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { expect } from "@playwright/test" - -import test from "../test" -import { downloadGitHubRelease, unzip } from "../utils" -import config from "../config" - -test.describe("Dapps", () => { - test.beforeAll(async ({ }) => { - const version = '5.18.5' - await downloadGitHubRelease(version) - const currentVersionDir = await unzip(version) - config.distDir = currentVersionDir - }) - for (const useStarknetKitModal of [true, false] as const) { - - test(`connect from testDapp using starknetKitModal ${useStarknetKitModal}`, async ({ extension, browserContext }) => { - - //setup wallet - await extension.wallet.newWalletOnboarding() - await extension.open() - await extension.dapps.requestConnectionFromDapp( - { - browserContext, - starknetKitModal: useStarknetKitModal - } - ) - //accept connection from Argent X - await extension.dapps.accept.click() - //check connect dapps - await extension.navigation.showSettingsLocator.click() - await extension.settings.account(extension.account.accountName1).click() - await extension.page.getByRole('button', { name: 'Connected dapps' }).click() - await expect( - extension.dapps.connected(), - ).toBeVisible() - //disconnect dapp from Argent X - await extension.dapps - .disconnect() - .click() - await expect( - extension.dapps.connected(), - ).toBeHidden() - await extension.page.getByRole('button', { name: 'Connected dapps' }).click() - await expect(extension.page.getByRole('heading', { name: 'No connected dapps' })).toBeVisible() - }) - } -}) diff --git a/e2e/src/test.ts b/e2e/src/test.ts deleted file mode 100644 index 8490a03..0000000 --- a/e2e/src/test.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { - ChromiumBrowserContext, - Page, - TestInfo, - chromium, - test as testBase, -} from "@playwright/test" -import { v4 as uuid } from "uuid" -import type { TestExtensions } from "./fixtures" -import ExtensionPage from "./page-objects/ExtensionPage" -import config from "./config" -import { logInfo } from "./utils" -import path from "path" -import fs from "fs-extra" - -declare global { - interface Window { - PLAYWRIGHT?: boolean - } -} -const outputFolder = (testInfo: TestInfo) => - testInfo.title.replace(/\s+/g, "_").replace(/\W/g, "") -const artifactFilename = (testInfo: TestInfo, label: string) => - `${testInfo.retry}-${testInfo.status}-${label}-${testInfo.workerIndex}` -const isKeepArtifacts = (testInfo: TestInfo) => - testInfo.config.preserveOutput === "always" || - (testInfo.config.preserveOutput === "failures-only" && - testInfo.status === "failed") || - testInfo.status === "timedOut" - -const artifactSetup = async (testInfo: TestInfo, label: string) => { - await fs.promises - .mkdir(path.resolve(config.artifactsDir, outputFolder(testInfo)), { - recursive: true, - }) - .catch((error) => { - console.error({ op: "artifactSetup", error }) - }) - return artifactFilename(testInfo, label) -} - -const saveHtml = async (testInfo: TestInfo, page: Page, label: string) => { - logInfo({ - op: "saveHtml", - label, - }) - const fileName = await artifactSetup(testInfo, label) - const htmlContent = await page.content() - await fs.promises - .writeFile( - path.resolve( - config.artifactsDir, - outputFolder(testInfo), - `${fileName}.html`, - ), - htmlContent, - ) - .catch((error) => { - console.error({ op: "saveHtml", error }) - }) -} - -const keepVideos = async (testInfo: TestInfo, page: Page, label: string) => { - logInfo({ - op: "keepVideos", - label, - }) - const fileName = await artifactSetup(testInfo, label) - await page - .video() - ?.saveAs( - path.resolve( - config.artifactsDir, - outputFolder(testInfo), - `${fileName}.webm`, - ), - ) - .catch((error) => { - console.error({ op: "keepVideos", error }) - }) -} - -const isExtensionURL = (url: string) => url.startsWith("chrome-extension://") -let browserCtx: ChromiumBrowserContext -const closePages = async (browserContext: ChromiumBrowserContext) => { - const pages = browserContext?.pages() || [] - for (const page of pages) { - if (!isExtensionURL(page.url())) { - await page.close() - } - } -} - -const createBrowserContext = async (userDataDir: string, buildDir: string) => { - const context = await chromium.launchPersistentContext(userDataDir, { - headless: false, - args: [ - "--disable-dev-shm-usage", - "--ipc=host", - `--disable-extensions-except=${buildDir}`, - `--load-extension=${buildDir}`, - ], - viewport: config.viewportSize, - ignoreDefaultArgs: ["--disable-component-extensions-with-background-pages"], - recordVideo: { - dir: config.artifactsDir, - size: config.viewportSize, - }, - }) - await context.addInitScript(() => { - window.PLAYWRIGHT = true - window.localStorage.setItem( - "seenNetworkStatusState", - JSON.stringify({ state: { lastSeen: Date.now() }, version: 0 }), - ) - window.localStorage.setItem("onboardingExperiment", "E1A1") - }) - return context -} - -const initBrowserWithExtension = async ( - userDataDir: string, - buildDir: string, -) => { - const browserContext = await createBrowserContext(userDataDir, buildDir) - const page = await browserContext.newPage() - - await page.bringToFront() - await page.goto("chrome://extensions") - await page.locator('[id="devMode"]').click() - const extensionId = await page - .locator('[id="extension-id"]') - .first() - .textContent() - .then((text) => text?.replace("ID: ", "")) - - const extensionURL = `chrome-extension://${extensionId}/index.html` - await page.goto(extensionURL) - await page.waitForTimeout(500) - - await page.emulateMedia({ reducedMotion: "reduce" }) - return { browserContext, extensionURL, page } -} - -function createExtension(label: string, upgrade: boolean = false) { - return async ({}, use: any, testInfo: TestInfo) => { - const userDataDir = `/tmp/test-user-data-${uuid()}` - let buildDir = config.distDir - if (upgrade) { - fs.copy(buildDir, config.migVersionDir) - buildDir = config.migVersionDir - } - const { browserContext, page, extensionURL } = - await initBrowserWithExtension(userDataDir, buildDir) - process.env.workerIndex = testInfo.workerIndex.toString() - const extension = new ExtensionPage(page, extensionURL, upgrade) - await closePages(browserContext) - browserCtx = browserContext - await use(extension) - - if (isKeepArtifacts(testInfo)) { - await saveHtml(testInfo, page, label) - await keepVideos(testInfo, page, label) - } - await browserContext.close() - } -} - -function getContext() { - return async ({}, use: any, _testInfo: TestInfo) => { - await use(browserCtx) - } -} - -const test = testBase.extend({ - extension: createExtension("extension"), - secondExtension: createExtension("secondExtension"), - thirdExtension: createExtension("thirdExtension"), - browserContext: getContext(), - upgradeExtension: createExtension("upgradeExtension", true), -}) - -export default test diff --git a/e2e/src/utils/Clipboard.ts b/e2e/src/utils/Clipboard.ts deleted file mode 100644 index e85290e..0000000 --- a/e2e/src/utils/Clipboard.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { Page } from "@playwright/test" - -export default class Clipboard { - page: Page - private static clipboards: Map = new Map() - private readonly workerIndex: number - - constructor(page: Page) { - this.page = page - this.workerIndex = Number(process.env.workerIndex) - } - - async setClipboard(): Promise { - const text = String( - await this.page.evaluate(`navigator.clipboard.readText()`), - ) - Clipboard.clipboards.set(this.workerIndex, text) - } - - async setClipboardText(text: string): Promise { - Clipboard.clipboards.set(this.workerIndex, text) - } - - async getClipboard(): Promise { - return Clipboard.clipboards.get(this.workerIndex) || "" - } - - async paste(): Promise { - const content = Clipboard.clipboards.get(this.workerIndex) || "" - await this.page.evaluate( - (text) => navigator.clipboard.writeText(text), - content, - ) - const key = process.platform === "darwin" ? "Meta" : "Control" - await this.page.keyboard.press(`${key}+v`) - } - - async clear(): Promise { - Clipboard.clipboards.delete(this.workerIndex) - } - - // Optional: method to clear all clipboards - static clearAll(): void { - Clipboard.clipboards.clear() - } -} diff --git a/e2e/src/utils/common.ts b/e2e/src/utils/common.ts deleted file mode 100644 index 2ce9d34..0000000 --- a/e2e/src/utils/common.ts +++ /dev/null @@ -1,30 +0,0 @@ -import config from "../config" -import { v4 as uuid } from "uuid" - -export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)) -const app = "argentx" -export const expireBESession = async (email: string) => { - const requestOptions = { - method: "GET", - } - const request = `${ - config.beAPIUrl - }/debug/expireCredentials?application=${app}&email=${encodeURIComponent( - email, - )}` - const response = await fetch(request, requestOptions) - if (response.status != 200) { - console.error(response.body) - throw new Error(`Error expiring session: ${request}`) - } - return response.status -} - -export const logInfo = (message: string | object) => { - const canLogInfo = process.env.E2E_LOG_INFO || false - if (canLogInfo) { - console.log(message) - } -} - -export const generateEmail = () => `e2e_2fa_${uuid()}@mail.com` diff --git a/e2e/src/utils/downloadGitHubRelease.ts b/e2e/src/utils/downloadGitHubRelease.ts deleted file mode 100644 index 129251c..0000000 --- a/e2e/src/utils/downloadGitHubRelease.ts +++ /dev/null @@ -1,64 +0,0 @@ -import axios from "axios" -import * as fs from "fs" -import * as path from "path" -import { pipeline } from "stream" -import { promisify } from "util" -import config from "../config" - -const streamPipeline = promisify(pipeline) - -export async function downloadGitHubRelease(version: string): Promise { - const owner = config.migRepoOwner - const repo = config.migRepo - const tag = `v${version}` - const assetName = config.migReleaseName - const token = config.migRepoToken - const outputPath = `${config.migDir}${version}.zip` - try { - // Get release by tag name - const releaseResponse = await axios.get( - `https://api.github.com/repos/${owner}/${repo}/releases/tags/${tag}`, - { - headers: { - Authorization: `token ${token}`, - Accept: "application/vnd.github.v3+json", - "User-Agent": "Node.js", - }, - }, - ) - - const releaseData = releaseResponse.data - - // Find the asset by name - const asset = releaseData.assets.find((a: any) => a.name === assetName) - - if (!asset) { - throw new Error(`Asset ${assetName} not found in release ${tag}`) - } - - const assetUrl = asset.url - - // Download the asset - const assetResponse = await axios.get(assetUrl, { - headers: { - Authorization: `token ${token}`, - Accept: "application/octet-stream", - "User-Agent": "Node.js", - }, - responseType: "stream", // Important for streaming the response - }) - - // Ensure the output directory exists - const dir = path.dirname(outputPath) - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }) - } - - // Write the file - await streamPipeline(assetResponse.data, fs.createWriteStream(outputPath)) - - console.log(`Asset downloaded to ${outputPath}`) - } catch (error: any) { - console.error(`Error: ${error.message}`) - } -} diff --git a/e2e/src/utils/getBranchVersion.sh b/e2e/src/utils/getBranchVersion.sh deleted file mode 100755 index 1bc0956..0000000 --- a/e2e/src/utils/getBranchVersion.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -# Enable command printing -#set -x - -# Function to run a command and check its exit status -run_command() { - output=$("$@") - local status=$? - if [ $status -ne 0 ]; then - echo "Error: Command '$*' failed with exit status $status" >&2 - exit 1 - fi - echo "$output" -} - -# Extract the version -VERSION=$(run_command grep -m1 '"version":' ../extension/dist/manifest.json | awk -F: '{ print $2 }' | sed 's/[", ]//g') - -# Print the version -echo "$VERSION" - -# Return the version as the script's output -exit 0 \ No newline at end of file diff --git a/e2e/src/utils/getBranchVersion.ts b/e2e/src/utils/getBranchVersion.ts deleted file mode 100644 index 32a47a5..0000000 --- a/e2e/src/utils/getBranchVersion.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { execSync } from "child_process" -import * as path from "path" -import * as fs from "fs" - -export const getBranchVersion = (): string => { - const scriptPath = path.join(__dirname, "getBranchVersion.sh") - - try { - // Make the script executable - fs.chmodSync(scriptPath, "755") - - // Execute the script synchronously - const stdout = execSync(`bash ${scriptPath}`, { encoding: "utf8" }) - - console.log(`Version:${stdout}`) - - return stdout.trim() - } catch (error) { - console.error(`getVersion Error: ${error}`) - throw error - } -} diff --git a/e2e/src/utils/global.teardown.ts b/e2e/src/utils/global.teardown.ts deleted file mode 100644 index 294c45b..0000000 --- a/e2e/src/utils/global.teardown.ts +++ /dev/null @@ -1,16 +0,0 @@ -import * as fs from "fs" -import config from "../config" - -export default async function tearDown() { - console.time("tearDown") - try { - fs.readdirSync(config.artifactsDir) - .filter((f) => f.endsWith("webm")) - .forEach((fileToDelete) => { - fs.rmSync(`${config.artifactsDir}/${fileToDelete}`) - }) - } catch (error) { - console.error({ op: "tearDown", error }) - } - console.timeEnd("tearDown") -} diff --git a/e2e/src/utils/index.ts b/e2e/src/utils/index.ts deleted file mode 100644 index ca8e644..0000000 --- a/e2e/src/utils/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -export { sleep, expireBESession, logInfo, generateEmail } from "./common" -export { default as Clipboard } from "./Clipboard" - -export { - TokenSymbol, - TokenName, - FeeTokens, - AccountsToSetup, - transferTokens, - getTokenInfo, - validateTx, - isScientific, - convertScientificToDecimal, - getBalance, -} from "./assets" - -export { unzip } from "./unzip" - -export { downloadGitHubRelease } from "./downloadGitHubRelease" diff --git a/e2e/src/utils/unzip.sh b/e2e/src/utils/unzip.sh deleted file mode 100755 index 58881fe..0000000 --- a/e2e/src/utils/unzip.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/bin/bash - -# Enable command printing -#set -x - -# Function to run a command and check its exit status -run_command() { - "$@" - local status=$? - if [ $status -ne 0 ]; then - echo "Error: Command '$*' failed with exit status $status" - exit 1 - fi - return $status -} - -# Check if the correct number of arguments are provided -if [ $# -ne 2 ]; then - echo "Usage: $0 " - exit 1 -fi - -ZIP_FILE="$1" -OUTPUT_DIR="$2" - -# Check if ZIP_FILE exists -if [ ! -f "$ZIP_FILE" ]; then - echo "Error: ZIP file $ZIP_FILE does not exist" - exit 1 -fi - -BASE_NAME=$(basename "$ZIP_FILE" .zip) -echo "Removing directory: $OUTPUT_DIR/$BASE_NAME" -run_command rm -rf "$OUTPUT_DIR/$BASE_NAME" -run_command rm -rf "$OUTPUT_DIR/__MACOSX" - -# Create the output directory if it doesn't exist -run_command mkdir -p "$OUTPUT_DIR" - -# Check if bsdtar is installed, if not, install it -if ! command -v bsdtar &> /dev/null; then - echo "bsdtar not found. Installing libarchive-tools..." - run_command apt-get update - run_command apt-get install -y libarchive-tools -fi - -# Extract the zip file using bsdtar with verbose output -echo "Extracting $ZIP_FILE to $OUTPUT_DIR" -run_command bsdtar --no-xattrs -xf "$ZIP_FILE" -C "$OUTPUT_DIR" - -echo "Extraction completed" - -# Print the contents of the output directory for verification -#echo "Contents of $OUTPUT_DIR:" -#run_command ls -R "$OUTPUT_DIR" - -# Disable command printing -set +x \ No newline at end of file diff --git a/e2e/src/utils/unzip.ts b/e2e/src/utils/unzip.ts deleted file mode 100644 index d1ba798..0000000 --- a/e2e/src/utils/unzip.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { exec } from "child_process" -import * as path from "path" -import { promisify } from "util" -import config from "../config" - -const execAsync = promisify(exec) - -export const unzip = async (version: string): Promise => { - const zipFilePath = path.join(config.migDir, `${version}.zip`) - const outputDir = path.join(config.migDir, version) - const scriptPath = path.join(__dirname, "unzip.sh") - - try { - console.log(`###### Unzipping ${version}.zip`) - - // Ensure the script is executable - await execAsync(`chmod +x ${scriptPath}`) - - // Execute the unzip script - const { stdout, stderr } = await execAsync( - `bash "${scriptPath}" "${zipFilePath}" "${outputDir}"`, - { maxBuffer: 1024 * 1024 * 10 }, // Increase buffer size to 10MB - ) - - console.log(`Unzip Output:\n${stdout}`) - - if (stderr) { - console.warn(`Unzip Warnings:\n${stderr}`) - } - } catch (error) { - console.error(`Error during unzip: ${error}`) - throw error - } - - return `${outputDir}` -} diff --git a/e2e/src/webwallet/config.ts b/e2e/src/webwallet/config.ts new file mode 100644 index 0000000..ac79268 --- /dev/null +++ b/e2e/src/webwallet/config.ts @@ -0,0 +1,30 @@ +import path from "path" +import dotenv from "dotenv" +import fs from "fs" +import commonConfig from "./shared/config" + +const envPath = path.resolve(__dirname, ".env") +if (fs.existsSync(envPath)) { + dotenv.config({ path: envPath }) +} +const config = { + validLogin: { + email: process.env.WW_EMAIL!, + pin: process.env.WW_PIN!, + password: process.env.WW_LOGIN_PASSWORD!, + }, + emailPassword: process.env.EMAIL_PASSWORD!, + acc_destination: commonConfig.destinationAddress!, + vw_acc_addr: process.env.VW_ACC_ADDR!, + url: "https://web.argent.xyz", + ...commonConfig, +} + +// check that no value of config is undefined, otherwise throw error +Object.entries(config).forEach(([key, value]) => { + if (value === undefined) { + throw new Error(`Missing ${key} config variable; check .env file`) + } +}) + +export default config diff --git a/e2e/src/webwallet/fixtures.ts b/e2e/src/webwallet/fixtures.ts new file mode 100644 index 0000000..f9bf060 --- /dev/null +++ b/e2e/src/webwallet/fixtures.ts @@ -0,0 +1,8 @@ +import { BrowserContext, Page } from "@playwright/test" +import type WebWalletPage from "./page-objects/WebWalletPage" + +export interface TestPages { + webWallet: WebWalletPage + dApp: Page + browserContext: BrowserContext +} diff --git a/e2e/src/webwallet/page-objects/Dapps.ts b/e2e/src/webwallet/page-objects/Dapps.ts new file mode 100644 index 0000000..1fd127c --- /dev/null +++ b/e2e/src/webwallet/page-objects/Dapps.ts @@ -0,0 +1,104 @@ +import { Page, expect } from "@playwright/test" +import { ICredentials } from "./Login" +import Navigation from "./Navigation" +import { artifactsDir } from "../shared/cfg/test" +import { randomUUID } from "crypto" +import SapoEmailClient from "../shared/src/emailClient" +import config from "../config" +type DappUrl = 'http://localhost:3000/' +let mailClient: SapoEmailClient +export default class Dapps extends Navigation { + + constructor(page: Page) { + super(page) + mailClient = new SapoEmailClient(config.validLogin.email, config.emailPassword); + } + + async requestConnectionFromDapp({ + dApp, + dappUrl, + credentials, + newAccount = false, + }: { + dApp: Page + dappUrl: DappUrl + credentials: ICredentials + newAccount: boolean + }) { + const starknetKitModal = true + + await dApp.setViewportSize({ width: 1080, height: 720 }) + await dApp.goto(dappUrl) + await dApp.getByRole('button', { name: 'Connection' }).click() + if (starknetKitModal) { + await dApp.getByRole('button', { name: 'Starknetkit Modal' }).click() + } + const popup = await this.handlePopup(dApp, credentials, newAccount) + await this.verifyEmailInPopup(popup, credentials.email) + await popup.locator('button[type="submit"]').click() + return dApp + } + + private async handleDappSpecificLogic(dApp: Page, dappUrl: DappUrl) { + switch (dappUrl) { + case "http://localhost:3000/": + await this.handleStarknetIdDapp(dApp); + break; + + default: + throw new Error(`Unsupported dApp URL: ${dappUrl}`); + } + } + private async handlePortfolionDapp(dApp: Page) { + await dApp.locator('button:has-text("Connect wallet")').first().click(); + } + + private async handleRubyDapp(dApp: Page) { + await dApp + .locator('button:has-text("starknetkit@latest")') + .first() + .click(); + } + + private async handleStarknetkitDapp(dApp: Page) { + await dApp.locator('button:has-text("Connect")').first().click(); + } + + private async handleStarknetIdDapp(dApp: Page) { + await expect(dApp.locator("text=CONNECT ARGENT")).toBeVisible(); + await dApp.locator("text=CONNECT ARGENT").click(); + } + + private async handlePopup(dApp: Page, credentials: ICredentials, newAccount: boolean) { + const popupPromise = dApp.waitForEvent("popup") + await expect(dApp.locator("p:text-is('Email')")).toBeVisible() + await dApp.locator("p:text-is('Email')").click() + const popup = await popupPromise + // Wait for the popup to load. + await popup.waitForLoadState() + await popup.locator("[name=email]").fill(credentials.email) + await popup.locator('button[type="submit"]').click() + const pin = await mailClient.getPin() + console.log("PIN:", pin) + await popup.locator('[id^="pin-input"]').first().click() + await popup.locator('[id^="pin-input"]').first().fill(pin!) + if (newAccount) { + await popup.locator("[name=password]").fill(credentials.password) + await popup.locator("[name=repeatPassword]").fill(credentials.password) + } else { + await popup.locator("[name=password]").fill(credentials.password) + } + await popup.locator('button[type="submit"]').click() + await popup.waitForLoadState() + return popup + } + + private async verifyEmailInPopup(popup: Page, email: string) { + await expect(popup.locator(`text="${email}"`)) + .toBeVisible() + .catch(async () => { + await popup.screenshot({ path: `${artifactsDir}/${randomUUID()}.png` }) + throw new Error("Email not visible") + }) + } +} diff --git a/e2e/src/webwallet/page-objects/Login.ts b/e2e/src/webwallet/page-objects/Login.ts new file mode 100644 index 0000000..27f4071 --- /dev/null +++ b/e2e/src/webwallet/page-objects/Login.ts @@ -0,0 +1,88 @@ +import { Page, expect } from "@playwright/test" + +import config from "../config" +import Navigation from "./Navigation" + +export interface ICredentials { + email: string + pin: string + password: string +} + +export default class Login extends Navigation { + constructor(page: Page) { + super(page) + } + + get email() { + return this.page.locator("input[name=email]") + } + + get pinInput() { + return this.page.locator('[id^="pin-input"]') + } + + get password() { + return this.page.locator("input[name=password]") + } + get repeatPassword() { + return this.page.locator("input[name=repeatPassword]") + } + get wrongPassword() { + return this.page.locator( + '//input[@name="password"][@aria-invalid="true"]/following::label[contains(text(), "Wrong password")]', + ) + } + + get resendPin() { + return this.page.getByText("Not received the email?") + } + + get pinResendConfirmation() { + return this.page.getByText("Email sent!") + } + + get forgetPassword() { + return this.page.getByText("Forgotten your password?") + } + + get recoveryOptionsTitle() { + return this.page.getByText("Recovery options") + } + + get differentAccount() { + return this.page.locator('p:text-is("Use a different account")') + } + + async fillPin(pin: string) { + await this.continue.click() + await this.pinInput.first().click() + await this.pinInput.first().fill(pin) + } + + async success(credentials: ICredentials = config.validLogin) { + await this.email.fill(credentials.email) + await this.fillPin(credentials.pin) + await this.password.fill(credentials.password) + await expect(this.forgetPassword).toBeVisible() + await expect(this.differentAccount).toBeVisible() + await Promise.all([ + this.page.waitForURL(`${config.url}/settings`), + this.continue.click(), + ]) + await expect(this.lock).toBeVisible() + } + + async createWallet(credentials: ICredentials) { + await this.email.fill(credentials.email) + //await this.continue.click() + await this.fillPin(credentials.pin) + await this.password.fill(credentials.password) + await this.repeatPassword.fill(credentials.password) + await Promise.all([ + this.page.waitForURL(`${config.url}/settings`), + this.continue.click(), + ]) + await expect(this.lock).toBeVisible() + } +} diff --git a/e2e/src/webwallet/page-objects/Navigation.ts b/e2e/src/webwallet/page-objects/Navigation.ts new file mode 100644 index 0000000..92f4c86 --- /dev/null +++ b/e2e/src/webwallet/page-objects/Navigation.ts @@ -0,0 +1,44 @@ +import type { Page } from "@playwright/test" + +export default class Navigation { + page: Page + constructor(page: Page) { + this.page = page + } + + get viewYourAccountTitle() { + return this.page.locator("text=View your smart account") + } + + get viewYourAccountDescription() { + return this.page.locator("text=See your smart account on Argent Web.") + } + + get continue() { + return this.page.locator(`button:text-is("Continue")`) + } + + get addFunds() { + return this.page.getByRole("link", { name: "Add funds" }) + } + + get send() { + return this.page.getByRole("link", { name: "Send" }) + } + + get authorizedDapps() { + return this.page.getByRole("link", { name: "Authorized dapps" }) + } + + get changePassword() { + return this.page.getByRole("link", { name: "Change password" }) + } + + get lock() { + return this.page.getByRole("button", { name: "Lock" }) + } + + get switchTheme() { + return this.page.getByRole("button", { name: "Switch theme" }) + } +} diff --git a/e2e/src/webwallet/page-objects/WalletHome.ts b/e2e/src/webwallet/page-objects/WalletHome.ts new file mode 100644 index 0000000..376ab96 --- /dev/null +++ b/e2e/src/webwallet/page-objects/WalletHome.ts @@ -0,0 +1,90 @@ +import type { Page } from "@playwright/test" +import email from '../config'; + +export default class WalletHome { + page: Page + constructor(page: Page) { + this.page = page + } + + get webWalletTitle() { + return this.page.locator(`p:text-is("${email}")`) + } + + get viewInPortfolio() { + return this.page.locator('p:text-is("View your smart account")') + } + + get viewInPortfolioParagraph() { + return this.page.locator('p:text-is("See your smart account on Argent Web.")') + } + + get authorizedDapps() { + return this.page.locator('p:text-is("Authorized dapps")') + } + + get manageDappsParagraph() { + return this.page.locator('p:text-is("Manage dapps connected with your account.")') + } + + get passwordInput() { + return this.page.locator('input[type="password"]') + } + + get changePasswordParagraph() { + return this.page.locator('p:text-is("Change password for your smart account.")') + } + + get downloadPrivateKeyButton() { + return this.page.locator('button:text-is("Download private key")') + } + + get downloadButton() { + return this.page.locator('button:text-is("Download")') + } + + get lockAccountParagraph() { + return this.page.locator('p:text-is("Lock your account.")') + } + + get lockButton() { + return this.page.locator('button:text-is("Lock")') + } + + get termsOfServiceParagraph() { + return this.page.locator('p:text-is("Terms of service")') + } + + get privacyPolicyParagraph() { + return this.page.locator('p:text-is("Privacy policy")') + } + + get versionParagraph() { + return this.page.locator('p:text-is("Version")') + } + + + async verifyLayout() { + const elements = [ + this.webWalletTitle, + this.viewInPortfolio, + this.viewInPortfolioParagraph, + this.authorizedDapps, + this.manageDappsParagraph, + this.passwordInput, + this.changePasswordParagraph, + this.downloadPrivateKeyButton, + this.downloadButton, + this.lockAccountParagraph, + this.lockButton, + this.termsOfServiceParagraph, + this.privacyPolicyParagraph + ]; + + for (const element of elements) { + await element.isVisible(); + } + } + + +} diff --git a/e2e/src/webwallet/page-objects/WalletHomeSubpages.ts b/e2e/src/webwallet/page-objects/WalletHomeSubpages.ts new file mode 100644 index 0000000..a52547d --- /dev/null +++ b/e2e/src/webwallet/page-objects/WalletHomeSubpages.ts @@ -0,0 +1,124 @@ +import type { Page } from "@playwright/test" +import email from '../config'; + +export default class WalletHomeSubpages { + page: Page + constructor(page: Page) { + this.page = page + } + + // Define the locators for the elements on the Wallet subpage: Portfolio + get portfolioHeading() { + return this.page.locator('text-is("Portfolio")'); + } + + get portfolioSubTitle() { + return this.page.locator('text="Theh best place to track your portfolio on Starknet"'); + } + + // Define the locators for the elements on the Wallet subpage: Authorized dapps + get authorizedDappsHeading() { + return this.page.locator('h1:text-is("Authorized dapps")') + } + get goBackButton() { + return this.page.locator(`button[aria-label="Go back"]`); + } + + get connectedDappsTab() { + return this.page.locator('tab:text-is("Connected dapps")'); + } + + get noConnectedDappsMessage() { + return this.page.locator('text="No connected dapps"'); + } + + get activeSessionsTab() { + return this.page.locator('text="Active sessions"'); + } + + get noActiveSessionsMessage() { + return this.page.locator('text="No active sessions"'); + } + + //Define the locators for the elements on the Wallet subpage: Change password + + get changePasswordHeading() { + return this.page.locator('h1:text-is("Change password")') + } + + get enterCodeMessage() { + return this.page.locator(`text="Enter the code we sent to" ${email}`); + } + + get changePasswordDescription() { + return this.page.locator(`text="We've sent you an email with a code. Enter it below so you can change your password."`) + } + + get enterNewPasswordParagraph() { + return this.page.locator('text="Enter new password"'); + } + + get passwordInput() { + return this.page.locator('input[name="password"]'); + } + + get repeatNewPasswordParagraph() { + return this.page.locator('text="Repeat new password"'); + } + + get repeatPasswordInput() { + return this.page.locator('input[name="repeatPassword"]'); + } + + get continueButton() { + return this.page.locator('button:text-is("Continue")'); + } + + get passwordChangedHeading() { + return this.page.locator('element[name="Password successfully changed."]'); + } + + get goBackToSettingsButton() { + return this.page.locator('button[name="Go back to settings"]'); + } + + // Define the locators for the elements on the Wallet subpage: Download private key + get securityCompromisedHeading() { + return this.page.locator('h1:text-is("Security of your wallet might get compromised")') + } + + get securityCompromisedParagraph() { + return this.page.locator('p:text-is("We strongly recommend to not download your private key if you\'re not sure what it means")') + } + + get downloadKeyButton() { + return this.page.locator('button:text-is("Download private key")') + } + + get closeButton() { + return this.page.locator('button:text-is("Close")') + } + + // Define the locators for the elements on the Wallet subpage: Lock account + get welcomeBackHeading() { + return this.page.locator('h1:text-is("Welcome Back")'); + } + + get emailAddressHeading() { + return this.page.locator(`text=${email}`); + } + + get enterYourPasswordHeading() { + return this.page.locator('h1:text-is("Enter your password")'); + } + + // Define the locators for the elements on Terms of service and Privacy policy Pages (outside WebWallet) + get argentAppTermsOfServiceHeading() { + return this.page.locator('h1:text-is("Argent App - Terms of Service")') + } + + get argentAppPrivacyPolicyHeading() { + return this.page.locator('h1:text-is("Argent App - Privacy Policy")') + } + +} diff --git a/e2e/src/webwallet/page-objects/WebWalletPage.ts b/e2e/src/webwallet/page-objects/WebWalletPage.ts new file mode 100644 index 0000000..3d2f46d --- /dev/null +++ b/e2e/src/webwallet/page-objects/WebWalletPage.ts @@ -0,0 +1,35 @@ +import type { Page } from "@playwright/test" +import { v4 as uuid } from "uuid" + +import config from "../config" +import Login from "./Login" +import Navigation from "./Navigation" + +export const generateEmail = () => `newWallet_${uuid()}@mail.com` + +import Dapps from "./Dapps" +import WalletHome from "./WalletHome" +import WalletHomeSubpages from "./WalletHomeSubpages" +export default class WebWalletPage { + page: Page + login: Login + navigation: Navigation + dapps: Dapps + wallethome: WalletHome + walletHomeSubpages: WalletHomeSubpages + + constructor(page: Page) { + this.page = page + this.login = new Login(page) + this.navigation = new Navigation(page) + this.dapps = new Dapps(page) + this.wallethome = new WalletHome(page) + this.walletHomeSubpages = new WalletHomeSubpages(page) + } + + open() { + return this.page.goto(config.url) + } + + generateEmail = () => `e2e_webwallet_${uuid()}@mail.com` +} diff --git a/e2e/src/webwallet/shared/cfg/global.teardown.ts b/e2e/src/webwallet/shared/cfg/global.teardown.ts new file mode 100644 index 0000000..2153e09 --- /dev/null +++ b/e2e/src/webwallet/shared/cfg/global.teardown.ts @@ -0,0 +1,16 @@ +import { artifactsDir } from "./test" +import * as fs from "fs" + +export default function cleanArtifactDir() { + console.time("cleanArtifactDir") + try { + fs.readdirSync(artifactsDir) + .filter((f) => f.endsWith("webm")) + .forEach((fileToDelete) => { + fs.rmSync(`${artifactsDir}/${fileToDelete}`) + }) + } catch (error) { + console.error({ op: "cleanArtifactDir", error }) + } + console.timeEnd("cleanArtifactDir") +} diff --git a/e2e/src/webwallet/shared/cfg/test.ts b/e2e/src/webwallet/shared/cfg/test.ts new file mode 100644 index 0000000..39a883e --- /dev/null +++ b/e2e/src/webwallet/shared/cfg/test.ts @@ -0,0 +1,75 @@ +import dotenv from "dotenv" +dotenv.config() + +import * as fs from "fs" +import path from "path" + +import { Page, TestInfo } from "@playwright/test" +import { logInfo } from "../src/common" +export const artifactsDir = path.resolve( + __dirname, + "../../artifacts/playwright", +) +export const reportsDir = path.resolve(__dirname, "../../artifacts/reports") +export const isCI = Boolean(process.env.CI) +export const outputFolder = (testInfo: TestInfo) => + testInfo.title.replace(/\s+/g, "_").replace(/\W/g, "") +export const artifactFilename = (testInfo: TestInfo, label: string) => + `${testInfo.retry}-${testInfo.status}-${label}-${testInfo.workerIndex}` +export const isKeepArtifacts = (testInfo: TestInfo) => + testInfo.config.preserveOutput === "always" || + (testInfo.config.preserveOutput === "failures-only" && + testInfo.status === "failed") || + testInfo.status === "timedOut" + +export const artifactSetup = async (testInfo: TestInfo, label: string) => { + await fs.promises + .mkdir(path.resolve(artifactsDir, outputFolder(testInfo)), { + recursive: true, + }) + .catch((error) => { + console.error({ op: "artifactSetup", error }) + }) + return artifactFilename(testInfo, label) +} + +export const saveHtml = async ( + testInfo: TestInfo, + page: Page, + label: string, +) => { + logInfo({ + op: "saveHtml", + label, + }) + const fileName = await artifactSetup(testInfo, label) + const htmlContent = await page.content() + await fs.promises + .writeFile( + path.resolve(artifactsDir, outputFolder(testInfo), `${fileName}.html`), + htmlContent, + ) + .catch((error) => { + console.error({ op: "saveHtml", error }) + }) +} + +export const keepVideos = async ( + testInfo: TestInfo, + page: Page, + label: string, +) => { + logInfo({ + op: "keepVideos", + label, + }) + const fileName = await artifactSetup(testInfo, label) + await page + .video() + ?.saveAs( + path.resolve(artifactsDir, outputFolder(testInfo), `${fileName}.webm`), + ) + .catch((error) => { + console.error({ op: "keepVideos", error }) + }) +} diff --git a/e2e/src/webwallet/shared/config.ts b/e2e/src/webwallet/shared/config.ts new file mode 100644 index 0000000..d296860 --- /dev/null +++ b/e2e/src/webwallet/shared/config.ts @@ -0,0 +1,27 @@ +import path from "path" +import dotenv from "dotenv" +import fs from "fs" + +const envPath = path.resolve(__dirname, "../../.env") +if (fs.existsSync(envPath)) { + dotenv.config({ path: envPath }) +} + +const commonConfig = { + isProdTesting: process.env.ARGENT_X_ENVIRONMENT === "prod" ? true : false, + //accounts used for setup + senderAddrs: process.env.E2E_SENDER_ADDRESSES?.split(",") || [], + senderKeys: process.env.E2E_SENDER_PRIVATEKEYS?.split(",") || [], + destinationAddress: process.env.E2E_SENDER_ADDRESSES?.split(",")[0] || '', //used as transfers destination + // urls + rpcUrl: process.env.ARGENT_SEPOLIA_RPC_URL || '', +} + +// check that no value of config is undefined, otherwise throw error +Object.entries(commonConfig).forEach(([key, value]) => { + if (value === undefined) { + throw new Error(`Missing ${key} config variable; check .env file`) + } +}) + +export default commonConfig diff --git a/e2e/src/webwallet/shared/src/Utils.ts b/e2e/src/webwallet/shared/src/Utils.ts new file mode 100644 index 0000000..e5050f7 --- /dev/null +++ b/e2e/src/webwallet/shared/src/Utils.ts @@ -0,0 +1,21 @@ +import type { Page } from "@playwright/test" + +export default class Utils { + page: Page + constructor(page: Page) { + this.page = page + } + + async setClipBoardContent(text: string) { + await this.page.evaluate(`navigator.clipboard.writeText('${text}')`) + } + + async getClipboard() { + return String(await this.page.evaluate(`navigator.clipboard.readText()`)) + } + + async paste() { + const key = process.env.CI ? "Control" : "Meta" + await this.page.keyboard.press(`${key}+KeyV`) + } +} diff --git a/e2e/src/utils/assets.ts b/e2e/src/webwallet/shared/src/assets.ts similarity index 93% rename from e2e/src/utils/assets.ts rename to e2e/src/webwallet/shared/src/assets.ts index 4b98650..5f810bd 100644 --- a/e2e/src/utils/assets.ts +++ b/e2e/src/webwallet/shared/src/assets.ts @@ -23,27 +23,16 @@ const isEqualAddress = (a?: string, b?: string) => { return false } -export type TokenSymbol = - | "ETH" - | "WBTC" - | "STRK" - | "SWAY" - | "USDC" - | "DAI" - | "ádfas" +export type TokenSymbol = "ETH" | "WBTC" | "STRK" | "SWAY" | "USDC" | "DAI" export type TokenName = | "Ethereum" | "Wrapped BTC" | "Starknet" | "Standard Weighted Adalian Yield" + | "USD Coin" | "DAI" - | "USD Coin (Fake)" export type FeeTokens = "ETH" | "STRK" export interface AccountsToSetup { - assets: { - token: TokenSymbol - balance: number - }[] deploy?: boolean feeToken?: FeeTokens } @@ -85,11 +74,7 @@ tokenAddresses.set("SWAY", { address: "0x0030058F19Ed447208015F6430F0102e8aB82D6c291566D7E73fE8e613c3D2ed", decimals: 18, }) -tokenAddresses.set("USDC", { - name: "USD Coin (Fake)", - address: "0x07ab0b8855a61f480b4423c46c32fa7c553f0aac3531bbddaa282d86244f7a23", - decimals: 6, -}) + export const getTokenInfo = (tkn: string) => { const tokenInfo = tokenAddresses.get(tkn) if (!tokenInfo) { @@ -192,7 +177,6 @@ const getTXData = async (txHash: string) => { } return { nodeUpdated, txData } } - export async function transferTokens( amount: number, to: string, @@ -232,7 +216,7 @@ export async function transferTokens( } console.error( - `[Failed to place TX] ${tx.transaction_hash} ${JSON.stringify(txStatusResponse)}`, + `[Failed to place TX] ${tx.transaction_hash} ${txStatusResponse}`, ) } catch (e) { if (e instanceof Error) { @@ -250,10 +234,7 @@ export async function transferTokens( return null } -export async function getBalance( - accountAddress: string, - token: TokenSymbol = "ETH", -) { +async function getBalance(accountAddress: string, token: TokenSymbol = "ETH") { const tokenInfo = getTokenInfo(token) logInfo({ op: "getBalance", accountAddress, token, tokenInfo }) const balanceOfCall = { diff --git a/e2e/src/webwallet/shared/src/common.ts b/e2e/src/webwallet/shared/src/common.ts new file mode 100644 index 0000000..82b8829 --- /dev/null +++ b/e2e/src/webwallet/shared/src/common.ts @@ -0,0 +1,8 @@ +export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)) + +export const logInfo = (message: string | object) => { + const canLogInfo = process.env.E2E_LOG_INFO || false + if (canLogInfo) { + console.log(message) + } +} diff --git a/e2e/src/webwallet/shared/src/emailClient.ts b/e2e/src/webwallet/shared/src/emailClient.ts new file mode 100644 index 0000000..272ded8 --- /dev/null +++ b/e2e/src/webwallet/shared/src/emailClient.ts @@ -0,0 +1,139 @@ +import * as ImapClient from 'imap-simple'; +import { simpleParser } from 'mailparser'; + +export default class SapoEmailClient { + private config: any; + + constructor(email: string, password: string) { + this.config = { + imap: { + user: email, + password: password, + host: 'imap.sapo.pt', + port: 993, + tls: true, + tlsOptions: { rejectUnauthorized: false }, + authTimeout: 3000 + } + }; + } + + async readEmails(options: { + folder?: string; + limit?: number; + markSeen?: boolean; + onlyUnread?: boolean; + } = {}) { + const { + folder = 'INBOX', + limit = 10, + markSeen = true, + onlyUnread = true + } = options; + + try { + const connection = await ImapClient.connect(this.config); + await connection.openBox(folder); + + // Search criteria for unread messages + const searchCriteria = onlyUnread ? ['UNSEEN'] : ['ALL']; + + // Fetch messages + const messages = await connection.search(searchCriteria, { + bodies: [''], + markSeen: markSeen + }); + + // Get latest messages based on limit + const latestMessages = messages.slice(-limit); + + // Parse messages + const emails = await Promise.all( + latestMessages.map(async (item) => { + const fullBody = item.parts.find(part => part.which === ''); + + if (fullBody) { + const parsed = await simpleParser(fullBody.body); + return { + id: item.attributes.uid, + subject: parsed.subject, + from: parsed.from?.text, + date: parsed.date, + text: parsed.text, + html: parsed.html + }; + } + }) + ); + + await connection.end(); + return emails.filter(email => email !== undefined); + + } catch (error) { + console.error('Error reading emails:', error); + throw error; + } + } + + async waitForNewEmail(options: { + timeout?: number; + subject?: string; + markSeen?: boolean; + pollInterval?: number; + } = {}): Promise { + const { + timeout = 30000, + subject = '', + markSeen = true, + pollInterval = 2000 // Check every 2 seconds by default + } = options; + + const startTime = Date.now(); + console.log('Waiting for new unread email...'); + + while (Date.now() - startTime < timeout) { + try { + const emails = await this.readEmails({ + limit: 5, + markSeen: markSeen, + onlyUnread: true + }); + + if (emails && emails.length > 0) { + // If subject is specified, filter by it + const matchingEmail = emails.find(email => + !subject || + (email.subject && email.subject.includes(subject)) + ); + + if (matchingEmail) { + console.log('Found matching email:', { + subject: matchingEmail.subject, + }); + return matchingEmail; + } + } + + // Wait before next check + await new Promise(resolve => setTimeout(resolve, pollInterval)); + } catch (error) { + console.error('Error checking emails:', error); + // Wait before retry after error + await new Promise(resolve => setTimeout(resolve, pollInterval)); + } + } + + throw new Error(`No new email found within timeout of ${timeout}ms`); + } + async getPin(): Promise { + try { + const email = await this.waitForNewEmail(); + return email.subject.match(/\d{6}/)?.[0]; + + } catch (error) { + console.error('Error:', error); + throw error; + } + } +} + diff --git a/e2e/src/webwallet/specs/dapps.spec.ts b/e2e/src/webwallet/specs/dapps.spec.ts new file mode 100644 index 0000000..9da7741 --- /dev/null +++ b/e2e/src/webwallet/specs/dapps.spec.ts @@ -0,0 +1,17 @@ +import test from "../test" +import config from "../config" + +test.describe(`Dapps`, () => { + + test("Connect from Dapp", async ({ webWallet, dApp }) => { + + + await webWallet.dapps.requestConnectionFromDapp({ + dApp, + dappUrl: "http://localhost:3000/", + credentials: config.validLogin, + newAccount: false, + }) + }) + +}) diff --git a/e2e/src/webwallet/test.ts b/e2e/src/webwallet/test.ts new file mode 100644 index 0000000..02b523c --- /dev/null +++ b/e2e/src/webwallet/test.ts @@ -0,0 +1,94 @@ +import { + artifactsDir, + isKeepArtifacts, + keepVideos, + saveHtml, +} from "./shared/cfg/test" + +import { + BrowserContext, + Browser, + TestInfo, + test as testBase, +} from "@playwright/test" + +import config from "./config" +import { TestPages } from "./fixtures" +import WebWalletPage from "./page-objects/WebWalletPage" + +let browserCtx: BrowserContext + +async function createContext({ + browser, + baseURL, +}: { + browser: Browser + baseURL: string + name: string + testInfo: TestInfo +}) { + const context = await browser.newContext({ + ignoreHTTPSErrors: true, + acceptDownloads: true, + recordVideo: { + dir: artifactsDir, + size: { + width: 1366, + height: 768, + }, + }, + baseURL, + viewport: { width: 1196, height: 724 }, + }) + + await context.addInitScript("window.PLAYWRIGHT = true;") + return context +} + +function createPage(pageType: "WebWallet" | "DApp" = "WebWallet") { + return async ( + { browser }: { browser: Browser }, + use: any, + testInfo: TestInfo, + ) => { + const url = config.url + + const context = await createContext({ + browser, + testInfo, + name: pageType, + baseURL: url, + }) + const page = await context.newPage() + browserCtx = context + if (pageType === "WebWallet") { + const webWalletPage = new WebWalletPage(page) + await webWalletPage.open() + await use(webWalletPage) + } else { + await use(page) + } + + const keepArtifacts = isKeepArtifacts(testInfo) + if (keepArtifacts) { + await saveHtml(testInfo, page, pageType) + await context.close() + await keepVideos(testInfo, page, pageType) + } else { + await context.close() + } + } +} +function getContext() { + return async ({ }, use: any, _testInfo: TestInfo) => { + await use(browserCtx) + } +} + +const test = testBase.extend({ + webWallet: createPage(), + browserContext: getContext(), + dApp: createPage("DApp"), +}) + +export default test diff --git a/e2e/tsconfig.json b/e2e/tsconfig.json index 4c48cae..2571efc 100644 --- a/e2e/tsconfig.json +++ b/e2e/tsconfig.json @@ -13,10 +13,18 @@ "inlineSources": true, "inlineSourceMap": true, "composite": true, - "types": ["node"], + "types": [ + "node" + ], "noEmit": true, "skipLibCheck": true }, - "include": ["**/src", "**/shared"], - "exclude": ["node_modules"] -} + "include": [ + "**/src", + "**/shared", + "config.ts" + ], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/package.json b/package.json index bb73693..863b444 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "demo-dapp-starknet", "version": "0.1.0", "private": true, - "packageManager": "pnpm@9.1.0", + "engines": { "node": "20.x" }, @@ -11,7 +11,8 @@ "build": "next build", "start": "next start", "lint": "next lint", - "test": "pnpm run --filter @demo-dapp-starket/e2e test", + "test:argentx": "pnpm run --filter @demo-dapp-starket/e2e test:argentx", + "test:webwallet": "pnpm run --filter @demo-dapp-starket/e2e test:webwallet", "test:headed": "playwright test --headed", "test:ui": "playwright test --ui", "prepare": "husky" @@ -34,7 +35,7 @@ "@types/react": "^18", "@types/react-dom": "^18", "autoprefixer": "^10.4.20", - "eslint": "^8", + "eslint": "^9.15.0", "eslint-config-next": "15.0.2", "husky": "^9.1.6", "lint-staged": "^15.2.10", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a708adc..021fe2f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -55,11 +55,11 @@ importers: specifier: ^10.4.20 version: 10.4.20(postcss@8.4.49) eslint: - specifier: ^8 - version: 8.57.1 + specifier: ^9.15.0 + version: 9.15.0(jiti@2.3.3) eslint-config-next: specifier: 15.0.2 - version: 15.0.2(eslint@8.57.1)(typescript@5.6.3) + version: 15.0.2(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3) husky: specifier: ^9.1.6 version: 9.1.6 @@ -87,9 +87,24 @@ importers: '@scure/bip39': specifier: ^1.2.1 version: 1.4.0 + axios: + specifier: ^1.7.7 + version: 1.7.7 + fs-extra: + specifier: ^11.2.0 + version: 11.2.0 + imap-simple: + specifier: ^5.1.0 + version: 5.1.0 lodash-es: specifier: ^4.17.21 version: 4.17.21 + mailparser: + specifier: ^3.7.1 + version: 3.7.1 + nodemailer: + specifier: ^6.9.16 + version: 6.9.16 object-hash: specifier: ^3.0.0 version: 3.0.0 @@ -112,9 +127,21 @@ importers: '@types/axios': specifier: ^0.14.0 version: 0.14.4 + '@types/fs-extra': + specifier: ^11.0.4 + version: 11.0.4 + '@types/imap-simple': + specifier: ^4.2.9 + version: 4.2.9 + '@types/mailparser': + specifier: ^3.4.5 + version: 3.4.5 '@types/node': specifier: ^22.0.0 - version: 22.9.0 + version: 22.9.3 + '@types/nodemailer': + specifier: ^6.4.17 + version: 6.4.17 '@types/uuid': specifier: ^10.0.0 version: 10.0.0 @@ -227,13 +254,29 @@ packages: resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/eslintrc@2.1.4': - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@eslint/config-array@0.19.0': + resolution: {integrity: sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@8.57.1': - resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@eslint/core@0.9.0': + resolution: {integrity: sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.2.0': + resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.15.0': + resolution: {integrity: sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.4': + resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.2.3': + resolution: {integrity: sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@ethersproject/abstract-provider@5.7.0': resolution: {integrity: sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==} @@ -286,18 +329,25 @@ packages: '@ethersproject/web@5.7.1': resolution: {integrity: sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==} - '@humanwhocodes/config-array@0.13.0': - resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} - engines: {node: '>=10.10.0'} - deprecated: Use @eslint/config-array instead + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} - '@humanwhocodes/object-schema@2.0.3': - resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} - deprecated: Use @eslint/object-schema instead + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.1': + resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} + engines: {node: '>=18.18'} '@img/sharp-darwin-arm64@0.33.5': resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} @@ -633,6 +683,9 @@ packages: '@scure/starknet@1.0.0': resolution: {integrity: sha512-o5J57zY0f+2IL/mq8+AYJJ4Xpc1fOtDhr+mFQKbHnYFmm3WQrC+8zj2HEgxak1a+x86mhmBC1Kq305KUpVf0wg==} + '@selderee/plugin-htmlparser2@0.11.0': + resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} + '@stablelib/aead@1.0.1': resolution: {integrity: sha512-q39ik6sxGHewqtO0nP4BuSe3db5G1fEJE8ukvngS2gLkBXyy6E7pLubhbYgnkDFv6V8cWaxcE4Xn0t6LWcJkyg==} @@ -735,14 +788,38 @@ packages: '@types/conventional-commits-parser@5.0.0': resolution: {integrity: sha512-loB369iXNmAZglwWATL+WRe+CRMmmBPtpolYzIebFaX4YA3x+BEfLqhUAV9WanycKI3TG1IMr5bMJDajDKLlUQ==} + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + + '@types/fs-extra@11.0.4': + resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} + + '@types/imap-simple@4.2.9': + resolution: {integrity: sha512-qROCP+BJfSpelnlhL48QGNU3bY1GWZpOgubiWnbs7HkIiLxmpP6TPE9Q/ROPjVzL9L89kB+vtBbQNNQTYNmuew==} + + '@types/imap@0.8.42': + resolution: {integrity: sha512-FusePG9Cp2GYN6OLow9xBCkjznFkAR7WCz0Fm+j1p/ER6C8V8P71DtjpSmwrZsS7zekCeqdTPHEk9N5OgPwcsg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/jsonfile@6.1.4': + resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} + + '@types/mailparser@3.4.5': + resolution: {integrity: sha512-EPERBp7fLeFZh7tS2X36MF7jawUx3Y6/0rXciZah3CTYgwLi3e0kpGUJ6FOmUabgzis/U1g+3/JzrVWbWIOGjg==} + '@types/node@20.17.4': resolution: {integrity: sha512-Fi1Bj8qTJr4f1FDdHFR7oMlOawEYSzkHNdBJK+aRjcDDNHwEV3jPPjuZP2Lh2QNgXeqzM8Y+U6b6urKAog2rZw==} - '@types/node@22.9.0': - resolution: {integrity: sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==} + '@types/node@22.9.3': + resolution: {integrity: sha512-F3u1fs/fce3FFk+DAxbxc78DF8x0cY09RRL8GnXLmkJ1jvx3TtPdWoTT5/NiYfI5ASqXBmfqJi9dZ3gxMx4lzw==} + + '@types/nodemailer@6.4.17': + resolution: {integrity: sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==} '@types/prop-types@15.7.13': resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==} @@ -813,9 +890,6 @@ packages: resolution: {integrity: sha512-PChz8UaKQAVNHghsHcPyx1OMHoFRUEA7rJSK/mDhdq85bk+PLsUHUBqTQTFt18VJZbmxBovM65fezlheQRsSDA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@ungap/structured-clone@1.2.0': - resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - '@walletconnect/core@2.17.1': resolution: {integrity: sha512-SMgJR5hEyEE/tENIuvlEb4aB9tmMXPzQ38Y61VgYBmwAFEhOHtpt8EDfnfRWqEhMyXuBXG4K70Yh8c67Yry+Xw==} engines: {node: '>=18'} @@ -1170,6 +1244,9 @@ packages: cookie-es@1.2.2: resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + cosmiconfig-typescript-loader@5.1.0: resolution: {integrity: sha512-7PtBB+6FdsOvZyJtlF3hEPpACq7RQX6BVGsgC7/lfVXnKMvNCu/XY3ykreqG5w/rBNdu2z8LCIKoF3kpHHdHlA==} engines: {node: '>=v16'} @@ -1191,6 +1268,10 @@ packages: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + crossws@0.3.1: resolution: {integrity: sha512-HsZgeVYaG+b5zA+9PbIPGq4+J/CJynJuearykPsXx4V/eMhyQ5EDVg3Ak2FBZtVXCiOLu/U7IiwDHTr9MA+IKw==} @@ -1245,6 +1326,10 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -1285,9 +1370,18 @@ packages: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} - doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.1.0: + resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} dot-prop@5.3.0: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} @@ -1321,6 +1415,14 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + encoding-japanese@2.0.0: + resolution: {integrity: sha512-++P0RhebUC8MJAwJOsT93dT+5oc5oPImp1HubZpAuCZ5kTLnhuuBhKHj2jJeO/Gj93idPBWmIuQ9QWMe5rX3pQ==} + engines: {node: '>=8.10.0'} + + encoding-japanese@2.1.0: + resolution: {integrity: sha512-58XySVxUgVlBikBTbQ8WdDxBDHIdXucB16LO5PBHR8t75D54wQrNo4cg+58+R1CtJfKnsVsvt9XlteRaR8xw1w==} + engines: {node: '>=8.10.0'} + end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} @@ -1328,6 +1430,10 @@ packages: resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} engines: {node: '>=10.13.0'} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} @@ -1452,23 +1558,31 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-scope@8.2.0: + resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint@8.57.1: - resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + eslint-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.15.0: + resolution: {integrity: sha512-7CrWySmIibCgT1Os28lUU6upBshZ+GxybLOrmRzi08kS8MBuO8QA7pXEgYgY5W8vK3e74xv0lpjo9DbaGU9Rkw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true - espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + espree@10.3.0: + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} @@ -1532,9 +1646,9 @@ packages: fetch-cookie@3.0.1: resolution: {integrity: sha512-ZGXe8Y5Z/1FWqQ9q/CrJhkUD73DyBU9VF0hBQmEO/wPHe4A9PKTjplFDLeFX8aOsYypZUcX5Ji/eByn3VCVO3Q==} - file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} @@ -1552,9 +1666,9 @@ packages: resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==} engines: {node: '>=18'} - flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} flatted@3.3.1: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} @@ -1586,8 +1700,9 @@ packages: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fs-extra@11.2.0: + resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} + engines: {node: '>=14.14'} fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} @@ -1655,17 +1770,13 @@ packages: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported - global-directory@4.0.1: resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} engines: {node: '>=18'} - globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} @@ -1712,9 +1823,20 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + hmac-drbg@1.0.1: resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} + html-to-text@9.0.5: + resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==} + engines: {node: '>=14'} + + htmlparser2@8.0.2: + resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} + http-shutdown@1.2.2: resolution: {integrity: sha512-S9wWkJ/VSY9/k4qcjG318bqJNruzE4HySUhFYknwmu6LBP97KLLfwNf+n4V1BHurvFNkSKLFnK/RsuUnRTf9Vw==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} @@ -1728,6 +1850,14 @@ packages: engines: {node: '>=18'} hasBin: true + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + idb-keyval@6.2.1: resolution: {integrity: sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==} @@ -1735,6 +1865,14 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + imap-simple@5.1.0: + resolution: {integrity: sha512-FLZm1v38C5ekN46l/9X5gBRNMQNVc5TSLYQ3Hsq3xBLvKwt1i5fcuShyth8MYMPuvId1R46oaPNrH92hFGHr/g==} + engines: {node: '>=6'} + + imap@0.8.19: + resolution: {integrity: sha512-z5DxEA1uRnZG73UcPA4ES5NSCGnPuuouUx43OPX7KZx1yzq3N8/vx2mtXEShT5inxB3pRgnfG1hijfu7XN2YMw==} + engines: {node: '>=0.8.0'} + import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -1746,10 +1884,6 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -1865,9 +1999,8 @@ packages: resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} engines: {node: '>=8'} - is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} + is-promise@1.0.1: + resolution: {integrity: sha512-mjWH5XxnhMA8cFnDchr6qRP9S/kLntKuEfIYku+PaN1CnS8v+OG9O/BKpRCVRJvpIkgAZm0Pf5Is3iSSOILlcg==} is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} @@ -1923,6 +2056,9 @@ packages: resolution: {integrity: sha512-jv+8jaWCl0g2lSBkNSVXdzfBA0npK1HGC2KtWM9FumFRoGS94g3NbCCLVnCYHLjp4GrW2KZeeSTMo5ddtznmGw==} engines: {node: '>=18'} + isarray@0.0.1: + resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} + isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} @@ -2005,10 +2141,31 @@ packages: resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} engines: {node: '>=0.10'} + leac@0.6.0: + resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + libbase64@1.2.1: + resolution: {integrity: sha512-l+nePcPbIG1fNlqMzrh68MLkX/gTxk/+vdvAb388Ssi7UuUN31MI44w4Yf33mM3Cm4xDfw48mdf3rkdHszLNew==} + + libbase64@1.3.0: + resolution: {integrity: sha512-GgOXd0Eo6phYgh0DJtjQ2tO8dc0IVINtZJeARPeiIJqge+HdsWSuaDTe8ztQ7j/cONByDZ3zeB325AHiv5O0dg==} + + libmime@5.2.0: + resolution: {integrity: sha512-X2U5Wx0YmK0rXFbk67ASMeqYIkZ6E5vY7pNWRKtnNzqjvdYYG8xtPDpCnuUEnPU9vlgNev+JoSrcaKSUaNvfsw==} + + libmime@5.3.5: + resolution: {integrity: sha512-nSlR1yRZ43L3cZCiWEw7ali3jY29Hz9CQQ96Oy+sSspYnIP5N54ucOPHqooBsXzwrX1pwn13VUE05q4WmzfaLg==} + + libqp@2.0.1: + resolution: {integrity: sha512-Ka0eC5LkF3IPNQHJmYBWljJsw0UvM6j+QdKRbWyCdTmYwvIDE6a7bCm0UkTAL/K+3KXK5qXT/ClcInU01OpdLg==} + + libqp@2.1.0: + resolution: {integrity: sha512-O6O6/fsG5jiUVbvdgT7YX3xY3uIadR6wEZ7+vy9u7PKHAlSEB6blvC1o5pHBjgsi95Uo0aiBBdkyFecj6jtb7A==} + lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} @@ -2020,6 +2177,9 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + lint-staged@15.2.10: resolution: {integrity: sha512-5dY5t743e1byO19P9I4b3x8HJwalIznL5E1FWYnU6OWw33KxNBSLAc6Cy7F2PsFEO8FKnLwjwm5hx7aMF0jzZg==} engines: {node: '>=18.12.0'} @@ -2088,6 +2248,12 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + mailparser@3.7.1: + resolution: {integrity: sha512-RCnBhy5q8XtB3mXzxcAfT1huNqN93HTYYyL6XawlIKycfxM/rXPg9tXoZ7D46+SgCS1zxKzw+BayDQSvncSTTw==} + + mailsplit@5.4.0: + resolution: {integrity: sha512-wnYxX5D5qymGIPYLwnp6h8n1+6P6vz/MJn5AzGjZ8pwICWssL+CCQjWBIToOVHASmATot4ktvlLo6CyLfOXWYA==} + meow@12.1.1: resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==} engines: {node: '>=16.10'} @@ -2211,6 +2377,17 @@ packages: node-releases@2.0.18: resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + nodeify@1.0.1: + resolution: {integrity: sha512-n7C2NyEze8GCo/z73KdbjRsBiLbv6eBn1FxwYKQ23IqGo7pQY3mhQan61Sv7eEDJCiyUjTVrVkXTzJCo1dW7Aw==} + + nodemailer@6.9.13: + resolution: {integrity: sha512-7o38Yogx6krdoBf3jCAqnIN4oSQFx+fMa0I7dK1D+me9kBxx12D+/33wSb+fhOCtIxvYJ+4x4IMEhmhCKfAiOA==} + engines: {node: '>=6.0.0'} + + nodemailer@6.9.16: + resolution: {integrity: sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==} + engines: {node: '>=6.0.0'} + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -2313,6 +2490,9 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + parseley@0.12.1: + resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -2321,10 +2501,6 @@ packages: resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -2343,6 +2519,9 @@ packages: pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + peberminta@0.9.0: + resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2447,6 +2626,9 @@ packages: process-warning@1.0.0: resolution: {integrity: sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==} + promise@1.3.0: + resolution: {integrity: sha512-R9WrbTF3EPkVtWjp7B7umQGVndpsi+rsDAfrR4xAALQpFLa/+2OriecLhawxzvii2gd9+DZFwROWDuUUaqS5yA==} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -2456,6 +2638,10 @@ packages: psl@1.9.0: resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -2473,6 +2659,10 @@ packages: quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + quoted-printable@1.0.1: + resolution: {integrity: sha512-cihC68OcGiQOjGiXuo5Jk6XHANTHl1K4JLk/xlEJRTIXfy19Sg6XzB95XonYgr+1rB88bCpr7WZE7D7AlZow4g==} + hasBin: true + radix3@1.1.2: resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} @@ -2500,6 +2690,9 @@ packages: read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + readable-stream@1.1.14: + resolution: {integrity: sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==} + readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} @@ -2564,11 +2757,6 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -2587,12 +2775,22 @@ packages: resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} engines: {node: '>=10'} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} scheduler@0.25.0-rc-02c0e824-20241028: resolution: {integrity: sha512-GysnKjmMSaWcwsKTLzeJO0IhU3EyIiC0ivJKE6yDNLqt3IMxDByx8b6lSNXRNdN+ULUY0WLLjSPaZ0LuU/GnTg==} + selderee@0.11.0: + resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==} + + semver@5.3.0: + resolution: {integrity: sha512-mfmm3/H9+67MCVix1h+IXTpDwL6710LyHuk7+cWC9T1mE0qz4iHhh6r4hU2wrIT9iTsAAC2XQRvfblL028cpLw==} + hasBin: true + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -2719,6 +2917,9 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} + string_decoder@0.10.31: + resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==} + string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} @@ -2793,9 +2994,6 @@ packages: resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==} engines: {node: '>=8'} - text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} engines: {node: '>=0.8'} @@ -2812,6 +3010,10 @@ packages: tinyexec@0.3.1: resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} + tlds@1.252.0: + resolution: {integrity: sha512-GA16+8HXvqtfEnw/DTcwB0UU354QE1n3+wh08oFjr6Znl7ZLAeUgYzCcK+/CCrOyE0vnHR8/pu3XXG3vDijXpQ==} + hasBin: true + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -2854,10 +3056,6 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - typed-array-buffer@1.0.2: resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} engines: {node: '>= 0.4'} @@ -2879,6 +3077,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + ufo@1.5.4: resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} @@ -2975,9 +3176,18 @@ packages: url-parse@1.5.10: resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + utf7@1.0.2: + resolution: {integrity: sha512-qQrPtYLLLl12NF4DrM9CvfkxkYI97xOb5dsnGZHE3teFr0tWiEZ9UdgMPczv24vl708cYMpe6mGXGHrotIp3Bw==} + + utf8@2.1.2: + resolution: {integrity: sha512-QXo+O/QkLP/x1nyi54uQiG0XrODxdysuQvE5dtVqv7F5K2Qb6FsN+qbr6KhF5wQ20tfcV3VQp0/2x1e1MRSPWg==} + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uuencode@0.0.4: + resolution: {integrity: sha512-yEEhCuCi5wRV7Z5ZVf9iV2gWMvUZqKJhAs1ecFdKJ0qzbyaVelmsE3QjYAamehfp9FKLiZbKldd+jklG3O0LfA==} + uuid@11.0.3: resolution: {integrity: sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==} hasBin: true @@ -3222,19 +3432,29 @@ snapshots: tslib: 2.8.0 optional: true - '@eslint-community/eslint-utils@4.4.1(eslint@8.57.1)': + '@eslint-community/eslint-utils@4.4.1(eslint@9.15.0(jiti@2.3.3))': dependencies: - eslint: 8.57.1 + eslint: 9.15.0(jiti@2.3.3) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} - '@eslint/eslintrc@2.1.4': + '@eslint/config-array@0.19.0': + dependencies: + '@eslint/object-schema': 2.1.4 + debug: 4.3.7 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/core@0.9.0': {} + + '@eslint/eslintrc@3.2.0': dependencies: ajv: 6.12.6 debug: 4.3.7 - espree: 9.6.1 - globals: 13.24.0 + espree: 10.3.0 + globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.0 js-yaml: 4.1.0 @@ -3243,7 +3463,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@8.57.1': {} + '@eslint/js@9.15.0': {} + + '@eslint/object-schema@2.1.4': {} + + '@eslint/plugin-kit@0.2.3': + dependencies: + levn: 0.4.1 '@ethersproject/abstract-provider@5.7.0': dependencies: @@ -3356,17 +3582,18 @@ snapshots: '@ethersproject/properties': 5.7.0 '@ethersproject/strings': 5.7.0 - '@humanwhocodes/config-array@0.13.0': + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': dependencies: - '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.7 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 '@humanwhocodes/module-importer@1.0.1': {} - '@humanwhocodes/object-schema@2.0.3': {} + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.1': {} '@img/sharp-darwin-arm64@0.33.5': optionalDependencies: @@ -3627,6 +3854,11 @@ snapshots: '@noble/curves': 1.3.0 '@noble/hashes': 1.3.3 + '@selderee/plugin-htmlparser2@0.11.0': + dependencies: + domhandler: 5.0.3 + selderee: 0.11.0 + '@stablelib/aead@1.0.1': {} '@stablelib/binary@1.0.1': @@ -3766,16 +3998,47 @@ snapshots: dependencies: '@types/node': 20.17.4 + '@types/estree@1.0.6': {} + + '@types/fs-extra@11.0.4': + dependencies: + '@types/jsonfile': 6.1.4 + '@types/node': 22.9.3 + + '@types/imap-simple@4.2.9': + dependencies: + '@types/imap': 0.8.42 + '@types/node': 22.9.3 + + '@types/imap@0.8.42': + dependencies: + '@types/node': 22.9.3 + + '@types/json-schema@7.0.15': {} + '@types/json5@0.0.29': {} + '@types/jsonfile@6.1.4': + dependencies: + '@types/node': 22.9.3 + + '@types/mailparser@3.4.5': + dependencies: + '@types/node': 22.9.3 + iconv-lite: 0.6.3 + '@types/node@20.17.4': dependencies: undici-types: 6.19.8 - '@types/node@22.9.0': + '@types/node@22.9.3': dependencies: undici-types: 6.19.8 + '@types/nodemailer@6.4.17': + dependencies: + '@types/node': 22.9.3 + '@types/prop-types@15.7.13': {} '@types/react-dom@18.3.1': @@ -3789,15 +4052,15 @@ snapshots: '@types/uuid@10.0.0': {} - '@typescript-eslint/eslint-plugin@8.12.2(@typescript-eslint/parser@8.12.2(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)': + '@typescript-eslint/eslint-plugin@8.12.2(@typescript-eslint/parser@8.12.2(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3))(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.12.2(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/parser': 8.12.2(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3) '@typescript-eslint/scope-manager': 8.12.2 - '@typescript-eslint/type-utils': 8.12.2(eslint@8.57.1)(typescript@5.6.3) - '@typescript-eslint/utils': 8.12.2(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/type-utils': 8.12.2(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3) + '@typescript-eslint/utils': 8.12.2(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3) '@typescript-eslint/visitor-keys': 8.12.2 - eslint: 8.57.1 + eslint: 9.15.0(jiti@2.3.3) graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 @@ -3807,14 +4070,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.12.2(eslint@8.57.1)(typescript@5.6.3)': + '@typescript-eslint/parser@8.12.2(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3)': dependencies: '@typescript-eslint/scope-manager': 8.12.2 '@typescript-eslint/types': 8.12.2 '@typescript-eslint/typescript-estree': 8.12.2(typescript@5.6.3) '@typescript-eslint/visitor-keys': 8.12.2 debug: 4.3.7 - eslint: 8.57.1 + eslint: 9.15.0(jiti@2.3.3) optionalDependencies: typescript: 5.6.3 transitivePeerDependencies: @@ -3825,10 +4088,10 @@ snapshots: '@typescript-eslint/types': 8.12.2 '@typescript-eslint/visitor-keys': 8.12.2 - '@typescript-eslint/type-utils@8.12.2(eslint@8.57.1)(typescript@5.6.3)': + '@typescript-eslint/type-utils@8.12.2(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3)': dependencies: '@typescript-eslint/typescript-estree': 8.12.2(typescript@5.6.3) - '@typescript-eslint/utils': 8.12.2(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/utils': 8.12.2(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3) debug: 4.3.7 ts-api-utils: 1.4.0(typescript@5.6.3) optionalDependencies: @@ -3854,13 +4117,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.12.2(eslint@8.57.1)(typescript@5.6.3)': + '@typescript-eslint/utils@8.12.2(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3)': dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0(jiti@2.3.3)) '@typescript-eslint/scope-manager': 8.12.2 '@typescript-eslint/types': 8.12.2 '@typescript-eslint/typescript-estree': 8.12.2(typescript@5.6.3) - eslint: 8.57.1 + eslint: 9.15.0(jiti@2.3.3) transitivePeerDependencies: - supports-color - typescript @@ -3870,8 +4133,6 @@ snapshots: '@typescript-eslint/types': 8.12.2 eslint-visitor-keys: 3.4.3 - '@ungap/structured-clone@1.2.0': {} - '@walletconnect/core@2.17.1': dependencies: '@walletconnect/heartbeat': 1.2.2 @@ -4414,6 +4675,8 @@ snapshots: cookie-es@1.2.2: {} + core-util-is@1.0.3: {} + cosmiconfig-typescript-loader@5.1.0(@types/node@20.17.4)(cosmiconfig@9.0.0(typescript@5.6.3))(typescript@5.6.3): dependencies: '@types/node': 20.17.4 @@ -4436,6 +4699,12 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + crossws@0.3.1: dependencies: uncrypto: 0.1.3 @@ -4478,6 +4747,8 @@ snapshots: deep-is@0.1.4: {} + deepmerge@4.3.1: {} + define-data-property@1.1.4: dependencies: es-define-property: 1.0.0 @@ -4511,9 +4782,23 @@ snapshots: dependencies: esutils: 2.0.3 - doctrine@3.0.0: + dom-serializer@2.0.0: dependencies: - esutils: 2.0.3 + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.1.0: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 dot-prop@5.3.0: dependencies: @@ -4558,6 +4843,10 @@ snapshots: emoji-regex@9.2.2: {} + encoding-japanese@2.0.0: {} + + encoding-japanese@2.1.0: {} + end-of-stream@1.4.4: dependencies: once: 1.4.0 @@ -4567,6 +4856,8 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.2.1 + entities@4.5.0: {} + env-paths@2.2.1: {} environment@1.1.0: {} @@ -4671,19 +4962,19 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-next@15.0.2(eslint@8.57.1)(typescript@5.6.3): + eslint-config-next@15.0.2(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3): dependencies: '@next/eslint-plugin-next': 15.0.2 '@rushstack/eslint-patch': 1.10.4 - '@typescript-eslint/eslint-plugin': 8.12.2(@typescript-eslint/parser@8.12.2(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3) - '@typescript-eslint/parser': 8.12.2(eslint@8.57.1)(typescript@5.6.3) - eslint: 8.57.1 + '@typescript-eslint/eslint-plugin': 8.12.2(@typescript-eslint/parser@8.12.2(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3))(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3) + '@typescript-eslint/parser': 8.12.2(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3) + eslint: 9.15.0(jiti@2.3.3) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.12.2(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.12.2(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) - eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) - eslint-plugin-react: 7.37.2(eslint@8.57.1) - eslint-plugin-react-hooks: 5.0.0(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.12.2(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.15.0(jiti@2.3.3)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.12.2(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.15.0(jiti@2.3.3)) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.15.0(jiti@2.3.3)) + eslint-plugin-react: 7.37.2(eslint@9.15.0(jiti@2.3.3)) + eslint-plugin-react-hooks: 5.0.0(eslint@9.15.0(jiti@2.3.3)) optionalDependencies: typescript: 5.6.3 transitivePeerDependencies: @@ -4699,37 +4990,37 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.12.2(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.12.2(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.15.0(jiti@2.3.3)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.3.7 enhanced-resolve: 5.17.1 - eslint: 8.57.1 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.12.2(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.12.2(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) + eslint: 9.15.0(jiti@2.3.3) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.12.2(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.12.2(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.15.0(jiti@2.3.3)))(eslint@9.15.0(jiti@2.3.3)) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.12.2(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.12.2(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.15.0(jiti@2.3.3)) transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.12.2(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.12.2(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.12.2(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.12.2(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.15.0(jiti@2.3.3)))(eslint@9.15.0(jiti@2.3.3)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.12.2(eslint@8.57.1)(typescript@5.6.3) - eslint: 8.57.1 + '@typescript-eslint/parser': 8.12.2(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3) + eslint: 9.15.0(jiti@2.3.3) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.12.2(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.12.2(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.15.0(jiti@2.3.3)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.12.2(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.12.2(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.15.0(jiti@2.3.3)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -4738,9 +5029,9 @@ snapshots: array.prototype.flatmap: 1.3.2 debug: 3.2.7 doctrine: 2.1.0 - eslint: 8.57.1 + eslint: 9.15.0(jiti@2.3.3) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.12.2(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.12.2(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.12.2(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.12.2(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.15.0(jiti@2.3.3)))(eslint@9.15.0(jiti@2.3.3)) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -4752,13 +5043,13 @@ snapshots: string.prototype.trimend: 1.0.8 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.12.2(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/parser': 8.12.2(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.1): + eslint-plugin-jsx-a11y@6.10.2(eslint@9.15.0(jiti@2.3.3)): dependencies: aria-query: 5.3.2 array-includes: 3.1.8 @@ -4768,7 +5059,7 @@ snapshots: axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - eslint: 8.57.1 + eslint: 9.15.0(jiti@2.3.3) hasown: 2.0.2 jsx-ast-utils: 3.3.5 language-tags: 1.0.9 @@ -4777,11 +5068,11 @@ snapshots: safe-regex-test: 1.0.3 string.prototype.includes: 2.0.1 - eslint-plugin-react-hooks@5.0.0(eslint@8.57.1): + eslint-plugin-react-hooks@5.0.0(eslint@9.15.0(jiti@2.3.3)): dependencies: - eslint: 8.57.1 + eslint: 9.15.0(jiti@2.3.3) - eslint-plugin-react@7.37.2(eslint@8.57.1): + eslint-plugin-react@7.37.2(eslint@9.15.0(jiti@2.3.3)): dependencies: array-includes: 3.1.8 array.prototype.findlast: 1.2.5 @@ -4789,7 +5080,7 @@ snapshots: array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 es-iterator-helpers: 1.1.0 - eslint: 8.57.1 + eslint: 9.15.0(jiti@2.3.3) estraverse: 5.3.0 hasown: 2.0.2 jsx-ast-utils: 3.3.5 @@ -4803,61 +5094,61 @@ snapshots: string.prototype.matchall: 4.0.11 string.prototype.repeat: 1.0.0 - eslint-scope@7.2.2: + eslint-scope@8.2.0: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 eslint-visitor-keys@3.4.3: {} - eslint@8.57.1: + eslint-visitor-keys@4.2.0: {} + + eslint@9.15.0(jiti@2.3.3): dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0(jiti@2.3.3)) '@eslint-community/regexpp': 4.12.1 - '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.57.1 - '@humanwhocodes/config-array': 0.13.0 + '@eslint/config-array': 0.19.0 + '@eslint/core': 0.9.0 + '@eslint/eslintrc': 3.2.0 + '@eslint/js': 9.15.0 + '@eslint/plugin-kit': 0.2.3 + '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.2.0 + '@humanwhocodes/retry': 0.4.1 + '@types/estree': 1.0.6 + '@types/json-schema': 7.0.15 ajv: 6.12.6 chalk: 4.1.2 - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 debug: 4.3.7 - doctrine: 3.0.0 escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 + eslint-scope: 8.2.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 + file-entry-cache: 8.0.0 find-up: 5.0.0 glob-parent: 6.0.2 - globals: 13.24.0 - graphemer: 1.4.0 ignore: 5.3.2 imurmurhash: 0.1.4 is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-yaml: 4.1.0 json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 lodash.merge: 4.6.2 minimatch: 3.1.2 natural-compare: 1.4.0 optionator: 0.9.4 - strip-ansi: 6.0.1 - text-table: 0.2.0 + optionalDependencies: + jiti: 2.3.3 transitivePeerDependencies: - supports-color - espree@9.6.1: + espree@10.3.0: dependencies: acorn: 8.14.0 acorn-jsx: 5.3.2(acorn@8.14.0) - eslint-visitor-keys: 3.4.3 + eslint-visitor-keys: 4.2.0 esprima@4.0.1: {} @@ -4924,9 +5215,9 @@ snapshots: set-cookie-parser: 2.7.1 tough-cookie: 4.1.4 - file-entry-cache@6.0.1: + file-entry-cache@8.0.0: dependencies: - flat-cache: 3.2.0 + flat-cache: 4.0.1 fill-range@7.1.1: dependencies: @@ -4945,11 +5236,10 @@ snapshots: path-exists: 5.0.0 unicorn-magic: 0.1.0 - flat-cache@3.2.0: + flat-cache@4.0.1: dependencies: flatted: 3.3.1 keyv: 4.5.4 - rimraf: 3.0.2 flatted@3.3.1: {} @@ -4978,7 +5268,11 @@ snapshots: jsonfile: 6.1.0 universalify: 2.0.1 - fs.realpath@1.0.0: {} + fs-extra@11.2.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 fsevents@2.3.2: optional: true @@ -5050,22 +5344,11 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 - glob@7.2.3: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - global-directory@4.0.1: dependencies: ini: 4.1.1 - globals@13.24.0: - dependencies: - type-fest: 0.20.2 + globals@14.0.0: {} globalthis@1.0.4: dependencies: @@ -5118,22 +5401,61 @@ snapshots: dependencies: function-bind: 1.1.2 + he@1.2.0: {} + hmac-drbg@1.0.1: dependencies: hash.js: 1.1.7 minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 + html-to-text@9.0.5: + dependencies: + '@selderee/plugin-htmlparser2': 0.11.0 + deepmerge: 4.3.1 + dom-serializer: 2.0.0 + htmlparser2: 8.0.2 + selderee: 0.11.0 + + htmlparser2@8.0.2: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.1.0 + entities: 4.5.0 + http-shutdown@1.2.2: {} human-signals@5.0.0: {} husky@9.1.6: {} + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + idb-keyval@6.2.1: {} ignore@5.3.2: {} + imap-simple@5.1.0: + dependencies: + iconv-lite: 0.4.24 + imap: 0.8.19 + nodeify: 1.0.1 + quoted-printable: 1.0.1 + utf8: 2.1.2 + uuencode: 0.0.4 + + imap@0.8.19: + dependencies: + readable-stream: 1.1.14 + utf7: 1.0.2 + import-fresh@3.3.0: dependencies: parent-module: 1.0.1 @@ -5143,11 +5465,6 @@ snapshots: imurmurhash@0.1.4: {} - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - inherits@2.0.4: {} ini@4.1.1: {} @@ -5245,7 +5562,7 @@ snapshots: is-obj@2.0.0: {} - is-path-inside@3.0.3: {} + is-promise@1.0.1: {} is-promise@4.0.0: {} @@ -5297,6 +5614,8 @@ snapshots: dependencies: system-architecture: 0.1.0 + isarray@0.0.1: {} + isarray@2.0.5: {} isexe@2.0.0: {} @@ -5379,17 +5698,45 @@ snapshots: dependencies: language-subtag-registry: 0.3.23 + leac@0.6.0: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 + libbase64@1.2.1: {} + + libbase64@1.3.0: {} + + libmime@5.2.0: + dependencies: + encoding-japanese: 2.0.0 + iconv-lite: 0.6.3 + libbase64: 1.2.1 + libqp: 2.0.1 + + libmime@5.3.5: + dependencies: + encoding-japanese: 2.1.0 + iconv-lite: 0.6.3 + libbase64: 1.3.0 + libqp: 2.1.0 + + libqp@2.0.1: {} + + libqp@2.1.0: {} + lilconfig@2.1.0: {} lilconfig@3.1.2: {} lines-and-columns@1.2.4: {} + linkify-it@5.0.0: + dependencies: + uc.micro: 2.1.0 + lint-staged@15.2.10: dependencies: chalk: 5.3.0 @@ -5481,6 +5828,25 @@ snapshots: lru-cache@10.4.3: {} + mailparser@3.7.1: + dependencies: + encoding-japanese: 2.1.0 + he: 1.2.0 + html-to-text: 9.0.5 + iconv-lite: 0.6.3 + libmime: 5.3.5 + linkify-it: 5.0.0 + mailsplit: 5.4.0 + nodemailer: 6.9.13 + punycode.js: 2.3.1 + tlds: 1.252.0 + + mailsplit@5.4.0: + dependencies: + libbase64: 1.2.1 + libmime: 5.2.0 + libqp: 2.0.1 + meow@12.1.1: {} merge-stream@2.0.0: {} @@ -5581,6 +5947,15 @@ snapshots: node-releases@2.0.18: {} + nodeify@1.0.1: + dependencies: + is-promise: 1.0.1 + promise: 1.3.0 + + nodemailer@6.9.13: {} + + nodemailer@6.9.16: {} + normalize-path@3.0.0: {} normalize-range@0.1.2: {} @@ -5691,12 +6066,15 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parseley@0.12.1: + dependencies: + leac: 0.6.0 + peberminta: 0.9.0 + path-exists@4.0.0: {} path-exists@5.0.0: {} - path-is-absolute@1.0.1: {} - path-key@3.1.1: {} path-key@4.0.0: {} @@ -5710,6 +6088,8 @@ snapshots: pathe@1.1.2: {} + peberminta@0.9.0: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -5806,6 +6186,10 @@ snapshots: process-warning@1.0.0: {} + promise@1.3.0: + dependencies: + is-promise: 1.0.1 + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -5816,6 +6200,8 @@ snapshots: psl@1.9.0: {} + punycode.js@2.3.1: {} + punycode@2.3.1: {} query-string@7.1.3: @@ -5831,6 +6217,10 @@ snapshots: quick-format-unescaped@4.0.4: {} + quoted-printable@1.0.1: + dependencies: + utf8: 2.1.2 + radix3@1.1.2: {} react-dom@18.3.1(react@18.3.1): @@ -5856,6 +6246,13 @@ snapshots: dependencies: pify: 2.3.0 + readable-stream@1.1.14: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 0.0.1 + string_decoder: 0.10.31 + readable-stream@3.6.2: dependencies: inherits: 2.0.4 @@ -5922,10 +6319,6 @@ snapshots: rfdc@1.4.1: {} - rimraf@3.0.2: - dependencies: - glob: 7.2.3 - run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -5947,12 +6340,20 @@ snapshots: safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} + scheduler@0.23.2: dependencies: loose-envify: 1.4.0 scheduler@0.25.0-rc-02c0e824-20241028: {} + selderee@0.11.0: + dependencies: + parseley: 0.12.1 + + semver@5.3.0: {} + semver@6.3.1: {} semver@7.6.3: {} @@ -6166,6 +6567,8 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.0.0 + string_decoder@0.10.31: {} + string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 @@ -6246,8 +6649,6 @@ snapshots: text-extensions@2.4.0: {} - text-table@0.2.0: {} - thenify-all@1.6.0: dependencies: thenify: 3.3.1 @@ -6264,6 +6665,8 @@ snapshots: tinyexec@0.3.1: {} + tlds@1.252.0: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -6305,8 +6708,6 @@ snapshots: dependencies: prelude-ls: 1.2.1 - type-fest@0.20.2: {} - typed-array-buffer@1.0.2: dependencies: call-bind: 1.0.7 @@ -6341,6 +6742,8 @@ snapshots: typescript@5.6.3: {} + uc.micro@2.1.0: {} + ufo@1.5.4: {} uint8arrays@3.1.0: @@ -6412,8 +6815,16 @@ snapshots: querystringify: 2.2.0 requires-port: 1.0.0 + utf7@1.0.2: + dependencies: + semver: 5.3.0 + + utf8@2.1.2: {} + util-deprecate@1.0.2: {} + uuencode@0.0.4: {} + uuid@11.0.3: {} viem@2.21.37(typescript@5.6.3)(zod@3.23.8):