From fdf09c6a2e987e3e72647843f7382c7db2ce8ef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michalina=20Ciencia=C5=82a?= Date: Tue, 13 Jun 2023 10:01:57 +0200 Subject: [PATCH 01/29] Create E2E tests for verified/unverified tokens This commit introduces E2E test that checks the functiolality of verified/unverified tokens. Following general steps are executed: 1. Import account 2. Enable `Show unverified assets` 3. Hide asset 4. Trust asset 5. Hide trusted asset A number of checks is performed during each step. The commit also introduces helper functions and adds `data-testid` attribute to a couple of DOM elements. As some of the code is similar or identical as in the https://github.com/tahowallet/extension/pull/3418 PR which is yet not merged to `main`, some conflicts may arise and will need to be resolved before this change lands on `main`. Also some changes will need to be made once https://github.com/tahowallet/extension/pull/3195 gets merged to `main`, as this PR solves a bug causing failures in the tests (the failing part of the test was temporarily commented out). --- e2e-tests/token-trust.spec.ts | 558 ++++++++++++++++++++ e2e-tests/utils.ts | 6 + e2e-tests/utils/assets.ts | 459 ++++++++++++++++ e2e-tests/utils/walletPageHelper.ts | 104 +++- ui/_locales/en/messages.json | 4 +- ui/components/Shared/SharedAssetInput.tsx | 9 +- ui/components/Shared/SharedAssetItem.tsx | 6 +- ui/components/Shared/SharedToggleButton.tsx | 1 + ui/components/Wallet/WalletHiddenAssets.tsx | 1 + 9 files changed, 1142 insertions(+), 6 deletions(-) create mode 100644 e2e-tests/token-trust.spec.ts create mode 100644 e2e-tests/utils/assets.ts diff --git a/e2e-tests/token-trust.spec.ts b/e2e-tests/token-trust.spec.ts new file mode 100644 index 0000000000..58872e5309 --- /dev/null +++ b/e2e-tests/token-trust.spec.ts @@ -0,0 +1,558 @@ +import { FeatureFlags } from "@tallyho/tally-background/features" +import { skipIfFeatureFlagged, test, expect } from "./utils" + +skipIfFeatureFlagged(FeatureFlags.SUPPORT_UNVERIFIED_ASSET) + +// This test verifies functionalites of verified/unverified tokens using a +// publicly known Mainnet wallet 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266. +// The wallet at the moment of writing the test owns several trusted and +// untrusted tokens. It may happen that those assets will be moved and we will +// need to update the test to reflect new state. In the future, if the bug +// https://github.com/tahowallet/extension/issues/3437 gets fixed, we may want +// to switch to testing on a forked Mainnet (at fixed block), which would +// increase stability of the test. +test.describe("Token Trust", () => { + test("User can mark tokens as trusted/untrusted", async ({ + walletPageHelper, + page: popup, + assetsHelper, + }) => { + await test.step("Import account", async () => { + await walletPageHelper.onboarding.addAccountFromSeed({ + phrase: "test test test test test test test test test test test junk", + }) + + await walletPageHelper.goToStartPage() + await walletPageHelper.setViewportSize() + + /** + * Verify we're on Ethereum network. Verify common elements on the main page. + */ + await walletPageHelper.verifyCommonElements( + /^Ethereum$/, + /^(Phoenix|Matilda|Sirius|Topa|Atos|Sport|Lola|Foz)$/ + ) + await walletPageHelper.verifyAnalyticsBanner() + + setTimeout(() => {}, 500000) // wait for 5s + + /** + * Verify that `Show unverified assets` is OFF by default. + */ + await popup + .locator(".tab_bar_wrap") + .getByText("Settings", { exact: true }) + .click() + await assetsHelper.assertShowUnverifiedAssetsSetting(false) + await popup + .locator(".tab_bar_wrap") + .getByText("Wallet", { exact: true }) + .click() + + // Commented out until https://github.com/tahowallet/extension/pull/3195 + // gets fixed. + // /** + // * Ensure the base asset is visible and is not unverified. + // */ + // await assetsHelper.assertVerifiedAssetOnWalletPage(/^ETH$/, "base") + + // /** + // * Ensure there are no fields related to unverified assets in the + // * base asset's details. + // */ + // await popup.locator(".asset_list_item").first().click() // We use `.first()` because the base asset should be first on the list + // await assetsHelper.assertAssetDetailsPage( + // /^Ethereum$/, + // /^(Phoenix|Matilda|Sirius|Topa|Atos|Sport|Lola|Foz)$/, + // /^ETH$/, + // /^(\d|,)+(\.\d{2,4})*$/, + // "base" + // ) + // await popup.getByRole("button", { name: "Back", exact: true }).click() + + /** + * Ensure the ERC-20 asset is visible and is not unverified. + */ + await popup + .locator(".asset_list_item") + .filter({ + has: popup.locator("span").filter({ hasText: /^DAI$/ }), + }) + .click({ trial: true }) + await assetsHelper.assertVerifiedAssetOnWalletPage(/^DAI$/, "knownERC20") + + /** + * Ensure there are no fields related to unverified assets in the + * ERC-20 asset's details. + */ + await popup + .locator(".asset_list_item") + .filter({ + has: popup.locator("span").filter({ hasText: /^DAI$/ }), + }) + .click() + await assetsHelper.assertAssetDetailsPage( + /^Ethereum$/, + /^(Phoenix|Matilda|Sirius|Topa|Atos|Sport|Lola|Foz)$/, + /^DAI$/, + /^(\d|,)+(\.\d{2,4})*$/, + "knownERC20", + "https://etherscan.io/token/0x6b175474e89094c44da98b954eedeac495271d0f" + ) + await popup.getByRole("button", { name: "Back", exact: true }).click() + + /** + * Ensure there are no unverified assets on the main page. + */ + await assetsHelper.assertNoUnverifiedAssetsOnWalletPage() + + // In order for the next few tests to make sense the wallet address should + // have a positive balance of at least one of the listed assets. At the + // moment of writing the test, the wallet had positive balance for all those + // assets. + const untrustedAssets = [ + "DANK", + "FOOL", + "JIZZ", + "M87", + "PHIBA", + "WLUNC", + "WTF", + ] + + /** + * Verify there are no unverified assets on the Send screen. + */ + await popup + .getByRole("button", { name: "Send", exact: true }) + .first() // TODO: Investigate why we need it + .click() + await popup.getByTestId("selected_asset_button").click() + await assetsHelper.assertAssetsNotPresentOnAssetsList(untrustedAssets) + await assetsHelper.closeSelectTokenPopup() + await popup.getByRole("button", { name: "Back", exact: true }).click() + + /** + * Verify there are no unverified assets on the Swap screen. + */ + await popup.getByRole("button", { name: "Swap", exact: true }).click() + await popup + .getByRole("button", { name: "Select token", exact: true }) + .first() + .click() + await assetsHelper.assertAssetsNotPresentOnAssetsList(untrustedAssets) + await assetsHelper.closeSelectTokenPopup() + await popup + .getByRole("button", { name: "Select token", exact: true }) + .nth(1) + .click() + await assetsHelper.assertAssetsNotPresentOnAssetsList(untrustedAssets) + await assetsHelper.closeSelectTokenPopup() + }) + + await test.step("Enable `Show unverified assets`", async () => { + /** + * Toggle `Show unverified assets` and make sure it's ON + */ + await popup + .locator(".tab_bar_wrap") + .getByText("Settings", { exact: true }) + .click() + await assetsHelper.toggleShowUnverifaiedAssetsSetting() + await assetsHelper.assertShowUnverifiedAssetsSetting(true) + await popup + .locator(".tab_bar_wrap") + .getByText("Wallet", { exact: true }) + .click() + + // Commented out until https://github.com/tahowallet/extension/pull/3195 + // gets fixed. + // /** + // * Ensure the base asset is visible and is not unverified. + // */ + // await assetsHelper.assertVerifiedAssetOnWalletPage(/^ETH$/, "base") + + // /** + // * Ensure there are no fields related to unverified assets in the + // * base asset's details. + // */ + // await popup.locator(".asset_list_item").first().click() // We use `.first()` because the base asset should be first on the list + // await assetsHelper.assertAssetDetailsPage( + // /^Ethereum$/, + // /^(Phoenix|Matilda|Sirius|Topa|Atos|Sport|Lola|Foz)$/, + // /^ETH$/, + // /^(\d|,)+(\.\d{2,4)*$/, + // "base" + // ) + // await popup.getByRole("button", { name: "Back", exact: true }).click() + + /** + * Ensure the verified ERC-20 asset is visible and is not unverified. + */ + await popup + .locator(".asset_list_item") + .filter({ + has: popup.locator("span").filter({ hasText: /^DAI$/ }), + }) + .click({ trial: true }) + await assetsHelper.assertVerifiedAssetOnWalletPage(/^DAI$/, "knownERC20") + + /** + * Ensure there are no fields related to unverified assets in the verified + * ERC-20 asset's details. + */ + await popup + .locator(".asset_list_item") + .filter({ + has: popup.locator("span").filter({ hasText: /^DAI$/ }), + }) + .click() + await assetsHelper.assertAssetDetailsPage( + /^Ethereum$/, + /^(Phoenix|Matilda|Sirius|Topa|Atos|Sport|Lola|Foz)$/, + /^DAI$/, + /^(\d|,)+(\.\d{2,4})*$/, + "knownERC20", + "https://etherscan.io/token/0x6b175474e89094c44da98b954eedeac495271d0f" + ) + await popup.getByRole("button", { name: "Back", exact: true }).click() + + /** + * Ensure there are unverified assets on the main page. + */ + await assetsHelper.assertUnverifiedAssetsPresentOnWalletPage() + + /** + * Ensure there are fields related to unverified assets in the unverified + * ERC-20 asset's details. + */ + await popup + .getByRole("button", { name: /^See unverified assets \(\d+\)$/ }) + .click() + await popup + .locator(".asset_list_item") + .filter({ + has: popup.locator("span").filter({ hasText: /^DANK$/ }), + }) + .click() + await assetsHelper.assertAssetDetailsPage( + /^Ethereum$/, + /^(Phoenix|Matilda|Sirius|Topa|Atos|Sport|Lola|Foz)$/, + /^DANK$/, + /^(\d|,)+(\.\d{2,4})*$/, + "unverified", + "https://etherscan.io/token/0x0cb8d0b37c7487b11d57f1f33defa2b1d3cfccfe", + "0x0cb8…fccfe" + ) + await popup.getByRole("button", { name: "Back", exact: true }).click() + + // In order for the next few tests to make sense the wallet address should + // have a positive balance of at least one of the listed assets. At the + // moment of writing the test, the wallet had positive balance for all those + // assets. + const untrustedAssets = [ + "DANK", + "FOOL", + "JIZZ", + "M87", + "PHIBA", + "WLUNC", + "WTF", + ] + + /** + * Verify there are no unverified assets on the Send screen. + */ + await popup.getByRole("button", { name: "Send", exact: true }).click() + await popup.getByTestId("selected_asset_button").click() + await assetsHelper.assertAssetsNotPresentOnAssetsList(untrustedAssets) + await assetsHelper.closeSelectTokenPopup() + await popup.getByRole("button", { name: "Back", exact: true }).click() + + /** + * Verify there are no unverified assets on the Swap screen. + */ + await popup.getByRole("button", { name: "Swap", exact: true }).click() + await popup + .getByRole("button", { name: "Select token", exact: true }) + .first() + .click() + await assetsHelper.assertAssetsNotPresentOnAssetsList(untrustedAssets) + await assetsHelper.closeSelectTokenPopup() + await popup + .getByRole("button", { name: "Select token", exact: true }) + .nth(1) + .click() + await assetsHelper.assertAssetsNotPresentOnAssetsList(untrustedAssets) + await assetsHelper.closeSelectTokenPopup() + }) + + await test.step("Hide asset", async () => { + /** + * Click `Don't show` on unverified ERC-20 asset + */ + await popup + .locator(".tab_bar_wrap") + .getByText("Wallet", { exact: true }) + .click() + + await popup + .getByRole("button", { name: /^See unverified assets \(\d+\)$/ }) + .click() + await popup + .locator(".asset_list_item") + .filter({ + has: popup.locator("span").filter({ hasText: /^DANK$/ }), + }) + .click() + await popup.getByRole("button", { name: "Verify asset" }).first().click() + await popup + .getByRole("button", { name: "Don’t show", exact: true }) + .click() + + /** + * Confirm there is `Asset removed from list` snackbar visible. + */ + // Sometimes the snackbar is not displayed, the DOM looks like this: + // + // The below check does not fail in such case. + // TODO: Check if this can be improved. Or if bug should be raised. + // TODO: Sometimes below assertion fails because element is not present in + // DOM. Raise a bug? + // await expect( + // popup.getByText("Asset removed from list").first() + // ).toBeVisible({ timeout: 5000 }) + + /** + * Make sure `Wallet` page is opened and there are unverified assets shown + */ + await walletPageHelper.verifyCommonElements( + /^Ethereum$/, + /^(Phoenix|Matilda|Sirius|Topa|Atos|Sport|Lola|Foz)$/ + ) + await walletPageHelper.verifyAnalyticsBanner() + // Commented out until https://github.com/tahowallet/extension/pull/3195 + // gets fixed. + // await assetsHelper.assertVerifiedAssetOnWalletPage(/^ETH$/, "base"") + await assetsHelper.assertUnverifiedAssetsPresentOnWalletPage() + + /** + * Make sure the recelntly hidden asset "DANK" is no longer shown on the + * `Wallet` page. + */ + await expect( + popup.locator(".asset_list_item").filter({ + has: popup.locator("span").filter({ hasText: /^DANK$/ }), + }) + ).not.toBeVisible() + + /** + * Verify there is no "DANK" asset on the Send screen. + */ + await popup.getByRole("button", { name: "Send", exact: true }).click() + await popup.getByTestId("selected_asset_button").click() + await assetsHelper.assertAssetsNotPresentOnAssetsList(["DANK"]) + await assetsHelper.closeSelectTokenPopup() + await popup.getByRole("button", { name: "Back", exact: true }).click() + + /** + * Verify there is no "DANK" asset on the Swap screen. + */ + await popup.getByRole("button", { name: "Swap", exact: true }).click() + await popup + .getByRole("button", { name: "Select token", exact: true }) + .first() + .click() + await assetsHelper.assertAssetsNotPresentOnAssetsList(["DANK"]) + await assetsHelper.closeSelectTokenPopup() + await popup + .getByRole("button", { name: "Select token", exact: true }) + .nth(1) + .click() + await assetsHelper.assertAssetsNotPresentOnAssetsList(["DANK"]) + await assetsHelper.closeSelectTokenPopup() + }) + + await test.step("Trust asset", async () => { + /** + * Click `Add to asset list` on unverified ERC-20 asset + */ + await popup + .locator(".tab_bar_wrap") + .getByText("Wallet", { exact: true }) + .click() + + await popup + .getByRole("button", { name: /^See unverified assets \(\d+\)$/ }) + .click() + await popup + .locator(".asset_list_item") + .filter({ + has: popup.locator("span").filter({ hasText: /^WLUNC$/ }), + }) + .click() + await popup.getByRole("button", { name: "Verify asset" }).first().click() + await popup + .getByRole("button", { name: "Add to asset list", exact: true }) + .click() + + /** + * Confirm there is `Asset added to list` snackbar visible. + */ + // TODO: Sometimes below assertion fails because element is not present in + // DOM. Raise a bug? + // await expect(popup.getByText("Asset added to list").first()).toBeVisible({ + // timeout: 5000, + // }) + + /** + * Confirm asset's details are opened. Ensure there are fields related to + * trusted assets in the trusted ERC-20 asset's details. + */ + await assetsHelper.assertAssetDetailsPage( + /^Ethereum$/, + /^(Phoenix|Matilda|Sirius|Topa|Atos|Sport|Lola|Foz)$/, + /^WLUNC$/, + /^(\d|,)+(\.\d{2,4})*$/, + "trusted", + "https://etherscan.io/token/0xd2877702675e6ceb975b4a1dff9fb7baf4c91ea9", + "0xd287…91ea9" + ) + await popup.getByRole("button", { name: "Back", exact: true }).click() + + /** + * Go to `Wallet` page and make sure the recently trusted asset is visible + * among verified assets. + */ + await popup + .locator(".tab_bar_wrap") + .getByText("Wallet", { exact: true }) + .click() + await assetsHelper.assertVerifiedAssetOnWalletPage(/^WLUNC$/, "trusted") + + /** + * Verify recently trusted asset is available on the Send screen. + */ + await popup.getByRole("button", { name: "Send", exact: true }).click() + await popup.getByTestId("selected_asset_button").click() + await assetsHelper.assertAssetsPresentOnAssetsList(["WLUNC"]) + await assetsHelper.closeSelectTokenPopup() + await popup.getByRole("button", { name: "Back", exact: true }).click() + + /** + * Verify recently trusted asset is available on the Swap screen. + */ + await popup.getByRole("button", { name: "Swap", exact: true }).click() + await popup + .getByRole("button", { name: "Select token", exact: true }) + .first() + .click() + await assetsHelper.assertAssetsPresentOnAssetsList(["WLUNC"]) + await assetsHelper.closeSelectTokenPopup() + await popup + .getByRole("button", { name: "Select token", exact: true }) + .nth(1) + .click() + await assetsHelper.assertAssetsPresentOnAssetsList(["WLUNC"]) + await assetsHelper.closeSelectTokenPopup() + }) + + await test.step("Hide trusted asset", async () => { + /** + * Click `Don't show` on trusted ERC-20 asset + */ + await popup + .locator(".tab_bar_wrap") + .getByText("Wallet", { exact: true }) + .click() + + await popup + .locator(".asset_list_item") + .filter({ + has: popup.locator("span").filter({ hasText: /^WLUNC$/ }), + }) + .click() + await popup.getByRole("button", { name: "Verified by you" }).click() + await popup + .getByRole("button", { name: "Don’t show", exact: true }) + .click() + + /** + * Confirm there is `Asset removed from list` snackbar visible. + */ + // Sometimes the snackbar is not displayed, the DOM looks like this: + // + // The below check does not fail in such case. + // TODO: Check if this can be improved. Or if bug should be raised. + // TODO: Sometimes below assertion fails because element is not present in + // DOM. Raise a bug? + // await expect( + // popup.getByText("Asset removed from list").first() + // ).toBeVisible({ timeout: 5000 }) + + /** + * Make sure `Wallet` page is opened and there are unverified assets shown + */ + await walletPageHelper.verifyCommonElements( + /^Ethereum$/, + /^(Phoenix|Matilda|Sirius|Topa|Atos|Sport|Lola|Foz)$/ + ) + await walletPageHelper.verifyAnalyticsBanner() + // Commented out until https://github.com/tahowallet/extension/pull/3195 + // gets fixed. + // await assetsHelper.assertVerifiedAssetOnWalletPage(/^ETH$/, "base"") + await assetsHelper.assertUnverifiedAssetsPresentOnWalletPage() + + /** + * Make sure the recelntly hidden asset "WLUNC" is no longer shown on the + * `Wallet` page. + */ + await expect( + popup.locator(".asset_list_item").filter({ + has: popup.locator("span").filter({ hasText: /^WLUNC$/ }), + }) + ).not.toBeVisible() + + /** + * Verify there is no "WLUNC" asset on the Send screen. + */ + await popup.getByRole("button", { name: "Send", exact: true }).click() + await popup.getByTestId("selected_asset_button").click() + await assetsHelper.assertAssetsNotPresentOnAssetsList(["WLUNC"]) + await assetsHelper.closeSelectTokenPopup() + await popup.getByRole("button", { name: "Back", exact: true }).click() + + /** + * Verify there is no "WLUNC" asset on the Swap screen. + */ + await popup.getByRole("button", { name: "Swap", exact: true }).click() + await popup + .getByRole("button", { name: "Select token", exact: true }) + .first() + .click() + await assetsHelper.assertAssetsNotPresentOnAssetsList(["WLUNC"]) + await assetsHelper.closeSelectTokenPopup() + await popup + .getByRole("button", { name: "Select token", exact: true }) + .nth(1) + .click() + await assetsHelper.assertAssetsNotPresentOnAssetsList(["WLUNC"]) + await assetsHelper.closeSelectTokenPopup() + }) + }) + + // Other tests that can be added in the future: + // - Hide or trust all unverified assets (make sure there's no `See + // unverified assets` section) + // - Add a custom asset and verify how it's displayed when it comes to + // verified/unverified aspects (can be done in a separate tests file for + // custom assets tests) + // - Verify `Show unverified assets` setting applies to all wallets (may + // be done in a separate tests file for Settings tests) + // - Verify that `Asset not verified` banner can be dismissed and that this + // gets remembered. +}) diff --git a/e2e-tests/utils.ts b/e2e-tests/utils.ts index 430284fd2a..31e644e802 100644 --- a/e2e-tests/utils.ts +++ b/e2e-tests/utils.ts @@ -3,6 +3,7 @@ import { test as base, chromium, Page } from "@playwright/test" import { FeatureFlagType, isEnabled } from "@tallyho/tally-background/features" import path from "path" import WalletPageHelper from "./utils/walletPageHelper" +import AssetsHelper from "./utils/assets" // Re-exporting so we don't mix imports export { expect } from "@playwright/test" @@ -10,6 +11,7 @@ export { expect } from "@playwright/test" type WalletTestFixtures = { extensionId: string walletPageHelper: WalletPageHelper + assetsHelper: AssetsHelper backgroundPage: Page } @@ -56,6 +58,10 @@ export const test = base.extend({ const helper = new WalletPageHelper(page, context, extensionId) await use(helper) }, + assetsHelper: async ({ page, walletPageHelper }, use) => { + const helper = new AssetsHelper(page, walletPageHelper) + await use(helper) + }, }) export const skipIfFeatureFlagged = (featureFlag: FeatureFlagType): void => diff --git a/e2e-tests/utils/assets.ts b/e2e-tests/utils/assets.ts new file mode 100644 index 0000000000..2bddcc9014 --- /dev/null +++ b/e2e-tests/utils/assets.ts @@ -0,0 +1,459 @@ +import { Page, expect } from "@playwright/test" +import WalletPageHelper from "./walletPageHelper" + +export default class AssetsHelper { + constructor( + public readonly popup: Page, + public readonly walletPageHelper: WalletPageHelper + ) {} + + /** + * This function verifies that the provided asset is present (an in case of + * base assets, displayed as first) on the list of assets on the Wallet page. + * It verifies that the asset has a balance (and a USD value, when aplicable) + * displayed and there are options to Send and Swap. It also makes sure the + * asset is not displayed in the unverified section. + */ + async assertVerifiedAssetOnWalletPage( + assetSymbol: RegExp, + assetType: "base" | "knownERC20" | "trusted" + ): Promise { + let asset: ReturnType | undefined + if (assetType === "base") { + asset = this.popup.locator(".asset_list_item").first() // We use `.first()` because the base asset should be first on the list + } else { + asset = this.popup.locator(".asset_list_item").filter({ + has: this.popup.locator("span").filter({ hasText: assetSymbol }), + }) + } + await this.popup.waitForTimeout(1000) // We need to wait for the background elements to disappear + await expect(asset.getByText(assetSymbol)).toBeVisible() + /** + * Make sure the asset is not listed among unverified assets. + */ + await expect( + this.popup + .getByTestId("hidden_assets_container") + .filter({ has: asset.getByText(assetSymbol) }) + ).not.toBeVisible() + + await expect(asset.getByText(/^(\d|,)+(\.\d{2,4})*$/)).toBeVisible() + if (assetType === "base" || assetType === "knownERC20") { + await expect(asset.getByText(/^\$(0|\d+\.\d{2})$/)).toBeVisible() + } + await asset.locator(".asset_icon_send").click({ trial: true }) + await asset.locator(".asset_icon_swap").click({ trial: true }) + } + + // This is a more robust version of + // `transactionsHelper.verifyAssetActivityScreen()`. After merge of PR #3418 + // let's merge both functions into one. + /** + * This function verifies elements on the asset details page. + */ + async assertAssetDetailsPage( + network: RegExp, + accountLabel: RegExp, + assetSymbol: RegExp, + expectedBalance: RegExp, + assetType: "base" | "knownERC20" | "unverified" | "trusted", + tokenLink?: string, // needed only when `assetType` is not `base` + tokenAddressShortened?: string // needed only when `assetType` is `unverified` or `trusted` + ): Promise { + /** + * Assert the top wrap. + */ + await this.walletPageHelper.verifyTopWrap(network, accountLabel) + + /** + * Assert the `Back` button. + */ + await this.popup + .getByRole("button", { name: "Back", exact: true }) + .click({ trial: true }) + + /** + * Assert the token name and make sure the balance equals (or gets updated + * to) the correct value. + */ + const activityLeftContainer = this.popup.locator(".left").filter({ + has: this.popup.locator("span").filter({ hasText: assetSymbol }), + }) + await expect(async () => { + const balance = await activityLeftContainer + .getByText(/^(\d|,)+(\.\d{2,4})*$/) + .textContent() + expect(balance).toMatch(expectedBalance) + }).toPass({ + timeout: 120000, + }) + + if (assetType === "base" || assetType === "knownERC20") { + await expect( + activityLeftContainer.getByText(/^\$(\d|,)+\.\d{2}$/) + ).toBeVisible() + } else { + await expect( + activityLeftContainer.getByText(/^\$(\d|,)+\.\d{2}$/) + ).not.toBeVisible() + } + + /** + * Assert the token link + */ + const tokenLinkIcon = activityLeftContainer + .getByRole("link") + .filter({ has: this.popup.locator(".icon_new_tab") }) + if (assetType !== "base") { + if (tokenLink !== undefined) { + await expect(tokenLinkIcon).toHaveAttribute("href", tokenLink) + } else { + throw new Error("`tokenLink` not defined.") + } + await tokenLinkIcon.click({ trial: true }) + } else { + await expect(tokenLinkIcon).not.toBeVisible() + } + + /** + * Assert elements related to asset verification. + */ + if (assetType === "unverified") { + await this.popup + .getByRole("button", { name: "Asset not verified" }) + .click() + await this.assertVerifyAssetPopup( + assetSymbol, + assetType, + tokenAddressShortened + ) + this.closeVerifyAssetPopup() + await this.popup.getByRole("button", { name: "Verify asset" }).click() + await this.assertVerifyAssetPopup( + assetSymbol, + assetType, + tokenAddressShortened + ) + this.closeVerifyAssetPopup() + } else { + await expect(this.popup.getByText("Asset not verified")).not.toBeVisible() + await expect(this.popup.getByText("Verify asset")).not.toBeVisible() + } + + if (assetType === "trusted") { + await this.popup.getByRole("button", { name: "Verified by you" }).click() + await this.assertVerifyAssetPopup( + assetSymbol, + assetType, + tokenAddressShortened + ) + this.closeVerifyAssetPopup() + } else { + await expect( + this.popup.getByRole("button", { name: "Verified by you" }) + ).not.toBeVisible() + } + + /** + * Assert the Send and Swap actions. + */ + if (assetType !== "unverified") { + await this.popup + .getByRole("button", { name: "Send", exact: true }) + .click({ trial: true }) + await this.popup + .getByRole("button", { name: "Swap", exact: true }) + .click({ trial: true }) + } else { + await expect( + this.popup.getByRole("button", { name: "Send", exact: true }) + ).not.toBeVisible() + await expect( + this.popup.getByRole("button", { name: "Swap", exact: true }) + ).not.toBeVisible() + } + + /** + * Assert the bottom wrap. + */ + await this.walletPageHelper.verifyBottomWrap() + } + + /** + * Function closing the verify asset popup + */ + async closeVerifyAssetPopup(): Promise { + const verifyAssetPopup = this.popup + .getByTestId("slide_up_menu") + .filter({ hasText: "Asset automatically imported" }) + await verifyAssetPopup.getByLabel("Close menu").click() + } + + /** + * Function asserting the Verify Asset popup + */ + async assertVerifyAssetPopup( + assetSymbol: RegExp, + assetType: "unverified" | "trusted", + tokenAddressShortened: string | undefined + ): Promise { + await expect( + this.popup.getByText("Asset automatically imported") + ).toBeVisible() + + const verifyAssetPopup = this.popup + .getByTestId("slide_up_menu") + .filter({ hasText: "Asset automatically imported" }) + + if (assetType === "trusted") { + await expect( + verifyAssetPopup.getByText("Asset verified by you") + ).toBeVisible() + await expect( + verifyAssetPopup.getByText("Asset not verified") + ).not.toBeVisible() + } else { + await expect( + verifyAssetPopup.getByText("Asset not verified") + ).toBeVisible() + await expect( + verifyAssetPopup.getByText("Asset verified by you") + ).not.toBeVisible() + } + + await expect( + verifyAssetPopup.getByText( + "Be aware of scam and spam assets, only interact with assets you trust." + ) + ).toBeVisible() + + await expect( + verifyAssetPopup + .locator("li") + .filter({ + hasText: "Symbol", + }) + .locator(".right") + ).toHaveText(assetSymbol) + + if (tokenAddressShortened !== undefined) { + await expect( + verifyAssetPopup + .locator("li") + .filter({ + hasText: "Contract address", + }) + .locator(".right") + ).toHaveText(tokenAddressShortened) + } else { + throw new Error("`tokenAddressShortened` not defined.") + } + + await verifyAssetPopup + .locator("li") + .filter({ + hasText: "Contract address", + }) + .locator(".right") + .click({ trial: true }) + // TODO: Click and verify the scan website address + + await verifyAssetPopup + .getByRole("button", { name: "Don’t show", exact: true }) + .click({ trial: true }) + + if (assetType === "unverified") { + await verifyAssetPopup + .getByRole("button", { name: "Add to asset list", exact: true }) + .click({ trial: true }) + } else { + await expect( + verifyAssetPopup.getByRole("button", { + name: "Add to asset list", + exact: true, + }) + ).not.toBeVisible() + } + } + + async assertNoUnverifiedAssetsOnWalletPage(): Promise { + await expect( + this.popup.getByRole("button", { name: "See unverified assets" }) + ).not.toBeVisible() + await expect(this.popup.getByText("Asset not verified")).not.toBeVisible() + await expect( + this.popup.getByRole("button", { name: "Verify asset" }) + ).not.toBeVisible() + await expect( + this.popup.getByTestId("hidden_assets_container") + ).not.toBeVisible() + } + + async assertUnverifiedAssetsPresentOnWalletPage(): Promise { + /** Make sure there is a `See unverified assets` button, but no unverified + * assets are listed before button is clicked. + */ + await expect( + this.popup.getByRole("button", { + name: /^See unverified assets \(\d+\)$/, + }) + ).toBeVisible() + await expect( + this.popup.getByRole("button", { + name: /^Hide unverified assets \(\d+\)$/, + }) + ).not.toBeVisible() + + await expect( + this.popup.getByTestId("hidden_assets_container") + ).not.toBeVisible() + // Below assertions fail, as `Verify asset` and `Asset not verified` + // elements are present in DOM even when unverified assets are collapsed. + // There's no easy way to assert that those elements are not visible to a + // human eye. + // await expect( + // this.popup.getByRole("button", { name: "Verify asset" }).first() + // ).toBeHidden() + // await expect(this.popup.getByText("Asset not verified")).not.toBeVisible() + // await expect( + // this.popup.getByText( + // "Be aware of scam and spam assets, only interact with assets you trust." + // ) + // ).not.toBeVisible() + + /** Click the `See unverified assets` button and make sure unverified + * assets do show up. + */ + await this.popup + .getByRole("button", { name: /^See unverified assets \(\d+\)$/ }) + .click() + + await expect( + this.popup.getByRole("button", { + name: /^Hide unverified assets \(\d+\)$/, + }) + ).toBeVisible() + await expect( + this.popup.getByRole("button", { name: "See unverified assets" }) + ).not.toBeVisible() + + await expect( + this.popup.getByTestId("hidden_assets_container") + ).toBeVisible() + // Similarly as commented above, below assertions are not perfect. The + // assertions can pass even when the list of unverified assets is collapsed. + await expect(this.popup.getByText("Asset not verified")).toBeVisible() + await expect( + this.popup.getByText( + "Be aware of scam and spam assets, only interact with assets you trust." + ) + ).toBeVisible() + await expect( + this.popup.getByRole("button", { name: "Verify asset" }).first() + ).toBeVisible() + + /** + * Click `Hide unverified assets` button, make sure unverified assets are + * not shown. + */ + await this.popup + .getByRole("button", { + name: /^Hide unverified assets \(\d+\)$/, + }) + .click() + await expect( + this.popup.getByRole("button", { + name: /^See unverified assets \(\d+\)$/, + }) + ).toBeVisible() + await expect( + this.popup.getByRole("button", { + name: /^Hide unverified assets \(\d+\)$/, + }) + ).not.toBeVisible() + await expect( + this.popup.getByTestId("hidden_assets_container") + ).not.toBeVisible() + } + + // TODO: Move it to transactionsHelper once #3418 gets merged to `main`. + async assertAssetsNotPresentOnAssetsList( + tokens: Array + ): Promise { + await expect(this.popup.getByTestId("assets_list")).toBeVisible() + await Promise.all( + tokens.map(async (token) => { + await expect( + this.popup.getByTitle(token as string, { exact: true }) + ).toHaveCount(0) + }) + ) + } + + // TODO: Move it to transactionsHelper once #3418 gets merged to `main`. + async assertAssetsPresentOnAssetsList(tokens: Array): Promise { + await expect(this.popup.getByTestId("assets_list")).toBeVisible() + await Promise.all( + tokens.map(async (token) => { + await expect( + this.popup.getByTitle(token as string, { exact: true }) + ).toHaveCount(1) + }) + ) + } + + // TODO: Move it to transactionsHelper once #3418 gets merged to `main`. + /** + * Function closing the Select token popup + */ + async closeSelectTokenPopup(): Promise { + const selectTokenPopup = this.popup + .getByTestId("slide_up_menu") + .filter({ hasText: "Select token" }) + await selectTokenPopup.getByLabel("Close menu").click() + } + + // TODO: Move to settingsHelper + /** + * Verifies the copy in the setting and tooltip, checks if toggle is in the + * expected position. + */ + async assertShowUnverifiedAssetsSetting(enabled: boolean): Promise { + const showUnverifiedAssetsSetting = this.popup.locator("li").filter({ + hasText: /^Show unverified assets$/, + }) + await showUnverifiedAssetsSetting + .getByTestId("toggle") + .click({ trial: true }) + await expect( + showUnverifiedAssetsSetting.getByTestId("toggle") + ).toHaveAttribute("aria-checked", enabled.toString()) + await showUnverifiedAssetsSetting + .getByTestId("tooltip_wrap") + .hover({ timeout: 5000 }) + await expect( + this.popup.getByText( + `Discover assets that you own but are not on our asset list. Some + spam/scam assets may show up, so treat them with caution.` + ) + ).toBeVisible() + await expect( + this.popup.getByText( + `They will show up on the bottom of the asset page until you verify + them.` + ) + ).toBeVisible() + } + + // TODO: Move to settingsHelper once we create dedicated e2e tests for + // Settings. + /** Verifies the copy in the setting and tooltip, checks if toggle is in + * the expected position. + */ + async toggleShowUnverifaiedAssetsSetting(): Promise { + const showUnverifiedAssetsSetting = this.popup.locator("li").filter({ + hasText: /^Show unverified assets$/, + }) + await showUnverifiedAssetsSetting.getByTestId("toggle").click() + } +} diff --git a/e2e-tests/utils/walletPageHelper.ts b/e2e-tests/utils/walletPageHelper.ts index eec1e9f5e2..42b9252be7 100644 --- a/e2e-tests/utils/walletPageHelper.ts +++ b/e2e-tests/utils/walletPageHelper.ts @@ -1,4 +1,4 @@ -import { Page, BrowserContext } from "@playwright/test" +import { Page, BrowserContext, expect } from "@playwright/test" import OnboardingHelper from "./onboarding" export default class WalletPageHelper { @@ -36,4 +36,106 @@ export default class WalletPageHelper { .getByRole("link", { name: tab }) .click() } + + async verifyTopWrap(network: RegExp, accountLabel: RegExp): Promise { + // TODO: maybe we could also verify graphical elements (network icon, profile picture, etc)? + + await expect( + this.popup.getByTestId("top_menu_network_switcher").last() + ).toHaveText(network) + await this.popup + .getByTestId("top_menu_network_switcher") + .last() + .click({ trial: true }) + + // There's no `connection_button` in the current code from `main`. TODO: + // verify if we'll need this or not. + // await this.popup.locator(".connection_button").last().click({ trial: true }) + + await expect( + this.popup.getByTestId("top_menu_profile_button").last() + ).toHaveText(accountLabel, { timeout: 240000 }) + await this.popup + .getByTestId("top_menu_profile_button") + .last() + .click({ trial: true }) + // TODO: verify 'Copy address' + } + + async verifyBottomWrap(): Promise { + await this.popup + .locator(".tab_bar_wrap") + .getByText("Wallet", { exact: true }) + .click({ trial: true }) + await this.popup + .locator(".tab_bar_wrap") + .getByText("NFTs", { exact: true }) + .click({ trial: true }) + await this.popup + .locator(".tab_bar_wrap") + .getByText("Portfolio", { exact: true }) + .click({ trial: true }) + await this.popup + .locator(".tab_bar_wrap") + .getByText("Settings", { exact: true }) + .click({ trial: true }) + } + + /** + * The function checks elements of the main page that should always be present. + */ + async verifyCommonElements( + network: RegExp, + accountLabel: RegExp + ): Promise { + await expect(this.popup.getByText("Total account balance")).toBeVisible({ + timeout: 240000, + }) // we need longer timeout, because on fork it often takes long to load this section + await expect(this.popup.getByTestId("wallet_balance")).toHaveText( + /^\$(0|\d+\.\d{2})$/ + ) + + await this.verifyTopWrap(network, accountLabel) + + await this.popup + .getByRole("button", { name: "Send", exact: true }) + .click({ trial: true }) + await this.popup + .getByRole("button", { name: "Swap", exact: true }) + .click({ trial: true }) + await this.popup + .getByRole("button", { name: "Receive", exact: true }) + .click({ trial: true }) + await this.popup + .getByTestId("panel_switcher") + .getByText("NFTs", { exact: true }) + .click({ trial: true }) + await this.popup + .getByTestId("panel_switcher") + .getByText("Assets", { exact: true }) + .click({ trial: true }) + await this.popup + .getByTestId("panel_switcher") + .getByText("Activity", { exact: true }) + .click({ trial: true }) + await this.verifyBottomWrap() + } + + async verifyAnalyticsBanner(): Promise { + const analyticsBanner = this.popup.locator("div").filter({ + has: this.popup.getByRole("heading", { + name: "Analytics are enabled", + exact: true, + }), + }) + await expect( + analyticsBanner.getByText( + "They help us improve the wallet. You can disable anytime", + { exact: true } + ) + ).toBeVisible() + await analyticsBanner + .getByText("Change settings", { exact: true }) + .click({ trial: true }) + } } diff --git a/ui/_locales/en/messages.json b/ui/_locales/en/messages.json index 75cc728fc5..70496d1730 100644 --- a/ui/_locales/en/messages.json +++ b/ui/_locales/en/messages.json @@ -576,8 +576,8 @@ }, "unverifiedAssets": { "tooltip": { - "firstPart": "Discover assets that you own but are not on our asset list. Some spam/scam assets will show up, so tread carefully.", - "secondPart": "These will show up on the bottom of the asset page until you verify them." + "firstPart": "Discover assets that you own but are not on our asset list. Some spam/scam assets may show up, so treat them with caution.", + "secondPart": "They will show up on the bottom of the asset page until you verify them." } } }, diff --git a/ui/components/Shared/SharedAssetInput.tsx b/ui/components/Shared/SharedAssetInput.tsx index b1d26ade0d..489c78e94c 100644 --- a/ui/components/Shared/SharedAssetInput.tsx +++ b/ui/components/Shared/SharedAssetInput.tsx @@ -196,7 +196,7 @@ function SelectAssetMenuContent(
-
    +
      {sortedFilteredAssets.map((assetWithOptionalAmount) => { const { asset } = assetWithOptionalAmount return ( @@ -363,7 +363,12 @@ function SelectedAssetButton(props: SelectedAssetButtonProps): ReactElement { } return ( -