From d53e01f71c24b22f6dc0c82ec1a50a1ff684dd85 Mon Sep 17 00:00:00 2001 From: Jorge Luis <28708889+hyphenized@users.noreply.github.com> Date: Mon, 10 Feb 2025 19:16:24 -0500 Subject: [PATCH 01/17] Update e2e test tags Removes dependency on env vars and adds a script shortcut to run fork tests --- e2e-tests/fork-based/transactions.spec.ts | 2 +- e2e-tests/regular/transactions.spec.ts | 2 +- package.json | 5 +++-- playwright.config.ts | 3 +-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/e2e-tests/fork-based/transactions.spec.ts b/e2e-tests/fork-based/transactions.spec.ts index 78986e21a3..a443702c4e 100644 --- a/e2e-tests/fork-based/transactions.spec.ts +++ b/e2e-tests/fork-based/transactions.spec.ts @@ -1,7 +1,7 @@ import { test, expect } from "../utils" import { account2 } from "../utils/onboarding" -test.describe("Transactions", () => { +test.describe("Transactions @fork", () => { test("User can send base asset", async ({ page: popup, walletPageHelper, diff --git a/e2e-tests/regular/transactions.spec.ts b/e2e-tests/regular/transactions.spec.ts index b975384a1d..7123351d6d 100644 --- a/e2e-tests/regular/transactions.spec.ts +++ b/e2e-tests/regular/transactions.spec.ts @@ -2,7 +2,7 @@ import { test, expect } from "../utils" import { account2 } from "../utils/onboarding" test.describe("Transactions", () => { - test("User can send base asset (on Sepolia testnet) @expensive", async ({ + test("User can send base asset (on Sepolia testnet) @testnet", async ({ page: popup, walletPageHelper, transactionsHelper, diff --git a/package.json b/package.json index d799e449a4..019ca97411 100644 --- a/package.json +++ b/package.json @@ -65,8 +65,9 @@ "test:unit": "echo \"> Running Unit Tests\" && jest \"unit.test.ts\" --forceExit", "test:ui": "echo \"> Running UI Tests\" && jest \".test.tsx\" --forceExit", "test:e2e": "echo \"> Running e2e Tests\" && run-s e2e:*", - "e2e:regular": "echo \"> Running base e2e Tests\" && playwright test --grep-invert @expensive", - "e2e:fork": "echo \"> Running base e2e Tests\" && playwright test --grep @expensive", + "e2e:regular": "echo \"> Running base e2e Tests\" && playwright test --grep-invert '@testnet|@fork'", + "e2e:testnet": "echo \"> Running testnet e2e Tests\" && playwright test --grep @testnet", + "e2e:fork": "echo \"> Running fork e2e Tests\" && playwright test --grep @fork", "preversion": "(which jq >&1 && which yq >&1) || (echo 'You must install jq and yq; scripts/macos-setup.sh for macOS.' && exit 1)", "version": "scripts/update-version.sh", "postversion": "git push --tags origin release-$npm_package_version", diff --git a/playwright.config.ts b/playwright.config.ts index 7972ee76da..551fc739d8 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -8,13 +8,12 @@ import "dotenv-defaults/config" const SECOND = 1e3 const CI_ENV = typeof process.env.CI === "string" -const FORK = process.env.USE_MAINNET_FORK === "true" /** * See https://playwright.dev/docs/test-configuration. */ const config: PlaywrightTestConfig = { - testDir: FORK ? "./e2e-tests/fork-based" : "./e2e-tests/regular", + testDir: "./e2e-tests/", /* Maximum time one test can run for. */ timeout: 240 * SECOND, expect: { From 9efb838d7d6bd8531d13bf1a63e9c64a47f7d0ca Mon Sep 17 00:00:00 2001 From: Jorge Luis <28708889+hyphenized@users.noreply.github.com> Date: Mon, 10 Feb 2025 19:16:54 -0500 Subject: [PATCH 02/17] Add test-id to asset list item --- ui/components/Wallet/AssetListItem/CommonAssetListItem.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ui/components/Wallet/AssetListItem/CommonAssetListItem.tsx b/ui/components/Wallet/AssetListItem/CommonAssetListItem.tsx index 725d4a40d7..c0696c1133 100644 --- a/ui/components/Wallet/AssetListItem/CommonAssetListItem.tsx +++ b/ui/components/Wallet/AssetListItem/CommonAssetListItem.tsx @@ -4,6 +4,7 @@ import { CompleteAssetAmount } from "@tallyho/tally-background/redux-slices/acco import { useTranslation } from "react-i18next" import { + getFullAssetID, isTrustedAsset, isUntrustedAsset, } from "@tallyho/tally-background/redux-slices/utils/asset-utils" @@ -72,7 +73,10 @@ export default function CommonAssetListItem( state: assetAmount.asset, }} > -
+
Date: Mon, 10 Feb 2025 19:17:37 -0500 Subject: [PATCH 03/17] Add test-id to account list slide menu --- ui/components/TopMenu/TopMenu.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/components/TopMenu/TopMenu.tsx b/ui/components/TopMenu/TopMenu.tsx index 50b792de92..e26dae67fe 100644 --- a/ui/components/TopMenu/TopMenu.tsx +++ b/ui/components/TopMenu/TopMenu.tsx @@ -48,6 +48,7 @@ export default function TopMenu(): ReactElement { { setIsNotificationsOpen(false) }} From c26315818744ed01990281cf957ed9858078fb40 Mon Sep 17 00:00:00 2001 From: Jorge Luis <28708889+hyphenized@users.noreply.github.com> Date: Mon, 10 Feb 2025 19:19:25 -0500 Subject: [PATCH 04/17] Add helper to change first account name This will come useful when we need a deterministic name for test assertions. --- e2e-tests/utils/walletPageHelper.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/e2e-tests/utils/walletPageHelper.ts b/e2e-tests/utils/walletPageHelper.ts index 7e2f37c5e9..c894f8f9c7 100644 --- a/e2e-tests/utils/walletPageHelper.ts +++ b/e2e-tests/utils/walletPageHelper.ts @@ -108,6 +108,33 @@ export default class WalletPageHelper { fs.unlinkSync(filePath) } + /** + * Changes the first account name + */ + async changeAccountName(newName: string) { + await this.popup + .getByTestId("top_menu_profile_button") + .getByRole("button") + .click() + + await this.popup.getByRole("menu").first().click() + await this.popup.getByRole("button", { name: "Edit name" }).click() + await this.popup.getByRole("textbox", { name: "Type new name" }).click() + await this.popup + .getByRole("textbox", { name: "Type new name" }) + .fill(newName) + + await this.popup + .getByRole("button", { name: "Save name", exact: true }) + .click() + + await this.popup + .getByTestId("accounts_list_slide_up") + .getByRole("button", { name: "Close menu" }) + .first() + .click() + } + async assertTopWrap(network: RegExp, accountLabel: RegExp): Promise { // TODO: maybe we could also verify graphical elements (network icon, profile picture, etc)? From bdea96a88db2d5e4acfeec3205fbfd00d8eb1a4e Mon Sep 17 00:00:00 2001 From: Jorge Luis <28708889+hyphenized@users.noreply.github.com> Date: Mon, 10 Feb 2025 21:06:37 -0500 Subject: [PATCH 05/17] Schedule price updates more often Previous approach would have missed updates if a price fetch was requested after detecting a new token balance. This also increases the frequency of price updates. --- background/services/indexing/index.ts | 33 +++++++++---------- .../indexing/tests/index.integration.test.ts | 15 ++++----- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/background/services/indexing/index.ts b/background/services/indexing/index.ts index 9cafd66d90..aff13d3d41 100644 --- a/background/services/indexing/index.ts +++ b/background/services/indexing/index.ts @@ -124,7 +124,7 @@ export default class IndexingService extends BaseService { */ private scheduledTokenRefresh = false - private lastPriceAlarmTime = 0 + #pricesUpdateTimer: NodeJS.Timer | null = null private cachedAssets: Record = Object.fromEntries( @@ -178,9 +178,9 @@ export default class IndexingService extends BaseService { prices: { schedule: { delayInMinutes: 1, - periodInMinutes: 10, + periodInMinutes: 1, }, - handler: () => this.handlePriceAlarm(), + handler: () => this.scheduleUpdateAssetsPrices(), }, }) } @@ -194,7 +194,7 @@ export default class IndexingService extends BaseService { const tokenListLoad = this.fetchAndCacheTokenLists() this.chainService.emitter.once("serviceStarted").then(async () => { - this.handlePriceAlarm() + this.scheduleUpdateAssetsPrices() const trackedNetworks = await this.chainService.getTrackedNetworks() @@ -476,7 +476,7 @@ export default class IndexingService extends BaseService { await this.loadAccountBalancesFor(addressOnNetwork) // FIXME Refactor this to only update prices for tokens with balances. - this.handlePriceAlarm() + this.scheduleUpdateAssetsPrices() }, ) @@ -939,21 +939,20 @@ export default class IndexingService extends BaseService { } } - private async handlePriceAlarm(): Promise { - if (Date.now() < this.lastPriceAlarmTime + 5 * SECOND) { - // If this is quickly called multiple times (for example when - // using a network for the first time with a wallet loaded - // with many accounts) only fetch prices once. + private async scheduleUpdateAssetsPrices(): Promise { + // If there's a pending update do nothing + if (this.#pricesUpdateTimer) { return } - this.lastPriceAlarmTime = Date.now() - - // Avoid awaiting here so price fetching can happen in the background - // and the extension can go on doing whatever it needs to do while waiting - // for prices to come back. - this.getBaseAssetsPrices() - this.getTrackedAssetsPrices() + this.#pricesUpdateTimer = setTimeout(() => { + this.#pricesUpdateTimer = null + // Avoid awaiting here so price fetching can happen in the background + // and the extension can go on doing whatever it needs to do while waiting + // for prices to come back. + this.getBaseAssetsPrices() + this.getTrackedAssetsPrices() + }, 5 * SECOND) } private async fetchAndCacheTokenLists(): Promise { diff --git a/background/services/indexing/tests/index.integration.test.ts b/background/services/indexing/tests/index.integration.test.ts index ba3337bda9..da2ea14d64 100644 --- a/background/services/indexing/tests/index.integration.test.ts +++ b/background/services/indexing/tests/index.integration.test.ts @@ -266,10 +266,9 @@ describe("IndexingService", () => { await indexingDb.addAssetToTrack(smartContractAsset) - const spy = getPrivateMethodSpy( - indexingService, - "handlePriceAlarm", - ) + const spy = getPrivateMethodSpy< + IndexingService["scheduleUpdateAssetsPrices"] + >(indexingService, "scheduleUpdateAssetsPrices") await Promise.all([ chainService.startService(), @@ -306,9 +305,9 @@ describe("IndexingService", () => { await indexingDb.addAssetToTrack(smartContractAsset) // Skip loading prices at service init - getPrivateMethodSpy( + getPrivateMethodSpy( indexingService, - "handlePriceAlarm", + "scheduleUpdateAssetsPrices", ).mockResolvedValue(Promise.resolve()) await Promise.all([ @@ -359,9 +358,9 @@ describe("IndexingService", () => { await indexingDb.addAssetToTrack(smartContractAsset) // Skip loading prices at service init - getPrivateMethodSpy( + getPrivateMethodSpy( indexingService, - "handlePriceAlarm", + "scheduleUpdateAssetsPrices", ).mockResolvedValue(Promise.resolve()) await Promise.all([ From 643bed86205cb73c2b06e7e9f04e4eacc2f141ce Mon Sep 17 00:00:00 2001 From: Jorge Luis <28708889+hyphenized@users.noreply.github.com> Date: Tue, 11 Feb 2025 21:21:24 -0500 Subject: [PATCH 06/17] Limit price lookups batch size on e2e tests Since local nodes require higher gas to fulfill read only calls these would sometimes fail silently when done through multicall. --- background/lib/priceOracle.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/background/lib/priceOracle.ts b/background/lib/priceOracle.ts index 21eb011a4c..e78d37e343 100644 --- a/background/lib/priceOracle.ts +++ b/background/lib/priceOracle.ts @@ -32,11 +32,14 @@ import { toFixedPoint } from "./fixed-point" import SerialFallbackProvider from "../services/chain/serial-fallback-provider" import { EVMNetwork } from "../networks" import logger, { logRejectedAndReturnFulfilledResults } from "./logger" +import { FeatureFlags } from "../features" // The size of a batch of on-chain price lookups. Too high and the request will // fail due to running out of gas, as eth_call is still subject to gas limits. // Too low and we will make additional unnecessary RPC requests. -const BATCH_SIZE = 20 +// On mainnet fork, even though the node caches evm states, local execution +// creates a strict limit on gas per call +const BATCH_SIZE = FeatureFlags.USE_MAINNET_FORK ? 2 : 20 // Oracle Documentation and Address references can be found // at https://docs.1inch.io/docs/spot-price-aggregator/introduction/ From 7ffeb9ab26502cdd0b1b3597c9e7a41c4146187b Mon Sep 17 00:00:00 2001 From: Jorge Luis <28708889+hyphenized@users.noreply.github.com> Date: Fri, 14 Feb 2025 18:20:10 -0500 Subject: [PATCH 07/17] Replace hardhat with anvil This updates fork tests to run on anvil instead of hardhat. The change significantly improves test speed and prevents hangups. Even for simple cases anvil performs better without a cache. --- .env.defaults | 2 + .github/workflows/build.yml | 46 ++--- .../services/chain/asset-data-helper.ts | 2 + e2e-tests/fork-based/transactions.spec.ts | 189 ++++++++++++------ e2e-tests/utils.ts | 7 + e2e-tests/utils/fork-env-helper.ts | 108 ++++++++++ e2e-tests/utils/onboarding.ts | 4 +- e2e-tests/utils/transactions.ts | 3 + e2e-tests/utils/walletPageHelper.ts | 9 +- package.json | 1 + playwright.config.ts | 2 +- scripts/create-json-wallet.mjs | 20 ++ 12 files changed, 292 insertions(+), 101 deletions(-) create mode 100644 e2e-tests/utils/fork-env-helper.ts create mode 100644 scripts/create-json-wallet.mjs diff --git a/.env.defaults b/.env.defaults index 7a60a1790a..8322fbf2c1 100644 --- a/.env.defaults +++ b/.env.defaults @@ -37,3 +37,5 @@ USE_MAINNET_FORK=false ARBITRUM_FORK_RPC=https://rpc.tenderly.co/fork/2fc2cf12-5c58-439f-9b5e-967bfd02191a TESTNET_TAHO_DEPLOYER_ADDRESS=0x55B180c3470dA8E31761d45468e4E61DbE13Eb9B TESTNET_TAHO_ADDRESS=0x78f04eC76df38Fcb37971Efa8EcbcB33f52dae0F +FORK_TEST_WALLET_JSON_BODY= +FORK_TEST_WALLET_JSON_PASSWORD= diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7b9fa1abe8..9fafc225f5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -242,33 +242,32 @@ jobs: name: extension-builds-fork-${{ github.event.number || github.event.head_commit.id }} - name: Extract extension run: unzip -o chrome-fork.zip -d dist/chrome - - name: Restore Hardhat cache - uses: actions/cache/restore@v3 + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 with: - path: ci/cache - # 18291960 is a forking block used in the tests. - key: hardhat-18291960-${{ github.ref_name }} - restore-keys: | - hardhat-18291960- - hardhat- - - name: Run Hardhat + cache-key: anvil-21802314-${{ github.ref_name }}-${{ github.sha }} + cache-restore-keys: | + anvil-21802314-${{ github.ref_name }}-${{ github.sha }} + anvil-21802314-${{ github.ref_name }}- + anvil-21802314- + - name: Run Anvil env: CHAIN_API_URL: https://eth-mainnet.g.alchemy.com/v2/${{ secrets.DEV_ALCHEMY_API_KEY }} MAINNET_FORK_CHAIN_ID: 1337 # We're using a fixed block number as a start of the fork to get - # consistent behavior in tests. The `18291960` block is a block at - # which the wallet used in tests (`testertesting.eth`) had the two - # assets used in e2e tests. - FORKING_BLOCK: 18291960 + # consistent behavior in tests. The `21802314` block is a block at + # which the state of the chain allows the tests to pass + FORKING_BLOCK: 21802314 run: | - cd ci - yarn install - npx hardhat node --network hardhat & - sleep 20 + anvil -q --chain-id $MAINNET_FORK_CHAIN_ID --port 8545 \ + --fork-block-number $FORKING_BLOCK --fork-url $CHAIN_API_URL \ + --preserve-historical-states --fork-chain-id 1 \ + --gas-limit 65000000 & + sleep 10 - name: Run Playwright tests designed for fork env: - TEST_WALLET_JSON_BODY: ${{ secrets.TEST_WALLET_JSON_BODY }} - TEST_WALLET_JSON_PASSWORD: ${{ secrets.TEST_WALLET_JSON_PASSWORD }} + FORK_TEST_WALLET_JSON_BODY: ${{ secrets.TEST_WALLET_JSON_BODY }} + FORK_TEST_WALLET_JSON_PASSWORD: ${{ secrets.TEST_WALLET_JSON_PASSWORD }} USE_MAINNET_FORK: true run: xvfb-run npx playwright test - uses: actions/upload-artifact@v4 @@ -279,12 +278,3 @@ jobs: test-results/ #videos/ retention-days: 30 - - name: Save Hardhat cache - uses: actions/cache/save@v3 - # We want to save the cache even if the tests failed. Without the cache - # the tests almost always fail, so without `if: always()` we would have - # problem with creating the first cache. - if: always() - with: - path: ci/cache - key: hardhat-18291960-${{ github.ref_name }}-${{ hashFiles('ci/cache/**/*.json') }} diff --git a/background/services/chain/asset-data-helper.ts b/background/services/chain/asset-data-helper.ts index a9d9f770ba..aa6a91e4e0 100644 --- a/background/services/chain/asset-data-helper.ts +++ b/background/services/chain/asset-data-helper.ts @@ -98,6 +98,8 @@ export default class AssetDataHelper { // Load balances of tokens on the mainnet fork if (isEnabled(FeatureFlags.USE_MAINNET_FORK)) { const tokens = [ + "0x6b175474e89094c44da98b954eedeac495271d0f", // DAI + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // USDC "0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9", // AAVE "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984", // UNI "0x3A283D9c08E8b55966afb64C515f5143cf907611", // crvCVXETH diff --git a/e2e-tests/fork-based/transactions.spec.ts b/e2e-tests/fork-based/transactions.spec.ts index a443702c4e..d34f3f931c 100644 --- a/e2e-tests/fork-based/transactions.spec.ts +++ b/e2e-tests/fork-based/transactions.spec.ts @@ -1,22 +1,64 @@ +import { Wallet, utils } from "ethers" import { test, expect } from "../utils" -import { account2 } from "../utils/onboarding" +import ForkEnvHelper from "../utils/fork-env-helper" + +const WALLET_JSON = process.env.FORK_TEST_WALLET_JSON_BODY ?? "" +const WALLET_PASSWORD = process.env.FORK_TEST_WALLET_JSON_PASSWORD ?? "" test.describe("Transactions @fork", () => { + const ERC20_ASSET_WALLET = "0x40ec5B33f54e0E8A33A975908C5BA1c14e5BbbDf" + const DAI_CONTRACT = "0x6b175474e89094c44da98b954eedeac495271d0f" + const USDC_CONTRACT = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" + + test.beforeAll(async ({ localNodeAlive, context }) => { + // FIXME: These should be test.skip but there's a bug in playwright causing + // the beforeAll hook to run before `test.skip` + test.fixme( + WALLET_JSON === "" || WALLET_PASSWORD === "", + "FORK_TEST_WALLET_JSON_BODY and FORK_TEST_WALLET_JSON_PASSWORD must be set", + ) + + test.fixme(!localNodeAlive, "Local node must be up") + + const wallet = Wallet.fromEncryptedJsonSync(WALLET_JSON!, WALLET_PASSWORD!) + + const forkEnv = new ForkEnvHelper(context) + await forkEnv.setBalance(wallet.address, utils.parseUnits("20", "ether")) + + await forkEnv.impersonateAccount(ERC20_ASSET_WALLET) + + await forkEnv.transferERC20(DAI_CONTRACT, wallet.address, "2.62") + await forkEnv.transferERC20(USDC_CONTRACT, wallet.address, "2.62", 6) + + await forkEnv.stopImpersonating(ERC20_ASSET_WALLET) + }) + test("User can send base asset", async ({ page: popup, walletPageHelper, transactionsHelper, assetsHelper, }) => { + const RECIPIENT_ADDRESS = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" + await test.step("Import account", async () => { /** * Import the `testertesting.eth` account using onboarding with a JSON * file. */ - await walletPageHelper.onboardWithJSON(account2) + + await walletPageHelper.onboardWithJSON( + "custom", + WALLET_JSON!, + WALLET_PASSWORD!, + ) + await walletPageHelper.goToStartPage() + await walletPageHelper.setViewportSize() + await walletPageHelper.changeAccountName("TEST_ACCOUNT") + /** * Verify we're on Ethereum network. Verify common elements on the main * page. @@ -24,7 +66,7 @@ test.describe("Transactions @fork", () => { await walletPageHelper.assertCommonElements( /^Ethereum$/, false, - account2.name, + /TEST_ACCOUNT/i, ) await walletPageHelper.assertAnalyticsBanner() @@ -33,8 +75,8 @@ test.describe("Transactions @fork", () => { */ const ethAsset = popup.locator(".asset_list_item").first() // We use `.first()` because the base asset should be first on the list await expect(ethAsset.getByText(/^ETH$/)).toBeVisible() - await expect(ethAsset.getByText(/^0\.0386$/)).toBeVisible() - await expect(ethAsset.getByText(/^\$(0|\d+\.\d{2})$/)).toBeVisible() + await expect(ethAsset.getByText(/^20$/)).toBeVisible() + await ethAsset.locator(".asset_icon_send").click({ trial: true }) await ethAsset.locator(".asset_icon_swap").click({ trial: true }) @@ -51,19 +93,19 @@ test.describe("Transactions @fork", () => { * isn't active. */ await transactionsHelper.assertUnfilledSendAssetScreen( - /^Ethereum$/, - account2.name, + /Ethereum/i, + /TEST_ACCOUNT/i, "ETH", - "0\\.0386", + "20", true, ) /** - * Enter amount and receipient. Verify `Continue` isn't active. + * Enter amount and recipient. Verify `Continue` isn't active. */ - await popup.locator("input.input_amount").fill("0.01") + await popup.locator("input.input_amount").fill("0.10") await expect( - popup.locator(".value").getByText(/^\$\d+\.\d{2}$/), + popup.locator(".value").getByText(/^\$\d+.*\d{2}$/), ).toBeVisible() await expect( @@ -73,8 +115,7 @@ test.describe("Transactions @fork", () => { .getByRole("button", { name: "Continue", exact: true }) .click({ force: true }) - const receipientAddress = "0x47745a7252e119431ccf973c0ebd4279638875a6" - await popup.locator("#send_address").fill(receipientAddress) + await popup.locator("#send_address").fill(RECIPIENT_ADDRESS) /** * Click `Continue`. @@ -88,10 +129,10 @@ test.describe("Transactions @fork", () => { */ await transactionsHelper.assertTransferScreen( "Ethereum", - "testertesting\\.eth", - "0x47745a7252e119431ccf973c0ebd4279638875a6", - "0x4774…875a6", - "0\\.01", + "TEST_ACCOUNT", + RECIPIENT_ADDRESS, + "0x7099…c79c8", + "0.1", "ETH", true, ) @@ -119,9 +160,9 @@ test.describe("Transactions @fork", () => { */ await assetsHelper.assertAssetDetailsPage( /^Ethereum$/, - account2.name, + /TEST_ACCOUNT/, /^ETH$/, - /^0\.0284$/, + /^19.\d+$/, "base", ) // This is what we expect currently on forked network. If ve ever fix @@ -169,7 +210,7 @@ test.describe("Transactions @fork", () => { await walletPageHelper.assertCommonElements( /^Ethereum$/, false, - account2.name, + /TEST_ACCOUNT/i, ) await walletPageHelper.assertAnalyticsBanner() }) @@ -181,15 +222,22 @@ test.describe("Transactions @fork", () => { transactionsHelper, assetsHelper, }) => { + const ERC20_RECIPIENT = "0x47745a7252e119431ccf973c0ebd4279638875a6" await test.step("Import account", async () => { /** * Import the `testertesting.eth` account using onboarding with a JSON * file. */ - await walletPageHelper.onboardWithJSON(account2) + await walletPageHelper.onboardWithJSON( + "custom", + WALLET_JSON!, + WALLET_PASSWORD!, + ) await walletPageHelper.goToStartPage() await walletPageHelper.setViewportSize() + await walletPageHelper.changeAccountName("TEST_ACCOUNT2") + /** * Verify we're on Ethereum network. Verify common elements on the main * page. @@ -197,7 +245,7 @@ test.describe("Transactions @fork", () => { await walletPageHelper.assertCommonElements( /^Ethereum$/, false, - account2.name, + /TEST_ACCOUNT2/, ) await walletPageHelper.assertAnalyticsBanner() @@ -207,12 +255,15 @@ test.describe("Transactions @fork", () => { const daiAsset = popup .locator(".asset_list_item") .filter({ has: popup.locator("span").filter({ hasText: /^DAI$/ }) }) + // Wait for asset to load await expect(daiAsset.getByText(/^2\.62$/)).toBeVisible({ timeout: 120000, }) + // Wait for prices to load await expect(daiAsset.getByText(/^\$\d+\.\d{2}$/)).toBeVisible({ timeout: 120000, }) + await daiAsset.locator(".asset_icon_send").click({ trial: true }) await daiAsset.locator(".asset_icon_swap").click({ trial: true }) @@ -229,8 +280,8 @@ test.describe("Transactions @fork", () => { * isn't active. */ await transactionsHelper.assertUnfilledSendAssetScreen( - /^Ethereum$/, - account2.name, + /ethereum/i, + /TEST_ACCOUNT2/, "DAI", "2\\.62", false, @@ -240,9 +291,9 @@ test.describe("Transactions @fork", () => { * Enter amount and receipient. Verify `Continue` isn't active. */ await popup.getByPlaceholder(/^0\.0$/).fill("1.257") - await expect( - popup.locator(".value").getByText(/^\$\d+\.\d{2}$/), - ).toBeVisible() + // await expect( + // popup.locator(".value").getByText(/^\$\d+\.\d{2}$/), + // ).toBeVisible() await expect( popup.getByRole("button", { name: "Continue", exact: true }), @@ -251,8 +302,7 @@ test.describe("Transactions @fork", () => { .getByRole("button", { name: "Continue", exact: true }) .click({ force: true }) - const receipientAddress = "0x47745a7252e119431ccf973c0ebd4279638875a6" - await popup.locator("#send_address").fill(receipientAddress) + await popup.locator("#send_address").fill(ERC20_RECIPIENT) /** * Click `Continue`. @@ -266,8 +316,8 @@ test.describe("Transactions @fork", () => { */ await transactionsHelper.assertTransferScreen( "Ethereum", - "testertesting\\.eth", - "0x47745a7252e119431ccf973c0ebd4279638875a6", + "TEST_ACCOUNT2", + ERC20_RECIPIENT, "0x4774…875a6", "1\\.25", "DAI", @@ -297,7 +347,7 @@ test.describe("Transactions @fork", () => { */ await assetsHelper.assertAssetDetailsPage( /^Ethereum$/, - account2.name, + /TEST_ACCOUNT2/, /^DAI$/, /^2\.62$/, "knownERC20", @@ -327,10 +377,16 @@ test.describe("Transactions @fork", () => { * Import the `testertesting.eth` account using onboarding with a JSON * file. */ - await walletPageHelper.onboardWithJSON(account2) + await walletPageHelper.onboardWithJSON( + "custom", + WALLET_JSON!, + WALLET_PASSWORD!, + ) await walletPageHelper.goToStartPage() await walletPageHelper.setViewportSize() + await walletPageHelper.changeAccountName("TEST_ACCOUNT3") + /** * Verify we're on Ethereum network. Verify common elements on the main * page. @@ -338,24 +394,24 @@ test.describe("Transactions @fork", () => { await walletPageHelper.assertCommonElements( /^Ethereum$/, false, - account2.name, + /TEST_ACCOUNT3/, ) await walletPageHelper.assertAnalyticsBanner() /** * Verify DAI is visible on the asset list and has the correct balance */ - const daiAsset = popup + const usdcAsset = popup .locator("div.asset_list_item") - .filter({ has: popup.locator("span").filter({ hasText: /^DAI$/ }) }) - await expect(daiAsset.getByText(/^2\.62$/)).toBeVisible({ + .filter({ has: popup.locator("span").filter({ hasText: /^USDC$/ }) }) + await expect(usdcAsset.getByText(/^2\.62$/)).toBeVisible({ timeout: 120000, }) - await expect(daiAsset.getByText(/^\$\d+\.\d{2}$/)).toBeVisible({ + await expect(usdcAsset.getByText(/^\$\d+\.\d{2}$/)).toBeVisible({ timeout: 120000, }) - await daiAsset.locator(".asset_icon_send").click({ trial: true }) - await daiAsset.locator(".asset_icon_swap").click({ trial: true }) + await usdcAsset.locator(".asset_icon_send").click({ trial: true }) + await usdcAsset.locator(".asset_icon_swap").click({ trial: true }) /** * Click on the Send button (from header) @@ -370,8 +426,8 @@ test.describe("Transactions @fork", () => { * isn't active. */ await transactionsHelper.assertUnfilledSendAssetScreen( - /^Ethereum$/, - account2.name, + /Ethereum/i, + /TEST_ACCOUNT3/i, "ETH", "\\d+\\.\\d{4}", true, @@ -396,37 +452,37 @@ test.describe("Transactions @fork", () => { /** * Verify ERC-20 token (DAI) is present on the list */ - const daiToken = await popup + const usdcToken = await popup .locator(".token_group") - .filter({ has: popup.locator("div").filter({ hasText: /^DAI$/ }) }) - await expect(daiToken.getByText(/^Dai$/)).toBeVisible() - await expect(daiToken.getByText(/^2\.62$/)).toBeVisible() - await expect(daiToken.locator(".icon")).toBeVisible() - await daiToken.locator(".icon").click({ trial: true }) // TODO: click and verify if correct address is opened + .filter({ has: popup.locator("div").filter({ hasText: /^USDC$/ }) }) + await expect(usdcToken.getByText(/^USDC$/i)).toBeVisible() + await expect(usdcToken.getByText(/^2\.62$/)).toBeVisible() + await expect(usdcToken.locator(".icon")).toBeVisible() + await usdcToken.locator(".icon").click({ trial: true }) // TODO: click and verify if correct address is opened /** * Filter by `dai` and verify that only DAI token is present */ - await popup.getByPlaceholder("Search by name or address").fill("dai") - await expect(daiToken.getByText(/^Dai$/)).toBeVisible() - await expect(daiToken.getByText(/^2\.62$/)).toBeVisible() - await expect(daiToken.locator(".icon")).toBeVisible() - await daiToken.locator(".icon").click({ trial: true }) + await popup.getByPlaceholder("Search by name or address").fill("usdc") + await expect(usdcToken.getByText(/^USDC$/i)).toBeVisible() + await expect(usdcToken.getByText(/^2\.62$/)).toBeVisible() + await expect(usdcToken.locator(".icon")).toBeVisible() + await usdcToken.locator(".icon").click({ trial: true }) await expect(popup.locator(".token_group")).toHaveCount(2) // On a forked environment the asset list shows `DAI` and `UNIDAIETH`. /** * Select DAI token */ - await daiToken.click() + await usdcToken.click() /** * Verify elements on the page after selecting token. Make sure * `Continue` isn't active. */ await transactionsHelper.assertUnfilledSendAssetScreen( - /^Ethereum$/, - account2.name, - "DAI", + /Ethereum/i, + /TEST_ACCOUNT3/i, + "USDC", "2\\.62", false, ) @@ -434,7 +490,7 @@ test.describe("Transactions @fork", () => { /** * Enter amount and receipient. Verify `Continue` isn't active. */ - await popup.getByPlaceholder(/^0\.0$/).fill("1.3456") + await popup.getByPlaceholder(/^0\.0$/).fill("1.34") await expect( popup.locator(".value").getByText(/^\$\d+\.\d{2}$/), ).toBeVisible() @@ -446,8 +502,9 @@ test.describe("Transactions @fork", () => { .getByRole("button", { name: "Continue", exact: true }) .click({ force: true }) - const receipientAddress = "0x47745a7252e119431ccf973c0ebd4279638875a6" - await popup.locator("#send_address").fill(receipientAddress) + await popup + .locator("#send_address") + .fill("0x47745a7252e119431ccf973c0ebd4279638875a6") /** * Click `Continue`. @@ -461,11 +518,11 @@ test.describe("Transactions @fork", () => { */ await transactionsHelper.assertTransferScreen( "Ethereum", - "testertesting\\.eth", + "TEST_ACCOUNT3", "0x47745a7252e119431ccf973c0ebd4279638875a6", "0x4774…875a6", "1\\.34", - "DAI", + "USDC", false, ) @@ -492,11 +549,11 @@ test.describe("Transactions @fork", () => { */ await assetsHelper.assertAssetDetailsPage( /^Ethereum$/, - account2.name, - /^DAI$/, + /TEST_ACCOUNT3/, + /^USDC$/i, /^1\.28$/, "knownERC20", - "https://etherscan.io/token/0x6b175474e89094c44da98b954eedeac495271d0f", + `https://etherscan.io/token/${USDC_CONTRACT}`, ) // This is what we expect currently on forked network. If ve ever fix // displaying activity on fork, we should perform following checks @@ -543,7 +600,7 @@ test.describe("Transactions @fork", () => { await walletPageHelper.assertCommonElements( /^Ethereum$/, false, - account2.name, + /TEST_ACCOUNT3/, ) await walletPageHelper.assertAnalyticsBanner() }) diff --git a/e2e-tests/utils.ts b/e2e-tests/utils.ts index afa6b1fd97..ac3479a87a 100644 --- a/e2e-tests/utils.ts +++ b/e2e-tests/utils.ts @@ -16,12 +16,19 @@ type WalletTestFixtures = { backgroundPage: Worker isExtensionRequest: (request: Request) => boolean waitForExtensionPage: () => Promise + localNodeAlive: boolean } /** * Extended instance of playwright's `test` with our fixtures */ export const test = base.extend({ + localNodeAlive: async ({ request }, use) => { + const alive = await request.get("http://127.0.0.1:8545").catch(() => false) + + await use(!!alive) + }, + context: async ({}, use) => { const pathToExtension = path.resolve(__dirname, "../dist/chrome") const context = await chromium.launchPersistentContext("", { diff --git a/e2e-tests/utils/fork-env-helper.ts b/e2e-tests/utils/fork-env-helper.ts new file mode 100644 index 0000000000..dc6886a68b --- /dev/null +++ b/e2e-tests/utils/fork-env-helper.ts @@ -0,0 +1,108 @@ +import { BrowserContext } from "@playwright/test" +import { BigNumber, utils } from "ethers" +import { FunctionFragment, Interface } from "ethers/lib/utils" + +export default class ForkEnvHelper { + url = "http://127.0.0.1:8545" + + #request_id = 0 + + defaultAccounts = [ + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", + "0x90F79bf6EB2c4f870365E785982E1f101E93b906", + "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65", + "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", + "0x976EA74026E726554dB657fA54763abd0C3a0aa9", + "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955", + "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f", + "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720", + ] + + emulatedAccount: string | null = null + + constructor(private context: BrowserContext) {} + + async send(method: string, params: unknown[]) { + this.#request_id += 1 + + return this.context.request.post(this.url, { + data: JSON.stringify({ + id: this.#request_id, + jsonrpc: "2.0", + method, + params, + }), + headers: { "Content-Type": "application/json" }, + }) + } + + impersonateAccount(addr: string) { + this.emulatedAccount = addr + return this.send("hardhat_impersonateAccount", [addr]) + } + + stopImpersonating(addr: string) { + this.emulatedAccount = null + return this.send("hardhat_stopImpersonatingAccount", [addr]) + } + + setBalance(address: string, amount: BigNumber) { + return this.send("hardhat_setBalance", [address, amount.toHexString()]) + } + + transferERC20( + contract: string, + addressTo: string, + amount: string, + decimals = 18, + ) { + if (this.emulatedAccount === null) { + throw new Error("Must call impersonateAccount first") + } + + this.setBalance(this.emulatedAccount, utils.parseUnits("10.0")) + + const fragment = FunctionFragment.from("transfer(address to, uint amount)") + + const iface = new Interface([fragment]) + + const balance = utils.parseUnits(amount, decimals) + + const data = iface.encodeFunctionData("transfer", [addressTo, balance]) + + return this.send("eth_sendTransaction", [ + { from: this.emulatedAccount, to: contract, data }, + ]) + } + + transferNFT( + addressFrom: string, + addressTo: string, + nftContract: string, + tokenId: string, + ) { + if (this.emulatedAccount === null) { + throw new Error("Must call impersonateAccount first") + } + + this.setBalance(this.emulatedAccount, utils.parseUnits("10.0")) + + const fragment = FunctionFragment.from( + "safeTransferFrom(address from, address to, uint256 tokenId)", + ) + + const iface = new Interface([fragment]) + + const data = iface.encodeFunctionData("safeTransferFrom", [ + addressFrom, + addressTo, + BigNumber.from(tokenId), + ]) + + return this.send("eth_sendTransaction", [ + { from: this.emulatedAccount, to: nftContract, data }, + ]) + } +} diff --git a/e2e-tests/utils/onboarding.ts b/e2e-tests/utils/onboarding.ts index 1ee434bdc4..d35b712aa2 100644 --- a/e2e-tests/utils/onboarding.ts +++ b/e2e-tests/utils/onboarding.ts @@ -43,8 +43,8 @@ export const account1: Account = { export const account2 = { address: "0x6e80164ea60673d64d5d6228beb684a1274bb017", name: /^testertesting\.eth$/, - jsonBody: process.env.TEST_WALLET_JSON_BODY, - jsonPassword: process.env.TEST_WALLET_JSON_PASSWORD, + jsonBody: process.env.FORK_TEST_WALLET_JSON_BODY, + jsonPassword: process.env.FORK_TEST_WALLET_JSON_PASSWORD, } export default class OnboardingHelper { diff --git a/e2e-tests/utils/transactions.ts b/e2e-tests/utils/transactions.ts index 86e73c94bb..cefef25896 100644 --- a/e2e-tests/utils/transactions.ts +++ b/e2e-tests/utils/transactions.ts @@ -37,6 +37,7 @@ export default class TransactionsHelper { const assetBalance = await this.popup.locator(".available") const balanceRegEx = new RegExp(`^Balance: ${regexAssetBalance}$`) expect(assetBalance).toHaveText(balanceRegEx) + if (baseAsset) { expect( await this.popup.getByRole("button", { name: "Max" }).count(), @@ -45,10 +46,12 @@ export default class TransactionsHelper { await expect( this.popup.getByRole("button", { name: "Max" }), ).toBeVisible() + await this.popup .getByRole("button", { name: "Max" }) .click({ trial: true }) } + await expect(this.popup.getByPlaceholder(/^0\.0$/)).toBeVisible() await expect(this.popup.getByText(/^\$-$/)).toBeVisible() diff --git a/e2e-tests/utils/walletPageHelper.ts b/e2e-tests/utils/walletPageHelper.ts index c894f8f9c7..66d97fcff7 100644 --- a/e2e-tests/utils/walletPageHelper.ts +++ b/e2e-tests/utils/walletPageHelper.ts @@ -153,7 +153,7 @@ export default class WalletPageHelper { await expect( this.popup.getByTestId("top_menu_profile_button").last(), - ).toHaveText(accountLabel, { timeout: 240000 }) + ).toHaveText(accountLabel) await this.popup .getByTestId("top_menu_profile_button") .last() @@ -188,9 +188,7 @@ export default class WalletPageHelper { testnet: boolean, 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.getByText("Total account balance")).toBeVisible() await expect(this.popup.getByTestId("wallet_balance")).toHaveText( /^\$(\d|,)+(\.\d{1,2})*$/, ) @@ -206,12 +204,14 @@ export default class WalletPageHelper { await this.popup .getByRole("button", { name: "Receive", exact: true }) .click({ trial: true }) + if (testnet === false) { await this.popup .getByTestId("panel_switcher") .getByText("NFTs", { exact: true }) .click({ trial: true }) } + await this.popup .getByTestId("panel_switcher") .getByText("Assets", { exact: true }) @@ -220,6 +220,7 @@ export default class WalletPageHelper { .getByTestId("panel_switcher") .getByText("Activity", { exact: true }) .click({ trial: true }) + await this.assertBottomWrap() } diff --git a/package.json b/package.json index 019ca97411..11584ce02b 100644 --- a/package.json +++ b/package.json @@ -123,6 +123,7 @@ "dotenv-defaults": "^2.0.2", "dotenv-webpack": "^7.0.3", "eslint": "^8.48.0", + "ethers": "5.7.2", "fork-ts-checker-webpack-plugin": "^6.3.2", "install": "^0.13.0", "jest": "^29.5.0", diff --git a/playwright.config.ts b/playwright.config.ts index 551fc739d8..37a10c9322 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -34,7 +34,7 @@ const config: PlaywrightTestConfig = { */ workers: 1, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: "html", + reporter: CI_ENV ? "github" : "html", /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ diff --git a/scripts/create-json-wallet.mjs b/scripts/create-json-wallet.mjs new file mode 100644 index 0000000000..fb3d28899a --- /dev/null +++ b/scripts/create-json-wallet.mjs @@ -0,0 +1,20 @@ +/* eslint-disable */ +import { Wallet } from "ethers" +import { createInterface } from "node:readline/promises" + +const readline = createInterface({ + input: process.stdin, + output: process.stdout, +}) + +const wallet = Wallet.createRandom() +async function print() { + const password = await readline.question("Encrypted wallet JSON Password:\n") + + const json = await wallet.encrypt(password.trim()) + readline.close() + // eslint-disable-next-line no-console + console.log(json) +} + +print() From 2b840ff87bd038f3d6e37ddf0ef49ab6f21d1c5f Mon Sep 17 00:00:00 2001 From: Jorge Luis <28708889+hyphenized@users.noreply.github.com> Date: Mon, 24 Feb 2025 01:36:37 -0500 Subject: [PATCH 08/17] Update CI e2e commands These should be consistent between local/ci --- .github/workflows/build.yml | 19 ++++++++++++------- e2e-tests/utils/onboarding.ts | 4 ++-- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9fafc225f5..f388231b93 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -193,7 +193,7 @@ jobs: env: E2E_TEST_ONLY_WALLET_JSON_BODY: ${{ secrets.E2E_TEST_ONLY_WALLET_JSON_BODY }} E2E_TEST_ONLY_WALLET_JSON_PASSWORD: ${{ secrets.E2E_TEST_ONLY_WALLET_JSON_PASSWORD }} - run: xvfb-run npx playwright test --grep-invert @expensive + run: xvfb-run yarn e2e:regular #env: # DEBUG: pw:api* - name: Run costing Playwright tests @@ -202,9 +202,9 @@ jobs: || contains(github.head_ref, 'e2e') || needs.detect-if-flag-changed.outputs.path-filter == 'true' env: - TEST_WALLET_JSON_BODY: ${{ secrets.TEST_WALLET_JSON_BODY }} - TEST_WALLET_JSON_PASSWORD: ${{ secrets.TEST_WALLET_JSON_PASSWORD }} - run: xvfb-run npx playwright test --grep @expensive + TESTNET_TEST_WALLET_JSON_BODY: ${{ secrets.TEST_WALLET_JSON_BODY }} + TESTNET_TEST_WALLET_JSON_PASSWORD: ${{ secrets.TEST_WALLET_JSON_PASSWORD }} + run: xvfb-run yarn e2e:testnet - uses: actions/upload-artifact@v4 if: ${{ failure() || cancelled() }} with: @@ -245,8 +245,13 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 with: - cache-key: anvil-21802314-${{ github.ref_name }}-${{ github.sha }} - cache-restore-keys: | + cache: false + - name: Restore Anvil cache + uses: actions/cache/restore@v3 + with: + path: /home/runner/.foundry/cache + key: anvil-21802314-${{ github.ref_name }}-${{ github.sha }} + restore-keys: | anvil-21802314-${{ github.ref_name }}-${{ github.sha }} anvil-21802314-${{ github.ref_name }}- anvil-21802314- @@ -269,7 +274,7 @@ jobs: FORK_TEST_WALLET_JSON_BODY: ${{ secrets.TEST_WALLET_JSON_BODY }} FORK_TEST_WALLET_JSON_PASSWORD: ${{ secrets.TEST_WALLET_JSON_PASSWORD }} USE_MAINNET_FORK: true - run: xvfb-run npx playwright test + run: xvfb-run yarn e2e:fork - uses: actions/upload-artifact@v4 if: ${{ failure() || cancelled() }} with: diff --git a/e2e-tests/utils/onboarding.ts b/e2e-tests/utils/onboarding.ts index d35b712aa2..bff4c87475 100644 --- a/e2e-tests/utils/onboarding.ts +++ b/e2e-tests/utils/onboarding.ts @@ -43,8 +43,8 @@ export const account1: Account = { export const account2 = { address: "0x6e80164ea60673d64d5d6228beb684a1274bb017", name: /^testertesting\.eth$/, - jsonBody: process.env.FORK_TEST_WALLET_JSON_BODY, - jsonPassword: process.env.FORK_TEST_WALLET_JSON_PASSWORD, + jsonBody: process.env.TESTNET_TEST_WALLET_JSON_BODY, + jsonPassword: process.env.TESTNET_TEST_WALLET_JSON_PASSWORD, } export default class OnboardingHelper { From 96b6961237e92bc92dd4ad1871a8e0e86026fe2f Mon Sep 17 00:00:00 2001 From: Jorge Luis <28708889+hyphenized@users.noreply.github.com> Date: Mon, 24 Feb 2025 01:37:47 -0500 Subject: [PATCH 09/17] Fix anvil ci cache persistance By sending sigint to the process this script will stall until anvil polled requests finish so it can flush the cache to disk before the job completes --- .github/workflows/build.yml | 13 +++++++++++++ ci/stop-anvil.sh | 15 +++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 ci/stop-anvil.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f388231b93..272e0fff1e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -283,3 +283,16 @@ jobs: test-results/ #videos/ retention-days: 30 + # Wait for anvil to finish and save its cache to disk + - name: Stop Anvil + if: ${{ !cancelled() }} + run: sh ./ci/stop-anvil.sh + - name: Save Anvil Cache + uses: actions/cache/save@v3 + # We want to save the cache even if the tests failed. Without the cache + # the tests almost always fail, so without `if: always()` we would have + # problem with creating the first cache. + if: always() + with: + path: /home/runner/.foundry/cache + key: anvil-21802314-${{ github.ref_name }}-${{ github.sha }} diff --git a/ci/stop-anvil.sh b/ci/stop-anvil.sh new file mode 100644 index 0000000000..dcbe0cadc3 --- /dev/null +++ b/ci/stop-anvil.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +# This script sends a SIGINT to anvil so it flushes its cache +# to disk and terminates properly + +PID=$(pgrep -o anvil) +while kill -2 "$PID" 2>/dev/null; do + sleep 1; +done; + +echo "Process $PID has exited"; + +SIZE=$(du -sh $HOME/.foundry/cache | awk '{print $1}') + +echo "Cache size: $SIZE" \ No newline at end of file From 905ab2ab838af390a37f42efc3b503d0c1944b22 Mon Sep 17 00:00:00 2001 From: Jorge Luis <28708889+hyphenized@users.noreply.github.com> Date: Mon, 24 Feb 2025 01:44:40 -0500 Subject: [PATCH 10/17] Add testid to wallet balance loader --- e2e-tests/regular/onboarding.spec.ts | 1 + e2e-tests/utils/walletPageHelper.ts | 3 +++ ui/components/Shared/SharedSkeletonLoader.tsx | 17 +++++++++++------ .../Wallet/WalletAccountBalanceControl.tsx | 1 + 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/e2e-tests/regular/onboarding.spec.ts b/e2e-tests/regular/onboarding.spec.ts index 4ef5565b74..09eb2f8b28 100644 --- a/e2e-tests/regular/onboarding.spec.ts +++ b/e2e-tests/regular/onboarding.spec.ts @@ -20,6 +20,7 @@ test.describe("Onboarding", () => { ).toHaveText(readOnlyAddress) }).toPass() + await expect(popup.getByTestId("account_balance_loader")).not.toBeVisible() expect(popup.getByTestId("wallet_balance").innerText()).not.toContain("$0") }) diff --git a/e2e-tests/utils/walletPageHelper.ts b/e2e-tests/utils/walletPageHelper.ts index 66d97fcff7..dbcef1c116 100644 --- a/e2e-tests/utils/walletPageHelper.ts +++ b/e2e-tests/utils/walletPageHelper.ts @@ -188,6 +188,9 @@ export default class WalletPageHelper { testnet: boolean, accountLabel: RegExp, ): Promise { + await expect( + this.popup.getByTestId("account_balance_loader"), + ).not.toBeVisible({ timeout: 20000 }) await expect(this.popup.getByText("Total account balance")).toBeVisible() await expect(this.popup.getByTestId("wallet_balance")).toHaveText( /^\$(\d|,)+(\.\d{1,2})*$/, diff --git a/ui/components/Shared/SharedSkeletonLoader.tsx b/ui/components/Shared/SharedSkeletonLoader.tsx index 17d204f26b..258098a110 100644 --- a/ui/components/Shared/SharedSkeletonLoader.tsx +++ b/ui/components/Shared/SharedSkeletonLoader.tsx @@ -6,10 +6,19 @@ export default function SharedSkeletonLoader(props: { height?: number borderRadius?: number children?: ReactNode + testId?: string isLoaded?: boolean style?: CSSProperties }): ReactElement { - const { width, height, borderRadius, isLoaded, style, children } = props + const { + width, + height, + borderRadius, + isLoaded, + testId = "loading_skeleton", + style, + children, + } = props // Want to return a ReactElement to make this maximally easy to integrate, // whereas children can be a ReactNode; Fragment will let us achieve that. @@ -17,11 +26,7 @@ export default function SharedSkeletonLoader(props: { if (isLoaded) return <>{children} return ( -
+