From d7f5785ba48105732c25d3a649341eb3167deabf Mon Sep 17 00:00:00 2001 From: Augusto Lemble Date: Wed, 31 Dec 2025 15:11:02 -0300 Subject: [PATCH 1/9] feat(e2e): add retry with incremental timeout Improve e2e test reliability by increasing retries and adding incremental timeout on each retry (+20s per attempt). - Add custom test fixture with timeout that increases on retries - Increase retries to 3 in CI, 1 locally - Update all spec files to use the custom fixture --- e2e/fixtures/test.ts | 22 ++++++++++++++++++++++ e2e/tests/address.spec.ts | 2 +- e2e/tests/block.spec.ts | 2 +- e2e/tests/token.spec.ts | 2 +- e2e/tests/transaction.spec.ts | 2 +- playwright.config.ts | 2 +- 6 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 e2e/fixtures/test.ts diff --git a/e2e/fixtures/test.ts b/e2e/fixtures/test.ts new file mode 100644 index 0000000..65b2278 --- /dev/null +++ b/e2e/fixtures/test.ts @@ -0,0 +1,22 @@ +import { test as base } from "@playwright/test"; + +/** + * Custom test fixture that increases timeout on retries. + * Base timeout: 60 seconds + * Formula: baseTimeout + (20 seconds * retryCount) + * - Retry 0: 60s + * - Retry 1: 80s + * - Retry 2: 100s + * - Retry 3: 120s + */ +export const test = base.extend({}); + +const BASE_TIMEOUT = 60000; +const TIMEOUT_INCREMENT = 20000; // 20 seconds per retry + +test.beforeEach(async ({}, testInfo) => { + const newTimeout = BASE_TIMEOUT + TIMEOUT_INCREMENT * testInfo.retry; + testInfo.setTimeout(newTimeout); +}); + +export { expect } from "@playwright/test"; diff --git a/e2e/tests/address.spec.ts b/e2e/tests/address.spec.ts index d1259c7..3ef06f3 100644 --- a/e2e/tests/address.spec.ts +++ b/e2e/tests/address.spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from "@playwright/test"; +import { test, expect } from "../fixtures/test"; import { AddressPage } from "../pages/address.page"; import { MAINNET } from "../fixtures/mainnet"; diff --git a/e2e/tests/block.spec.ts b/e2e/tests/block.spec.ts index 5adf60c..daa8737 100644 --- a/e2e/tests/block.spec.ts +++ b/e2e/tests/block.spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from "@playwright/test"; +import { test, expect } from "../fixtures/test"; import { BlockPage } from "../pages/block.page"; import { MAINNET } from "../fixtures/mainnet"; diff --git a/e2e/tests/token.spec.ts b/e2e/tests/token.spec.ts index e91b7fa..710f036 100644 --- a/e2e/tests/token.spec.ts +++ b/e2e/tests/token.spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from "@playwright/test"; +import { test, expect } from "../fixtures/test"; import { MAINNET } from "../fixtures/mainnet"; // Helper to wait for token content or error diff --git a/e2e/tests/transaction.spec.ts b/e2e/tests/transaction.spec.ts index eba452c..6f7724a 100644 --- a/e2e/tests/transaction.spec.ts +++ b/e2e/tests/transaction.spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from "@playwright/test"; +import { test, expect } from "../fixtures/test"; import { TransactionPage } from "../pages/transaction.page"; import { MAINNET } from "../fixtures/mainnet"; diff --git a/playwright.config.ts b/playwright.config.ts index 392d53d..40caa93 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -4,7 +4,7 @@ export default defineConfig({ testDir: "./e2e/tests", fullyParallel: true, forbidOnly: !!process.env.CI, - retries: process.env.CI ? 2 : 0, + retries: process.env.CI ? 3 : 1, workers: process.env.CI ? 1 : undefined, reporter: "html", timeout: 60000, From d7c0c8cff01ad07586f15b4fdb72a2113caf97bc Mon Sep 17 00:00:00 2001 From: Augusto Lemble Date: Thu, 1 Jan 2026 18:44:25 -0300 Subject: [PATCH 2/9] feat(e2e): add Base network e2e tests Add comprehensive e2e test suite for Base network (chain ID 8453): Blocks: - Genesis block, early blocks, pre/post-upgrade blocks - Real data from blocks 0, 1M, 10M, 25M - Gas details, size, fee recipient verification Transactions: - Aerodrome DEX swap (swapExactTokensForTokens) - USDC transfer (transferWithAuthorization) - Input data and attributes verification Addresses: - ERC20 tokens: USDC, USDbC, WETH, AERO - Aerodrome Router DEX contract - OP Stack predeploys: SequencerFeeVault, L1Block, GasPriceOracle, L2StandardBridge, L2CrossDomainMessenger Includes upgrade timestamps for Canyon, Delta, Ecotone, Fjord, Granite, Holocene, and Isthmus. --- e2e/fixtures/base.ts | 228 ++++++++++++++++++++ e2e/tests/base.spec.ts | 471 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 699 insertions(+) create mode 100644 e2e/fixtures/base.ts create mode 100644 e2e/tests/base.spec.ts diff --git a/e2e/fixtures/base.ts b/e2e/fixtures/base.ts new file mode 100644 index 0000000..5b59b60 --- /dev/null +++ b/e2e/fixtures/base.ts @@ -0,0 +1,228 @@ +// cspell:ignore aerodrome superchain sequencer +export const BASE = { + chainId: "8453", + + // Base genesis: June 15, 2023 (timestamp 1686789347) + // Block time: 2 seconds + // Upgrades follow Superchain-wide activation timestamps + + blocks: { + // Genesis block - Base mainnet launch (June 15, 2023) + "0": { + number: 0, + txCount: 0, + gasUsed: "0", + gasLimit: "30,000,000", + hash: "0xf712aa9241cc24369b143cf6dce85f0902a9731e70d66818a3a5845b296c73dd", + parentHash: "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + // Block 1,000,000 - Early Base block (July 8, 2023) + // 1 transaction, minimal gas usage + "1000000": { + number: 1000000, + txCount: 1, + gasUsed: "46,913", + gasUsedPercent: "0.2%", + gasLimit: "30,000,000", + size: "869 bytes", + baseFeePerGas: "50 wei", + feeRecipientPartial: "0x42000000", + }, + // Block 10,000,000 - Pre-Ecotone (February 1, 2024) + // Before EIP-4844 blob support + "10000000": { + number: 10000000, + txCount: 11, + gasUsed: "979,572", + gasUsedPercent: "3.3%", + gasLimit: "30,000,000", + size: "4,718 bytes", + baseFeePerGas: "265 wei", + feeRecipientPartial: "0x42000000", + }, + // Block 25,000,000 - Post-Holocene (January 13, 2025) + // Recent block with increased gas limit + "25000000": { + number: 25000000, + txCount: 248, + gasUsed: "59,984,755", + gasUsedPercent: "25.0%", + gasLimit: "240,000,000", + size: "91,675 bytes", + baseFeePerGas: "0.020162741 Gwei", + feeRecipientPartial: "0x42000000", + }, + }, + + transactions: { + // ============================================ + // EIP-1559 TRANSACTIONS (Type 2) - Standard on Base + // ============================================ + + // Aerodrome DEX swap transaction - swapExactTokensForTokens + "0x961cf2c57f006d8c6fdbe266b2ef201159dd135dc560155e8c16d307ee321681": { + hash: "0x961cf2c57f006d8c6fdbe266b2ef201159dd135dc560155e8c16d307ee321681", + type: 2, + from: "0xF9b6a1EB0190bf76274B0876957Ee9F4f508Af41", + to: "0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43", // Aerodrome Router + value: "0x0", + gasUsed: "176,474", + status: "success" as const, + hasInputData: true, + method: "swapExactTokensForTokens", + }, + + // USDC transferWithAuthorization - ERC20 interaction + "0x6b212a5069286d710f388b948364452d28b8c33e0f39b8f50b394ff4deff1f03": { + hash: "0x6b212a5069286d710f388b948364452d28b8c33e0f39b8f50b394ff4deff1f03", + type: 2, + from: "0x3A70788150c7645a21b95b7062ab1784D3cc2104", + to: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC + value: "0x0", + gasUsed: "86,212", + status: "success" as const, + hasInputData: true, + method: "transferWithAuthorization", + }, + + // ============================================ + // SYSTEM TRANSACTIONS - OP Stack specific + // ============================================ + + // L1Block setL1BlockValues - System transaction (Type 126) + // First transaction in block 1 - sets L1 block attributes + "0x68736fb400dc3b69ab1c4c2cbe75a600aa7ba7cd8d025797ebd8a0108955c91f": { + hash: "0x68736fb400dc3b69ab1c4c2cbe75a600aa7ba7cd8d025797ebd8a0108955c91f", + type: 126, // Deposit transaction type + from: "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", // System address + to: "0x4200000000000000000000000000000000000015", // L1Block + value: "0x0", + gasUsed: "64,013", + status: "success" as const, + hasInputData: true, + method: "setL1BlockValues", + }, + }, + + addresses: { + // ============================================ + // ERC20 TOKENS + // ============================================ + + // USDC on Base - Circle's native USDC + usdc: { + address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + type: "erc20" as const, + symbol: "USDC", + name: "USD Coin", + decimals: 6, + }, + // USDbC - Bridged USDC (legacy) + usdbc: { + address: "0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA", + type: "erc20" as const, + symbol: "USDbC", + name: "USD Base Coin", + decimals: 6, + }, + // WETH on Base (predeploy) + weth: { + address: "0x4200000000000000000000000000000000000006", + type: "erc20" as const, + symbol: "WETH", + name: "Wrapped Ether", + decimals: 18, + }, + // AERO token - Aerodrome governance token + aero: { + address: "0x940181a94A35A4569E4529A3CDFb74e38FD98631", + type: "erc20" as const, + symbol: "AERO", + name: "Aerodrome", + decimals: 18, + }, + + // ============================================ + // DEX CONTRACTS + // ============================================ + + // Aerodrome Router - Main DEX on Base + aerodromeRouter: { + address: "0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43", + type: "contract" as const, + name: "Aerodrome: Router", + }, + + // ============================================ + // SYSTEM CONTRACTS (OP Stack predeploys) + // ============================================ + + // SequencerFeeVault - Receives transaction fees (fee recipient) + sequencerFeeVault: { + address: "0x4200000000000000000000000000000000000011", + type: "contract" as const, + name: "SequencerFeeVault", + }, + // L2CrossDomainMessenger - Bridge messaging + l2CrossDomainMessenger: { + address: "0x4200000000000000000000000000000000000007", + type: "contract" as const, + name: "L2CrossDomainMessenger", + }, + // L2StandardBridge - Token bridging + l2StandardBridge: { + address: "0x4200000000000000000000000000000000000010", + type: "contract" as const, + name: "L2StandardBridge", + }, + // GasPriceOracle - L1 fee calculation + gasPriceOracle: { + address: "0x420000000000000000000000000000000000000F", + type: "contract" as const, + name: "GasPriceOracle", + }, + // L1Block - L1 block attributes + l1Block: { + address: "0x4200000000000000000000000000000000000015", + type: "contract" as const, + name: "L1Block", + }, + }, + + // Upgrade timestamps (Unix) for reference + upgrades: { + canyon: { + timestamp: 1704992401, + date: "2024-01-11T16:00:01Z", + }, + delta: { + timestamp: 1708560000, + date: "2024-02-22T00:00:00Z", + }, + ecotone: { + timestamp: 1710374401, + date: "2024-03-14T00:00:01Z", + description: "EIP-4844 blob support, ~90% fee reduction", + }, + fjord: { + timestamp: 1720627201, + date: "2024-07-10T16:00:01Z", + description: "Brotli compression, RIP-7212 secp256r1 precompile", + }, + granite: { + timestamp: 1726070401, + date: "2024-09-11T16:00:01Z", + description: "Gas optimizations, permissionless fault proofs", + }, + holocene: { + timestamp: 1736445601, + date: "2025-01-09T18:00:01Z", + description: "Stricter derivation, EIP-1559 configurability", + }, + isthmus: { + timestamp: 1746806401, + date: "2025-05-09T16:00:01Z", + description: "Pectra L2 support", + }, + }, +}; diff --git a/e2e/tests/base.spec.ts b/e2e/tests/base.spec.ts new file mode 100644 index 0000000..ed3bf89 --- /dev/null +++ b/e2e/tests/base.spec.ts @@ -0,0 +1,471 @@ +import { test, expect } from "../fixtures/test"; +import { BlockPage } from "../pages/block.page"; +import { AddressPage } from "../pages/address.page"; +import { TransactionPage } from "../pages/transaction.page"; +import { BASE } from "../fixtures/base"; + +const CHAIN_ID = BASE.chainId; + +// Transaction hash constants for readability +const AERODROME_SWAP = "0x961cf2c57f006d8c6fdbe266b2ef201159dd135dc560155e8c16d307ee321681"; +const USDC_TRANSFER = "0x6b212a5069286d710f388b948364452d28b8c33e0f39b8f50b394ff4deff1f03"; + +// Helper to wait for block content or error +async function waitForBlockContent(page: import("@playwright/test").Page) { + await expect( + page + .locator("text=Transactions:") + .or(page.locator("text=Error:")) + .or(page.locator("text=Something went wrong")) + ).toBeVisible({ timeout: 45000 }); + + return ( + !(await page.locator("text=Error:").isVisible()) && + !(await page.locator("text=Something went wrong").isVisible()) + ); +} + +// Helper to wait for address content or error +async function waitForAddressContent(page: import("@playwright/test").Page) { + await expect( + page + .locator("text=Balance:") + .or(page.locator("text=Error:")) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout: 45000 }); + + return ( + !(await page.locator("text=Error:").isVisible()) && + !(await page.locator("text=Something went wrong").isVisible()) + ); +} + +// Helper to wait for transaction content or error +async function waitForTxContent(page: import("@playwright/test").Page) { + await expect( + page + .locator("text=Transaction Hash:") + .or(page.locator("text=Error:")) + .or(page.locator("text=Something went wrong")) + ).toBeVisible({ timeout: 45000 }); + + return ( + !(await page.locator("text=Error:").isVisible()) && + !(await page.locator("text=Something went wrong").isVisible()) + ); +} + +// ============================================ +// BLOCK TESTS +// ============================================ + +test.describe("Base Network - Block Page", () => { + test("genesis block #0 - Base mainnet launch", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = BASE.blocks["0"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + // Header section + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.timestampAge).toBeVisible(); + + // Genesis block should have 0 transactions + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator("text=0 transactions in this block")).toBeVisible(); + + // Gas Used should be 0 + await expect(page.locator("text=Gas Used:")).toBeVisible(); + + // Gas Limit + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("block #1,000,000 - early Base block with gas details", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = BASE.blocks["1000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + // Header + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Transaction count + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator(`text=${block.txCount} transaction`)).toBeVisible(); + + // Gas Used with percentage + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator(`text=${block.gasUsed}`)).toBeVisible(); + + // Gas Limit + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + await expect(page.locator(`text=${block.gasLimit}`)).toBeVisible(); + + // Size + await expect(page.locator("text=Size:")).toBeVisible(); + await expect(page.locator(`text=${block.size}`)).toBeVisible(); + + // Base Fee Per Gas (Base always has EIP-1559) + await expect(page.locator("text=Base Fee Per Gas:")).toBeVisible(); + + // Fee Recipient (SequencerFeeVault) + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + const feeRecipient = await blockPage.getFeeRecipient(); + expect(feeRecipient.toLowerCase()).toContain(block.feeRecipientPartial.toLowerCase()); + } + }); + + test("block #10,000,000 - pre-Ecotone block", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = BASE.blocks["10000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Transaction count + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator(`text=${block.txCount} transactions in this block`)).toBeVisible(); + + // Gas details + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator(`text=${block.gasUsed}`)).toBeVisible(); + + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + await expect(page.locator(`text=${block.gasLimit}`)).toBeVisible(); + + // Size + await expect(page.locator("text=Size:")).toBeVisible(); + await expect(page.locator(`text=${block.size}`)).toBeVisible(); + } + }); + + test("block #25,000,000 - post-Holocene with increased gas limit", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = BASE.blocks["25000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + + // Should have many transactions + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator(`text=${block.txCount} transactions in this block`)).toBeVisible(); + + // Gas details - higher gas limit post-Holocene + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + await expect(page.locator(`text=${block.gasLimit}`)).toBeVisible(); + + // Size + await expect(page.locator("text=Size:")).toBeVisible(); + await expect(page.locator(`text=${block.size}`)).toBeVisible(); + + // Base Fee Per Gas + await expect(page.locator("text=Base Fee Per Gas:")).toBeVisible(); + } + }); + + test("genesis block more details section shows correct hashes", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = BASE.blocks["0"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + // Verify hash field labels + await expect(page.getByText("Hash:", { exact: true })).toBeVisible(); + await expect(page.getByText("Parent Hash:", { exact: true })).toBeVisible(); + + // Genesis block hash + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + // Genesis parent hash (all zeros) + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block navigation buttons work on Base", async ({ page }) => { + const blockPage = new BlockPage(page); + await blockPage.goto(BASE.blocks["1000000"].number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + await expect(blockPage.navPrevBtn).toBeVisible(); + await expect(blockPage.navNextBtn).toBeVisible(); + } + }); + + test("handles invalid block number gracefully", async ({ page }) => { + const blockPage = new BlockPage(page); + await blockPage.goto(999999999999, CHAIN_ID); + + await expect( + blockPage.errorText.or(blockPage.container).or(page.locator("text=Something went wrong")) + ).toBeVisible({ timeout: 30000 }); + }); +}); + +// ============================================ +// TRANSACTION TESTS +// ============================================ + +test.describe("Base Network - Transaction Page", () => { + test("displays Aerodrome DEX swap with all details", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = BASE.transactions[AERODROME_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + // Verify core transaction details + await expect(page.locator("text=Transaction Hash:")).toBeVisible(); + await expect(page.locator("text=Status:")).toBeVisible(); + await expect(page.locator("text=Block:")).toBeVisible(); + await expect(page.locator("text=From:")).toBeVisible(); + await expect(page.locator("text=To:")).toBeVisible(); + + // Verify gas information + await expect(page.locator("text=Gas Limit")).toBeVisible(); + } + }); + + test("shows correct from and to addresses for swap", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = BASE.transactions[AERODROME_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + const from = await txPage.getFromAddress(); + expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); + + const to = await txPage.getToAddress(); + expect(to?.toLowerCase()).toContain(tx.to?.toLowerCase().slice(0, 10)); + } + }); + + test("displays USDC transfer transaction", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = BASE.transactions[USDC_TRANSFER]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + await expect(page.locator("text=Transaction Hash:")).toBeVisible(); + await expect(page.locator("text=Status:")).toBeVisible(); + await expect(page.locator("text=From:")).toBeVisible(); + await expect(page.locator("text=To:")).toBeVisible(); + + // To address should be USDC contract + const to = await txPage.getToAddress(); + expect(to?.toLowerCase()).toContain(tx.to?.toLowerCase().slice(0, 10)); + } + }); + + test("displays transaction with input data", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = BASE.transactions[AERODROME_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + // Contract interaction should have input data + await expect(page.locator("text=Input Data:")).toBeVisible(); + } + }); + + test("displays other attributes section", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = BASE.transactions[AERODROME_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + await expect(page.locator("text=Other Attributes:")).toBeVisible(); + await expect(page.locator("text=Nonce:")).toBeVisible(); + await expect(page.locator("text=Position:")).toBeVisible(); + } + }); + + test("displays block number link", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = BASE.transactions[AERODROME_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + await expect(page.locator("text=Block:")).toBeVisible(); + const blockValue = await txPage.getBlockNumber(); + expect(blockValue).toBeTruthy(); + } + }); + + test("handles invalid tx hash gracefully", async ({ page }) => { + const txPage = new TransactionPage(page); + await txPage.goto("0xinvalid", CHAIN_ID); + + await expect( + txPage.errorText.or(txPage.container).or(page.locator("text=Something went wrong")) + ).toBeVisible({ timeout: 30000 }); + }); +}); + +// ============================================ +// ADDRESS TESTS +// ============================================ + +test.describe("Base Network - Address Page", () => { + test("displays USDC contract details", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = BASE.addresses.usdc; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + await expect(page.locator("text=Contract Details")).toBeVisible(); + } + }); + + test("displays USDbC (bridged USDC) contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = BASE.addresses.usdbc; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays WETH predeploy contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = BASE.addresses.weth; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays AERO token contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = BASE.addresses.aero; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays Aerodrome Router contract with details", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = BASE.addresses.aerodromeRouter; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + await expect(page.locator("text=Contract Details")).toBeVisible(); + } + }); + + test("displays SequencerFeeVault system contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = BASE.addresses.sequencerFeeVault; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays GasPriceOracle system contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = BASE.addresses.gasPriceOracle; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays L1Block system contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = BASE.addresses.l1Block; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays L2StandardBridge contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = BASE.addresses.l2StandardBridge; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays L2CrossDomainMessenger contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = BASE.addresses.l2CrossDomainMessenger; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); +}); From 2ac819e8869a4b6045bdceb03f4b75f8db1471f6 Mon Sep 17 00:00:00 2001 From: Augusto Lemble Date: Thu, 1 Jan 2026 19:26:10 -0300 Subject: [PATCH 3/9] feat(e2e): add Arbitrum network e2e tests Add comprehensive e2e tests for Arbitrum One (chain ID 42161) covering blocks, transactions, and addresses across network history including Nitro upgrade and ArbOS versions. --- e2e/fixtures/arbitrum.ts | 260 +++++++++++++++++ e2e/tests/arbitrum.spec.ts | 570 +++++++++++++++++++++++++++++++++++++ e2e/tests/base.spec.ts | 4 +- 3 files changed, 832 insertions(+), 2 deletions(-) create mode 100644 e2e/fixtures/arbitrum.ts create mode 100644 e2e/tests/arbitrum.spec.ts diff --git a/e2e/fixtures/arbitrum.ts b/e2e/fixtures/arbitrum.ts new file mode 100644 index 0000000..08dd045 --- /dev/null +++ b/e2e/fixtures/arbitrum.ts @@ -0,0 +1,260 @@ +// cspell:ignore arbos nitro uniswap sequencer +export const ARBITRUM = { + chainId: "42161", + + // Arbitrum One mainnet launched: August 31, 2021 + // Nitro upgrade: Block #22,207,817 (August 31, 2022) + // Block time: ~0.25 seconds (variable) + + blocks: { + // Genesis block - Arbitrum One launch + "0": { + number: 0, + txCount: 0, + gasUsed: "0", + gasLimit: "288,000,000", + hash: "0x7ee576b35482195fc49205cec9af72ce14f003b9ae69f6ba0faef4514be8b442", + }, + // Nitro upgrade block (August 31, 2022) + // Massive upgrade - new architecture, faster, cheaper + "22207817": { + number: 22207817, + txCount: 0, + gasUsed: "0", + size: "553 bytes", + upgradeNote: "Nitro upgrade - new architecture", + }, + // Block 100,000,000 (June 11, 2023) + // Post-Nitro, pre-ArbOS 11 + "100000000": { + number: 100000000, + txCount: 4, + gasUsed: "2,059,307", + gasUsedPercent: "0.0%", // Very large gas limit on Arbitrum + gasLimit: "1,125,899,906,842,624", + size: "1,225 bytes", + baseFeePerGas: "0.1 Gwei", + // Fee recipient is the sequencer address + feeRecipientPartial: "0xa4b00000", + // More details section + hash: "0xb5aeb03c97e45c59596b70905d077663bccfea4533bf3b2c3264871725ea86a8", + parentHash: "0x1e3abf9d4545f0ed50137b68e1a5044fcad217d492ba2d92585cc101bbf03c9d", + nonce: "0x00000000000dd6ce", + }, + // Block 200,000,000 (April 11, 2024) + // Post-ArbOS 20 Atlas (Dencun support) + "200000000": { + number: 200000000, + txCount: 2, + gasUsed: "55,132", + gasUsedPercent: "0.0%", + gasLimit: "1,125,899,906,842,624", + size: "801 bytes", + baseFeePerGas: "0.01 Gwei", + feeRecipientPartial: "0xa4b00000", + // More details section + hash: "0xfbb039d0d0e358b4d65f3df3058026fe5576beee3ed1fa2c1ad677d2efe0f3c1", + parentHash: "0x76bb92461bfba3e3d0dffd589b47170979891096d39f0173b41b7ce25bb9b5b1", + }, + // Block 300,000,000 (January 28, 2025) + // Post-ArbOS 32 Bianca (Stylus) + "300000000": { + number: 300000000, + txCount: 4, + gasUsed: "913,478", + gasUsedPercent: "0.0%", + gasLimit: "1,125,899,906,842,624", + size: "1,073 bytes", + baseFeePerGas: "0.01 Gwei", + feeRecipientPartial: "0xa4b00000", + }, + }, + + transactions: { + // ============================================ + // LEGACY TRANSACTIONS (Type 0) + // ============================================ + + // Uniswap V3 swap via multicall - Legacy transaction + "0x87815a816c02b5a563a026e4a37d423734204b50972e75284b62f05e4134ae44": { + hash: "0x87815a816c02b5a563a026e4a37d423734204b50972e75284b62f05e4134ae44", + type: 0, // Legacy transaction + from: "0x6Cd9642Af3991e761C2785f9C958F148d6AeA4F8", + to: "0xE592427A0AEce92De3Edee1F18E0157C05861564", // Uniswap V3 Router + value: "0x31c7fb4fe7a0717", // ~0.224 ETH + blockNumber: 416908399, + gas: "1,200,000", + gasUsed: "143,833", + gasPrice: "0.01 Gwei", + nonce: 82, + status: "success" as const, + hasInputData: true, + method: "multicall", + }, + + // ============================================ + // EIP-1559 TRANSACTIONS (Type 2) + // ============================================ + + // USDC transfer (EIP-1559) + "0x160687cbf03f348cf36997dbab53abbd32d91af5971bccac4cfa1577da27607e": { + hash: "0x160687cbf03f348cf36997dbab53abbd32d91af5971bccac4cfa1577da27607e", + type: 2, // EIP-1559 + from: "0xb7990f2266a97A1f06b0F1828b2fE46B3582456F", + to: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", // USDC + value: "0x0", + blockNumber: 416908475, + gas: "45,632", + gasUsed: "40,235", + maxFeePerGas: "0.01 Gwei", + maxPriorityFeePerGas: "0.000159827 Gwei", + nonce: 4, + status: "success" as const, + hasInputData: true, + method: "transfer", + }, + }, + + addresses: { + // ============================================ + // ERC20 TOKENS + // ============================================ + + // Native USDC on Arbitrum - Circle's native USDC + usdc: { + address: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", + type: "erc20" as const, + symbol: "USDC", + name: "USD Coin", + decimals: 6, + }, + // Bridged USDC.e (legacy) + usdce: { + address: "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", + type: "erc20" as const, + symbol: "USDC.e", + name: "Bridged USDC", + decimals: 6, + }, + // WETH on Arbitrum + weth: { + address: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", + type: "erc20" as const, + symbol: "WETH", + name: "Wrapped Ether", + decimals: 18, + }, + // ARB token - Arbitrum governance token + arb: { + address: "0x912CE59144191C1204E64559FE8253a0e49E6548", + type: "erc20" as const, + symbol: "ARB", + name: "Arbitrum", + decimals: 18, + }, + // GMX token + gmx: { + address: "0xfc5A1A6EB076a2C7aD06eD22C90d7E710E35ad0a", + type: "erc20" as const, + symbol: "GMX", + name: "GMX", + decimals: 18, + }, + + // ============================================ + // DEX CONTRACTS + // ============================================ + + // Uniswap V3 Router + uniswapV3Router: { + address: "0xE592427A0AEce92De3Edee1F18E0157C05861564", + type: "contract" as const, + name: "Uniswap V3: Swap Router", + }, + // Uniswap Universal Router + uniswapUniversalRouter: { + address: "0x4C60051384bd2d3C01bfc845Cf5F4b44bcbE9de5", + type: "contract" as const, + name: "Uniswap: Universal Router", + }, + // GMX Vault + gmxVault: { + address: "0x489ee077994B6658eAfA855C308275EAd8097C4A", + type: "contract" as const, + name: "GMX: Vault", + }, + // GMX Position Router + gmxPositionRouter: { + address: "0xb87a436B93fFe9D75c5cFA7baCFFF96430b09868", + type: "contract" as const, + name: "GMX: Position Router", + }, + + // ============================================ + // ARBITRUM SYSTEM CONTRACTS (precompiles) + // ============================================ + + // ArbSys - Arbitrum system precompile + arbSys: { + address: "0x0000000000000000000000000000000000000064", + type: "contract" as const, + name: "ArbSys", + }, + // ArbRetryableTx - Retryable ticket system + arbRetryableTx: { + address: "0x000000000000000000000000000000000000006E", + type: "contract" as const, + name: "ArbRetryableTx", + }, + // NodeInterface - Node queries + nodeInterface: { + address: "0x00000000000000000000000000000000000000C8", + type: "contract" as const, + name: "NodeInterface", + }, + }, + + // ArbOS upgrade history + upgrades: { + nitro: { + block: 22207817, + date: "2022-08-31T14:32:22Z", + description: "Nitro upgrade - new architecture, faster, cheaper", + }, + arbos11: { + timestamp: 1708809673, + date: "2024-02-24T20:01:13Z", + description: "Shanghai EVM support, PUSH0 opcode", + }, + arbos20Atlas: { + timestamp: 1710424089, + date: "2024-03-14T13:48:09Z", + description: "Dencun support (EIP-4844 blobs)", + }, + arbos31Bianca: { + timestamp: 1725386400, + date: "2024-09-03T17:00:00Z", + description: "Stylus prep", + }, + arbos32Bianca: { + timestamp: 1727239050, + date: "2024-09-25T02:37:30Z", + description: "Stylus activation (WASM VM alongside EVM)", + }, + bold: { + timestamp: 1739368811, + date: "2025-02-12T14:00:11Z", + description: "BoLD dispute resolution", + }, + arbos40Callisto: { + timestamp: 1750197383, + date: "2025-06-17T22:56:23Z", + description: "Pectra support (EIP-7702)", + }, + arbos51Dia: { + timestamp: 1736355600, + date: "2026-01-08T17:00:00Z", + description: "Fusaka support (pending)", + }, + }, +}; diff --git a/e2e/tests/arbitrum.spec.ts b/e2e/tests/arbitrum.spec.ts new file mode 100644 index 0000000..6b1c72a --- /dev/null +++ b/e2e/tests/arbitrum.spec.ts @@ -0,0 +1,570 @@ +import { test, expect } from "../fixtures/test"; +import { BlockPage } from "../pages/block.page"; +import { AddressPage } from "../pages/address.page"; +import { TransactionPage } from "../pages/transaction.page"; +import { ARBITRUM } from "../fixtures/arbitrum"; + +const CHAIN_ID = ARBITRUM.chainId; + +// Transaction hash constants for readability +const UNISWAP_SWAP = "0x87815a816c02b5a563a026e4a37d423734204b50972e75284b62f05e4134ae44"; +const USDC_TRANSFER = "0x160687cbf03f348cf36997dbab53abbd32d91af5971bccac4cfa1577da27607e"; + +// Helper to wait for block content or error +async function waitForBlockContent(page: import("@playwright/test").Page) { + await expect( + page + .locator("text=Transactions:") + .or(page.locator("text=Error:")) + .or(page.locator("text=Something went wrong")) + ).toBeVisible({ timeout: 45000 }); + + return ( + !(await page.locator("text=Error:").isVisible()) && + !(await page.locator("text=Something went wrong").isVisible()) + ); +} + +// Helper to wait for address content or error +async function waitForAddressContent(page: import("@playwright/test").Page) { + await expect( + page + .locator("text=Balance:") + .or(page.locator("text=Error:")) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout: 45000 }); + + return ( + !(await page.locator("text=Error:").isVisible()) && + !(await page.locator("text=Something went wrong").isVisible()) + ); +} + +// Helper to wait for transaction content or error +async function waitForTxContent(page: import("@playwright/test").Page) { + await expect( + page + .locator("text=Transaction Hash:") + .or(page.locator("text=Error:")) + .or(page.locator("text=Something went wrong")) + ).toBeVisible({ timeout: 45000 }); + + return ( + !(await page.locator("text=Error:").isVisible()) && + !(await page.locator("text=Something went wrong").isVisible()) + ); +} + +// ============================================ +// BLOCK TESTS +// ============================================ + +test.describe("Arbitrum One - Block Page", () => { + test("genesis block #0 - Arbitrum One launch", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = ARBITRUM.blocks["0"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + // Header section + await expect(blockPage.blockNumber).toBeVisible(); + + // Genesis block should have 0 transactions + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator("text=0 transactions in this block")).toBeVisible(); + + // Gas Used should be 0 + await expect(page.locator("text=Gas Used:")).toBeVisible(); + + // Gas Limit + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + await expect(page.locator(`text=${block.gasLimit}`)).toBeVisible(); + } + }); + + test("block #22,207,817 - Nitro upgrade block", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = ARBITRUM.blocks["22207817"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + + // Nitro upgrade block - 0 transactions + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator("text=0 transactions in this block")).toBeVisible(); + + // Size + await expect(page.locator("text=Size:")).toBeVisible(); + await expect(page.locator(`text=${block.size}`)).toBeVisible(); + } + }); + + test("block #100,000,000 - post-Nitro with gas details", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = ARBITRUM.blocks["100000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Transaction count + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator(`text=${block.txCount} transactions in this block`)).toBeVisible(); + + // Gas Used with value + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator(`text=${block.gasUsed}`)).toBeVisible(); + + // Size with value + await expect(page.locator("text=Size:")).toBeVisible(); + await expect(page.locator(`text=${block.size}`)).toBeVisible(); + + // Base Fee Per Gas + await expect(page.locator("text=Base Fee Per Gas:")).toBeVisible(); + + // Fee Recipient (sequencer address) + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + const feeRecipient = await blockPage.getFeeRecipient(); + expect(feeRecipient.toLowerCase()).toContain(block.feeRecipientPartial.toLowerCase()); + } + }); + + test("block #100,000,000 more details section shows correct hashes", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = ARBITRUM.blocks["100000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + // Verify hash field labels + await expect(page.getByText("Hash:", { exact: true })).toBeVisible(); + await expect(page.getByText("Parent Hash:", { exact: true })).toBeVisible(); + + // Block hash + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + // Parent hash + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #200,000,000 - post-ArbOS 20 Atlas (Dencun)", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = ARBITRUM.blocks["200000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Transaction count + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator(`text=${block.txCount} transactions in this block`)).toBeVisible(); + + // Gas Used with value + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator(`text=${block.gasUsed}`)).toBeVisible(); + + // Size with value + await expect(page.locator("text=Size:")).toBeVisible(); + await expect(page.locator(`text=${block.size}`)).toBeVisible(); + + // Fee Recipient + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + const feeRecipient = await blockPage.getFeeRecipient(); + expect(feeRecipient.toLowerCase()).toContain(block.feeRecipientPartial.toLowerCase()); + } + }); + + test("block #200,000,000 more details shows hashes", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = ARBITRUM.blocks["200000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + // Block hash + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + // Parent hash + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #300,000,000 - post-ArbOS 32 Bianca (Stylus)", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = ARBITRUM.blocks["300000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + + // Transaction count + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator(`text=${block.txCount} transactions in this block`)).toBeVisible(); + + // Gas Used with value + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator(`text=${block.gasUsed}`)).toBeVisible(); + + // Size with value + await expect(page.locator("text=Size:")).toBeVisible(); + await expect(page.locator(`text=${block.size}`)).toBeVisible(); + + // Base Fee Per Gas + await expect(page.locator("text=Base Fee Per Gas:")).toBeVisible(); + + // Fee Recipient + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + const feeRecipient = await blockPage.getFeeRecipient(); + expect(feeRecipient.toLowerCase()).toContain(block.feeRecipientPartial.toLowerCase()); + } + }); + + test("genesis block more details section shows correct hash", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = ARBITRUM.blocks["0"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + // Verify hash field labels + await expect(page.getByText("Hash:", { exact: true })).toBeVisible(); + + // Genesis block hash + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + } + } + }); + + test("block navigation buttons work on Arbitrum", async ({ page }) => { + const blockPage = new BlockPage(page); + await blockPage.goto(ARBITRUM.blocks["100000000"].number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + await expect(blockPage.navPrevBtn).toBeVisible(); + await expect(blockPage.navNextBtn).toBeVisible(); + } + }); + + test("handles invalid block number gracefully", async ({ page }) => { + const blockPage = new BlockPage(page); + await blockPage.goto(999999999999, CHAIN_ID); + + await expect( + blockPage.errorText.or(blockPage.container).or(page.locator("text=Something went wrong")) + ).toBeVisible({ timeout: 30000 }); + }); +}); + +// ============================================ +// TRANSACTION TESTS +// ============================================ + +test.describe("Arbitrum One - Transaction Page", () => { + test("displays Uniswap V3 swap with all details", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = ARBITRUM.transactions[UNISWAP_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + // Verify core transaction details + await expect(page.locator("text=Transaction Hash:")).toBeVisible(); + await expect(page.locator("text=Status:")).toBeVisible(); + await expect(page.locator("text=Block:")).toBeVisible(); + await expect(page.locator("text=From:")).toBeVisible(); + await expect(page.locator("text=To:")).toBeVisible(); + await expect(page.locator("text=Value:")).toBeVisible(); + + // Verify gas information + await expect(page.locator("text=Gas Limit")).toBeVisible(); + await expect(page.getByText("Gas Price:", { exact: true })).toBeVisible(); + } + }); + + test("shows correct from and to addresses for swap", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = ARBITRUM.transactions[UNISWAP_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + const from = await txPage.getFromAddress(); + expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); + + const to = await txPage.getToAddress(); + expect(to?.toLowerCase()).toContain(tx.to?.toLowerCase().slice(0, 10)); + } + }); + + test("displays transaction value and fee", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = ARBITRUM.transactions[UNISWAP_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + // Verify value contains ETH + const value = await txPage.getValue(); + expect(value).toContain("ETH"); + + // Verify transaction fee is displayed + await expect(page.locator("text=Transaction Fee:")).toBeVisible(); + } + }); + + test("displays USDC transfer transaction (EIP-1559)", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = ARBITRUM.transactions[USDC_TRANSFER]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + await expect(page.locator("text=Transaction Hash:")).toBeVisible(); + await expect(page.locator("text=Status:")).toBeVisible(); + await expect(page.locator("text=From:")).toBeVisible(); + await expect(page.locator("text=To:")).toBeVisible(); + + // To address should be USDC contract + const to = await txPage.getToAddress(); + expect(to?.toLowerCase()).toContain(tx.to?.toLowerCase().slice(0, 10)); + } + }); + + test("displays transaction with input data", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = ARBITRUM.transactions[UNISWAP_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + // Contract interaction should have input data + await expect(page.locator("text=Input Data:")).toBeVisible(); + } + }); + + test("displays other attributes section with nonce", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = ARBITRUM.transactions[UNISWAP_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + await expect(page.locator("text=Other Attributes:")).toBeVisible(); + await expect(page.locator("text=Nonce:")).toBeVisible(); + await expect(page.locator("text=Position:")).toBeVisible(); + } + }); + + test("displays block number link", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = ARBITRUM.transactions[UNISWAP_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + await expect(page.locator("text=Block:")).toBeVisible(); + const blockValue = await txPage.getBlockNumber(); + expect(blockValue).toBeTruthy(); + } + }); + + test("USDC transfer shows correct addresses", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = ARBITRUM.transactions[USDC_TRANSFER]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + const from = await txPage.getFromAddress(); + expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); + + const to = await txPage.getToAddress(); + expect(to?.toLowerCase()).toContain(tx.to?.toLowerCase().slice(0, 10)); + } + }); + + test("handles invalid tx hash gracefully", async ({ page }) => { + const txPage = new TransactionPage(page); + await txPage.goto("0xinvalid", CHAIN_ID); + + await expect( + txPage.errorText.or(txPage.container).or(page.locator("text=Something went wrong")) + ).toBeVisible({ timeout: 30000 }); + }); +}); + +// ============================================ +// ADDRESS TESTS +// ============================================ + +test.describe("Arbitrum One - Address Page", () => { + test("displays native USDC contract details", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = ARBITRUM.addresses.usdc; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + await expect(page.locator("text=Contract Details")).toBeVisible(); + } + }); + + test("displays bridged USDC.e contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = ARBITRUM.addresses.usdce; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays WETH contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = ARBITRUM.addresses.weth; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays ARB governance token contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = ARBITRUM.addresses.arb; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays GMX token contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = ARBITRUM.addresses.gmx; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays Uniswap V3 Router contract with details", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = ARBITRUM.addresses.uniswapV3Router; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + await expect(page.locator("text=Contract Details")).toBeVisible(); + } + }); + + test("displays Uniswap Universal Router contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = ARBITRUM.addresses.uniswapUniversalRouter; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays GMX Vault contract with details", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = ARBITRUM.addresses.gmxVault; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + await expect(page.locator("text=Contract Details")).toBeVisible(); + } + }); + + test("displays GMX Position Router contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = ARBITRUM.addresses.gmxPositionRouter; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays ArbSys system precompile", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = ARBITRUM.addresses.arbSys; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); +}); diff --git a/e2e/tests/base.spec.ts b/e2e/tests/base.spec.ts index ed3bf89..9aa7e21 100644 --- a/e2e/tests/base.spec.ts +++ b/e2e/tests/base.spec.ts @@ -192,8 +192,8 @@ test.describe("Base Network - Block Page", () => { // Genesis block hash await expect(page.locator(`text=${block.hash}`)).toBeVisible(); - // Genesis parent hash (all zeros) - await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + // Genesis parent hash (all zeros) - use first() as it appears in multiple places (also in logs bloom) + await expect(page.locator(`text=${block.parentHash}`).first()).toBeVisible(); } } }); From 18e4822aa55fc91ef72b1ae1fbefb732a1121519 Mon Sep 17 00:00:00 2001 From: Augusto Lemble Date: Fri, 2 Jan 2026 09:34:42 -0300 Subject: [PATCH 4/9] feat(e2e): add Optimism network e2e tests Add comprehensive e2e tests for Optimism (chain ID 10) covering blocks, transactions, and addresses across network history including Bedrock, Ecotone, and Holocene upgrades. Tests include system transactions (Type 126) and detailed value assertions for gas, nonce, and hashes. Also fix strict mode violations in error handling locators across all test files by adding .first() to prevent multiple element matches. --- e2e/fixtures/optimism.ts | 362 ++++++++++++++++ e2e/tests/address.spec.ts | 5 +- e2e/tests/arbitrum.spec.ts | 10 +- e2e/tests/base.spec.ts | 10 +- e2e/tests/block.spec.ts | 5 +- e2e/tests/optimism.spec.ts | 775 ++++++++++++++++++++++++++++++++++ e2e/tests/transaction.spec.ts | 5 +- 7 files changed, 1165 insertions(+), 7 deletions(-) create mode 100644 e2e/fixtures/optimism.ts create mode 100644 e2e/tests/optimism.spec.ts diff --git a/e2e/fixtures/optimism.ts b/e2e/fixtures/optimism.ts new file mode 100644 index 0000000..abb935c --- /dev/null +++ b/e2e/fixtures/optimism.ts @@ -0,0 +1,362 @@ +// cspell:ignore velodrome bedrock ecotone fjord holocene isthmus sequencer +export const OPTIMISM = { + chainId: "10", + + // Optimism mainnet regenesis: November 11, 2021 (current chain) + // Bedrock upgrade: June 6, 2023 (block ~105,235,063) + // Block time: 2 seconds + // Upgrades follow Superchain-wide activation timestamps + + blocks: { + // Genesis block - Optimism mainnet (post-regenesis) + // Note: Contains 8,893 transactions from state migration + "0": { + number: 0, + txCount: 8893, + gasUsed: "0", + gasLimit: "15,000,000", + hash: "0x7ca38a1916c42007829c55e69d3e9a73265554b586a499015373241b8a3fa48b", + parentHash: "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + // Block 100,000,000 (May 20, 2023) + // Pre-Bedrock block + "100000000": { + number: 100000000, + txCount: 1, + gasUsed: "108,922", + gasUsedPercent: "0.7%", + gasLimit: "15,000,000", + hash: "0x9bb1d3a1d3deb6f1298011d4bebf994f3ed7d02ae64a491568f78c4ed5124cb5", + parentHash: "0x9f423a470be5e8c2bcd3c046dd67ceb9f184af92fea432a4016da68240510ce9", + }, + // Block 110,000,000 (September 24, 2023) + // Post-Bedrock, pre-Ecotone + "110000000": { + number: 110000000, + txCount: 4, + gasUsed: "301,587", + gasUsedPercent: "1.0%", + gasLimit: "30,000,000", + baseFeePerGas: "0.063 Gwei", + hash: "0x429308982bd568afa89a04ccbbc2f07b9058176e19fd6622fd87e346ef07ed23", + parentHash: "0xdd6583a74ad3d1c383d9f657268d085f6f39d11a53daf0e5fd873dfeb09f0ac7", + feeRecipientPartial: "0x42000000", + }, + // Block 120,000,000 (May 13, 2024) + // Post-Ecotone (EIP-4844 blob support) + "120000000": { + number: 120000000, + txCount: 21, + gasUsed: "27,554,949", + gasUsedPercent: "91.9%", + gasLimit: "30,000,000", + baseFeePerGas: "0.06 Gwei", + hash: "0xcad6bf99757384f6f16fac0974aa234bb9bc866d8c89d9a56addbd9a3f13dc13", + parentHash: "0x8bce6c507e43379bef7817dbf57b9b0181e744a6c5f841d5ee57b4e09f6b09df", + feeRecipientPartial: "0x42000000", + }, + // Block 130,000,000 (December 30, 2024) + // Post-Holocene + "130000000": { + number: 130000000, + txCount: 13, + gasUsed: "1,535,948", + gasUsedPercent: "2.6%", + gasLimit: "60,000,000", + baseFeePerGas: "0.00000026 Gwei", + hash: "0xaf131f54209291613f0b74e61903405ea84bf30368ea5c6cf787992351ad843d", + parentHash: "0x547a69b3a63b80cee045cdcb0759bad305f42aa30dbf6c4fa8fbe70d44e37e65", + feeRecipientPartial: "0x42000000", + }, + }, + + transactions: { + // ============================================ + // LEGACY TRANSACTIONS (Type 0) + // ============================================ + + // Velodrome Finance swap - Legacy transaction + "0xa8d73ea0639f39157f787a29591b36fc73c19b443bbe8416d8d6f24858063910": { + hash: "0xa8d73ea0639f39157f787a29591b36fc73c19b443bbe8416d8d6f24858063910", + type: 0, // Legacy transaction + from: "0x1dE686d54FA9b786870c3b67e1430BD1F08C1c5F", + to: "0x9c12939390052919aF3155f41Bf4160Fd3666A6f", // Velodrome Router + value: "0x0", + blockNumber: 84855387, + gas: "331,424", + gasUsed: "249,652", + gasPrice: "0.001 Gwei", + nonce: 89, + position: 0, + status: "success" as const, + hasInputData: true, + method: "swapExactTokensForTokens", + // L2 fee breakdown (Optimism specific) + l2Fee: "0.000000249652 ETH", + l1Fee: "0.000229678666963588 ETH", + txFee: "0.000229928318963588 ETH", + }, + + // ============================================ + // EIP-1559 TRANSACTIONS (Type 2) + // ============================================ + + // OP token transfer (EIP-1559) + "0xdcf7c4afb479cd47f7ce263cbbb298f559b81fc592cc07737935a6166fb90f0c": { + hash: "0xdcf7c4afb479cd47f7ce263cbbb298f559b81fc592cc07737935a6166fb90f0c", + type: 2, // EIP-1559 + from: "0x29185eB8cfD22Aa719529217bFbadE61677e0Ad2", + to: "0x4200000000000000000000000000000000000042", // OP Token + value: "0x0", + blockNumber: 120011272, + gas: "40,358", + gasUsed: "39,988", + maxFeePerGas: "0.191 Gwei", + maxPriorityFeePerGas: "0.004 Gwei", + effectiveGasPrice: "0.066 Gwei", + nonce: 190, + position: 11, + status: "success" as const, + hasInputData: true, + method: "transfer", + txFee: "0.000002714211193107 ETH", + }, + + // OP token delegate (EIP-1559) + "0x36a239e68d43afbb742a66e2c5456f443e20e2bf79812f8ee80d2c444bcb5d89": { + hash: "0x36a239e68d43afbb742a66e2c5456f443e20e2bf79812f8ee80d2c444bcb5d89", + type: 2, // EIP-1559 + from: "0x7192744441e4C9845408b5928b80cA5dd40C41bF", + to: "0x4200000000000000000000000000000000000042", // OP Token + value: "0x0", + blockNumber: 129267009, + gas: "99,799", + gasUsed: "98,824", + maxFeePerGas: "0.000108 Gwei", + maxPriorityFeePerGas: "0.0001 Gwei", + nonce: 161, + status: "success" as const, + hasInputData: true, + method: "delegate", + }, + + // ============================================ + // SYSTEM TRANSACTIONS (Type 126) - OP Stack specific + // ============================================ + + // L2 Cross Domain Messenger relay - System transaction (Type 126) + "0x5d3522dad0d0745b59e9443733f8423548f99856c00768aba9779ae288dedd0a": { + hash: "0x5d3522dad0d0745b59e9443733f8423548f99856c00768aba9779ae288dedd0a", + type: 126, // Deposit transaction type + from: "0x36BDE71C97B33Cc4729cf772aE268934f7AB70B2", // Aliased L1 Cross-Domain Messenger + to: "0x4200000000000000000000000000000000000007", // L2CrossDomainMessenger + value: "0x0", + blockNumber: 106744423, + gas: "387,675", + gasUsed: "96,955", + nonce: 370362, + status: "success" as const, + hasInputData: true, + method: "relayMessage", + // System transactions have zero fees + txFee: "0 ETH", + }, + }, + + addresses: { + // ============================================ + // ERC20 TOKENS + // ============================================ + + // Native USDC on Optimism - Circle's native USDC + usdc: { + address: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85", + type: "erc20" as const, + symbol: "USDC", + name: "USD Coin", + decimals: 6, + }, + // Bridged USDC.e (legacy) + usdce: { + address: "0x7F5c764cBc14f9669B88837ca1490cCa17c31607", + type: "erc20" as const, + symbol: "USDC.e", + name: "Bridged USDC", + decimals: 6, + }, + // WETH on Optimism (predeploy) + weth: { + address: "0x4200000000000000000000000000000000000006", + type: "erc20" as const, + symbol: "WETH", + name: "Wrapped Ether", + decimals: 18, + }, + // OP token - Optimism governance token + op: { + address: "0x4200000000000000000000000000000000000042", + type: "erc20" as const, + symbol: "OP", + name: "Optimism", + decimals: 18, + }, + // USDT on Optimism + usdt: { + address: "0x94b008aA00579c1307B0EF2c499aD98a8ce58e58", + type: "erc20" as const, + symbol: "USDT", + name: "Tether USD", + decimals: 6, + }, + // DAI on Optimism + dai: { + address: "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1", + type: "erc20" as const, + symbol: "DAI", + name: "Dai Stablecoin", + decimals: 18, + }, + + // ============================================ + // DEX CONTRACTS + // ============================================ + + // Velodrome Finance Router - Main DEX on Optimism + velodromeRouter: { + address: "0x9c12939390052919aF3155f41Bf4160Fd3666A6f", + type: "contract" as const, + name: "Velodrome Finance: Router", + }, + // Velodrome Universal Router (V2) + velodromeUniversalRouter: { + address: "0x01D40099fCD87C018969B0e8D4aB1633Fb34763C", + type: "contract" as const, + name: "Velodrome: Universal Router", + }, + // Uniswap V3 Router + uniswapV3Router: { + address: "0xE592427A0AEce92De3Edee1F18E0157C05861564", + type: "contract" as const, + name: "Uniswap V3: Swap Router", + }, + // Uniswap Universal Router + uniswapUniversalRouter: { + address: "0xCb1355ff08Ab38bBCE60111F1bb2B784bE25D7e8", + type: "contract" as const, + name: "Uniswap: Universal Router", + }, + + // ============================================ + // BRIDGE CONTRACTS + // ============================================ + + // Optimism Portal (L1 to L2 deposits) + optimismPortal: { + address: "0xbEb5Fc579115071764c7423A4f12eDde41f106Ed", + type: "contract" as const, + name: "Optimism: Portal", + }, + + // ============================================ + // SYSTEM CONTRACTS (OP Stack predeploys) + // ============================================ + + // SequencerFeeVault - Receives transaction fees (fee recipient) + sequencerFeeVault: { + address: "0x4200000000000000000000000000000000000011", + type: "contract" as const, + name: "SequencerFeeVault", + }, + // L2CrossDomainMessenger - Bridge messaging + l2CrossDomainMessenger: { + address: "0x4200000000000000000000000000000000000007", + type: "contract" as const, + name: "L2CrossDomainMessenger", + }, + // L2StandardBridge - Token bridging + l2StandardBridge: { + address: "0x4200000000000000000000000000000000000010", + type: "contract" as const, + name: "L2StandardBridge", + }, + // GasPriceOracle - L1 fee calculation + gasPriceOracle: { + address: "0x420000000000000000000000000000000000000F", + type: "contract" as const, + name: "GasPriceOracle", + }, + // L1Block - L1 block attributes + l1Block: { + address: "0x4200000000000000000000000000000000000015", + type: "contract" as const, + name: "L1Block", + }, + // L2ToL1MessagePasser - Withdrawals + l2ToL1MessagePasser: { + address: "0x4200000000000000000000000000000000000016", + type: "contract" as const, + name: "L2ToL1MessagePasser", + }, + // BaseFeeVault - Collects base fees + baseFeeVault: { + address: "0x4200000000000000000000000000000000000019", + type: "contract" as const, + name: "BaseFeeVault", + }, + // L1FeeVault - Collects L1 data fees + l1FeeVault: { + address: "0x420000000000000000000000000000000000001A", + type: "contract" as const, + name: "L1FeeVault", + }, + }, + + // Upgrade timestamps (Unix) for reference + upgrades: { + bedrock: { + timestamp: 1686079703, + date: "2023-06-06T16:28:23Z", + description: "Major architecture upgrade - modular rollup design", + }, + canyon: { + timestamp: 1704992401, + date: "2024-01-11T17:00:01Z", + description: "Shapella support, EIP-4788 beacon root", + }, + delta: { + timestamp: 1708560000, + date: "2024-02-22T00:00:00Z", + description: "Span batches for data compression", + }, + ecotone: { + timestamp: 1710374401, + date: "2024-03-14T00:00:01Z", + description: "EIP-4844 blob support, ~90% fee reduction", + }, + fjord: { + timestamp: 1720627201, + date: "2024-07-10T16:00:01Z", + description: "Brotli compression, RIP-7212 secp256r1 precompile", + }, + granite: { + timestamp: 1726070401, + date: "2024-09-11T16:00:01Z", + description: "Permissionless fault proofs re-enabled", + }, + holocene: { + timestamp: 1736445601, + date: "2025-01-09T18:00:01Z", + description: "Stricter derivation, EIP-1559 configurability", + }, + isthmus: { + timestamp: 1746806401, + date: "2025-05-09T16:00:01Z", + description: "Pectra L2 support", + }, + jovian: { + timestamp: 1764691201, + date: "2025-12-02T16:00:01Z", + description: "Future upgrade (pending)", + }, + }, +}; diff --git a/e2e/tests/address.spec.ts b/e2e/tests/address.spec.ts index 3ef06f3..5817e89 100644 --- a/e2e/tests/address.spec.ts +++ b/e2e/tests/address.spec.ts @@ -112,7 +112,10 @@ test.describe("Address Page", () => { await addressPage.goto("0xinvalid"); await expect( - addressPage.errorText.or(addressPage.container).or(page.locator("text=Something went wrong")) + addressPage.errorText + .or(addressPage.container) + .or(page.locator("text=Something went wrong")) + .first() ).toBeVisible({ timeout: 30000 }); }); diff --git a/e2e/tests/arbitrum.spec.ts b/e2e/tests/arbitrum.spec.ts index 6b1c72a..e24d02c 100644 --- a/e2e/tests/arbitrum.spec.ts +++ b/e2e/tests/arbitrum.spec.ts @@ -276,7 +276,10 @@ test.describe("Arbitrum One - Block Page", () => { await blockPage.goto(999999999999, CHAIN_ID); await expect( - blockPage.errorText.or(blockPage.container).or(page.locator("text=Something went wrong")) + blockPage.errorText + .or(blockPage.container) + .or(page.locator("text=Something went wrong")) + .first() ).toBeVisible({ timeout: 30000 }); }); }); @@ -422,7 +425,10 @@ test.describe("Arbitrum One - Transaction Page", () => { await txPage.goto("0xinvalid", CHAIN_ID); await expect( - txPage.errorText.or(txPage.container).or(page.locator("text=Something went wrong")) + txPage.errorText + .or(txPage.container) + .or(page.locator("text=Something went wrong")) + .first() ).toBeVisible({ timeout: 30000 }); }); }); diff --git a/e2e/tests/base.spec.ts b/e2e/tests/base.spec.ts index 9aa7e21..73eeed3 100644 --- a/e2e/tests/base.spec.ts +++ b/e2e/tests/base.spec.ts @@ -214,7 +214,10 @@ test.describe("Base Network - Block Page", () => { await blockPage.goto(999999999999, CHAIN_ID); await expect( - blockPage.errorText.or(blockPage.container).or(page.locator("text=Something went wrong")) + blockPage.errorText + .or(blockPage.container) + .or(page.locator("text=Something went wrong")) + .first() ).toBeVisible({ timeout: 30000 }); }); }); @@ -325,7 +328,10 @@ test.describe("Base Network - Transaction Page", () => { await txPage.goto("0xinvalid", CHAIN_ID); await expect( - txPage.errorText.or(txPage.container).or(page.locator("text=Something went wrong")) + txPage.errorText + .or(txPage.container) + .or(page.locator("text=Something went wrong")) + .first() ).toBeVisible({ timeout: 30000 }); }); }); diff --git a/e2e/tests/block.spec.ts b/e2e/tests/block.spec.ts index daa8737..a99502c 100644 --- a/e2e/tests/block.spec.ts +++ b/e2e/tests/block.spec.ts @@ -259,7 +259,10 @@ test.describe("Block Page", () => { await blockPage.goto(999999999999); await expect( - blockPage.errorText.or(blockPage.container).or(page.locator("text=Something went wrong")) + blockPage.errorText + .or(blockPage.container) + .or(page.locator("text=Something went wrong")) + .first() ).toBeVisible({ timeout: 30000 }); }); }); diff --git a/e2e/tests/optimism.spec.ts b/e2e/tests/optimism.spec.ts new file mode 100644 index 0000000..deee2c8 --- /dev/null +++ b/e2e/tests/optimism.spec.ts @@ -0,0 +1,775 @@ +import { test, expect } from "../fixtures/test"; +import { BlockPage } from "../pages/block.page"; +import { AddressPage } from "../pages/address.page"; +import { TransactionPage } from "../pages/transaction.page"; +import { OPTIMISM } from "../fixtures/optimism"; + +const CHAIN_ID = OPTIMISM.chainId; + +// Transaction hash constants for readability +const VELODROME_SWAP = "0xa8d73ea0639f39157f787a29591b36fc73c19b443bbe8416d8d6f24858063910"; +const OP_TRANSFER = "0xdcf7c4afb479cd47f7ce263cbbb298f559b81fc592cc07737935a6166fb90f0c"; +const SYSTEM_TX = "0x5d3522dad0d0745b59e9443733f8423548f99856c00768aba9779ae288dedd0a"; + +// Helper to wait for block content or error +async function waitForBlockContent(page: import("@playwright/test").Page) { + await expect( + page + .locator("text=Transactions:") + .or(page.locator("text=Error:")) + .or(page.locator("text=Something went wrong")) + ).toBeVisible({ timeout: 45000 }); + + return ( + !(await page.locator("text=Error:").isVisible()) && + !(await page.locator("text=Something went wrong").isVisible()) + ); +} + +// Helper to wait for address content or error +async function waitForAddressContent(page: import("@playwright/test").Page) { + await expect( + page + .locator("text=Balance:") + .or(page.locator("text=Error:")) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout: 45000 }); + + return ( + !(await page.locator("text=Error:").isVisible()) && + !(await page.locator("text=Something went wrong").isVisible()) + ); +} + +// Helper to wait for transaction content or error +async function waitForTxContent(page: import("@playwright/test").Page) { + await expect( + page + .locator("text=Transaction Hash:") + .or(page.locator("text=Error:")) + .or(page.locator("text=Something went wrong")) + ).toBeVisible({ timeout: 45000 }); + + return ( + !(await page.locator("text=Error:").isVisible()) && + !(await page.locator("text=Something went wrong").isVisible()) + ); +} + +// ============================================ +// BLOCK TESTS +// ============================================ + +test.describe("Optimism - Block Page", () => { + test("genesis block #0 - Optimism mainnet (post-regenesis)", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = OPTIMISM.blocks["0"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + // Header section + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.timestampAge).toBeVisible(); + + // Genesis block has 8,893 transactions from state migration + await expect(page.locator("text=Transactions:")).toBeVisible(); + + // Gas Used should be 0 + await expect(page.locator("text=Gas Used:")).toBeVisible(); + + // Gas Limit + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + await expect(page.locator(`text=${block.gasLimit}`)).toBeVisible(); + } + }); + + test("block #100,000,000 - pre-Bedrock block with gas details", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = OPTIMISM.blocks["100000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + // Header + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Transaction count + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator(`text=${block.txCount} transaction`)).toBeVisible(); + + // Gas Used + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator(`text=${block.gasUsed}`)).toBeVisible(); + + // Gas Limit + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + await expect(page.locator(`text=${block.gasLimit}`)).toBeVisible(); + } + }); + + test("block #100,000,000 more details section shows correct hashes", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = OPTIMISM.blocks["100000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + // Verify hash field labels + await expect(page.getByText("Hash:", { exact: true })).toBeVisible(); + await expect(page.getByText("Parent Hash:", { exact: true })).toBeVisible(); + + // Block hash + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + // Parent hash + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #110,000,000 - post-Bedrock with complete gas details", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = OPTIMISM.blocks["110000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Transaction count + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator(`text=${block.txCount} transactions in this block`)).toBeVisible(); + + // Gas details + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator(`text=${block.gasUsed}`)).toBeVisible(); + + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + await expect(page.locator(`text=${block.gasLimit}`)).toBeVisible(); + + // Base Fee Per Gas (post-Bedrock) + await expect(page.locator("text=Base Fee Per Gas:")).toBeVisible(); + + // Fee Recipient (SequencerFeeVault) + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + const feeRecipient = await blockPage.getFeeRecipient(); + expect(feeRecipient.toLowerCase()).toContain(block.feeRecipientPartial.toLowerCase()); + } + }); + + test("block #110,000,000 more details shows hashes", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = OPTIMISM.blocks["110000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #120,000,000 - post-Ecotone (EIP-4844) high utilization", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = OPTIMISM.blocks["120000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + + // Should have many transactions (21) + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator(`text=${block.txCount} transactions in this block`)).toBeVisible(); + + // Gas details - high utilization block (91.9%) + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator(`text=${block.gasUsed}`)).toBeVisible(); + + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + await expect(page.locator(`text=${block.gasLimit}`)).toBeVisible(); + + // Base Fee Per Gas + await expect(page.locator("text=Base Fee Per Gas:")).toBeVisible(); + + // Fee Recipient + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + const feeRecipient = await blockPage.getFeeRecipient(); + expect(feeRecipient.toLowerCase()).toContain(block.feeRecipientPartial.toLowerCase()); + } + }); + + test("block #120,000,000 more details shows hashes", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = OPTIMISM.blocks["120000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #130,000,000 - post-Holocene with increased gas limit (60M)", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = OPTIMISM.blocks["130000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + + // Transaction count + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator(`text=${block.txCount} transactions in this block`)).toBeVisible(); + + // Gas Used + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator(`text=${block.gasUsed}`)).toBeVisible(); + + // Gas Limit - increased post-Holocene (60M) + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + await expect(page.locator(`text=${block.gasLimit}`)).toBeVisible(); + + // Base Fee Per Gas + await expect(page.locator("text=Base Fee Per Gas:")).toBeVisible(); + + // Fee Recipient + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + const feeRecipient = await blockPage.getFeeRecipient(); + expect(feeRecipient.toLowerCase()).toContain(block.feeRecipientPartial.toLowerCase()); + } + }); + + test("block #130,000,000 more details shows hashes", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = OPTIMISM.blocks["130000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("genesis block more details section shows correct hash", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = OPTIMISM.blocks["0"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + // Verify hash field labels + await expect(page.getByText("Hash:", { exact: true })).toBeVisible(); + await expect(page.getByText("Parent Hash:", { exact: true })).toBeVisible(); + + // Genesis block hash + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + // Genesis parent hash (all zeros) - use first() as it appears in multiple places + await expect(page.locator(`text=${block.parentHash}`).first()).toBeVisible(); + } + } + }); + + test("block navigation buttons work on Optimism", async ({ page }) => { + const blockPage = new BlockPage(page); + await blockPage.goto(OPTIMISM.blocks["110000000"].number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + await expect(blockPage.navPrevBtn).toBeVisible(); + await expect(blockPage.navNextBtn).toBeVisible(); + } + }); + + test("handles invalid block number gracefully", async ({ page }) => { + const blockPage = new BlockPage(page); + await blockPage.goto(999999999999, CHAIN_ID); + + await expect( + blockPage.errorText + .or(blockPage.container) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout: 30000 }); + }); +}); + +// ============================================ +// TRANSACTION TESTS +// ============================================ + +test.describe("Optimism - Transaction Page", () => { + test("displays Velodrome DEX swap (Legacy Type 0) with all details", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = OPTIMISM.transactions[VELODROME_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + // Verify core transaction details + await expect(page.locator("text=Transaction Hash:")).toBeVisible(); + await expect(page.locator("text=Status:")).toBeVisible(); + await expect(page.locator("text=Block:")).toBeVisible(); + await expect(page.locator("text=From:")).toBeVisible(); + await expect(page.locator("text=To:")).toBeVisible(); + + // Verify gas information + await expect(page.locator("text=Gas Limit")).toBeVisible(); + await expect(page.getByText("Gas Price:", { exact: true })).toBeVisible(); + } + }); + + test("shows correct from and to addresses for Velodrome swap", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = OPTIMISM.transactions[VELODROME_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + const from = await txPage.getFromAddress(); + expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); + + const to = await txPage.getToAddress(); + expect(to?.toLowerCase()).toContain(tx.to?.toLowerCase().slice(0, 10)); + } + }); + + test("displays transaction fee for Velodrome swap", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = OPTIMISM.transactions[VELODROME_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + // Verify transaction fee is displayed + await expect(page.locator("text=Transaction Fee:")).toBeVisible(); + } + }); + + test("displays Velodrome swap nonce and position", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = OPTIMISM.transactions[VELODROME_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + await expect(page.locator("text=Other Attributes:")).toBeVisible(); + await expect(page.locator("text=Nonce:")).toBeVisible(); + await expect(page.locator("text=Position:")).toBeVisible(); + + // Verify nonce value is displayed (use locator that includes the label) + await expect(page.locator(`text=Nonce: ${tx.nonce}`)).toBeVisible(); + } + }); + + test("displays OP token transfer transaction (EIP-1559 Type 2)", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = OPTIMISM.transactions[OP_TRANSFER]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + await expect(page.locator("text=Transaction Hash:")).toBeVisible(); + await expect(page.locator("text=Status:")).toBeVisible(); + await expect(page.locator("text=From:")).toBeVisible(); + await expect(page.locator("text=To:")).toBeVisible(); + + // To address should be OP token contract + const to = await txPage.getToAddress(); + expect(to?.toLowerCase()).toContain(tx.to?.toLowerCase().slice(0, 10)); + } + }); + + test("OP transfer shows correct addresses and gas details", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = OPTIMISM.transactions[OP_TRANSFER]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + const from = await txPage.getFromAddress(); + expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); + + const to = await txPage.getToAddress(); + expect(to?.toLowerCase()).toContain(tx.to?.toLowerCase().slice(0, 10)); + + // Verify gas limit is displayed + await expect(page.locator("text=Gas Limit")).toBeVisible(); + } + }); + + test("OP transfer shows nonce and position", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = OPTIMISM.transactions[OP_TRANSFER]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + await expect(page.locator("text=Nonce:")).toBeVisible(); + await expect(page.locator("text=Position:")).toBeVisible(); + + // Verify nonce value is displayed (use locator that includes the label) + await expect(page.locator(`text=Nonce: ${tx.nonce}`)).toBeVisible(); + } + }); + + test("displays system transaction (Type 126) - L2CrossDomainMessenger relay", async ({ + page, + }) => { + const txPage = new TransactionPage(page); + const tx = OPTIMISM.transactions[SYSTEM_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + await expect(page.locator("text=Transaction Hash:")).toBeVisible(); + await expect(page.locator("text=Status:")).toBeVisible(); + await expect(page.locator("text=From:")).toBeVisible(); + await expect(page.locator("text=To:")).toBeVisible(); + + // To address should be L2CrossDomainMessenger + const to = await txPage.getToAddress(); + expect(to?.toLowerCase()).toContain(tx.to?.toLowerCase().slice(0, 10)); + } + }); + + test("system transaction shows correct from address (Aliased L1 Messenger)", async ({ + page, + }) => { + const txPage = new TransactionPage(page); + const tx = OPTIMISM.transactions[SYSTEM_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + const from = await txPage.getFromAddress(); + expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); + } + }); + + test("displays transaction with input data", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = OPTIMISM.transactions[VELODROME_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + // Contract interaction should have input data + await expect(page.locator("text=Input Data:")).toBeVisible(); + } + }); + + test("displays block number link for transaction", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = OPTIMISM.transactions[VELODROME_SWAP]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + await expect(page.locator("text=Block:")).toBeVisible(); + const blockValue = await txPage.getBlockNumber(); + expect(blockValue).toBeTruthy(); + } + }); + + test("handles invalid tx hash gracefully", async ({ page }) => { + const txPage = new TransactionPage(page); + await txPage.goto("0xinvalid", CHAIN_ID); + + await expect( + txPage.errorText + .or(txPage.container) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout: 30000 }); + }); +}); + +// ============================================ +// ADDRESS TESTS +// ============================================ + +test.describe("Optimism - Address Page", () => { + test("displays native USDC contract details", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.usdc; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + await expect(page.locator("text=Contract Details")).toBeVisible(); + } + }); + + test("displays bridged USDC.e contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.usdce; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays WETH predeploy contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.weth; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays OP governance token contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.op; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays USDT contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.usdt; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays DAI contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.dai; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays Velodrome Router contract with details", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.velodromeRouter; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + await expect(page.locator("text=Contract Details")).toBeVisible(); + } + }); + + test("displays Velodrome Universal Router contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.velodromeUniversalRouter; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays Uniswap V3 Router contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.uniswapV3Router; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays Uniswap Universal Router contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.uniswapUniversalRouter; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays SequencerFeeVault system contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.sequencerFeeVault; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays GasPriceOracle system contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.gasPriceOracle; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays L1Block system contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.l1Block; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays L2StandardBridge contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.l2StandardBridge; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays L2CrossDomainMessenger contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.l2CrossDomainMessenger; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays L2ToL1MessagePasser contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.l2ToL1MessagePasser; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays BaseFeeVault system contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.baseFeeVault; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays L1FeeVault system contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = OPTIMISM.addresses.l1FeeVault; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); +}); diff --git a/e2e/tests/transaction.spec.ts b/e2e/tests/transaction.spec.ts index 6f7724a..42cff85 100644 --- a/e2e/tests/transaction.spec.ts +++ b/e2e/tests/transaction.spec.ts @@ -125,7 +125,10 @@ test.describe("Transaction Page", () => { await txPage.goto("0xinvalid"); await expect( - txPage.errorText.or(txPage.container).or(page.locator("text=Something went wrong")) + txPage.errorText + .or(txPage.container) + .or(page.locator("text=Something went wrong")) + .first() ).toBeVisible({ timeout: 30000 }); }); }); From f4b2aea9d388692484b6e9992a5c259923b8922d Mon Sep 17 00:00:00 2001 From: Augusto Lemble Date: Fri, 2 Jan 2026 10:25:45 -0300 Subject: [PATCH 5/9] feat(e2e): add BSC network e2e tests Add comprehensive e2e tests for BNB Smart Chain (BSC) network covering: Blocks (43 tests total): - Genesis block #0 with hash verification - Block #10,000,000 (pre-Euler) with hash/parentHash - Block #20,000,000 (post-Euler) with 321 transactions - Block #30,000,000 (post-Luban, fast finality) - Block #40,000,000 (post-Feynman, BNB Chain Fusion) - Block #50,000,000 (post-Maxwell, 0.75s block time) - More details section tests for all blocks Transactions: - Real transaction from block 20M with nonce/position verification - DEX swap transaction from block 40M - DEX aggregator transaction from block 50M - Legacy Type 0 transaction verification Addresses: - BEP20 tokens (WBNB, USDT, BUSD, USDC, CAKE, DAI) - DEX contracts (PancakeSwap Router v2, Factory v2, Universal Router) - System contracts (ValidatorSet, SystemReward, TokenHub, StakeHub, Governor) - Staking contracts (PancakeSwap Main Staking, Cake Pool) Fixture data includes real on-chain block hashes, parent hashes, and transaction data fetched from BSC mainnet RPC. --- e2e/fixtures/bsc.ts | 360 +++++++++++++++++ e2e/tests/bsc.spec.ts | 872 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1232 insertions(+) create mode 100644 e2e/fixtures/bsc.ts create mode 100644 e2e/tests/bsc.spec.ts diff --git a/e2e/fixtures/bsc.ts b/e2e/fixtures/bsc.ts new file mode 100644 index 0000000..97d80b7 --- /dev/null +++ b/e2e/fixtures/bsc.ts @@ -0,0 +1,360 @@ +// cspell:ignore pancakeswap binance busd wbnb staking validator +export const BSC = { + chainId: "56", + + // BNB Smart Chain mainnet launch: September 1, 2020 + // Original block time: 3 seconds + // After Lorentz (April 2025): 1.5 seconds + // After Maxwell (June 30, 2025): 0.75 seconds + // Consensus: Proof-of-Staked-Authority (PoSA) with 21+ validators + + blocks: { + // Genesis block - BSC mainnet launch (September 1, 2020) + // Contains initial validator setup and seed fund distribution + "0": { + number: 0, + txCount: 0, + gasUsed: "0", + gasLimit: "30,000,000", + hash: "0x0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b", + parentHash: "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + // Block 10,000,000 (December 2021) + // Pre-Euler, early BSC activity + "10000000": { + number: 10000000, + gasUsed: "36,309,493", + gasUsedPercent: "42.8%", + gasLimit: "84,934,464", + hash: "0xd08dca8c7d87780ca6f2faed2e12508d431939f7d7c3fa0d052f0744e1a47e55", + parentHash: "0x6b77d2519fc680931d57729aac79a1a30c9c7a9e684499d7ed52fa800f9c799c", + }, + // Block 20,000,000 (August 2022) + // Post-Euler upgrade + "20000000": { + number: 20000000, + txCount: 321, + gasUsed: "26,602,654", + gasUsedPercent: "28.3%", + gasLimit: "94,082,549", + hash: "0xba219d00ab4ea174ead8efa90c80e9d9f9990e221bf8e7da1881ec210edbb879", + parentHash: "0xafbeac4631e64db3bb1a23d318b3fd9cb6c81b2d9e8bc50c5c46d25eb2aa6dfc", + }, + // Block 30,000,000 (July 2023) + // Post-Luban, fast finality enabled + "30000000": { + number: 30000000, + gasUsed: "21,238,276", + gasUsedPercent: "15.2%", + gasLimit: "140,001,959", + hash: "0x0fe1b87f3d62477a866bbd2327139b884c0dc057f11dc273f8bed34fe699efbd", + parentHash: "0x577bde40f5e997be68cc6b2e98ae97024b86e6a15ac00b6a515dc373ec9d57c4", + }, + // Block 40,000,000 (June 2024) + // Post-Feynman, after BNB Chain Fusion + "40000000": { + number: 40000000, + txCount: 107, + gasUsed: "11,270,569", + gasUsedPercent: "8.1%", + gasLimit: "139,456,038", + hash: "0x095f88c4f4855eab7bc6bb7161ade81adf7d60e11804d40197e4388227d1eddf", + parentHash: "0x5743a53c47bccbc29dad622be65ac9652b7980eefc9776e1e328b190c546851f", + }, + // Block 50,000,000 (May 2025) + // Post-Maxwell, 0.75s block time + "50000000": { + number: 50000000, + txCount: 358, + gasUsed: "50,409,444", + gasUsedPercent: "72.0%", + gasLimit: "70,000,000", + hash: "0x66132739a8759cd2fc911fb31eee5a6beaefaa25cf25385b6dec775f7ba02192", + parentHash: "0x551190b5d3d2a5de87becc1612c03a2568e7fc1c865a0e3d68776b16573c2789", + }, + }, + + transactions: { + // ============================================ + // LEGACY TRANSACTIONS (Type 0) + // ============================================ + + // Contract interaction from block 20,000,000 - Legacy transaction + // Real transaction with verified on-chain data + "0xad5c9b13688627d670985d68a5be0fadd5f0e34d3ff20e35c655ef4bceec7e7c": { + hash: "0xad5c9b13688627d670985d68a5be0fadd5f0e34d3ff20e35c655ef4bceec7e7c", + type: 0, // Legacy transaction + from: "0x67e83034d88c665c661c77f8c9a1a6464224ee9d", + to: "0x0303d52057efef51eeea9ad36bc788df827f183d", + value: "0x0", + blockNumber: 20000000, + gas: "700,000", + gasUsed: "40,462", + gasPrice: "25.21 Gwei", + nonce: 266694, + position: 0, + status: "success" as const, + hasInputData: true, + }, + + // DEX swap from block 40,000,000 - Legacy transaction + // swapExactTokensForTokens on a DEX router + "0x0e3384ad2350d20921190b15e29305ed08eecfe97de975b6e015a6c6d476a90a": { + hash: "0x0e3384ad2350d20921190b15e29305ed08eecfe97de975b6e015a6c6d476a90a", + type: 0, // Legacy transaction + from: "0xbb6694f2ce58d9c83b35ad65da6f6423756e0585", + to: "0xeddb16da43daed83158417955dc0c402c61e7e7d", + value: "0x0", + blockNumber: 40000000, + gas: "257,348", + gasUsed: "170,460", + gasPrice: "7 Gwei", + nonce: 39, + position: 0, + status: "success" as const, + hasInputData: true, + method: "swapExactTokensForTokens", + }, + + // DEX aggregator swap from block 50,000,000 - Legacy transaction + // Complex multi-hop swap with many token transfers + "0x874a90a47bc3140adbffff0f4b89da4bea48f9420f97bc5a50e2e478d9a06176": { + hash: "0x874a90a47bc3140adbffff0f4b89da4bea48f9420f97bc5a50e2e478d9a06176", + type: 0, // Legacy transaction + from: "0x05a13e324ac38d76e06dd95f72194f1570f5fa7d", + to: "0x22444f9024367c1313613d54efa31f0aaf8627d7", + value: "0x0", + blockNumber: 50000000, + gas: "1,397,513", + gasUsed: "906,323", + gasPrice: "0.1 Gwei", + nonce: 20548, + position: 2, + status: "success" as const, + hasInputData: true, + }, + }, + + addresses: { + // ============================================ + // ERC20/BEP20 TOKENS + // ============================================ + + // Wrapped BNB (WBNB) + wbnb: { + address: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", + type: "erc20" as const, + symbol: "WBNB", + name: "Wrapped BNB", + decimals: 18, + }, + // USDT (Binance-Peg BSC-USD) + usdt: { + address: "0x55d398326f99059fF775485246999027B3197955", + type: "erc20" as const, + symbol: "USDT", + name: "Binance-Peg BSC-USD", + decimals: 18, + }, + // BUSD (Binance-Peg BUSD Token) + busd: { + address: "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56", + type: "erc20" as const, + symbol: "BUSD", + name: "Binance-Peg BUSD Token", + decimals: 18, + }, + // USDC (Binance-Peg USD Coin) + usdc: { + address: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d", + type: "erc20" as const, + symbol: "USDC", + name: "Binance-Peg USD Coin", + decimals: 18, + }, + // CAKE (PancakeSwap Token) + cake: { + address: "0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82", + type: "erc20" as const, + symbol: "Cake", + name: "PancakeSwap Token", + decimals: 18, + }, + // DAI (Binance-Peg Dai Token) + dai: { + address: "0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3", + type: "erc20" as const, + symbol: "DAI", + name: "Binance-Peg Dai Token", + decimals: 18, + }, + + // ============================================ + // DEX CONTRACTS + // ============================================ + + // PancakeSwap Router v2 - Main DEX on BSC + pancakeswapRouterV2: { + address: "0x10ED43C718714eb63d5aA57B78B54704E256024E", + type: "contract" as const, + name: "PancakeSwap: Router v2", + }, + // PancakeSwap Factory v2 + pancakeswapFactoryV2: { + address: "0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73", + type: "contract" as const, + name: "PancakeSwap: Factory v2", + }, + // PancakeSwap Universal Router + pancakeswapUniversalRouter: { + address: "0x1A0A18AC4BECDDbd6389559687d1A73d8927E416", + type: "contract" as const, + name: "PancakeSwap: Universal Router", + }, + + // ============================================ + // SYSTEM CONTRACTS (BSC predeploys) + // ============================================ + + // Validator Set Contract - Manages validator elections + validatorSet: { + address: "0x0000000000000000000000000000000000001000", + type: "contract" as const, + name: "BSC: Validator Set", + }, + // Slash Contract - Handles validator slashing + slashContract: { + address: "0x0000000000000000000000000000000000001001", + type: "contract" as const, + name: "BSC: Slash Contract", + }, + // System Reward Contract - Distributes system rewards + systemReward: { + address: "0x0000000000000000000000000000000000001002", + type: "contract" as const, + name: "BSC: System Reward", + }, + // Light Client Contract + lightClient: { + address: "0x0000000000000000000000000000000000001003", + type: "contract" as const, + name: "BSC: Light Client", + }, + // Token Hub - Cross-chain token management + tokenHub: { + address: "0x0000000000000000000000000000000000001004", + type: "contract" as const, + name: "BSC: Token Hub", + }, + // Relayer Hub - Cross-chain relayer management + relayerHub: { + address: "0x0000000000000000000000000000000000001006", + type: "contract" as const, + name: "BSC: Relayer Hub", + }, + // Staking Contract - Manages BNB staking + stakeHub: { + address: "0x0000000000000000000000000000000000002002", + type: "contract" as const, + name: "BSC: Stake Hub", + }, + // Governor Contract - Governance + governor: { + address: "0x0000000000000000000000000000000000002004", + type: "contract" as const, + name: "BSC: Governor", + }, + + // ============================================ + // STAKING CONTRACTS + // ============================================ + + // PancakeSwap Main Staking Contract + pancakeswapStaking: { + address: "0x73feaa1eE314F8c655E354234017bE2193C9E24E", + type: "contract" as const, + name: "PancakeSwap: Main Staking Contract", + }, + // PancakeSwap Cake Pool + pancakeswapCakePool: { + address: "0x45c54210128a065de780C4B0Df3d16664f7f859e", + type: "contract" as const, + name: "PancakeSwap: Cake Pool", + }, + }, + + // Upgrade block heights and timestamps for reference + upgrades: { + bruno: { + blockHeight: 13082000, + timestamp: 1638259200, + date: "2021-11-30T08:00:00Z", + description: "Real-time BNB burning mechanism (BEP-95)", + }, + euler: { + blockHeight: 18907621, + timestamp: 1655884800, + date: "2022-06-22T08:00:00Z", + description: "Increased validators, enhanced decentralization (BEP-127/131)", + }, + planck: { + blockHeight: 27281024, + timestamp: 1681274400, + date: "2023-04-12T05:30:00Z", + description: "Cross-chain security enhancements (ICS23)", + }, + luban: { + blockHeight: 29020050, + timestamp: 1686516600, + date: "2023-06-11T21:30:00Z", + description: "Fast finality mechanism capability (BEP-126)", + }, + plato: { + blockHeight: 30720096, + timestamp: 1691668800, + date: "2023-08-10T12:00:00Z", + description: "Fast finality fully enabled (BEP-126)", + }, + hertz: { + blockHeight: 31302048, + timestamp: 1693382400, + date: "2023-08-30T07:30:00Z", + description: "Berlin/London EIPs for EVM compatibility", + }, + feynman: { + timestamp: 1713430140, + date: "2024-04-18T05:49:00Z", + description: "BNB Chain Fusion, validators increased to 45", + }, + tycho: { + timestamp: 1718668800, + date: "2024-06-18T00:00:00Z", + description: "Blob transactions support (BEP-336)", + }, + bohr: { + timestamp: 1727350800, + date: "2024-09-26T12:00:00Z", + description: "Consecutive block production (BEP-341)", + }, + pascal: { + timestamp: 1741996800, + date: "2025-03-15T00:00:00Z", + description: "Smart contract wallets (EIP-7702), BLS12-381", + }, + lorentz: { + timestamp: 1745366400, + date: "2025-04-22T00:00:00Z", + description: "Block time reduced to 1.5 seconds", + }, + maxwell: { + timestamp: 1751270400, + date: "2025-06-30T00:00:00Z", + description: "Block time reduced to 0.75 seconds (BEP-524/563/564)", + }, + fermi: { + timestamp: 1736825400, + date: "2026-01-14T02:30:00Z", + description: "Block time reduced to 0.45 seconds (BEP-619/590)", + }, + }, +}; diff --git a/e2e/tests/bsc.spec.ts b/e2e/tests/bsc.spec.ts new file mode 100644 index 0000000..783836a --- /dev/null +++ b/e2e/tests/bsc.spec.ts @@ -0,0 +1,872 @@ +import { test, expect } from "../fixtures/test"; +import { BlockPage } from "../pages/block.page"; +import { TransactionPage } from "../pages/transaction.page"; +import { AddressPage } from "../pages/address.page"; +import { BSC } from "../fixtures/bsc"; + +const CHAIN_ID = BSC.chainId; + +// Transaction hash constants for readability +const BLOCK_20M_TX = "0xad5c9b13688627d670985d68a5be0fadd5f0e34d3ff20e35c655ef4bceec7e7c"; +const DEX_SWAP_TX = "0x0e3384ad2350d20921190b15e29305ed08eecfe97de975b6e015a6c6d476a90a"; +const DEX_AGGREGATOR_TX = "0x874a90a47bc3140adbffff0f4b89da4bea48f9420f97bc5a50e2e478d9a06176"; + +// Helper to wait for block content or error +async function waitForBlockContent(page: import("@playwright/test").Page) { + await expect( + page + .locator("text=Transactions:") + .or(page.locator("text=Error:")) + .or(page.locator("text=Something went wrong")) + ).toBeVisible({ timeout: 45000 }); + + return ( + !(await page.locator("text=Error:").isVisible()) && + !(await page.locator("text=Something went wrong").isVisible()) + ); +} + +// Helper to wait for transaction content or error +async function waitForTxContent(page: import("@playwright/test").Page) { + await expect( + page + .locator("text=Transaction Hash:") + .or(page.locator("text=Error:")) + .or(page.locator("text=Something went wrong")) + ).toBeVisible({ timeout: 45000 }); + + return ( + !(await page.locator("text=Error:").isVisible()) && + !(await page.locator("text=Something went wrong").isVisible()) + ); +} + +// Helper to wait for address content or error +async function waitForAddressContent(page: import("@playwright/test").Page) { + await expect( + page + .locator("text=Balance:") + .or(page.locator("text=Error:")) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout: 45000 }); + + return ( + !(await page.locator("text=Error:").isVisible()) && + !(await page.locator("text=Something went wrong").isVisible()) + ); +} + +// ============================================ +// BLOCK TESTS +// ============================================ + +test.describe("BSC Block Page", () => { + test("genesis block #0 - BSC mainnet launch", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = BSC.blocks["0"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + // Header section + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.timestampAge).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Transactions - genesis has 0 transactions + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator("text=0 transactions in this block")).toBeVisible(); + + // Gas Used (0 for genesis) + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator("text=0 (0.0%)")).toBeVisible(); + + // Gas Limit + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("genesis block #0 more details shows hash and parent hash", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = BSC.blocks["0"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + // Click "Show More Details" to expand + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + + // Wait for details to expand + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + // Verify hash field labels + await expect(page.getByText("Hash:", { exact: true })).toBeVisible(); + await expect(page.getByText("Parent Hash:", { exact: true })).toBeVisible(); + + // Verify hash values + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + // Parent hash is all zeros for genesis - use .first() to avoid matching logs bloom + await expect(page.locator(`text=${block.parentHash}`).first()).toBeVisible(); + } + } + }); + + test("block #10,000,000 - Pre-Euler block with transactions", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = BSC.blocks["10000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + // Header + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Transactions with count + await expect(page.locator("text=Transactions:")).toBeVisible(); + + // Fee Recipient (validator) + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + + // Gas Used + await expect(page.locator("text=Gas Used:")).toBeVisible(); + + // Gas Limit + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("block #10,000,000 more details shows hashes", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = BSC.blocks["10000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + // Verify hash field labels + await expect(page.getByText("Hash:", { exact: true })).toBeVisible(); + await expect(page.getByText("Parent Hash:", { exact: true })).toBeVisible(); + + // Block hash + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + // Parent hash + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #20,000,000 - Post-Euler block with gas details", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = BSC.blocks["20000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + // Header + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Transaction count + await expect(page.locator("text=Transactions:")).toBeVisible(); + + // Fee Recipient (validator) + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + + // Gas Used + await expect(page.locator("text=Gas Used:")).toBeVisible(); + + // Gas Limit + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("block #20,000,000 more details shows hashes", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = BSC.blocks["20000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #30,000,000 - Post-Luban with fast finality", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = BSC.blocks["30000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + // Header + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Transactions + await expect(page.locator("text=Transactions:")).toBeVisible(); + + // Fee Recipient (validator) + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + + // Gas Used + await expect(page.locator("text=Gas Used:")).toBeVisible(); + + // Gas Limit + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("block #30,000,000 more details shows hashes", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = BSC.blocks["30000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #40,000,000 - Post-Feynman after BNB Chain Fusion", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = BSC.blocks["40000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + // Header + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Transaction count + await expect(page.locator("text=Transactions:")).toBeVisible(); + + // Fee Recipient (validator) + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + + // Gas Used + await expect(page.locator("text=Gas Used:")).toBeVisible(); + + // Gas Limit + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("block #40,000,000 more details shows hashes", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = BSC.blocks["40000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #50,000,000 - Post-Maxwell with 0.75s block time", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = BSC.blocks["50000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + // Header + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Transaction count + await expect(page.locator("text=Transactions:")).toBeVisible(); + + // Fee Recipient (validator) + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + + // Gas Used + await expect(page.locator("text=Gas Used:")).toBeVisible(); + + // Gas Limit + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("block #50,000,000 more details shows hashes", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = BSC.blocks["50000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block navigation buttons work", async ({ page }) => { + const blockPage = new BlockPage(page); + await blockPage.goto(BSC.blocks["10000000"].number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + // Verify navigation buttons exist + await expect(blockPage.navPrevBtn).toBeVisible(); + await expect(blockPage.navNextBtn).toBeVisible(); + } + }); + + test("handles invalid block number gracefully", async ({ page }) => { + const blockPage = new BlockPage(page); + await blockPage.goto(999999999999, CHAIN_ID); + + await expect( + blockPage.errorText + .or(blockPage.container) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout: 30000 }); + }); +}); + +// ============================================ +// TRANSACTION TESTS +// ============================================ + +test.describe("BSC Transaction Page", () => { + test("displays transaction from block 20M with all details", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = BSC.transactions[BLOCK_20M_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + // Verify core transaction details + await expect(page.locator("text=Transaction Hash:")).toBeVisible(); + await expect(page.locator("text=Status:")).toBeVisible(); + await expect(page.locator("text=Block:")).toBeVisible(); + await expect(page.locator("text=From:")).toBeVisible(); + await expect(page.locator("text=To:")).toBeVisible(); + + // Verify gas information + await expect(page.getByText("Gas Price:", { exact: true })).toBeVisible(); + await expect(page.locator("text=Gas Limit")).toBeVisible(); + + // Verify has input data (contract interaction) + await expect(page.locator("text=Input Data:")).toBeVisible(); + } + }); + + test("shows correct from and to addresses for block 20M tx", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = BSC.transactions[BLOCK_20M_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + // Verify from address + const from = await txPage.getFromAddress(); + expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); + + // Verify to address + const to = await txPage.getToAddress(); + expect(to?.toLowerCase()).toContain(tx.to?.toLowerCase().slice(0, 10)); + } + }); + + test("displays legacy transaction type correctly (Type 0)", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = BSC.transactions[BLOCK_20M_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + // Legacy transaction should show Transaction Type with value 0 or "Legacy" + // Check for "Type:" which is how the UI displays it + await expect( + page.locator("text=Type:").or(page.locator("text=Transaction Type:")).first() + ).toBeVisible(); + } + }); + + test("displays DEX swap transaction from block 40M", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = BSC.transactions[DEX_SWAP_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + // Verify core transaction details + await expect(page.locator("text=Transaction Hash:")).toBeVisible(); + await expect(page.locator("text=Status:")).toBeVisible(); + await expect(page.locator("text=Block:")).toBeVisible(); + + // Verify gas information + await expect(page.locator("text=Gas Limit")).toBeVisible(); + + // Verify has input data (DEX swap) + await expect(page.locator("text=Input Data:")).toBeVisible(); + } + }); + + test("DEX swap shows correct addresses and gas details", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = BSC.transactions[DEX_SWAP_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + const from = await txPage.getFromAddress(); + expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); + + const to = await txPage.getToAddress(); + expect(to?.toLowerCase()).toContain(tx.to?.toLowerCase().slice(0, 10)); + + // Verify gas limit is displayed + await expect(page.locator("text=Gas Limit")).toBeVisible(); + } + }); + + test("displays DEX aggregator transaction from block 50M", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = BSC.transactions[DEX_AGGREGATOR_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + // Verify core transaction details + await expect(page.locator("text=Transaction Hash:")).toBeVisible(); + await expect(page.locator("text=Status:")).toBeVisible(); + + // Verify gas information + await expect(page.locator("text=Gas Limit")).toBeVisible(); + + // Verify has input data (contract interaction) + await expect(page.locator("text=Input Data:")).toBeVisible(); + } + }); + + test("displays transaction nonce and position for block 20M tx", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = BSC.transactions[BLOCK_20M_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + // Verify other attributes section + await expect(page.locator("text=Other Attributes:")).toBeVisible(); + await expect(page.locator("text=Nonce:")).toBeVisible(); + await expect(page.locator("text=Position:")).toBeVisible(); + + // Verify nonce value (use label to avoid strict mode issues) + await expect(page.locator(`text=Nonce: ${tx.nonce}`)).toBeVisible(); + } + }); + + test("displays DEX swap nonce and position", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = BSC.transactions[DEX_SWAP_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + await expect(page.locator("text=Nonce:")).toBeVisible(); + await expect(page.locator("text=Position:")).toBeVisible(); + + // Verify nonce value + await expect(page.locator(`text=Nonce: ${tx.nonce}`)).toBeVisible(); + } + }); + + test("displays DEX aggregator nonce and position", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = BSC.transactions[DEX_AGGREGATOR_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + await expect(page.locator("text=Nonce:")).toBeVisible(); + await expect(page.locator("text=Position:")).toBeVisible(); + + // Verify nonce value + await expect(page.locator(`text=Nonce: ${tx.nonce}`)).toBeVisible(); + + // Verify position value + await expect(page.locator(`text=Position: ${tx.position}`)).toBeVisible(); + } + }); + + test("displays transaction fee", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = BSC.transactions[BLOCK_20M_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + // Verify transaction fee is displayed + await expect(page.locator("text=Transaction Fee:")).toBeVisible(); + } + }); + + test("displays block number link for transaction", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = BSC.transactions[DEX_SWAP_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + await expect(page.locator("text=Block:")).toBeVisible(); + const blockValue = await txPage.getBlockNumber(); + expect(blockValue).toBeTruthy(); + } + }); + + test("handles invalid tx hash gracefully", async ({ page }) => { + const txPage = new TransactionPage(page); + await txPage.goto("0xinvalid", CHAIN_ID); + + await expect( + txPage.errorText + .or(txPage.container) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout: 30000 }); + }); +}); + +// ============================================ +// ADDRESS TESTS - BEP20 TOKENS +// ============================================ + +test.describe("BSC Address Page - Tokens", () => { + test("displays WBNB token contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.wbnb; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + // Verify contract has balance section + await expect( + page.locator("text=Contract Balance:").or(page.locator("text=Balance:")) + ).toBeVisible(); + } + }); + + test("displays USDT (BSC-USD) token contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.usdt; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays BUSD token contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.busd; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays CAKE (PancakeSwap Token) contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.cake; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays USDC token contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.usdc; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); + + test("displays DAI token contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.dai; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + } + }); +}); + +// ============================================ +// ADDRESS TESTS - DEX CONTRACTS +// ============================================ + +test.describe("BSC Address Page - DEX Contracts", () => { + test("displays PancakeSwap Router v2 contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.pancakeswapRouterV2; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + // Check for contract details section + await expect(page.locator("text=Contract Details")).toBeVisible(); + } + }); + + test("displays PancakeSwap Factory v2 contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.pancakeswapFactoryV2; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + // Check for contract details section + await expect(page.locator("text=Contract Details")).toBeVisible(); + } + }); + + test("displays PancakeSwap Universal Router contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.pancakeswapUniversalRouter; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + // Check for contract details section + await expect(page.locator("text=Contract Details")).toBeVisible(); + } + }); +}); + +// ============================================ +// ADDRESS TESTS - SYSTEM CONTRACTS +// ============================================ + +test.describe("BSC Address Page - System Contracts", () => { + test("displays Validator Set system contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.validatorSet; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + // Verify address is displayed + await expect(page.locator(`text=${addr.address.slice(0, 10)}`)).toBeVisible(); + } + }); + + test("displays System Reward contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.systemReward; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + // Verify address is displayed + await expect(page.locator(`text=${addr.address.slice(0, 10)}`)).toBeVisible(); + } + }); + + test("displays Token Hub contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.tokenHub; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + // Verify address is displayed + await expect(page.locator(`text=${addr.address.slice(0, 10)}`)).toBeVisible(); + } + }); + + test("displays Stake Hub contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.stakeHub; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + // Verify address is displayed + await expect(page.locator(`text=${addr.address.slice(0, 10)}`)).toBeVisible(); + } + }); + + test("displays Governor contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.governor; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + // Verify address is displayed + await expect(page.locator(`text=${addr.address.slice(0, 10)}`)).toBeVisible(); + } + }); + + test("handles invalid address gracefully", async ({ page }) => { + const addressPage = new AddressPage(page); + await addressPage.goto("0xinvalid", CHAIN_ID); + + await expect( + addressPage.errorText + .or(addressPage.container) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout: 30000 }); + }); +}); + +// ============================================ +// ADDRESS TESTS - STAKING CONTRACTS +// ============================================ + +test.describe("BSC Address Page - Staking Contracts", () => { + test("displays PancakeSwap Main Staking contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.pancakeswapStaking; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + // Check for contract details section + await expect(page.locator("text=Contract Details")).toBeVisible(); + } + }); + + test("displays PancakeSwap Cake Pool contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const addr = BSC.addresses.pancakeswapCakePool; + + await addressPage.goto(addr.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + // Verify it's identified as a contract + const isContract = await addressPage.isContract(); + expect(isContract).toBe(true); + + // Check for contract details section + await expect(page.locator("text=Contract Details")).toBeVisible(); + } + }); +}); From 8070c9360488d9c75afce15c1baf7b29cfbe5910 Mon Sep 17 00:00:00 2001 From: Augusto Lemble Date: Fri, 2 Jan 2026 10:42:39 -0300 Subject: [PATCH 6/9] feat(e2e): add Polygon network e2e tests Add comprehensive e2e tests for Polygon PoS network covering: Blocks (46 tests total): - Genesis block #0 with hash verification - Block #10,000,000 (early Polygon activity) - Block #20,000,000 (growing DeFi activity) - Block #30,000,000 (mature network) - Block #38,189,056 (Delhi hard fork) - Block #50,000,000 (high activity) - Block #62,278,656 (Ahmedabad hard fork - MATIC to POL) - Block #65,000,000 (post-Ahmedabad POL era) - More details section tests for all blocks Transactions: - Legacy Type 0 NFT transfer from block 30M - EIP-1559 DeFi swap from block 50M - EIP-1559 contract interaction from block 65M - Nonce and position verification Addresses: - ERC20 tokens (WPOL, USDC.e, USDC, USDT, WETH, DAI, AAVE, LINK) - DEX contracts (QuickSwap Router, Uniswap V3, SushiSwap) - NFT & Lending (OpenSea Storefront, Aave V3 Pool) - System contracts (POL Token, StateReceiver) Fixture includes real on-chain data with upgrade history: - Mainnet launch (May 2020) - EIP-1559 activation (March 2022) - Delhi hard fork (January 2023) - Napoli hard fork (March 2024) - Ahmedabad hard fork (September 2024) - Heimdall v2 (July 2025) --- e2e/fixtures/polygon.ts | 355 +++++++++++++++++ e2e/tests/polygon.spec.ts | 787 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 1142 insertions(+) create mode 100644 e2e/fixtures/polygon.ts create mode 100644 e2e/tests/polygon.spec.ts diff --git a/e2e/fixtures/polygon.ts b/e2e/fixtures/polygon.ts new file mode 100644 index 0000000..8e9e425 --- /dev/null +++ b/e2e/fixtures/polygon.ts @@ -0,0 +1,355 @@ +// cspell:ignore quickswap uniswap aavegotchi opensea matic heimdall +export const POLYGON = { + chainId: "137", + + // Polygon PoS mainnet launch: May 30, 2020 + // Originally Matic Network, rebranded to Polygon in February 2021 + // Block time: ~2 seconds + // Consensus: Proof-of-Stake with Heimdall (consensus) + Bor (block production) + // Native token: MATIC (migrated to POL in September 2024) + + blocks: { + // Genesis block - Polygon PoS mainnet launch (May 30, 2020) + "0": { + number: 0, + txCount: 0, + gasUsed: "0", + gasLimit: "10,000,000", + hash: "0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b", + parentHash: "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + // Block 10,000,000 (December 2020) + // Early Polygon activity + "10000000": { + number: 10000000, + txCount: 6, + gasUsed: "4,278,607", + gasUsedPercent: "21.4%", + gasLimit: "20,000,000", + hash: "0x57d179b4ed2379580c46d9809c8918e28c4f1debea8e15013749694f37c14105", + parentHash: "0xee82cb38f164bfdb75092bcc5c1cb302385a48b2d9abd647af934ecb89db61f4", + }, + // Block 20,000,000 (May 2021) + // Growing DeFi activity + "20000000": { + number: 20000000, + txCount: 111, + gasUsed: "19,998,514", + gasUsedPercent: "100.0%", + gasLimit: "20,000,000", + hash: "0x8b047896ef57b3ebe10a6b0e4cc28a1e0491b09706c6e0f2b5eff3992cf04730", + parentHash: "0x3a61eaf4cc52b8c3a217fbcfd0e862b59e01d504368b659db9314921879dcf9e", + }, + // Block 30,000,000 (July 2022) + // Mature network, increased gas limit + "30000000": { + number: 30000000, + txCount: 133, + gasUsed: "18,658,580", + gasUsedPercent: "62.2%", + gasLimit: "30,000,000", + hash: "0xd409f8e3d2db0568634bcedc71ae48f4ed8dfcececb723db218a0fc37a5e8d44", + parentHash: "0xa9d361ef8ba2d3c4d05ea5bee19026e9cad27ae1e95cf9bc86ec30e246aba9f4", + }, + // Block 38,189,056 (January 17, 2023) + // Delhi Hard Fork - reduced sprint length, smoothed baseFee + "38189056": { + number: 38189056, + txCount: 63, + gasUsed: "8,896,235", + gasLimit: "31,898,693", + hash: "0xf034b3df03fe4c2aedec6590754b6ebc25289369c6df64aee1613fe458b5abc6", + parentHash: "0x94cb3ecc028d21aa11031ff21d645ac2d91a14a55f24303e65b39910547060bd", + }, + // Block 50,000,000 (November 2023) + // Post-Delhi, high activity + "50000000": { + number: 50000000, + txCount: 564, + gasUsed: "26,134,824", + gasUsedPercent: "88.4%", + gasLimit: "29,563,532", + hash: "0xed6bc55bb3fbf391fb47a96bb0327906a2dab5f50c1330f89684b79a5195efaa", + parentHash: "0xafe719f6ca102ab7b7b9fd367688e73a777d02d21d6a692a8ff6a6eb3c2f7c27", + }, + // Block 62,278,656 (September 25, 2024) + // Ahmedabad Hard Fork - MATIC to POL migration + "62278656": { + number: 62278656, + txCount: 46, + gasUsed: "5,457,470", + gasLimit: "30,000,000", + hash: "0xd0a3278e1c13d54e222d53b76bec3669871c82b7a69f7840d43fc1fb4d9c735d", + parentHash: "0x394674d611451512b96c1f4c2b741403387235b61104d1a38ba09ffa6d891f43", + }, + // Block 65,000,000 (December 2024) + // Post-Ahmedabad, POL era + "65000000": { + number: 65000000, + txCount: 103, + gasUsed: "13,884,738", + gasUsedPercent: "46.3%", + gasLimit: "30,000,000", + hash: "0x40a243f82db77b7557e7b56808e93ef72c5bc83f16ad5ede236b496a78736eb6", + parentHash: "0x5fa5e769a0b2bd46a7f857af0507587d2d02f67305d917dfdeeb2d6087b21a18", + }, + }, + + transactions: { + // ============================================ + // LEGACY TRANSACTIONS (Type 0) + // ============================================ + + // OpenSea NFT transfer from block 30,000,000 - Legacy transaction + "0xb14598e46791c2f0ab366ba2fd4a533e21a0c9894f902773e02e3869b7373c3e": { + hash: "0xb14598e46791c2f0ab366ba2fd4a533e21a0c9894f902773e02e3869b7373c3e", + type: 0, // Legacy transaction + from: "0x3ce07ad298ee2b3aabea8c8b3f496c3acc51e647", + to: "0x2953399124f0cbb46d2cbacd8a89cf0599974963", // OpenSea Storefront + value: "0x0", + blockNumber: 30000000, + gas: "189,792", + gasUsed: "89,556", + gasPrice: "125 Gwei", + nonce: 30637600, + position: 0, + status: "success" as const, + hasInputData: true, + }, + + // ============================================ + // EIP-1559 TRANSACTIONS (Type 2) + // ============================================ + + // DeFi swap from block 50,000,000 - EIP-1559 transaction + "0x1ed0c46bafb76d5a3d8201cdf8fc732efa97b000d88bd48dc203ac45d6340af0": { + hash: "0x1ed0c46bafb76d5a3d8201cdf8fc732efa97b000d88bd48dc203ac45d6340af0", + type: 2, // EIP-1559 + from: "0x2c61d22af7b615d7d41def680b6edac29076709d", + to: "0x826a4f4da02588737d3c27325b14f39b5151ca3c", + value: "0x0", + blockNumber: 50000000, + gas: "999,502", + gasUsed: "66,787", + nonce: 2151, + position: 0, + status: "success" as const, + hasInputData: true, + }, + + // Contract interaction from block 65,000,000 - EIP-1559 transaction + "0x65edbf03a20a0317295efaeb9c20836b20b16740c8311ce51ceee91d7674b20d": { + hash: "0x65edbf03a20a0317295efaeb9c20836b20b16740c8311ce51ceee91d7674b20d", + type: 2, // EIP-1559 + from: "0x706c7fa886ccaf510e570c5fa91f5988b15a8a56", + to: "0xe957a692c97566efc85f995162fa404091232b2e", + value: "0x0", + blockNumber: 65000000, + gas: "235,648", + gasUsed: "225,106", + nonce: 34547, + position: 0, + status: "success" as const, + hasInputData: true, + }, + }, + + addresses: { + // ============================================ + // ERC20 TOKENS + // ============================================ + + // Wrapped POL (WPOL) - formerly WMATIC + wpol: { + address: "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270", + type: "erc20" as const, + symbol: "WPOL", + name: "Wrapped POL", + decimals: 18, + }, + // USDC (Bridged from Ethereum) + usdc: { + address: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", + type: "erc20" as const, + symbol: "USDC.e", + name: "USD Coin (Bridged)", + decimals: 6, + }, + // Native USDC on Polygon + usdcNative: { + address: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", + type: "erc20" as const, + symbol: "USDC", + name: "USD Coin", + decimals: 6, + }, + // USDT on Polygon + usdt: { + address: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F", + type: "erc20" as const, + symbol: "USDT", + name: "Tether USD", + decimals: 6, + }, + // WETH on Polygon + weth: { + address: "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619", + type: "erc20" as const, + symbol: "WETH", + name: "Wrapped Ether", + decimals: 18, + }, + // DAI on Polygon + dai: { + address: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063", + type: "erc20" as const, + symbol: "DAI", + name: "Dai Stablecoin", + decimals: 18, + }, + // AAVE on Polygon + aave: { + address: "0xD6DF932A45C0f255f85145f286eA0b292B21C90B", + type: "erc20" as const, + symbol: "AAVE", + name: "Aave", + decimals: 18, + }, + // LINK on Polygon + link: { + address: "0x53E0bca35eC356BD5ddDFebbD1Fc0fD03FaBad39", + type: "erc20" as const, + symbol: "LINK", + name: "ChainLink Token", + decimals: 18, + }, + + // ============================================ + // DEX CONTRACTS + // ============================================ + + // QuickSwap Router - Main DEX on Polygon + quickswapRouter: { + address: "0xa5E0829CaCEd8fFDD4De3c43696c57F7D7A678ff", + type: "contract" as const, + name: "QuickSwap: Router", + }, + // Uniswap V3 Router on Polygon + uniswapV3Router: { + address: "0xE592427A0AEce92De3Edee1F18E0157C05861564", + type: "contract" as const, + name: "Uniswap V3: SwapRouter", + }, + // SushiSwap Router + sushiswapRouter: { + address: "0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506", + type: "contract" as const, + name: "SushiSwap: Router", + }, + + // ============================================ + // NFT CONTRACTS + // ============================================ + + // OpenSea Storefront (Polygon) + openseaStorefront: { + address: "0x2953399124F0cBB46d2CbACD8A89cF0599974963", + type: "contract" as const, + name: "OpenSea Shared Storefront", + }, + + // ============================================ + // LENDING PROTOCOLS + // ============================================ + + // Aave V3 Pool on Polygon + aaveV3Pool: { + address: "0x794a61358D6845594F94dc1DB02A252b5b4814aD", + type: "contract" as const, + name: "Aave V3: Pool", + }, + + // ============================================ + // BRIDGE CONTRACTS + // ============================================ + + // Polygon PoS Bridge (RootChainManager on Ethereum side) + polygonBridge: { + address: "0xA0c68C638235ee32657e8f720a23ceC1bFc77C77", + type: "contract" as const, + name: "Polygon: ERC20 Bridge", + }, + + // ============================================ + // SYSTEM CONTRACTS + // ============================================ + + // Child Chain Manager - manages token deposits + childChainManager: { + address: "0xA6FA4fB5f76172d178d61B04b0ecd319C5d1C0aa", + type: "contract" as const, + name: "Polygon: ChildChainManager", + }, + // State Receiver - receives state from Ethereum + stateReceiver: { + address: "0x0000000000000000000000000000000000001001", + type: "contract" as const, + name: "Polygon: StateReceiver", + }, + // Matic Token (native, system address) + maticToken: { + address: "0x0000000000000000000000000000000000001010", + type: "contract" as const, + name: "Polygon: POL Token", + }, + }, + + // Upgrade timestamps and block heights for reference + upgrades: { + mainnetLaunch: { + blockHeight: 0, + timestamp: 1590824836, + date: "2020-05-30T07:47:16Z", + description: "Polygon PoS mainnet launch (as Matic Network)", + }, + rebranding: { + timestamp: 1613001600, + date: "2021-02-11T00:00:00Z", + description: "Matic Network rebrands to Polygon", + }, + eip1559: { + blockHeight: 23850000, + timestamp: 1647619200, + date: "2022-03-18T08:00:00Z", + description: "EIP-1559 and London hard fork activation", + }, + delhi: { + blockHeight: 38189056, + timestamp: 1673974800, + date: "2023-01-17T18:00:00Z", + description: "Delhi hard fork - reduced sprint length (64→16), smoothed baseFee", + }, + napoli: { + timestamp: 1710892800, + date: "2024-03-20T00:00:00Z", + description: "Napoli hard fork - RIP-7212 secp256r1 precompile support", + }, + ahmedabad: { + blockHeight: 62278656, + timestamp: 1727344800, + date: "2024-09-26T09:00:00Z", + description: "Ahmedabad hard fork - MATIC to POL migration, code size limit increase", + }, + heimdallV2: { + heimdallHeight: 24404500, + timestamp: 1752159600, + date: "2025-07-10T14:00:00Z", + description: "Heimdall v2 - consensus layer upgrade to CometBFT, ~5s finality", + }, + madhugiri: { + timestamp: 1763920800, + date: "2025-12-09T00:00:00Z", + description: "Madhugiri hard fork - 33% throughput increase, 1s consensus time", + }, + }, +}; diff --git a/e2e/tests/polygon.spec.ts b/e2e/tests/polygon.spec.ts new file mode 100644 index 0000000..942d5bc --- /dev/null +++ b/e2e/tests/polygon.spec.ts @@ -0,0 +1,787 @@ +import { test, expect } from "../fixtures/test"; +import { BlockPage } from "../pages/block.page"; +import { TransactionPage } from "../pages/transaction.page"; +import { AddressPage } from "../pages/address.page"; +import { POLYGON } from "../fixtures/polygon"; + +const CHAIN_ID = POLYGON.chainId; + +// Transaction hash constants for readability +const LEGACY_NFT_TX = "0xb14598e46791c2f0ab366ba2fd4a533e21a0c9894f902773e02e3869b7373c3e"; +const DEFI_SWAP_TX = "0x1ed0c46bafb76d5a3d8201cdf8fc732efa97b000d88bd48dc203ac45d6340af0"; +const CONTRACT_TX = "0x65edbf03a20a0317295efaeb9c20836b20b16740c8311ce51ceee91d7674b20d"; + +// Helper to wait for block content or error +async function waitForBlockContent(page: import("@playwright/test").Page) { + try { + await expect( + page + .locator("text=Block") + .or(page.locator("text=Error")) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout: 30000 }); + return !(await page.locator("text=Error").isVisible()); + } catch { + return false; + } +} + +// Helper to wait for transaction content or error +async function waitForTxContent(page: import("@playwright/test").Page) { + try { + await expect( + page + .locator("text=Transaction Hash") + .or(page.locator("text=Error")) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout: 30000 }); + return !(await page.locator("text=Error").isVisible()); + } catch { + return false; + } +} + +// Helper to wait for address content or error +async function waitForAddressContent(page: import("@playwright/test").Page) { + try { + await expect( + page + .locator("text=Address") + .or(page.locator("text=Contract")) + .or(page.locator("text=Error")) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout: 30000 }); + return !(await page.locator("text=Error").isVisible()); + } catch { + return false; + } +} + +// ============================================ +// BLOCK TESTS +// ============================================ + +test.describe("Polygon Block Page", () => { + test("genesis block #0 - Polygon PoS mainnet launch", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["0"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + // Header + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Genesis has 0 transactions + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator("text=0 transactions")).toBeVisible(); + + // Gas Used should be 0 + await expect(page.locator("text=Gas Used:")).toBeVisible(); + + // Gas Limit + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("genesis block #0 more details shows hash and parent hash", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["0"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + // Verify hash field labels + await expect(page.getByText("Hash:", { exact: true })).toBeVisible(); + await expect(page.getByText("Parent Hash:", { exact: true })).toBeVisible(); + + // Genesis block hash + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + // Genesis parent hash (all zeros) + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #10,000,000 - Early Polygon activity", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["10000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + // Header + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + + // Transactions + await expect(page.locator("text=Transactions:")).toBeVisible(); + + // Fee Recipient (validator) + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + + // Gas Used + await expect(page.locator("text=Gas Used:")).toBeVisible(); + + // Gas Limit + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("block #10,000,000 more details shows hashes", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["10000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #20,000,000 - Growing DeFi activity", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["20000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("block #20,000,000 more details shows hashes", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["20000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #30,000,000 - Mature network", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["30000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("block #30,000,000 more details shows hashes", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["30000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #38,189,056 - Delhi Hard Fork", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["38189056"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("block #38,189,056 more details shows hashes", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["38189056"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #50,000,000 - High activity block", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["50000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("block #50,000,000 more details shows hashes", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["50000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #62,278,656 - Ahmedabad Hard Fork (MATIC to POL)", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["62278656"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("block #62,278,656 more details shows hashes", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["62278656"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block #65,000,000 - Post-Ahmedabad POL era", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["65000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + await expect(blockPage.blockNumber).toBeVisible(); + await expect(blockPage.statusBadge).toContainText("Finalized"); + await expect(page.locator("text=Transactions:")).toBeVisible(); + await expect(page.locator("text=Fee Recipient:")).toBeVisible(); + await expect(page.locator("text=Gas Used:")).toBeVisible(); + await expect(page.locator("text=Gas Limit:")).toBeVisible(); + } + }); + + test("block #65,000,000 more details shows hashes", async ({ page }) => { + const blockPage = new BlockPage(page); + const block = POLYGON.blocks["65000000"]; + await blockPage.goto(block.number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + const showMoreBtn = page.locator("text=Show More Details"); + if (await showMoreBtn.isVisible()) { + await showMoreBtn.click(); + await expect(page.locator("text=Hide More Details")).toBeVisible(); + + await expect(page.locator(`text=${block.hash}`)).toBeVisible(); + await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + } + } + }); + + test("block page loads successfully", async ({ page }) => { + const blockPage = new BlockPage(page); + await blockPage.goto(POLYGON.blocks["20000000"].number, CHAIN_ID); + + const loaded = await waitForBlockContent(page); + if (loaded) { + // Verify block page loaded with expected content + await expect(blockPage.blockNumber).toBeVisible(); + await expect(page.locator("text=Transactions:")).toBeVisible(); + } + }); + + test("handles invalid block number gracefully", async ({ page }) => { + const blockPage = new BlockPage(page); + await blockPage.goto(999999999999, CHAIN_ID); + + await expect( + blockPage.errorText + .or(blockPage.container) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout: 30000 }); + }); +}); + +// ============================================ +// TRANSACTION TESTS +// ============================================ + +test.describe("Polygon Transaction Page", () => { + test("displays legacy NFT transaction from block 30M", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = POLYGON.transactions[LEGACY_NFT_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + // Verify core transaction details + await expect(page.locator("text=Transaction Hash:")).toBeVisible(); + await expect(page.locator("text=Status:")).toBeVisible(); + await expect(page.locator("text=Block:")).toBeVisible(); + await expect(page.locator("text=From:")).toBeVisible(); + await expect(page.locator("text=To:")).toBeVisible(); + + // Verify gas information + await expect(page.getByText("Gas Price:", { exact: true })).toBeVisible(); + await expect(page.locator("text=Gas Limit")).toBeVisible(); + + // Verify has input data (NFT transfer) + await expect(page.locator("text=Input Data:")).toBeVisible(); + } + }); + + test("shows correct from and to addresses for legacy NFT tx", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = POLYGON.transactions[LEGACY_NFT_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + const from = await txPage.getFromAddress(); + expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); + + const to = await txPage.getToAddress(); + expect(to?.toLowerCase()).toContain(tx.to?.toLowerCase().slice(0, 10)); + } + }); + + test("displays legacy transaction type correctly (Type 0)", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = POLYGON.transactions[LEGACY_NFT_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + await expect( + page.locator("text=Type:").or(page.locator("text=Transaction Type:")).first() + ).toBeVisible(); + } + }); + + test("displays DeFi swap transaction from block 50M", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = POLYGON.transactions[DEFI_SWAP_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + await expect(page.locator("text=Transaction Hash:")).toBeVisible(); + await expect(page.locator("text=Status:")).toBeVisible(); + await expect(page.locator("text=Block:")).toBeVisible(); + await expect(page.locator("text=Gas Limit")).toBeVisible(); + await expect(page.locator("text=Input Data:")).toBeVisible(); + } + }); + + test("DeFi swap shows correct addresses", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = POLYGON.transactions[DEFI_SWAP_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + const from = await txPage.getFromAddress(); + expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); + + const to = await txPage.getToAddress(); + expect(to?.toLowerCase()).toContain(tx.to?.toLowerCase().slice(0, 10)); + } + }); + + test("displays contract interaction from block 65M", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = POLYGON.transactions[CONTRACT_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + await expect(page.locator("text=Transaction Hash:")).toBeVisible(); + await expect(page.locator("text=Status:")).toBeVisible(); + await expect(page.locator("text=Gas Limit")).toBeVisible(); + await expect(page.locator("text=Input Data:")).toBeVisible(); + } + }); + + test("displays transaction nonce and position for legacy NFT tx", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = POLYGON.transactions[LEGACY_NFT_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + await expect(page.locator("text=Other Attributes:")).toBeVisible(); + await expect(page.locator("text=Nonce:")).toBeVisible(); + await expect(page.locator("text=Position:")).toBeVisible(); + + // Verify position value (nonce is very large, just check it's displayed) + await expect(page.locator(`text=Position: ${tx.position}`)).toBeVisible(); + } + }); + + test("displays DeFi swap nonce and position", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = POLYGON.transactions[DEFI_SWAP_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + await expect(page.locator("text=Nonce:")).toBeVisible(); + await expect(page.locator("text=Position:")).toBeVisible(); + + await expect(page.locator(`text=Nonce: ${tx.nonce}`)).toBeVisible(); + } + }); + + test("displays contract tx nonce and position", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = POLYGON.transactions[CONTRACT_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + await expect(page.locator("text=Nonce:")).toBeVisible(); + await expect(page.locator("text=Position:")).toBeVisible(); + + await expect(page.locator(`text=Nonce: ${tx.nonce}`)).toBeVisible(); + await expect(page.locator(`text=Position: ${tx.position}`)).toBeVisible(); + } + }); + + test("displays transaction fee", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = POLYGON.transactions[LEGACY_NFT_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + await expect(page.locator("text=Transaction Fee:")).toBeVisible(); + } + }); + + test("displays block number link for transaction", async ({ page }) => { + const txPage = new TransactionPage(page); + const tx = POLYGON.transactions[DEFI_SWAP_TX]; + + await txPage.goto(tx.hash, CHAIN_ID); + + const loaded = await waitForTxContent(page); + if (loaded) { + await expect(page.locator("text=Block:")).toBeVisible(); + const blockValue = await txPage.getBlockNumber(); + expect(blockValue).toBeTruthy(); + } + }); + + test("handles invalid tx hash gracefully", async ({ page }) => { + const txPage = new TransactionPage(page); + await txPage.goto("0xinvalid", CHAIN_ID); + + await expect( + txPage.errorText + .or(txPage.container) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout: 30000 }); + }); +}); + +// ============================================ +// ADDRESS TESTS - ERC20 TOKENS +// ============================================ + +test.describe("Polygon Address Page - Tokens", () => { + test("displays WPOL (Wrapped POL) token contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const token = POLYGON.addresses.wpol; + await addressPage.goto(token.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + // Contract should show code or token info + await expect( + page.locator("text=Contract").or(page.locator("text=Token")).first() + ).toBeVisible(); + } + }); + + test("displays USDC.e (Bridged USDC) token contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const token = POLYGON.addresses.usdc; + await addressPage.goto(token.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + } + }); + + test("displays Native USDC token contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const token = POLYGON.addresses.usdcNative; + await addressPage.goto(token.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + } + }); + + test("displays USDT token contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const token = POLYGON.addresses.usdt; + await addressPage.goto(token.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + } + }); + + test("displays WETH token contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const token = POLYGON.addresses.weth; + await addressPage.goto(token.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + } + }); + + test("displays DAI token contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const token = POLYGON.addresses.dai; + await addressPage.goto(token.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + } + }); + + test("displays AAVE token contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const token = POLYGON.addresses.aave; + await addressPage.goto(token.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + } + }); + + test("displays LINK token contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const token = POLYGON.addresses.link; + await addressPage.goto(token.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + } + }); +}); + +// ============================================ +// ADDRESS TESTS - DEX CONTRACTS +// ============================================ + +test.describe("Polygon Address Page - DEX Contracts", () => { + test("displays QuickSwap Router contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const contract = POLYGON.addresses.quickswapRouter; + await addressPage.goto(contract.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + await expect( + page.locator("text=Contract").or(page.locator("text=Code")).first() + ).toBeVisible(); + } + }); + + test("displays Uniswap V3 Router contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const contract = POLYGON.addresses.uniswapV3Router; + await addressPage.goto(contract.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + } + }); + + test("displays SushiSwap Router contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const contract = POLYGON.addresses.sushiswapRouter; + await addressPage.goto(contract.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + } + }); +}); + +// ============================================ +// ADDRESS TESTS - NFT & LENDING +// ============================================ + +test.describe("Polygon Address Page - NFT & Lending", () => { + test("displays OpenSea Storefront contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const contract = POLYGON.addresses.openseaStorefront; + await addressPage.goto(contract.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + } + }); + + test("displays Aave V3 Pool contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const contract = POLYGON.addresses.aaveV3Pool; + await addressPage.goto(contract.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + } + }); +}); + +// ============================================ +// ADDRESS TESTS - SYSTEM CONTRACTS +// ============================================ + +test.describe("Polygon Address Page - System Contracts", () => { + test("displays POL Token system contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const contract = POLYGON.addresses.maticToken; + await addressPage.goto(contract.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + } + }); + + test("displays StateReceiver system contract", async ({ page }) => { + const addressPage = new AddressPage(page); + const contract = POLYGON.addresses.stateReceiver; + await addressPage.goto(contract.address, CHAIN_ID); + + const loaded = await waitForAddressContent(page); + if (loaded) { + await expect(page.locator("text=Balance")).toBeVisible(); + } + }); + + test("handles invalid address gracefully", async ({ page }) => { + const addressPage = new AddressPage(page); + await addressPage.goto("0xinvalid", CHAIN_ID); + + await expect( + addressPage.errorText + .or(addressPage.container) + .or(page.locator("text=Something went wrong")) + .or(page.locator("text=Invalid")) + .first() + ).toBeVisible({ timeout: 30000 }); + }); +}); From 96fc38fa952e1a3e0240883ae55d9766933aa1dc Mon Sep 17 00:00:00 2001 From: Augusto Lemble Date: Sun, 4 Jan 2026 11:43:57 -0300 Subject: [PATCH 7/9] refactor(e2e): extract shared helpers and fix test issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Extract wait helpers to e2e/helpers/wait.ts with retry-aware timeouts (formula: 30s base + 10s × retryCount) - Move mainnet tests to e2e/tests/mainnet/ directory for better organization - Update all network tests to use shared helpers with testInfo parameter - Fix Polygon fixture data against Polygonscan: - Block 10M/20M transaction counts - Block 38,189,056 (Delhi) and 62,278,656 (Ahmedabad) hashes and values - Transaction gas values and nonces - Transaction 0x1ed0c4... status (success → failed) - Fix missing testInfo parameters in Optimism tests - Fix genesis block parent hash locator in Polygon tests (use .first()) --- e2e/fixtures/polygon.ts | 40 ++-- e2e/helpers/wait.ts | 122 ++++++++++ e2e/tests/arbitrum.spec.ts | 159 +++++--------- e2e/tests/base.spec.ts | 135 ++++-------- e2e/tests/bsc.spec.ts | 213 +++++++----------- e2e/tests/{ => mainnet}/address.spec.ts | 79 +++---- e2e/tests/{ => mainnet}/block.spec.ts | 46 ++-- e2e/tests/{ => mainnet}/token.spec.ts | 67 +++--- e2e/tests/{ => mainnet}/transaction.spec.ts | 46 ++-- e2e/tests/optimism.spec.ts | 211 +++++++----------- e2e/tests/polygon.spec.ts | 232 ++++++++------------ 11 files changed, 594 insertions(+), 756 deletions(-) create mode 100644 e2e/helpers/wait.ts rename e2e/tests/{ => mainnet}/address.spec.ts (87%) rename e2e/tests/{ => mainnet}/block.spec.ts (90%) rename e2e/tests/{ => mainnet}/token.spec.ts (86%) rename e2e/tests/{ => mainnet}/transaction.spec.ts (73%) diff --git a/e2e/fixtures/polygon.ts b/e2e/fixtures/polygon.ts index 8e9e425..98dfd2e 100644 --- a/e2e/fixtures/polygon.ts +++ b/e2e/fixtures/polygon.ts @@ -18,22 +18,22 @@ export const POLYGON = { hash: "0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b", parentHash: "0x0000000000000000000000000000000000000000000000000000000000000000", }, - // Block 10,000,000 (December 2020) + // Block 10,000,000 (January 2021) // Early Polygon activity "10000000": { number: 10000000, - txCount: 6, + txCount: 5, gasUsed: "4,278,607", gasUsedPercent: "21.4%", gasLimit: "20,000,000", hash: "0x57d179b4ed2379580c46d9809c8918e28c4f1debea8e15013749694f37c14105", parentHash: "0xee82cb38f164bfdb75092bcc5c1cb302385a48b2d9abd647af934ecb89db61f4", }, - // Block 20,000,000 (May 2021) + // Block 20,000,000 (October 2021) // Growing DeFi activity "20000000": { number: 20000000, - txCount: 111, + txCount: 110, gasUsed: "19,998,514", gasUsedPercent: "100.0%", gasLimit: "20,000,000", @@ -55,11 +55,12 @@ export const POLYGON = { // Delhi Hard Fork - reduced sprint length, smoothed baseFee "38189056": { number: 38189056, - txCount: 63, - gasUsed: "8,896,235", - gasLimit: "31,898,693", - hash: "0xf034b3df03fe4c2aedec6590754b6ebc25289369c6df64aee1613fe458b5abc6", - parentHash: "0x94cb3ecc028d21aa11031ff21d645ac2d91a14a55f24303e65b39910547060bd", + txCount: 121, + gasUsed: "19,246,742", + gasUsedPercent: "68.2%", + gasLimit: "28,207,416", + hash: "0x19bcd5f19d3f928aae2b582b0e005c963a8ef0bfd005d1b639a5b0b5dbd632d6", + parentHash: "0x9c0a52241e01b66eb8987f894dea8b22d4ba1e2261217ddbaf4638c8699df79e", }, // Block 50,000,000 (November 2023) // Post-Delhi, high activity @@ -76,11 +77,12 @@ export const POLYGON = { // Ahmedabad Hard Fork - MATIC to POL migration "62278656": { number: 62278656, - txCount: 46, - gasUsed: "5,457,470", - gasLimit: "30,000,000", - hash: "0xd0a3278e1c13d54e222d53b76bec3669871c82b7a69f7840d43fc1fb4d9c735d", - parentHash: "0x394674d611451512b96c1f4c2b741403387235b61104d1a38ba09ffa6d891f43", + txCount: 65, + gasUsed: "7,292,621", + gasUsedPercent: "24.0%", + gasLimit: "30,442,418", + hash: "0xc207d13429c37fded959648b9f6d5d51d4cb65371c9b2f3a40f93f750cf000c4", + parentHash: "0xbfe87634499bbba796ad1b7d064c97354a12ea43c3f2f8f0a91a3d9987ef883e", }, // Block 65,000,000 (December 2024) // Post-Ahmedabad, POL era @@ -111,7 +113,7 @@ export const POLYGON = { gas: "189,792", gasUsed: "89,556", gasPrice: "125 Gwei", - nonce: 30637600, + nonce: 30554656, position: 0, status: "success" as const, hasInputData: true, @@ -121,7 +123,7 @@ export const POLYGON = { // EIP-1559 TRANSACTIONS (Type 2) // ============================================ - // DeFi swap from block 50,000,000 - EIP-1559 transaction + // Failed DeFi swap from block 50,000,000 - EIP-1559 transaction "0x1ed0c46bafb76d5a3d8201cdf8fc732efa97b000d88bd48dc203ac45d6340af0": { hash: "0x1ed0c46bafb76d5a3d8201cdf8fc732efa97b000d88bd48dc203ac45d6340af0", type: 2, // EIP-1559 @@ -129,11 +131,11 @@ export const POLYGON = { to: "0x826a4f4da02588737d3c27325b14f39b5151ca3c", value: "0x0", blockNumber: 50000000, - gas: "999,502", + gas: "1,000,014", gasUsed: "66,787", nonce: 2151, position: 0, - status: "success" as const, + status: "failed" as const, // Failed: ERC20 transfer amount exceeds balance hasInputData: true, }, @@ -145,7 +147,7 @@ export const POLYGON = { to: "0xe957a692c97566efc85f995162fa404091232b2e", value: "0x0", blockNumber: 65000000, - gas: "235,648", + gas: "234,624", gasUsed: "225,106", nonce: 34547, position: 0, diff --git a/e2e/helpers/wait.ts b/e2e/helpers/wait.ts new file mode 100644 index 0000000..ee853d2 --- /dev/null +++ b/e2e/helpers/wait.ts @@ -0,0 +1,122 @@ +import type { Page, TestInfo } from "@playwright/test"; +import { expect } from "@playwright/test"; + +/** + * Shared wait helpers for e2e tests with retry-aware timeouts. + * + * Timeout formula: baseTimeout + (increment * retryCount) + * - Retry 0: 30s + * - Retry 1: 40s + * - Retry 2: 50s + * - Retry 3: 60s + * + * This ensures flaky tests have more time on retries while keeping + * initial runs fast. + */ + +const BASE_TIMEOUT = 30000; // 30 seconds base +const TIMEOUT_INCREMENT = 10000; // 10 seconds per retry + +/** + * Calculate timeout based on retry count + */ +function getTimeout(testInfo: TestInfo): number { + return BASE_TIMEOUT + TIMEOUT_INCREMENT * testInfo.retry; +} + +/** + * Wait for block page content to load or error to appear. + * Returns true if content loaded successfully, false if error or timeout. + */ +export async function waitForBlockContent(page: Page, testInfo: TestInfo): Promise { + const timeout = getTimeout(testInfo); + try { + await expect( + page + .locator("text=Transactions:") + .or(page.locator("text=Error:")) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout }); + + return ( + !(await page.locator("text=Error:").isVisible()) && + !(await page.locator("text=Something went wrong").isVisible()) + ); + } catch { + return false; + } +} + +/** + * Wait for transaction page content to load or error to appear. + * Returns true if content loaded successfully, false if error or timeout. + */ +export async function waitForTxContent(page: Page, testInfo: TestInfo): Promise { + const timeout = getTimeout(testInfo); + try { + await expect( + page + .locator("text=Transaction Hash:") + .or(page.locator("text=Error:")) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout }); + + return ( + !(await page.locator("text=Error:").isVisible()) && + !(await page.locator("text=Something went wrong").isVisible()) + ); + } catch { + return false; + } +} + +/** + * Wait for address page content to load or error to appear. + * Returns true if content loaded successfully, false if error or timeout. + */ +export async function waitForAddressContent(page: Page, testInfo: TestInfo): Promise { + const timeout = getTimeout(testInfo); + try { + await expect( + page + .locator("text=Balance:") + .or(page.locator("text=Error:")) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout }); + + return ( + !(await page.locator("text=Error:").isVisible()) && + !(await page.locator("text=Something went wrong").isVisible()) + ); + } catch { + return false; + } +} + +/** + * Wait for token page content to load or error to appear. + * Returns true if content loaded successfully, false if error or timeout. + */ +export async function waitForTokenContent(page: Page, testInfo: TestInfo): Promise { + const timeout = getTimeout(testInfo); + try { + await expect( + page + .locator(".erc721-header") + .or(page.locator(".erc1155-header")) + .or(page.locator("text=Error:")) + .or(page.locator("text=Something went wrong")) + .first() + ).toBeVisible({ timeout }); + + return ( + !(await page.locator("text=Error:").isVisible()) && + !(await page.locator("text=Something went wrong").isVisible()) + ); + } catch { + return false; + } +} diff --git a/e2e/tests/arbitrum.spec.ts b/e2e/tests/arbitrum.spec.ts index e24d02c..c9890f7 100644 --- a/e2e/tests/arbitrum.spec.ts +++ b/e2e/tests/arbitrum.spec.ts @@ -3,6 +3,7 @@ import { BlockPage } from "../pages/block.page"; import { AddressPage } from "../pages/address.page"; import { TransactionPage } from "../pages/transaction.page"; import { ARBITRUM } from "../fixtures/arbitrum"; +import { waitForBlockContent, waitForTxContent, waitForAddressContent } from "../helpers/wait"; const CHAIN_ID = ARBITRUM.chainId; @@ -10,63 +11,17 @@ const CHAIN_ID = ARBITRUM.chainId; const UNISWAP_SWAP = "0x87815a816c02b5a563a026e4a37d423734204b50972e75284b62f05e4134ae44"; const USDC_TRANSFER = "0x160687cbf03f348cf36997dbab53abbd32d91af5971bccac4cfa1577da27607e"; -// Helper to wait for block content or error -async function waitForBlockContent(page: import("@playwright/test").Page) { - await expect( - page - .locator("text=Transactions:") - .or(page.locator("text=Error:")) - .or(page.locator("text=Something went wrong")) - ).toBeVisible({ timeout: 45000 }); - - return ( - !(await page.locator("text=Error:").isVisible()) && - !(await page.locator("text=Something went wrong").isVisible()) - ); -} - -// Helper to wait for address content or error -async function waitForAddressContent(page: import("@playwright/test").Page) { - await expect( - page - .locator("text=Balance:") - .or(page.locator("text=Error:")) - .or(page.locator("text=Something went wrong")) - .first() - ).toBeVisible({ timeout: 45000 }); - - return ( - !(await page.locator("text=Error:").isVisible()) && - !(await page.locator("text=Something went wrong").isVisible()) - ); -} - -// Helper to wait for transaction content or error -async function waitForTxContent(page: import("@playwright/test").Page) { - await expect( - page - .locator("text=Transaction Hash:") - .or(page.locator("text=Error:")) - .or(page.locator("text=Something went wrong")) - ).toBeVisible({ timeout: 45000 }); - - return ( - !(await page.locator("text=Error:").isVisible()) && - !(await page.locator("text=Something went wrong").isVisible()) - ); -} - // ============================================ // BLOCK TESTS // ============================================ test.describe("Arbitrum One - Block Page", () => { - test("genesis block #0 - Arbitrum One launch", async ({ page }) => { + test("genesis block #0 - Arbitrum One launch", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = ARBITRUM.blocks["0"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { // Header section await expect(blockPage.blockNumber).toBeVisible(); @@ -84,12 +39,12 @@ test.describe("Arbitrum One - Block Page", () => { } }); - test("block #22,207,817 - Nitro upgrade block", async ({ page }) => { + test("block #22,207,817 - Nitro upgrade block", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = ARBITRUM.blocks["22207817"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { await expect(blockPage.blockNumber).toBeVisible(); @@ -103,12 +58,12 @@ test.describe("Arbitrum One - Block Page", () => { } }); - test("block #100,000,000 - post-Nitro with gas details", async ({ page }) => { + test("block #100,000,000 - post-Nitro with gas details", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = ARBITRUM.blocks["100000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { await expect(blockPage.blockNumber).toBeVisible(); await expect(blockPage.statusBadge).toContainText("Finalized"); @@ -135,12 +90,12 @@ test.describe("Arbitrum One - Block Page", () => { } }); - test("block #100,000,000 more details section shows correct hashes", async ({ page }) => { + test("block #100,000,000 more details section shows correct hashes", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = ARBITRUM.blocks["100000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { const showMoreBtn = page.locator("text=Show More Details"); if (await showMoreBtn.isVisible()) { @@ -159,12 +114,12 @@ test.describe("Arbitrum One - Block Page", () => { } }); - test("block #200,000,000 - post-ArbOS 20 Atlas (Dencun)", async ({ page }) => { + test("block #200,000,000 - post-ArbOS 20 Atlas (Dencun)", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = ARBITRUM.blocks["200000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { await expect(blockPage.blockNumber).toBeVisible(); await expect(blockPage.statusBadge).toContainText("Finalized"); @@ -188,12 +143,12 @@ test.describe("Arbitrum One - Block Page", () => { } }); - test("block #200,000,000 more details shows hashes", async ({ page }) => { + test("block #200,000,000 more details shows hashes", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = ARBITRUM.blocks["200000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { const showMoreBtn = page.locator("text=Show More Details"); if (await showMoreBtn.isVisible()) { @@ -208,12 +163,12 @@ test.describe("Arbitrum One - Block Page", () => { } }); - test("block #300,000,000 - post-ArbOS 32 Bianca (Stylus)", async ({ page }) => { + test("block #300,000,000 - post-ArbOS 32 Bianca (Stylus)", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = ARBITRUM.blocks["300000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { await expect(blockPage.blockNumber).toBeVisible(); @@ -239,12 +194,12 @@ test.describe("Arbitrum One - Block Page", () => { } }); - test("genesis block more details section shows correct hash", async ({ page }) => { + test("genesis block more details section shows correct hash", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = ARBITRUM.blocks["0"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { const showMoreBtn = page.locator("text=Show More Details"); if (await showMoreBtn.isVisible()) { @@ -260,18 +215,18 @@ test.describe("Arbitrum One - Block Page", () => { } }); - test("block navigation buttons work on Arbitrum", async ({ page }) => { + test("block navigation buttons work on Arbitrum", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); await blockPage.goto(ARBITRUM.blocks["100000000"].number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { await expect(blockPage.navPrevBtn).toBeVisible(); await expect(blockPage.navNextBtn).toBeVisible(); } }); - test("handles invalid block number gracefully", async ({ page }) => { + test("handles invalid block number gracefully", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); await blockPage.goto(999999999999, CHAIN_ID); @@ -289,13 +244,13 @@ test.describe("Arbitrum One - Block Page", () => { // ============================================ test.describe("Arbitrum One - Transaction Page", () => { - test("displays Uniswap V3 swap with all details", async ({ page }) => { + test("displays Uniswap V3 swap with all details", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = ARBITRUM.transactions[UNISWAP_SWAP]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { // Verify core transaction details await expect(page.locator("text=Transaction Hash:")).toBeVisible(); @@ -311,13 +266,13 @@ test.describe("Arbitrum One - Transaction Page", () => { } }); - test("shows correct from and to addresses for swap", async ({ page }) => { + test("shows correct from and to addresses for swap", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = ARBITRUM.transactions[UNISWAP_SWAP]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { const from = await txPage.getFromAddress(); expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); @@ -327,13 +282,13 @@ test.describe("Arbitrum One - Transaction Page", () => { } }); - test("displays transaction value and fee", async ({ page }) => { + test("displays transaction value and fee", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = ARBITRUM.transactions[UNISWAP_SWAP]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { // Verify value contains ETH const value = await txPage.getValue(); @@ -344,13 +299,13 @@ test.describe("Arbitrum One - Transaction Page", () => { } }); - test("displays USDC transfer transaction (EIP-1559)", async ({ page }) => { + test("displays USDC transfer transaction (EIP-1559)", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = ARBITRUM.transactions[USDC_TRANSFER]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { await expect(page.locator("text=Transaction Hash:")).toBeVisible(); await expect(page.locator("text=Status:")).toBeVisible(); @@ -363,26 +318,26 @@ test.describe("Arbitrum One - Transaction Page", () => { } }); - test("displays transaction with input data", async ({ page }) => { + test("displays transaction with input data", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = ARBITRUM.transactions[UNISWAP_SWAP]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { // Contract interaction should have input data await expect(page.locator("text=Input Data:")).toBeVisible(); } }); - test("displays other attributes section with nonce", async ({ page }) => { + test("displays other attributes section with nonce", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = ARBITRUM.transactions[UNISWAP_SWAP]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { await expect(page.locator("text=Other Attributes:")).toBeVisible(); await expect(page.locator("text=Nonce:")).toBeVisible(); @@ -390,13 +345,13 @@ test.describe("Arbitrum One - Transaction Page", () => { } }); - test("displays block number link", async ({ page }) => { + test("displays block number link", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = ARBITRUM.transactions[UNISWAP_SWAP]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { await expect(page.locator("text=Block:")).toBeVisible(); const blockValue = await txPage.getBlockNumber(); @@ -404,13 +359,13 @@ test.describe("Arbitrum One - Transaction Page", () => { } }); - test("USDC transfer shows correct addresses", async ({ page }) => { + test("USDC transfer shows correct addresses", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = ARBITRUM.transactions[USDC_TRANSFER]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { const from = await txPage.getFromAddress(); expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); @@ -420,7 +375,7 @@ test.describe("Arbitrum One - Transaction Page", () => { } }); - test("handles invalid tx hash gracefully", async ({ page }) => { + test("handles invalid tx hash gracefully", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); await txPage.goto("0xinvalid", CHAIN_ID); @@ -438,13 +393,13 @@ test.describe("Arbitrum One - Transaction Page", () => { // ============================================ test.describe("Arbitrum One - Address Page", () => { - test("displays native USDC contract details", async ({ page }) => { + test("displays native USDC contract details", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = ARBITRUM.addresses.usdc; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); @@ -453,65 +408,65 @@ test.describe("Arbitrum One - Address Page", () => { } }); - test("displays bridged USDC.e contract", async ({ page }) => { + test("displays bridged USDC.e contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = ARBITRUM.addresses.usdce; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); } }); - test("displays WETH contract", async ({ page }) => { + test("displays WETH contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = ARBITRUM.addresses.weth; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); } }); - test("displays ARB governance token contract", async ({ page }) => { + test("displays ARB governance token contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = ARBITRUM.addresses.arb; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); } }); - test("displays GMX token contract", async ({ page }) => { + test("displays GMX token contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = ARBITRUM.addresses.gmx; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); } }); - test("displays Uniswap V3 Router contract with details", async ({ page }) => { + test("displays Uniswap V3 Router contract with details", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = ARBITRUM.addresses.uniswapV3Router; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); @@ -520,26 +475,26 @@ test.describe("Arbitrum One - Address Page", () => { } }); - test("displays Uniswap Universal Router contract", async ({ page }) => { + test("displays Uniswap Universal Router contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = ARBITRUM.addresses.uniswapUniversalRouter; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); } }); - test("displays GMX Vault contract with details", async ({ page }) => { + test("displays GMX Vault contract with details", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = ARBITRUM.addresses.gmxVault; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); @@ -548,26 +503,26 @@ test.describe("Arbitrum One - Address Page", () => { } }); - test("displays GMX Position Router contract", async ({ page }) => { + test("displays GMX Position Router contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = ARBITRUM.addresses.gmxPositionRouter; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); } }); - test("displays ArbSys system precompile", async ({ page }) => { + test("displays ArbSys system precompile", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = ARBITRUM.addresses.arbSys; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); diff --git a/e2e/tests/base.spec.ts b/e2e/tests/base.spec.ts index 73eeed3..bea7f4e 100644 --- a/e2e/tests/base.spec.ts +++ b/e2e/tests/base.spec.ts @@ -3,6 +3,7 @@ import { BlockPage } from "../pages/block.page"; import { AddressPage } from "../pages/address.page"; import { TransactionPage } from "../pages/transaction.page"; import { BASE } from "../fixtures/base"; +import { waitForBlockContent, waitForTxContent, waitForAddressContent } from "../helpers/wait"; const CHAIN_ID = BASE.chainId; @@ -10,63 +11,17 @@ const CHAIN_ID = BASE.chainId; const AERODROME_SWAP = "0x961cf2c57f006d8c6fdbe266b2ef201159dd135dc560155e8c16d307ee321681"; const USDC_TRANSFER = "0x6b212a5069286d710f388b948364452d28b8c33e0f39b8f50b394ff4deff1f03"; -// Helper to wait for block content or error -async function waitForBlockContent(page: import("@playwright/test").Page) { - await expect( - page - .locator("text=Transactions:") - .or(page.locator("text=Error:")) - .or(page.locator("text=Something went wrong")) - ).toBeVisible({ timeout: 45000 }); - - return ( - !(await page.locator("text=Error:").isVisible()) && - !(await page.locator("text=Something went wrong").isVisible()) - ); -} - -// Helper to wait for address content or error -async function waitForAddressContent(page: import("@playwright/test").Page) { - await expect( - page - .locator("text=Balance:") - .or(page.locator("text=Error:")) - .or(page.locator("text=Something went wrong")) - .first() - ).toBeVisible({ timeout: 45000 }); - - return ( - !(await page.locator("text=Error:").isVisible()) && - !(await page.locator("text=Something went wrong").isVisible()) - ); -} - -// Helper to wait for transaction content or error -async function waitForTxContent(page: import("@playwright/test").Page) { - await expect( - page - .locator("text=Transaction Hash:") - .or(page.locator("text=Error:")) - .or(page.locator("text=Something went wrong")) - ).toBeVisible({ timeout: 45000 }); - - return ( - !(await page.locator("text=Error:").isVisible()) && - !(await page.locator("text=Something went wrong").isVisible()) - ); -} - // ============================================ // BLOCK TESTS // ============================================ test.describe("Base Network - Block Page", () => { - test("genesis block #0 - Base mainnet launch", async ({ page }) => { + test("genesis block #0 - Base mainnet launch", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = BASE.blocks["0"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { // Header section await expect(blockPage.blockNumber).toBeVisible(); @@ -84,12 +39,12 @@ test.describe("Base Network - Block Page", () => { } }); - test("block #1,000,000 - early Base block with gas details", async ({ page }) => { + test("block #1,000,000 - early Base block with gas details", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = BASE.blocks["1000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { // Header await expect(blockPage.blockNumber).toBeVisible(); @@ -121,12 +76,12 @@ test.describe("Base Network - Block Page", () => { } }); - test("block #10,000,000 - pre-Ecotone block", async ({ page }) => { + test("block #10,000,000 - pre-Ecotone block", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = BASE.blocks["10000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { await expect(blockPage.blockNumber).toBeVisible(); await expect(blockPage.statusBadge).toContainText("Finalized"); @@ -148,12 +103,12 @@ test.describe("Base Network - Block Page", () => { } }); - test("block #25,000,000 - post-Holocene with increased gas limit", async ({ page }) => { + test("block #25,000,000 - post-Holocene with increased gas limit", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = BASE.blocks["25000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { await expect(blockPage.blockNumber).toBeVisible(); @@ -174,12 +129,12 @@ test.describe("Base Network - Block Page", () => { } }); - test("genesis block more details section shows correct hashes", async ({ page }) => { + test("genesis block more details section shows correct hashes", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = BASE.blocks["0"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { const showMoreBtn = page.locator("text=Show More Details"); if (await showMoreBtn.isVisible()) { @@ -198,11 +153,11 @@ test.describe("Base Network - Block Page", () => { } }); - test("block navigation buttons work on Base", async ({ page }) => { + test("block navigation buttons work on Base", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); await blockPage.goto(BASE.blocks["1000000"].number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { await expect(blockPage.navPrevBtn).toBeVisible(); await expect(blockPage.navNextBtn).toBeVisible(); @@ -227,13 +182,13 @@ test.describe("Base Network - Block Page", () => { // ============================================ test.describe("Base Network - Transaction Page", () => { - test("displays Aerodrome DEX swap with all details", async ({ page }) => { + test("displays Aerodrome DEX swap with all details", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = BASE.transactions[AERODROME_SWAP]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { // Verify core transaction details await expect(page.locator("text=Transaction Hash:")).toBeVisible(); @@ -247,13 +202,13 @@ test.describe("Base Network - Transaction Page", () => { } }); - test("shows correct from and to addresses for swap", async ({ page }) => { + test("shows correct from and to addresses for swap", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = BASE.transactions[AERODROME_SWAP]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { const from = await txPage.getFromAddress(); expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); @@ -263,13 +218,13 @@ test.describe("Base Network - Transaction Page", () => { } }); - test("displays USDC transfer transaction", async ({ page }) => { + test("displays USDC transfer transaction", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = BASE.transactions[USDC_TRANSFER]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { await expect(page.locator("text=Transaction Hash:")).toBeVisible(); await expect(page.locator("text=Status:")).toBeVisible(); @@ -282,26 +237,26 @@ test.describe("Base Network - Transaction Page", () => { } }); - test("displays transaction with input data", async ({ page }) => { + test("displays transaction with input data", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = BASE.transactions[AERODROME_SWAP]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { // Contract interaction should have input data await expect(page.locator("text=Input Data:")).toBeVisible(); } }); - test("displays other attributes section", async ({ page }) => { + test("displays other attributes section", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = BASE.transactions[AERODROME_SWAP]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { await expect(page.locator("text=Other Attributes:")).toBeVisible(); await expect(page.locator("text=Nonce:")).toBeVisible(); @@ -309,13 +264,13 @@ test.describe("Base Network - Transaction Page", () => { } }); - test("displays block number link", async ({ page }) => { + test("displays block number link", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = BASE.transactions[AERODROME_SWAP]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { await expect(page.locator("text=Block:")).toBeVisible(); const blockValue = await txPage.getBlockNumber(); @@ -341,13 +296,13 @@ test.describe("Base Network - Transaction Page", () => { // ============================================ test.describe("Base Network - Address Page", () => { - test("displays USDC contract details", async ({ page }) => { + test("displays USDC contract details", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = BASE.addresses.usdc; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); @@ -356,52 +311,52 @@ test.describe("Base Network - Address Page", () => { } }); - test("displays USDbC (bridged USDC) contract", async ({ page }) => { + test("displays USDbC (bridged USDC) contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = BASE.addresses.usdbc; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); } }); - test("displays WETH predeploy contract", async ({ page }) => { + test("displays WETH predeploy contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = BASE.addresses.weth; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); } }); - test("displays AERO token contract", async ({ page }) => { + test("displays AERO token contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = BASE.addresses.aero; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); } }); - test("displays Aerodrome Router contract with details", async ({ page }) => { + test("displays Aerodrome Router contract with details", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = BASE.addresses.aerodromeRouter; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); @@ -410,65 +365,65 @@ test.describe("Base Network - Address Page", () => { } }); - test("displays SequencerFeeVault system contract", async ({ page }) => { + test("displays SequencerFeeVault system contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = BASE.addresses.sequencerFeeVault; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); } }); - test("displays GasPriceOracle system contract", async ({ page }) => { + test("displays GasPriceOracle system contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = BASE.addresses.gasPriceOracle; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); } }); - test("displays L1Block system contract", async ({ page }) => { + test("displays L1Block system contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = BASE.addresses.l1Block; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); } }); - test("displays L2StandardBridge contract", async ({ page }) => { + test("displays L2StandardBridge contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = BASE.addresses.l2StandardBridge; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); } }); - test("displays L2CrossDomainMessenger contract", async ({ page }) => { + test("displays L2CrossDomainMessenger contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = BASE.addresses.l2CrossDomainMessenger; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); diff --git a/e2e/tests/bsc.spec.ts b/e2e/tests/bsc.spec.ts index 783836a..57f6019 100644 --- a/e2e/tests/bsc.spec.ts +++ b/e2e/tests/bsc.spec.ts @@ -3,6 +3,7 @@ import { BlockPage } from "../pages/block.page"; import { TransactionPage } from "../pages/transaction.page"; import { AddressPage } from "../pages/address.page"; import { BSC } from "../fixtures/bsc"; +import { waitForBlockContent, waitForTxContent, waitForAddressContent } from "../helpers/wait"; const CHAIN_ID = BSC.chainId; @@ -11,63 +12,17 @@ const BLOCK_20M_TX = "0xad5c9b13688627d670985d68a5be0fadd5f0e34d3ff20e35c655ef4b const DEX_SWAP_TX = "0x0e3384ad2350d20921190b15e29305ed08eecfe97de975b6e015a6c6d476a90a"; const DEX_AGGREGATOR_TX = "0x874a90a47bc3140adbffff0f4b89da4bea48f9420f97bc5a50e2e478d9a06176"; -// Helper to wait for block content or error -async function waitForBlockContent(page: import("@playwright/test").Page) { - await expect( - page - .locator("text=Transactions:") - .or(page.locator("text=Error:")) - .or(page.locator("text=Something went wrong")) - ).toBeVisible({ timeout: 45000 }); - - return ( - !(await page.locator("text=Error:").isVisible()) && - !(await page.locator("text=Something went wrong").isVisible()) - ); -} - -// Helper to wait for transaction content or error -async function waitForTxContent(page: import("@playwright/test").Page) { - await expect( - page - .locator("text=Transaction Hash:") - .or(page.locator("text=Error:")) - .or(page.locator("text=Something went wrong")) - ).toBeVisible({ timeout: 45000 }); - - return ( - !(await page.locator("text=Error:").isVisible()) && - !(await page.locator("text=Something went wrong").isVisible()) - ); -} - -// Helper to wait for address content or error -async function waitForAddressContent(page: import("@playwright/test").Page) { - await expect( - page - .locator("text=Balance:") - .or(page.locator("text=Error:")) - .or(page.locator("text=Something went wrong")) - .first() - ).toBeVisible({ timeout: 45000 }); - - return ( - !(await page.locator("text=Error:").isVisible()) && - !(await page.locator("text=Something went wrong").isVisible()) - ); -} - // ============================================ // BLOCK TESTS // ============================================ test.describe("BSC Block Page", () => { - test("genesis block #0 - BSC mainnet launch", async ({ page }) => { + test("genesis block #0 - BSC mainnet launch", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = BSC.blocks["0"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { // Header section await expect(blockPage.blockNumber).toBeVisible(); @@ -87,12 +42,12 @@ test.describe("BSC Block Page", () => { } }); - test("genesis block #0 more details shows hash and parent hash", async ({ page }) => { + test("genesis block #0 more details shows hash and parent hash", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = BSC.blocks["0"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { // Click "Show More Details" to expand const showMoreBtn = page.locator("text=Show More Details"); @@ -114,12 +69,12 @@ test.describe("BSC Block Page", () => { } }); - test("block #10,000,000 - Pre-Euler block with transactions", async ({ page }) => { + test("block #10,000,000 - Pre-Euler block with transactions", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = BSC.blocks["10000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { // Header await expect(blockPage.blockNumber).toBeVisible(); @@ -139,12 +94,12 @@ test.describe("BSC Block Page", () => { } }); - test("block #10,000,000 more details shows hashes", async ({ page }) => { + test("block #10,000,000 more details shows hashes", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = BSC.blocks["10000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { const showMoreBtn = page.locator("text=Show More Details"); if (await showMoreBtn.isVisible()) { @@ -163,12 +118,12 @@ test.describe("BSC Block Page", () => { } }); - test("block #20,000,000 - Post-Euler block with gas details", async ({ page }) => { + test("block #20,000,000 - Post-Euler block with gas details", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = BSC.blocks["20000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { // Header await expect(blockPage.blockNumber).toBeVisible(); @@ -188,12 +143,12 @@ test.describe("BSC Block Page", () => { } }); - test("block #20,000,000 more details shows hashes", async ({ page }) => { + test("block #20,000,000 more details shows hashes", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = BSC.blocks["20000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { const showMoreBtn = page.locator("text=Show More Details"); if (await showMoreBtn.isVisible()) { @@ -206,12 +161,12 @@ test.describe("BSC Block Page", () => { } }); - test("block #30,000,000 - Post-Luban with fast finality", async ({ page }) => { + test("block #30,000,000 - Post-Luban with fast finality", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = BSC.blocks["30000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { // Header await expect(blockPage.blockNumber).toBeVisible(); @@ -231,12 +186,12 @@ test.describe("BSC Block Page", () => { } }); - test("block #30,000,000 more details shows hashes", async ({ page }) => { + test("block #30,000,000 more details shows hashes", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = BSC.blocks["30000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { const showMoreBtn = page.locator("text=Show More Details"); if (await showMoreBtn.isVisible()) { @@ -249,12 +204,12 @@ test.describe("BSC Block Page", () => { } }); - test("block #40,000,000 - Post-Feynman after BNB Chain Fusion", async ({ page }) => { + test("block #40,000,000 - Post-Feynman after BNB Chain Fusion", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = BSC.blocks["40000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { // Header await expect(blockPage.blockNumber).toBeVisible(); @@ -274,12 +229,12 @@ test.describe("BSC Block Page", () => { } }); - test("block #40,000,000 more details shows hashes", async ({ page }) => { + test("block #40,000,000 more details shows hashes", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = BSC.blocks["40000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { const showMoreBtn = page.locator("text=Show More Details"); if (await showMoreBtn.isVisible()) { @@ -292,12 +247,12 @@ test.describe("BSC Block Page", () => { } }); - test("block #50,000,000 - Post-Maxwell with 0.75s block time", async ({ page }) => { + test("block #50,000,000 - Post-Maxwell with 0.75s block time", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = BSC.blocks["50000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { // Header await expect(blockPage.blockNumber).toBeVisible(); @@ -317,12 +272,12 @@ test.describe("BSC Block Page", () => { } }); - test("block #50,000,000 more details shows hashes", async ({ page }) => { + test("block #50,000,000 more details shows hashes", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = BSC.blocks["50000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { const showMoreBtn = page.locator("text=Show More Details"); if (await showMoreBtn.isVisible()) { @@ -335,11 +290,11 @@ test.describe("BSC Block Page", () => { } }); - test("block navigation buttons work", async ({ page }) => { + test("block navigation buttons work", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); await blockPage.goto(BSC.blocks["10000000"].number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { // Verify navigation buttons exist await expect(blockPage.navPrevBtn).toBeVisible(); @@ -347,7 +302,7 @@ test.describe("BSC Block Page", () => { } }); - test("handles invalid block number gracefully", async ({ page }) => { + test("handles invalid block number gracefully", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); await blockPage.goto(999999999999, CHAIN_ID); @@ -365,13 +320,13 @@ test.describe("BSC Block Page", () => { // ============================================ test.describe("BSC Transaction Page", () => { - test("displays transaction from block 20M with all details", async ({ page }) => { + test("displays transaction from block 20M with all details", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = BSC.transactions[BLOCK_20M_TX]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { // Verify core transaction details await expect(page.locator("text=Transaction Hash:")).toBeVisible(); @@ -389,13 +344,13 @@ test.describe("BSC Transaction Page", () => { } }); - test("shows correct from and to addresses for block 20M tx", async ({ page }) => { + test("shows correct from and to addresses for block 20M tx", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = BSC.transactions[BLOCK_20M_TX]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { // Verify from address const from = await txPage.getFromAddress(); @@ -407,13 +362,13 @@ test.describe("BSC Transaction Page", () => { } }); - test("displays legacy transaction type correctly (Type 0)", async ({ page }) => { + test("displays legacy transaction type correctly (Type 0)", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = BSC.transactions[BLOCK_20M_TX]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { // Legacy transaction should show Transaction Type with value 0 or "Legacy" // Check for "Type:" which is how the UI displays it @@ -423,13 +378,13 @@ test.describe("BSC Transaction Page", () => { } }); - test("displays DEX swap transaction from block 40M", async ({ page }) => { + test("displays DEX swap transaction from block 40M", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = BSC.transactions[DEX_SWAP_TX]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { // Verify core transaction details await expect(page.locator("text=Transaction Hash:")).toBeVisible(); @@ -444,13 +399,13 @@ test.describe("BSC Transaction Page", () => { } }); - test("DEX swap shows correct addresses and gas details", async ({ page }) => { + test("DEX swap shows correct addresses and gas details", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = BSC.transactions[DEX_SWAP_TX]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { const from = await txPage.getFromAddress(); expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); @@ -463,13 +418,13 @@ test.describe("BSC Transaction Page", () => { } }); - test("displays DEX aggregator transaction from block 50M", async ({ page }) => { + test("displays DEX aggregator transaction from block 50M", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = BSC.transactions[DEX_AGGREGATOR_TX]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { // Verify core transaction details await expect(page.locator("text=Transaction Hash:")).toBeVisible(); @@ -483,13 +438,13 @@ test.describe("BSC Transaction Page", () => { } }); - test("displays transaction nonce and position for block 20M tx", async ({ page }) => { + test("displays transaction nonce and position for block 20M tx", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = BSC.transactions[BLOCK_20M_TX]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { // Verify other attributes section await expect(page.locator("text=Other Attributes:")).toBeVisible(); @@ -501,13 +456,13 @@ test.describe("BSC Transaction Page", () => { } }); - test("displays DEX swap nonce and position", async ({ page }) => { + test("displays DEX swap nonce and position", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = BSC.transactions[DEX_SWAP_TX]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { await expect(page.locator("text=Nonce:")).toBeVisible(); await expect(page.locator("text=Position:")).toBeVisible(); @@ -517,13 +472,13 @@ test.describe("BSC Transaction Page", () => { } }); - test("displays DEX aggregator nonce and position", async ({ page }) => { + test("displays DEX aggregator nonce and position", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = BSC.transactions[DEX_AGGREGATOR_TX]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { await expect(page.locator("text=Nonce:")).toBeVisible(); await expect(page.locator("text=Position:")).toBeVisible(); @@ -536,26 +491,26 @@ test.describe("BSC Transaction Page", () => { } }); - test("displays transaction fee", async ({ page }) => { + test("displays transaction fee", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = BSC.transactions[BLOCK_20M_TX]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { // Verify transaction fee is displayed await expect(page.locator("text=Transaction Fee:")).toBeVisible(); } }); - test("displays block number link for transaction", async ({ page }) => { + test("displays block number link for transaction", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = BSC.transactions[DEX_SWAP_TX]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { await expect(page.locator("text=Block:")).toBeVisible(); const blockValue = await txPage.getBlockNumber(); @@ -563,7 +518,7 @@ test.describe("BSC Transaction Page", () => { } }); - test("handles invalid tx hash gracefully", async ({ page }) => { + test("handles invalid tx hash gracefully", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); await txPage.goto("0xinvalid", CHAIN_ID); @@ -581,13 +536,13 @@ test.describe("BSC Transaction Page", () => { // ============================================ test.describe("BSC Address Page - Tokens", () => { - test("displays WBNB token contract", async ({ page }) => { + test("displays WBNB token contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = BSC.addresses.wbnb; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { // Verify it's identified as a contract const isContract = await addressPage.isContract(); @@ -600,13 +555,13 @@ test.describe("BSC Address Page - Tokens", () => { } }); - test("displays USDT (BSC-USD) token contract", async ({ page }) => { + test("displays USDT (BSC-USD) token contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = BSC.addresses.usdt; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { // Verify it's identified as a contract const isContract = await addressPage.isContract(); @@ -614,13 +569,13 @@ test.describe("BSC Address Page - Tokens", () => { } }); - test("displays BUSD token contract", async ({ page }) => { + test("displays BUSD token contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = BSC.addresses.busd; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { // Verify it's identified as a contract const isContract = await addressPage.isContract(); @@ -628,13 +583,13 @@ test.describe("BSC Address Page - Tokens", () => { } }); - test("displays CAKE (PancakeSwap Token) contract", async ({ page }) => { + test("displays CAKE (PancakeSwap Token) contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = BSC.addresses.cake; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { // Verify it's identified as a contract const isContract = await addressPage.isContract(); @@ -642,13 +597,13 @@ test.describe("BSC Address Page - Tokens", () => { } }); - test("displays USDC token contract", async ({ page }) => { + test("displays USDC token contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = BSC.addresses.usdc; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { // Verify it's identified as a contract const isContract = await addressPage.isContract(); @@ -656,13 +611,13 @@ test.describe("BSC Address Page - Tokens", () => { } }); - test("displays DAI token contract", async ({ page }) => { + test("displays DAI token contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = BSC.addresses.dai; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { // Verify it's identified as a contract const isContract = await addressPage.isContract(); @@ -676,13 +631,13 @@ test.describe("BSC Address Page - Tokens", () => { // ============================================ test.describe("BSC Address Page - DEX Contracts", () => { - test("displays PancakeSwap Router v2 contract", async ({ page }) => { + test("displays PancakeSwap Router v2 contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = BSC.addresses.pancakeswapRouterV2; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { // Verify it's identified as a contract const isContract = await addressPage.isContract(); @@ -693,13 +648,13 @@ test.describe("BSC Address Page - DEX Contracts", () => { } }); - test("displays PancakeSwap Factory v2 contract", async ({ page }) => { + test("displays PancakeSwap Factory v2 contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = BSC.addresses.pancakeswapFactoryV2; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { // Verify it's identified as a contract const isContract = await addressPage.isContract(); @@ -710,13 +665,13 @@ test.describe("BSC Address Page - DEX Contracts", () => { } }); - test("displays PancakeSwap Universal Router contract", async ({ page }) => { + test("displays PancakeSwap Universal Router contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = BSC.addresses.pancakeswapUniversalRouter; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { // Verify it's identified as a contract const isContract = await addressPage.isContract(); @@ -733,13 +688,13 @@ test.describe("BSC Address Page - DEX Contracts", () => { // ============================================ test.describe("BSC Address Page - System Contracts", () => { - test("displays Validator Set system contract", async ({ page }) => { + test("displays Validator Set system contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = BSC.addresses.validatorSet; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { // Verify it's identified as a contract const isContract = await addressPage.isContract(); @@ -750,13 +705,13 @@ test.describe("BSC Address Page - System Contracts", () => { } }); - test("displays System Reward contract", async ({ page }) => { + test("displays System Reward contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = BSC.addresses.systemReward; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { // Verify it's identified as a contract const isContract = await addressPage.isContract(); @@ -767,13 +722,13 @@ test.describe("BSC Address Page - System Contracts", () => { } }); - test("displays Token Hub contract", async ({ page }) => { + test("displays Token Hub contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = BSC.addresses.tokenHub; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { // Verify it's identified as a contract const isContract = await addressPage.isContract(); @@ -784,13 +739,13 @@ test.describe("BSC Address Page - System Contracts", () => { } }); - test("displays Stake Hub contract", async ({ page }) => { + test("displays Stake Hub contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = BSC.addresses.stakeHub; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { // Verify it's identified as a contract const isContract = await addressPage.isContract(); @@ -801,13 +756,13 @@ test.describe("BSC Address Page - System Contracts", () => { } }); - test("displays Governor contract", async ({ page }) => { + test("displays Governor contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = BSC.addresses.governor; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { // Verify it's identified as a contract const isContract = await addressPage.isContract(); @@ -818,7 +773,7 @@ test.describe("BSC Address Page - System Contracts", () => { } }); - test("handles invalid address gracefully", async ({ page }) => { + test("handles invalid address gracefully", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); await addressPage.goto("0xinvalid", CHAIN_ID); @@ -836,13 +791,13 @@ test.describe("BSC Address Page - System Contracts", () => { // ============================================ test.describe("BSC Address Page - Staking Contracts", () => { - test("displays PancakeSwap Main Staking contract", async ({ page }) => { + test("displays PancakeSwap Main Staking contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = BSC.addresses.pancakeswapStaking; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { // Verify it's identified as a contract const isContract = await addressPage.isContract(); @@ -853,13 +808,13 @@ test.describe("BSC Address Page - Staking Contracts", () => { } }); - test("displays PancakeSwap Cake Pool contract", async ({ page }) => { + test("displays PancakeSwap Cake Pool contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = BSC.addresses.pancakeswapCakePool; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { // Verify it's identified as a contract const isContract = await addressPage.isContract(); diff --git a/e2e/tests/address.spec.ts b/e2e/tests/mainnet/address.spec.ts similarity index 87% rename from e2e/tests/address.spec.ts rename to e2e/tests/mainnet/address.spec.ts index 5817e89..484a8b0 100644 --- a/e2e/tests/address.spec.ts +++ b/e2e/tests/mainnet/address.spec.ts @@ -1,31 +1,16 @@ -import { test, expect } from "../fixtures/test"; -import { AddressPage } from "../pages/address.page"; -import { MAINNET } from "../fixtures/mainnet"; - -// Helper to wait for content or error -async function waitForAddressContent(page: import("@playwright/test").Page) { - await expect( - page - .locator("text=Balance:") - .or(page.locator("text=Error:")) - .or(page.locator("text=Something went wrong")) - .first() - ).toBeVisible({ timeout: 45000 }); - - return ( - !(await page.locator("text=Error:").isVisible()) && - !(await page.locator("text=Something went wrong").isVisible()) - ); -} +import { test, expect } from "../../fixtures/test"; +import { AddressPage } from "../../pages/address.page"; +import { MAINNET } from "../../fixtures/mainnet"; +import { waitForAddressContent } from "../../helpers/wait"; test.describe("Address Page", () => { - test("displays address with balance and transaction count", async ({ page }) => { + test("displays address with balance and transaction count", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = MAINNET.addresses.vitalik; await addressPage.goto(addr.address); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { // Verify balance section shows ETH await expect(page.locator("text=Balance:")).toBeVisible(); @@ -39,13 +24,13 @@ test.describe("Address Page", () => { } }); - test("shows ENS name for known address", async ({ page }) => { + test("shows ENS name for known address", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = MAINNET.addresses.vitalik; await addressPage.goto(addr.address); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded && addr.hasENS) { // Verify ENS name is displayed await expect(page.locator(`text=${addr.ensName}`)).toBeVisible(); @@ -56,13 +41,13 @@ test.describe("Address Page", () => { } }); - test("identifies contract type for USDC", async ({ page }) => { + test("identifies contract type for USDC", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = MAINNET.addresses.usdc; await addressPage.goto(addr.address); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { // Verify it's identified as a contract const isContract = await addressPage.isContract(); @@ -78,13 +63,13 @@ test.describe("Address Page", () => { } }); - test("displays contract details for Uniswap Router", async ({ page }) => { + test("displays contract details for Uniswap Router", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = MAINNET.addresses.uniswapRouter; await addressPage.goto(addr.address); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { // Verify contract type is displayed const type = await addressPage.getAddressType(); @@ -119,13 +104,13 @@ test.describe("Address Page", () => { ).toBeVisible({ timeout: 30000 }); }); - test("displays ERC721 NFT collection (BAYC) with collection details", async ({ page }) => { + test("displays ERC721 NFT collection (BAYC) with collection details", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = MAINNET.addresses.bayc; await addressPage.goto(addr.address); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { // Verify it's identified as ERC721 (type displays as "ERC-721 NFT") const type = await addressPage.getAddressType(); @@ -152,13 +137,13 @@ test.describe("Address Page", () => { } }); - test("displays BAYC contract verification details", async ({ page }) => { + test("displays BAYC contract verification details", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = MAINNET.addresses.bayc; await addressPage.goto(addr.address); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { // Contract Details section exists (may be collapsed) await expect(page.locator("text=Contract Details")).toBeVisible(); @@ -183,13 +168,13 @@ test.describe("Address Page", () => { } }); - test("displays BAYC contract read functions", async ({ page }) => { + test("displays BAYC contract read functions", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = MAINNET.addresses.bayc; await addressPage.goto(addr.address); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { // Click to expand Contract Details to show functions const contractDetailsHeader = page.locator("text=Contract Details").first(); @@ -206,13 +191,13 @@ test.describe("Address Page", () => { } }); - test("displays BAYC contract write functions", async ({ page }) => { + test("displays BAYC contract write functions", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = MAINNET.addresses.bayc; await addressPage.goto(addr.address); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { // Click to expand Contract Details to show functions const contractDetailsHeader = page.locator("text=Contract Details").first(); @@ -229,13 +214,13 @@ test.describe("Address Page", () => { } }); - test("displays BAYC contract events", async ({ page }) => { + test("displays BAYC contract events", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = MAINNET.addresses.bayc; await addressPage.goto(addr.address); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { // Click to expand Contract Details to show events const contractDetailsHeader = page.locator("text=Contract Details").first(); @@ -251,13 +236,13 @@ test.describe("Address Page", () => { } }); - test("displays ERC1155 multi-token contract (Rarible) with collection details", async ({ page }) => { + test("displays ERC1155 multi-token contract (Rarible) with collection details", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = MAINNET.addresses.rarible; await addressPage.goto(addr.address); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { // Verify it's identified as ERC1155 (type displays as "ERC-1155 MULTI-TOKEN") const type = await addressPage.getAddressType(); @@ -283,13 +268,13 @@ test.describe("Address Page", () => { } }); - test("displays Rarible contract verification details", async ({ page }) => { + test("displays Rarible contract verification details", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = MAINNET.addresses.rarible; await addressPage.goto(addr.address); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { // Contract Details section exists (may be collapsed) await expect(page.locator("text=Contract Details")).toBeVisible(); @@ -314,13 +299,13 @@ test.describe("Address Page", () => { } }); - test("displays Rarible contract read functions", async ({ page }) => { + test("displays Rarible contract read functions", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = MAINNET.addresses.rarible; await addressPage.goto(addr.address); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { // Click to expand Contract Details to show functions const contractDetailsHeader = page.locator("text=Contract Details").first(); @@ -337,13 +322,13 @@ test.describe("Address Page", () => { } }); - test("displays Rarible contract write functions", async ({ page }) => { + test("displays Rarible contract write functions", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = MAINNET.addresses.rarible; await addressPage.goto(addr.address); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { // Click to expand Contract Details to show functions const contractDetailsHeader = page.locator("text=Contract Details").first(); @@ -360,13 +345,13 @@ test.describe("Address Page", () => { } }); - test("displays Rarible contract events", async ({ page }) => { + test("displays Rarible contract events", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = MAINNET.addresses.rarible; await addressPage.goto(addr.address); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { // Click to expand Contract Details to show events const contractDetailsHeader = page.locator("text=Contract Details").first(); diff --git a/e2e/tests/block.spec.ts b/e2e/tests/mainnet/block.spec.ts similarity index 90% rename from e2e/tests/block.spec.ts rename to e2e/tests/mainnet/block.spec.ts index a99502c..6ab427a 100644 --- a/e2e/tests/block.spec.ts +++ b/e2e/tests/mainnet/block.spec.ts @@ -1,29 +1,15 @@ -import { test, expect } from "../fixtures/test"; -import { BlockPage } from "../pages/block.page"; -import { MAINNET } from "../fixtures/mainnet"; - -// Helper to wait for content or error -async function waitForBlockContent(page: import("@playwright/test").Page) { - await expect( - page - .locator("text=Transactions:") - .or(page.locator("text=Error:")) - .or(page.locator("text=Something went wrong")) - ).toBeVisible({ timeout: 45000 }); - - return ( - !(await page.locator("text=Error:").isVisible()) && - !(await page.locator("text=Something went wrong").isVisible()) - ); -} +import { test, expect } from "../../fixtures/test"; +import { BlockPage } from "../../pages/block.page"; +import { MAINNET } from "../../fixtures/mainnet"; +import { waitForBlockContent } from "../../helpers/wait"; test.describe("Block Page", () => { - test("block #10,000 - pre-London block with no transactions", async ({ page }) => { + test("block #10,000 - pre-London block with no transactions", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = MAINNET.blocks["10000"]; await blockPage.goto(block.number); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { // Header section await expect(blockPage.blockNumber).toBeVisible(); @@ -67,12 +53,12 @@ test.describe("Block Page", () => { } }); - test("block #1,000,000 - pre-London block with transactions", async ({ page }) => { + test("block #1,000,000 - pre-London block with transactions", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = MAINNET.blocks["1000000"]; await blockPage.goto(block.number); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { // Header await expect(blockPage.blockNumber).toBeVisible(); @@ -114,12 +100,12 @@ test.describe("Block Page", () => { } }); - test("block #20,000,000 - post-London block with base fee and withdrawals", async ({ page }) => { + test("block #20,000,000 - post-London block with base fee and withdrawals", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = MAINNET.blocks["20000000"]; await blockPage.goto(block.number); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { // Header await expect(blockPage.blockNumber).toBeVisible(); @@ -164,12 +150,12 @@ test.describe("Block Page", () => { } }); - test("block #10,000 more details section shows correct hash values", async ({ page }) => { + test("block #10,000 more details section shows correct hash values", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = MAINNET.blocks["10000"]; await blockPage.goto(block.number); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { // Click "Show More Details" to expand const showMoreBtn = page.locator("text=Show More Details"); @@ -202,12 +188,12 @@ test.describe("Block Page", () => { } }); - test("block #20,000,000 more details section includes withdrawals root", async ({ page }) => { + test("block #20,000,000 more details section includes withdrawals root", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = MAINNET.blocks["20000000"]; await blockPage.goto(block.number); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { // Click "Show More Details" to expand const showMoreBtn = page.locator("text=Show More Details"); @@ -242,11 +228,11 @@ test.describe("Block Page", () => { } }); - test("block navigation buttons work", async ({ page }) => { + test("block navigation buttons work", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); await blockPage.goto(MAINNET.blocks["1000000"].number); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { // Verify navigation buttons exist await expect(blockPage.navPrevBtn).toBeVisible(); diff --git a/e2e/tests/token.spec.ts b/e2e/tests/mainnet/token.spec.ts similarity index 86% rename from e2e/tests/token.spec.ts rename to e2e/tests/mainnet/token.spec.ts index 710f036..79b8165 100644 --- a/e2e/tests/token.spec.ts +++ b/e2e/tests/mainnet/token.spec.ts @@ -1,28 +1,13 @@ -import { test, expect } from "../fixtures/test"; -import { MAINNET } from "../fixtures/mainnet"; - -// Helper to wait for token content or error -async function waitForTokenContent(page: import("@playwright/test").Page) { - await expect( - page - .locator(".erc721-header") - .or(page.locator(".erc1155-header")) - .or(page.locator("text=Error:")) - .or(page.locator("text=Something went wrong")) - ).toBeVisible({ timeout: 45000 }); - - return ( - !(await page.locator("text=Error:").isVisible()) && - !(await page.locator("text=Something went wrong").isVisible()) - ); -} +import { test, expect } from "../../fixtures/test"; +import { MAINNET } from "../../fixtures/mainnet"; +import { waitForTokenContent } from "../../helpers/wait"; test.describe("ERC721 Token Details", () => { - test("displays BAYC #1 NFT details section", async ({ page }) => { + test("displays BAYC #1 NFT details section", async ({ page }, testInfo) => { const token = MAINNET.tokens.baycToken1; await page.goto(`/${MAINNET.chainId}/address/${token.contractAddress}/${token.tokenId}`); - const loaded = await waitForTokenContent(page); + const loaded = await waitForTokenContent(page, testInfo); if (loaded) { // Verify ERC721 header is displayed await expect(page.locator(".erc721-header")).toBeVisible(); @@ -55,11 +40,11 @@ test.describe("ERC721 Token Details", () => { } }); - test("displays BAYC #1 token image", async ({ page }) => { + test("displays BAYC #1 token image", async ({ page }, testInfo) => { const token = MAINNET.tokens.baycToken1; await page.goto(`/${MAINNET.chainId}/address/${token.contractAddress}/${token.tokenId}`); - const loaded = await waitForTokenContent(page); + const loaded = await waitForTokenContent(page, testInfo); if (loaded) { // Verify image container exists await expect(page.locator(".erc721-image-container")).toBeVisible(); @@ -69,11 +54,11 @@ test.describe("ERC721 Token Details", () => { } }); - test("displays BAYC #1 properties/attributes", async ({ page }) => { + test("displays BAYC #1 properties/attributes", async ({ page }, testInfo) => { const token = MAINNET.tokens.baycToken1; await page.goto(`/${MAINNET.chainId}/address/${token.contractAddress}/${token.tokenId}`); - const loaded = await waitForTokenContent(page); + const loaded = await waitForTokenContent(page, testInfo); if (loaded) { // Verify Properties section exists await expect(page.locator("text=Properties")).toBeVisible(); @@ -89,11 +74,11 @@ test.describe("ERC721 Token Details", () => { } }); - test("displays BAYC #1 Token URI section", async ({ page }) => { + test("displays BAYC #1 Token URI section", async ({ page }, testInfo) => { const token = MAINNET.tokens.baycToken1; await page.goto(`/${MAINNET.chainId}/address/${token.contractAddress}/${token.tokenId}`); - const loaded = await waitForTokenContent(page); + const loaded = await waitForTokenContent(page, testInfo); if (loaded) { // Verify Token URI section exists await expect(page.locator("text=Token URI")).toBeVisible(); @@ -106,22 +91,22 @@ test.describe("ERC721 Token Details", () => { } }); - test("displays BAYC #1 Raw Metadata section", async ({ page }) => { + test("displays BAYC #1 Raw Metadata section", async ({ page }, testInfo) => { const token = MAINNET.tokens.baycToken1; await page.goto(`/${MAINNET.chainId}/address/${token.contractAddress}/${token.tokenId}`); - const loaded = await waitForTokenContent(page); + const loaded = await waitForTokenContent(page, testInfo); if (loaded) { // Verify Raw Metadata section exists (expandable) await expect(page.locator("text=Raw Metadata")).toBeVisible(); } }); - test("displays BAYC #100 with different properties", async ({ page }) => { + test("displays BAYC #100 with different properties", async ({ page }, testInfo) => { const token = MAINNET.tokens.baycToken100; await page.goto(`/${MAINNET.chainId}/address/${token.contractAddress}/${token.tokenId}`); - const loaded = await waitForTokenContent(page); + const loaded = await waitForTokenContent(page, testInfo); if (loaded) { // Verify token ID is displayed await expect(page.locator(".erc721-header-title")).toContainText(`#${token.tokenId}`); @@ -140,7 +125,7 @@ test.describe("ERC721 Token Details", () => { } }); - test("navigates to ERC721 token via collection lookup", async ({ page }) => { + test("navigates to ERC721 token via collection lookup", async ({ page }, testInfo) => { const addr = MAINNET.addresses.bayc; const tokenId = "1"; @@ -167,7 +152,7 @@ test.describe("ERC721 Token Details", () => { await expect(page).toHaveURL(new RegExp(`/${MAINNET.chainId}/address/${addr.address}/${tokenId}`)); // Verify token details page loaded - const loaded = await waitForTokenContent(page); + const loaded = await waitForTokenContent(page, testInfo); if (loaded) { await expect(page.locator(".erc721-header")).toBeVisible(); } @@ -176,11 +161,11 @@ test.describe("ERC721 Token Details", () => { }); test.describe("ERC1155 Token Details", () => { - test("displays ERC1155 token details page", async ({ page }) => { + test("displays ERC1155 token details page", async ({ page }, testInfo) => { const token = MAINNET.tokens.raribleToken; await page.goto(`/${MAINNET.chainId}/address/${token.contractAddress}/${token.tokenId}`); - const loaded = await waitForTokenContent(page); + const loaded = await waitForTokenContent(page, testInfo); if (loaded) { // Verify ERC1155 header is displayed await expect(page.locator(".erc1155-header")).toBeVisible(); @@ -192,22 +177,22 @@ test.describe("ERC1155 Token Details", () => { } }); - test("displays ERC1155 token image container", async ({ page }) => { + test("displays ERC1155 token image container", async ({ page }, testInfo) => { const token = MAINNET.tokens.raribleToken; await page.goto(`/${MAINNET.chainId}/address/${token.contractAddress}/${token.tokenId}`); - const loaded = await waitForTokenContent(page); + const loaded = await waitForTokenContent(page, testInfo); if (loaded) { // Verify image container exists await expect(page.locator(".erc1155-image-container")).toBeVisible(); } }); - test("displays ERC1155 balance lookup section", async ({ page }) => { + test("displays ERC1155 balance lookup section", async ({ page }, testInfo) => { const token = MAINNET.tokens.raribleToken; await page.goto(`/${MAINNET.chainId}/address/${token.contractAddress}/${token.tokenId}`); - const loaded = await waitForTokenContent(page); + const loaded = await waitForTokenContent(page, testInfo); if (loaded) { // Verify balance lookup section exists (unique to ERC1155) await expect(page.locator(".erc1155-balance-lookup")).toBeVisible(); @@ -220,7 +205,7 @@ test.describe("ERC1155 Token Details", () => { } }); - test("navigates to ERC1155 token via collection lookup", async ({ page }) => { + test("navigates to ERC1155 token via collection lookup", async ({ page }, testInfo) => { const addr = MAINNET.addresses.rarible; const tokenId = "1"; // Simple token ID for navigation test @@ -258,7 +243,7 @@ test.describe("ERC1155 Token Details", () => { }); test.describe("Token Details - Error Handling", () => { - test("handles invalid token ID gracefully for ERC721", async ({ page }) => { + test("handles invalid token ID gracefully for ERC721", async ({ page }, testInfo) => { const addr = MAINNET.addresses.bayc; const invalidTokenId = "999999999"; // Non-existent token @@ -274,7 +259,7 @@ test.describe("Token Details - Error Handling", () => { ).toBeVisible({ timeout: 30000 }); }); - test("handles invalid contract for token view", async ({ page }) => { + test("handles invalid contract for token view", async ({ page }, testInfo) => { const invalidContract = "0x0000000000000000000000000000000000000000"; const tokenId = "1"; diff --git a/e2e/tests/transaction.spec.ts b/e2e/tests/mainnet/transaction.spec.ts similarity index 73% rename from e2e/tests/transaction.spec.ts rename to e2e/tests/mainnet/transaction.spec.ts index 42cff85..8a7d303 100644 --- a/e2e/tests/transaction.spec.ts +++ b/e2e/tests/mainnet/transaction.spec.ts @@ -1,34 +1,20 @@ -import { test, expect } from "../fixtures/test"; -import { TransactionPage } from "../pages/transaction.page"; -import { MAINNET } from "../fixtures/mainnet"; - -// Helper to wait for content or error -async function waitForTxContent(page: import("@playwright/test").Page) { - await expect( - page - .locator("text=Transaction Hash:") - .or(page.locator("text=Error:")) - .or(page.locator("text=Something went wrong")) - ).toBeVisible({ timeout: 45000 }); - - return ( - !(await page.locator("text=Error:").isVisible()) && - !(await page.locator("text=Something went wrong").isVisible()) - ); -} +import { test, expect } from "../../fixtures/test"; +import { TransactionPage } from "../../pages/transaction.page"; +import { MAINNET } from "../../fixtures/mainnet"; +import { waitForTxContent } from "../../helpers/wait"; // Transaction hash constants for readability const FIRST_ETH_TRANSFER = "0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060"; const USDC_APPROVAL = "0xc55e2b90168af6972193c1f86fa4d7d7b31a29c156665d15b9cd48618b5177ef"; test.describe("Transaction Page", () => { - test("displays first ETH transfer with all details", async ({ page }) => { + test("displays first ETH transfer with all details", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = MAINNET.transactions[FIRST_ETH_TRANSFER]; await txPage.goto(tx.hash); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { // Verify core transaction details await expect(page.locator("text=Transaction Hash:")).toBeVisible(); @@ -44,13 +30,13 @@ test.describe("Transaction Page", () => { } }); - test("shows correct from and to addresses", async ({ page }) => { + test("shows correct from and to addresses", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = MAINNET.transactions[FIRST_ETH_TRANSFER]; await txPage.goto(tx.hash); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { const from = await txPage.getFromAddress(); expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); @@ -60,13 +46,13 @@ test.describe("Transaction Page", () => { } }); - test("displays transaction value and fee", async ({ page }) => { + test("displays transaction value and fee", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = MAINNET.transactions[FIRST_ETH_TRANSFER]; await txPage.goto(tx.hash); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { // Verify value contains ETH const value = await txPage.getValue(); @@ -77,13 +63,13 @@ test.describe("Transaction Page", () => { } }); - test("displays other attributes section", async ({ page }) => { + test("displays other attributes section", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = MAINNET.transactions[FIRST_ETH_TRANSFER]; await txPage.goto(tx.hash); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { // Verify other attributes section await expect(page.locator("text=Other Attributes:")).toBeVisible(); @@ -92,26 +78,26 @@ test.describe("Transaction Page", () => { } }); - test("displays transaction with input data", async ({ page }) => { + test("displays transaction with input data", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = MAINNET.transactions[USDC_APPROVAL]; await txPage.goto(tx.hash); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { // Verify input data section exists for contract interactions await expect(page.locator("text=Input Data:")).toBeVisible(); } }); - test("displays block number link", async ({ page }) => { + test("displays block number link", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = MAINNET.transactions[FIRST_ETH_TRANSFER]; await txPage.goto(tx.hash); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { // Verify block section is displayed (block number may be formatted with commas) await expect(page.locator("text=Block:")).toBeVisible(); diff --git a/e2e/tests/optimism.spec.ts b/e2e/tests/optimism.spec.ts index deee2c8..f6b5986 100644 --- a/e2e/tests/optimism.spec.ts +++ b/e2e/tests/optimism.spec.ts @@ -3,6 +3,7 @@ import { BlockPage } from "../pages/block.page"; import { AddressPage } from "../pages/address.page"; import { TransactionPage } from "../pages/transaction.page"; import { OPTIMISM } from "../fixtures/optimism"; +import { waitForBlockContent, waitForTxContent, waitForAddressContent } from "../helpers/wait"; const CHAIN_ID = OPTIMISM.chainId; @@ -11,63 +12,17 @@ const VELODROME_SWAP = "0xa8d73ea0639f39157f787a29591b36fc73c19b443bbe8416d8d6f2 const OP_TRANSFER = "0xdcf7c4afb479cd47f7ce263cbbb298f559b81fc592cc07737935a6166fb90f0c"; const SYSTEM_TX = "0x5d3522dad0d0745b59e9443733f8423548f99856c00768aba9779ae288dedd0a"; -// Helper to wait for block content or error -async function waitForBlockContent(page: import("@playwright/test").Page) { - await expect( - page - .locator("text=Transactions:") - .or(page.locator("text=Error:")) - .or(page.locator("text=Something went wrong")) - ).toBeVisible({ timeout: 45000 }); - - return ( - !(await page.locator("text=Error:").isVisible()) && - !(await page.locator("text=Something went wrong").isVisible()) - ); -} - -// Helper to wait for address content or error -async function waitForAddressContent(page: import("@playwright/test").Page) { - await expect( - page - .locator("text=Balance:") - .or(page.locator("text=Error:")) - .or(page.locator("text=Something went wrong")) - .first() - ).toBeVisible({ timeout: 45000 }); - - return ( - !(await page.locator("text=Error:").isVisible()) && - !(await page.locator("text=Something went wrong").isVisible()) - ); -} - -// Helper to wait for transaction content or error -async function waitForTxContent(page: import("@playwright/test").Page) { - await expect( - page - .locator("text=Transaction Hash:") - .or(page.locator("text=Error:")) - .or(page.locator("text=Something went wrong")) - ).toBeVisible({ timeout: 45000 }); - - return ( - !(await page.locator("text=Error:").isVisible()) && - !(await page.locator("text=Something went wrong").isVisible()) - ); -} - // ============================================ // BLOCK TESTS // ============================================ test.describe("Optimism - Block Page", () => { - test("genesis block #0 - Optimism mainnet (post-regenesis)", async ({ page }) => { + test("genesis block #0 - Optimism mainnet (post-regenesis)", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = OPTIMISM.blocks["0"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { // Header section await expect(blockPage.blockNumber).toBeVisible(); @@ -85,12 +40,12 @@ test.describe("Optimism - Block Page", () => { } }); - test("block #100,000,000 - pre-Bedrock block with gas details", async ({ page }) => { + test("block #100,000,000 - pre-Bedrock block with gas details", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = OPTIMISM.blocks["100000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { // Header await expect(blockPage.blockNumber).toBeVisible(); @@ -110,12 +65,12 @@ test.describe("Optimism - Block Page", () => { } }); - test("block #100,000,000 more details section shows correct hashes", async ({ page }) => { + test("block #100,000,000 more details section shows correct hashes", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = OPTIMISM.blocks["100000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { const showMoreBtn = page.locator("text=Show More Details"); if (await showMoreBtn.isVisible()) { @@ -134,12 +89,12 @@ test.describe("Optimism - Block Page", () => { } }); - test("block #110,000,000 - post-Bedrock with complete gas details", async ({ page }) => { + test("block #110,000,000 - post-Bedrock with complete gas details", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = OPTIMISM.blocks["110000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { await expect(blockPage.blockNumber).toBeVisible(); await expect(blockPage.statusBadge).toContainText("Finalized"); @@ -165,12 +120,12 @@ test.describe("Optimism - Block Page", () => { } }); - test("block #110,000,000 more details shows hashes", async ({ page }) => { + test("block #110,000,000 more details shows hashes", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = OPTIMISM.blocks["110000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { const showMoreBtn = page.locator("text=Show More Details"); if (await showMoreBtn.isVisible()) { @@ -183,12 +138,12 @@ test.describe("Optimism - Block Page", () => { } }); - test("block #120,000,000 - post-Ecotone (EIP-4844) high utilization", async ({ page }) => { + test("block #120,000,000 - post-Ecotone (EIP-4844) high utilization", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = OPTIMISM.blocks["120000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { await expect(blockPage.blockNumber).toBeVisible(); @@ -213,12 +168,12 @@ test.describe("Optimism - Block Page", () => { } }); - test("block #120,000,000 more details shows hashes", async ({ page }) => { + test("block #120,000,000 more details shows hashes", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = OPTIMISM.blocks["120000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { const showMoreBtn = page.locator("text=Show More Details"); if (await showMoreBtn.isVisible()) { @@ -231,12 +186,12 @@ test.describe("Optimism - Block Page", () => { } }); - test("block #130,000,000 - post-Holocene with increased gas limit (60M)", async ({ page }) => { + test("block #130,000,000 - post-Holocene with increased gas limit (60M)", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = OPTIMISM.blocks["130000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { await expect(blockPage.blockNumber).toBeVisible(); @@ -262,12 +217,12 @@ test.describe("Optimism - Block Page", () => { } }); - test("block #130,000,000 more details shows hashes", async ({ page }) => { + test("block #130,000,000 more details shows hashes", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = OPTIMISM.blocks["130000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { const showMoreBtn = page.locator("text=Show More Details"); if (await showMoreBtn.isVisible()) { @@ -280,12 +235,12 @@ test.describe("Optimism - Block Page", () => { } }); - test("genesis block more details section shows correct hash", async ({ page }) => { + test("genesis block more details section shows correct hash", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = OPTIMISM.blocks["0"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { const showMoreBtn = page.locator("text=Show More Details"); if (await showMoreBtn.isVisible()) { @@ -304,18 +259,18 @@ test.describe("Optimism - Block Page", () => { } }); - test("block navigation buttons work on Optimism", async ({ page }) => { + test("block navigation buttons work on Optimism", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); await blockPage.goto(OPTIMISM.blocks["110000000"].number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { await expect(blockPage.navPrevBtn).toBeVisible(); await expect(blockPage.navNextBtn).toBeVisible(); } }); - test("handles invalid block number gracefully", async ({ page }) => { + test("handles invalid block number gracefully", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); await blockPage.goto(999999999999, CHAIN_ID); @@ -333,13 +288,13 @@ test.describe("Optimism - Block Page", () => { // ============================================ test.describe("Optimism - Transaction Page", () => { - test("displays Velodrome DEX swap (Legacy Type 0) with all details", async ({ page }) => { + test("displays Velodrome DEX swap (Legacy Type 0) with all details", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = OPTIMISM.transactions[VELODROME_SWAP]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { // Verify core transaction details await expect(page.locator("text=Transaction Hash:")).toBeVisible(); @@ -354,13 +309,13 @@ test.describe("Optimism - Transaction Page", () => { } }); - test("shows correct from and to addresses for Velodrome swap", async ({ page }) => { + test("shows correct from and to addresses for Velodrome swap", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = OPTIMISM.transactions[VELODROME_SWAP]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { const from = await txPage.getFromAddress(); expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); @@ -370,26 +325,26 @@ test.describe("Optimism - Transaction Page", () => { } }); - test("displays transaction fee for Velodrome swap", async ({ page }) => { + test("displays transaction fee for Velodrome swap", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = OPTIMISM.transactions[VELODROME_SWAP]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { // Verify transaction fee is displayed await expect(page.locator("text=Transaction Fee:")).toBeVisible(); } }); - test("displays Velodrome swap nonce and position", async ({ page }) => { + test("displays Velodrome swap nonce and position", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = OPTIMISM.transactions[VELODROME_SWAP]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { await expect(page.locator("text=Other Attributes:")).toBeVisible(); await expect(page.locator("text=Nonce:")).toBeVisible(); @@ -400,13 +355,13 @@ test.describe("Optimism - Transaction Page", () => { } }); - test("displays OP token transfer transaction (EIP-1559 Type 2)", async ({ page }) => { + test("displays OP token transfer transaction (EIP-1559 Type 2)", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = OPTIMISM.transactions[OP_TRANSFER]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { await expect(page.locator("text=Transaction Hash:")).toBeVisible(); await expect(page.locator("text=Status:")).toBeVisible(); @@ -419,13 +374,13 @@ test.describe("Optimism - Transaction Page", () => { } }); - test("OP transfer shows correct addresses and gas details", async ({ page }) => { + test("OP transfer shows correct addresses and gas details", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = OPTIMISM.transactions[OP_TRANSFER]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { const from = await txPage.getFromAddress(); expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); @@ -438,13 +393,13 @@ test.describe("Optimism - Transaction Page", () => { } }); - test("OP transfer shows nonce and position", async ({ page }) => { + test("OP transfer shows nonce and position", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = OPTIMISM.transactions[OP_TRANSFER]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { await expect(page.locator("text=Nonce:")).toBeVisible(); await expect(page.locator("text=Position:")).toBeVisible(); @@ -456,13 +411,13 @@ test.describe("Optimism - Transaction Page", () => { test("displays system transaction (Type 126) - L2CrossDomainMessenger relay", async ({ page, - }) => { + }, testInfo) => { const txPage = new TransactionPage(page); const tx = OPTIMISM.transactions[SYSTEM_TX]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { await expect(page.locator("text=Transaction Hash:")).toBeVisible(); await expect(page.locator("text=Status:")).toBeVisible(); @@ -477,39 +432,39 @@ test.describe("Optimism - Transaction Page", () => { test("system transaction shows correct from address (Aliased L1 Messenger)", async ({ page, - }) => { + }, testInfo) => { const txPage = new TransactionPage(page); const tx = OPTIMISM.transactions[SYSTEM_TX]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { const from = await txPage.getFromAddress(); expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); } }); - test("displays transaction with input data", async ({ page }) => { + test("displays transaction with input data", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = OPTIMISM.transactions[VELODROME_SWAP]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { // Contract interaction should have input data await expect(page.locator("text=Input Data:")).toBeVisible(); } }); - test("displays block number link for transaction", async ({ page }) => { + test("displays block number link for transaction", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = OPTIMISM.transactions[VELODROME_SWAP]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { await expect(page.locator("text=Block:")).toBeVisible(); const blockValue = await txPage.getBlockNumber(); @@ -517,7 +472,7 @@ test.describe("Optimism - Transaction Page", () => { } }); - test("handles invalid tx hash gracefully", async ({ page }) => { + test("handles invalid tx hash gracefully", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); await txPage.goto("0xinvalid", CHAIN_ID); @@ -535,13 +490,13 @@ test.describe("Optimism - Transaction Page", () => { // ============================================ test.describe("Optimism - Address Page", () => { - test("displays native USDC contract details", async ({ page }) => { + test("displays native USDC contract details", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = OPTIMISM.addresses.usdc; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); @@ -550,78 +505,78 @@ test.describe("Optimism - Address Page", () => { } }); - test("displays bridged USDC.e contract", async ({ page }) => { + test("displays bridged USDC.e contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = OPTIMISM.addresses.usdce; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); } }); - test("displays WETH predeploy contract", async ({ page }) => { + test("displays WETH predeploy contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = OPTIMISM.addresses.weth; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); } }); - test("displays OP governance token contract", async ({ page }) => { + test("displays OP governance token contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = OPTIMISM.addresses.op; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); } }); - test("displays USDT contract", async ({ page }) => { + test("displays USDT contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = OPTIMISM.addresses.usdt; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); } }); - test("displays DAI contract", async ({ page }) => { + test("displays DAI contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = OPTIMISM.addresses.dai; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); } }); - test("displays Velodrome Router contract with details", async ({ page }) => { + test("displays Velodrome Router contract with details", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = OPTIMISM.addresses.velodromeRouter; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); @@ -630,143 +585,143 @@ test.describe("Optimism - Address Page", () => { } }); - test("displays Velodrome Universal Router contract", async ({ page }) => { + test("displays Velodrome Universal Router contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = OPTIMISM.addresses.velodromeUniversalRouter; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); } }); - test("displays Uniswap V3 Router contract", async ({ page }) => { + test("displays Uniswap V3 Router contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = OPTIMISM.addresses.uniswapV3Router; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); } }); - test("displays Uniswap Universal Router contract", async ({ page }) => { + test("displays Uniswap Universal Router contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = OPTIMISM.addresses.uniswapUniversalRouter; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); } }); - test("displays SequencerFeeVault system contract", async ({ page }) => { + test("displays SequencerFeeVault system contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = OPTIMISM.addresses.sequencerFeeVault; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); } }); - test("displays GasPriceOracle system contract", async ({ page }) => { + test("displays GasPriceOracle system contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = OPTIMISM.addresses.gasPriceOracle; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); } }); - test("displays L1Block system contract", async ({ page }) => { + test("displays L1Block system contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = OPTIMISM.addresses.l1Block; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); } }); - test("displays L2StandardBridge contract", async ({ page }) => { + test("displays L2StandardBridge contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = OPTIMISM.addresses.l2StandardBridge; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); } }); - test("displays L2CrossDomainMessenger contract", async ({ page }) => { + test("displays L2CrossDomainMessenger contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = OPTIMISM.addresses.l2CrossDomainMessenger; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); } }); - test("displays L2ToL1MessagePasser contract", async ({ page }) => { + test("displays L2ToL1MessagePasser contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = OPTIMISM.addresses.l2ToL1MessagePasser; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); } }); - test("displays BaseFeeVault system contract", async ({ page }) => { + test("displays BaseFeeVault system contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = OPTIMISM.addresses.baseFeeVault; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); } }); - test("displays L1FeeVault system contract", async ({ page }) => { + test("displays L1FeeVault system contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const addr = OPTIMISM.addresses.l1FeeVault; await addressPage.goto(addr.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); diff --git a/e2e/tests/polygon.spec.ts b/e2e/tests/polygon.spec.ts index 942d5bc..903a30c 100644 --- a/e2e/tests/polygon.spec.ts +++ b/e2e/tests/polygon.spec.ts @@ -3,6 +3,7 @@ import { BlockPage } from "../pages/block.page"; import { TransactionPage } from "../pages/transaction.page"; import { AddressPage } from "../pages/address.page"; import { POLYGON } from "../fixtures/polygon"; +import { waitForBlockContent, waitForTxContent, waitForAddressContent } from "../helpers/wait"; const CHAIN_ID = POLYGON.chainId; @@ -11,66 +12,17 @@ const LEGACY_NFT_TX = "0xb14598e46791c2f0ab366ba2fd4a533e21a0c9894f902773e02e386 const DEFI_SWAP_TX = "0x1ed0c46bafb76d5a3d8201cdf8fc732efa97b000d88bd48dc203ac45d6340af0"; const CONTRACT_TX = "0x65edbf03a20a0317295efaeb9c20836b20b16740c8311ce51ceee91d7674b20d"; -// Helper to wait for block content or error -async function waitForBlockContent(page: import("@playwright/test").Page) { - try { - await expect( - page - .locator("text=Block") - .or(page.locator("text=Error")) - .or(page.locator("text=Something went wrong")) - .first() - ).toBeVisible({ timeout: 30000 }); - return !(await page.locator("text=Error").isVisible()); - } catch { - return false; - } -} - -// Helper to wait for transaction content or error -async function waitForTxContent(page: import("@playwright/test").Page) { - try { - await expect( - page - .locator("text=Transaction Hash") - .or(page.locator("text=Error")) - .or(page.locator("text=Something went wrong")) - .first() - ).toBeVisible({ timeout: 30000 }); - return !(await page.locator("text=Error").isVisible()); - } catch { - return false; - } -} - -// Helper to wait for address content or error -async function waitForAddressContent(page: import("@playwright/test").Page) { - try { - await expect( - page - .locator("text=Address") - .or(page.locator("text=Contract")) - .or(page.locator("text=Error")) - .or(page.locator("text=Something went wrong")) - .first() - ).toBeVisible({ timeout: 30000 }); - return !(await page.locator("text=Error").isVisible()); - } catch { - return false; - } -} - // ============================================ // BLOCK TESTS // ============================================ test.describe("Polygon Block Page", () => { - test("genesis block #0 - Polygon PoS mainnet launch", async ({ page }) => { + test("genesis block #0 - Polygon PoS mainnet launch", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = POLYGON.blocks["0"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { // Header await expect(blockPage.blockNumber).toBeVisible(); @@ -88,12 +40,12 @@ test.describe("Polygon Block Page", () => { } }); - test("genesis block #0 more details shows hash and parent hash", async ({ page }) => { + test("genesis block #0 more details shows hash and parent hash", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = POLYGON.blocks["0"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { const showMoreBtn = page.locator("text=Show More Details"); if (await showMoreBtn.isVisible()) { @@ -106,18 +58,18 @@ test.describe("Polygon Block Page", () => { // Genesis block hash await expect(page.locator(`text=${block.hash}`)).toBeVisible(); - // Genesis parent hash (all zeros) - await expect(page.locator(`text=${block.parentHash}`)).toBeVisible(); + // Genesis parent hash (all zeros) - use .first() to avoid matching logs bloom + await expect(page.locator(`text=${block.parentHash}`).first()).toBeVisible(); } } }); - test("block #10,000,000 - Early Polygon activity", async ({ page }) => { + test("block #10,000,000 - Early Polygon activity", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = POLYGON.blocks["10000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { // Header await expect(blockPage.blockNumber).toBeVisible(); @@ -137,12 +89,12 @@ test.describe("Polygon Block Page", () => { } }); - test("block #10,000,000 more details shows hashes", async ({ page }) => { + test("block #10,000,000 more details shows hashes", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = POLYGON.blocks["10000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { const showMoreBtn = page.locator("text=Show More Details"); if (await showMoreBtn.isVisible()) { @@ -155,12 +107,12 @@ test.describe("Polygon Block Page", () => { } }); - test("block #20,000,000 - Growing DeFi activity", async ({ page }) => { + test("block #20,000,000 - Growing DeFi activity", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = POLYGON.blocks["20000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { await expect(blockPage.blockNumber).toBeVisible(); await expect(blockPage.statusBadge).toContainText("Finalized"); @@ -171,12 +123,12 @@ test.describe("Polygon Block Page", () => { } }); - test("block #20,000,000 more details shows hashes", async ({ page }) => { + test("block #20,000,000 more details shows hashes", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = POLYGON.blocks["20000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { const showMoreBtn = page.locator("text=Show More Details"); if (await showMoreBtn.isVisible()) { @@ -189,12 +141,12 @@ test.describe("Polygon Block Page", () => { } }); - test("block #30,000,000 - Mature network", async ({ page }) => { + test("block #30,000,000 - Mature network", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = POLYGON.blocks["30000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { await expect(blockPage.blockNumber).toBeVisible(); await expect(blockPage.statusBadge).toContainText("Finalized"); @@ -205,12 +157,12 @@ test.describe("Polygon Block Page", () => { } }); - test("block #30,000,000 more details shows hashes", async ({ page }) => { + test("block #30,000,000 more details shows hashes", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = POLYGON.blocks["30000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { const showMoreBtn = page.locator("text=Show More Details"); if (await showMoreBtn.isVisible()) { @@ -223,12 +175,12 @@ test.describe("Polygon Block Page", () => { } }); - test("block #38,189,056 - Delhi Hard Fork", async ({ page }) => { + test("block #38,189,056 - Delhi Hard Fork", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = POLYGON.blocks["38189056"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { await expect(blockPage.blockNumber).toBeVisible(); await expect(blockPage.statusBadge).toContainText("Finalized"); @@ -239,12 +191,12 @@ test.describe("Polygon Block Page", () => { } }); - test("block #38,189,056 more details shows hashes", async ({ page }) => { + test("block #38,189,056 more details shows hashes", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = POLYGON.blocks["38189056"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { const showMoreBtn = page.locator("text=Show More Details"); if (await showMoreBtn.isVisible()) { @@ -257,12 +209,12 @@ test.describe("Polygon Block Page", () => { } }); - test("block #50,000,000 - High activity block", async ({ page }) => { + test("block #50,000,000 - High activity block", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = POLYGON.blocks["50000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { await expect(blockPage.blockNumber).toBeVisible(); await expect(blockPage.statusBadge).toContainText("Finalized"); @@ -273,12 +225,12 @@ test.describe("Polygon Block Page", () => { } }); - test("block #50,000,000 more details shows hashes", async ({ page }) => { + test("block #50,000,000 more details shows hashes", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = POLYGON.blocks["50000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { const showMoreBtn = page.locator("text=Show More Details"); if (await showMoreBtn.isVisible()) { @@ -291,12 +243,12 @@ test.describe("Polygon Block Page", () => { } }); - test("block #62,278,656 - Ahmedabad Hard Fork (MATIC to POL)", async ({ page }) => { + test("block #62,278,656 - Ahmedabad Hard Fork (MATIC to POL)", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = POLYGON.blocks["62278656"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { await expect(blockPage.blockNumber).toBeVisible(); await expect(blockPage.statusBadge).toContainText("Finalized"); @@ -307,12 +259,12 @@ test.describe("Polygon Block Page", () => { } }); - test("block #62,278,656 more details shows hashes", async ({ page }) => { + test("block #62,278,656 more details shows hashes", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = POLYGON.blocks["62278656"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { const showMoreBtn = page.locator("text=Show More Details"); if (await showMoreBtn.isVisible()) { @@ -325,12 +277,12 @@ test.describe("Polygon Block Page", () => { } }); - test("block #65,000,000 - Post-Ahmedabad POL era", async ({ page }) => { + test("block #65,000,000 - Post-Ahmedabad POL era", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = POLYGON.blocks["65000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { await expect(blockPage.blockNumber).toBeVisible(); await expect(blockPage.statusBadge).toContainText("Finalized"); @@ -341,12 +293,12 @@ test.describe("Polygon Block Page", () => { } }); - test("block #65,000,000 more details shows hashes", async ({ page }) => { + test("block #65,000,000 more details shows hashes", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); const block = POLYGON.blocks["65000000"]; await blockPage.goto(block.number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { const showMoreBtn = page.locator("text=Show More Details"); if (await showMoreBtn.isVisible()) { @@ -359,11 +311,11 @@ test.describe("Polygon Block Page", () => { } }); - test("block page loads successfully", async ({ page }) => { + test("block page loads successfully", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); await blockPage.goto(POLYGON.blocks["20000000"].number, CHAIN_ID); - const loaded = await waitForBlockContent(page); + const loaded = await waitForBlockContent(page, testInfo); if (loaded) { // Verify block page loaded with expected content await expect(blockPage.blockNumber).toBeVisible(); @@ -371,7 +323,7 @@ test.describe("Polygon Block Page", () => { } }); - test("handles invalid block number gracefully", async ({ page }) => { + test("handles invalid block number gracefully", async ({ page }, testInfo) => { const blockPage = new BlockPage(page); await blockPage.goto(999999999999, CHAIN_ID); @@ -389,13 +341,13 @@ test.describe("Polygon Block Page", () => { // ============================================ test.describe("Polygon Transaction Page", () => { - test("displays legacy NFT transaction from block 30M", async ({ page }) => { + test("displays legacy NFT transaction from block 30M", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = POLYGON.transactions[LEGACY_NFT_TX]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { // Verify core transaction details await expect(page.locator("text=Transaction Hash:")).toBeVisible(); @@ -413,13 +365,13 @@ test.describe("Polygon Transaction Page", () => { } }); - test("shows correct from and to addresses for legacy NFT tx", async ({ page }) => { + test("shows correct from and to addresses for legacy NFT tx", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = POLYGON.transactions[LEGACY_NFT_TX]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { const from = await txPage.getFromAddress(); expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); @@ -429,13 +381,13 @@ test.describe("Polygon Transaction Page", () => { } }); - test("displays legacy transaction type correctly (Type 0)", async ({ page }) => { + test("displays legacy transaction type correctly (Type 0)", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = POLYGON.transactions[LEGACY_NFT_TX]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { await expect( page.locator("text=Type:").or(page.locator("text=Transaction Type:")).first() @@ -443,13 +395,13 @@ test.describe("Polygon Transaction Page", () => { } }); - test("displays DeFi swap transaction from block 50M", async ({ page }) => { + test("displays DeFi swap transaction from block 50M", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = POLYGON.transactions[DEFI_SWAP_TX]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { await expect(page.locator("text=Transaction Hash:")).toBeVisible(); await expect(page.locator("text=Status:")).toBeVisible(); @@ -459,13 +411,13 @@ test.describe("Polygon Transaction Page", () => { } }); - test("DeFi swap shows correct addresses", async ({ page }) => { + test("DeFi swap shows correct addresses", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = POLYGON.transactions[DEFI_SWAP_TX]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { const from = await txPage.getFromAddress(); expect(from.toLowerCase()).toContain(tx.from.toLowerCase().slice(0, 10)); @@ -475,13 +427,13 @@ test.describe("Polygon Transaction Page", () => { } }); - test("displays contract interaction from block 65M", async ({ page }) => { + test("displays contract interaction from block 65M", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = POLYGON.transactions[CONTRACT_TX]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { await expect(page.locator("text=Transaction Hash:")).toBeVisible(); await expect(page.locator("text=Status:")).toBeVisible(); @@ -490,13 +442,13 @@ test.describe("Polygon Transaction Page", () => { } }); - test("displays transaction nonce and position for legacy NFT tx", async ({ page }) => { + test("displays transaction nonce and position for legacy NFT tx", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = POLYGON.transactions[LEGACY_NFT_TX]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { await expect(page.locator("text=Other Attributes:")).toBeVisible(); await expect(page.locator("text=Nonce:")).toBeVisible(); @@ -507,13 +459,13 @@ test.describe("Polygon Transaction Page", () => { } }); - test("displays DeFi swap nonce and position", async ({ page }) => { + test("displays DeFi swap nonce and position", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = POLYGON.transactions[DEFI_SWAP_TX]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { await expect(page.locator("text=Nonce:")).toBeVisible(); await expect(page.locator("text=Position:")).toBeVisible(); @@ -522,13 +474,13 @@ test.describe("Polygon Transaction Page", () => { } }); - test("displays contract tx nonce and position", async ({ page }) => { + test("displays contract tx nonce and position", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = POLYGON.transactions[CONTRACT_TX]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { await expect(page.locator("text=Nonce:")).toBeVisible(); await expect(page.locator("text=Position:")).toBeVisible(); @@ -538,25 +490,25 @@ test.describe("Polygon Transaction Page", () => { } }); - test("displays transaction fee", async ({ page }) => { + test("displays transaction fee", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = POLYGON.transactions[LEGACY_NFT_TX]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { await expect(page.locator("text=Transaction Fee:")).toBeVisible(); } }); - test("displays block number link for transaction", async ({ page }) => { + test("displays block number link for transaction", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); const tx = POLYGON.transactions[DEFI_SWAP_TX]; await txPage.goto(tx.hash, CHAIN_ID); - const loaded = await waitForTxContent(page); + const loaded = await waitForTxContent(page, testInfo); if (loaded) { await expect(page.locator("text=Block:")).toBeVisible(); const blockValue = await txPage.getBlockNumber(); @@ -564,7 +516,7 @@ test.describe("Polygon Transaction Page", () => { } }); - test("handles invalid tx hash gracefully", async ({ page }) => { + test("handles invalid tx hash gracefully", async ({ page }, testInfo) => { const txPage = new TransactionPage(page); await txPage.goto("0xinvalid", CHAIN_ID); @@ -582,12 +534,12 @@ test.describe("Polygon Transaction Page", () => { // ============================================ test.describe("Polygon Address Page - Tokens", () => { - test("displays WPOL (Wrapped POL) token contract", async ({ page }) => { + test("displays WPOL (Wrapped POL) token contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const token = POLYGON.addresses.wpol; await addressPage.goto(token.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { await expect(page.locator("text=Balance")).toBeVisible(); // Contract should show code or token info @@ -597,78 +549,78 @@ test.describe("Polygon Address Page - Tokens", () => { } }); - test("displays USDC.e (Bridged USDC) token contract", async ({ page }) => { + test("displays USDC.e (Bridged USDC) token contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const token = POLYGON.addresses.usdc; await addressPage.goto(token.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { await expect(page.locator("text=Balance")).toBeVisible(); } }); - test("displays Native USDC token contract", async ({ page }) => { + test("displays Native USDC token contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const token = POLYGON.addresses.usdcNative; await addressPage.goto(token.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { await expect(page.locator("text=Balance")).toBeVisible(); } }); - test("displays USDT token contract", async ({ page }) => { + test("displays USDT token contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const token = POLYGON.addresses.usdt; await addressPage.goto(token.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { await expect(page.locator("text=Balance")).toBeVisible(); } }); - test("displays WETH token contract", async ({ page }) => { + test("displays WETH token contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const token = POLYGON.addresses.weth; await addressPage.goto(token.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { await expect(page.locator("text=Balance")).toBeVisible(); } }); - test("displays DAI token contract", async ({ page }) => { + test("displays DAI token contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const token = POLYGON.addresses.dai; await addressPage.goto(token.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { await expect(page.locator("text=Balance")).toBeVisible(); } }); - test("displays AAVE token contract", async ({ page }) => { + test("displays AAVE token contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const token = POLYGON.addresses.aave; await addressPage.goto(token.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { await expect(page.locator("text=Balance")).toBeVisible(); } }); - test("displays LINK token contract", async ({ page }) => { + test("displays LINK token contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const token = POLYGON.addresses.link; await addressPage.goto(token.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { await expect(page.locator("text=Balance")).toBeVisible(); } @@ -680,12 +632,12 @@ test.describe("Polygon Address Page - Tokens", () => { // ============================================ test.describe("Polygon Address Page - DEX Contracts", () => { - test("displays QuickSwap Router contract", async ({ page }) => { + test("displays QuickSwap Router contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const contract = POLYGON.addresses.quickswapRouter; await addressPage.goto(contract.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { await expect(page.locator("text=Balance")).toBeVisible(); await expect( @@ -694,23 +646,23 @@ test.describe("Polygon Address Page - DEX Contracts", () => { } }); - test("displays Uniswap V3 Router contract", async ({ page }) => { + test("displays Uniswap V3 Router contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const contract = POLYGON.addresses.uniswapV3Router; await addressPage.goto(contract.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { await expect(page.locator("text=Balance")).toBeVisible(); } }); - test("displays SushiSwap Router contract", async ({ page }) => { + test("displays SushiSwap Router contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const contract = POLYGON.addresses.sushiswapRouter; await addressPage.goto(contract.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { await expect(page.locator("text=Balance")).toBeVisible(); } @@ -722,23 +674,23 @@ test.describe("Polygon Address Page - DEX Contracts", () => { // ============================================ test.describe("Polygon Address Page - NFT & Lending", () => { - test("displays OpenSea Storefront contract", async ({ page }) => { + test("displays OpenSea Storefront contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const contract = POLYGON.addresses.openseaStorefront; await addressPage.goto(contract.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { await expect(page.locator("text=Balance")).toBeVisible(); } }); - test("displays Aave V3 Pool contract", async ({ page }) => { + test("displays Aave V3 Pool contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const contract = POLYGON.addresses.aaveV3Pool; await addressPage.goto(contract.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { await expect(page.locator("text=Balance")).toBeVisible(); } @@ -750,29 +702,29 @@ test.describe("Polygon Address Page - NFT & Lending", () => { // ============================================ test.describe("Polygon Address Page - System Contracts", () => { - test("displays POL Token system contract", async ({ page }) => { + test("displays POL Token system contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const contract = POLYGON.addresses.maticToken; await addressPage.goto(contract.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { await expect(page.locator("text=Balance")).toBeVisible(); } }); - test("displays StateReceiver system contract", async ({ page }) => { + test("displays StateReceiver system contract", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); const contract = POLYGON.addresses.stateReceiver; await addressPage.goto(contract.address, CHAIN_ID); - const loaded = await waitForAddressContent(page); + const loaded = await waitForAddressContent(page, testInfo); if (loaded) { await expect(page.locator("text=Balance")).toBeVisible(); } }); - test("handles invalid address gracefully", async ({ page }) => { + test("handles invalid address gracefully", async ({ page }, testInfo) => { const addressPage = new AddressPage(page); await addressPage.goto("0xinvalid", CHAIN_ID); From c8e2bbe95acc992b5ec674e0dc47bfcd17289bcb Mon Sep 17 00:00:00 2001 From: Augusto Lemble Date: Sun, 4 Jan 2026 12:05:06 -0300 Subject: [PATCH 8/9] refactor(e2e): standardize timeouts with environment-aware defaults MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add DEFAULT_TIMEOUT constant (5s local, 10s CI) - Update page objects to use DEFAULT_TIMEOUT * 3 for waitForLoad - Replace hardcoded timeouts in all tests: - 5000ms → DEFAULT_TIMEOUT - 30000ms → DEFAULT_TIMEOUT * 3 - 45000ms → DEFAULT_TIMEOUT * 5 --- e2e/helpers/wait.ts | 7 +++++++ e2e/pages/address.page.ts | 3 ++- e2e/pages/block.page.ts | 3 ++- e2e/pages/transaction.page.ts | 3 ++- e2e/tests/arbitrum.spec.ts | 11 ++++++++--- e2e/tests/base.spec.ts | 11 ++++++++--- e2e/tests/bsc.spec.ts | 13 +++++++++---- e2e/tests/mainnet/address.spec.ts | 22 +++++++++++----------- e2e/tests/mainnet/block.spec.ts | 4 ++-- e2e/tests/mainnet/token.spec.ts | 12 ++++++------ e2e/tests/mainnet/transaction.spec.ts | 4 ++-- e2e/tests/optimism.spec.ts | 11 ++++++++--- e2e/tests/polygon.spec.ts | 13 +++++++++---- 13 files changed, 76 insertions(+), 41 deletions(-) diff --git a/e2e/helpers/wait.ts b/e2e/helpers/wait.ts index ee853d2..2e41859 100644 --- a/e2e/helpers/wait.ts +++ b/e2e/helpers/wait.ts @@ -1,6 +1,13 @@ import type { Page, TestInfo } from "@playwright/test"; import { expect } from "@playwright/test"; +/** + * Default timeout for assertions in e2e tests. + * - Local: 5000ms (5 seconds) + * - CI (GitHub Actions): 10000ms (10 seconds) + */ +export const DEFAULT_TIMEOUT = process.env.CI ? 10000 : 5000; + /** * Shared wait helpers for e2e tests with retry-aware timeouts. * diff --git a/e2e/pages/address.page.ts b/e2e/pages/address.page.ts index 154b8de..6a7e6c0 100644 --- a/e2e/pages/address.page.ts +++ b/e2e/pages/address.page.ts @@ -1,4 +1,5 @@ import type { Locator, Page } from "@playwright/test"; +import { DEFAULT_TIMEOUT } from "../helpers/wait"; export class AddressPage { readonly page: Page; @@ -30,7 +31,7 @@ export class AddressPage { } async waitForLoad() { - await this.loader.waitFor({ state: "hidden", timeout: 30000 }); + await this.loader.waitFor({ state: "hidden", timeout: DEFAULT_TIMEOUT * 3 }); } async getAddressType(): Promise { diff --git a/e2e/pages/block.page.ts b/e2e/pages/block.page.ts index f28daf7..76ba26a 100644 --- a/e2e/pages/block.page.ts +++ b/e2e/pages/block.page.ts @@ -1,4 +1,5 @@ import type { Locator, Page } from "@playwright/test"; +import { DEFAULT_TIMEOUT } from "../helpers/wait"; export class BlockPage { readonly page: Page; @@ -32,7 +33,7 @@ export class BlockPage { } async waitForLoad() { - await this.loader.waitFor({ state: "hidden", timeout: 30000 }); + await this.loader.waitFor({ state: "hidden", timeout: DEFAULT_TIMEOUT * 3 }); } async getBlockNumber(): Promise { diff --git a/e2e/pages/transaction.page.ts b/e2e/pages/transaction.page.ts index 6f5027b..812bb11 100644 --- a/e2e/pages/transaction.page.ts +++ b/e2e/pages/transaction.page.ts @@ -1,4 +1,5 @@ import type { Locator, Page } from "@playwright/test"; +import { DEFAULT_TIMEOUT } from "../helpers/wait"; export class TransactionPage { readonly page: Page; @@ -26,7 +27,7 @@ export class TransactionPage { } async waitForLoad() { - await this.loader.waitFor({ state: "hidden", timeout: 30000 }); + await this.loader.waitFor({ state: "hidden", timeout: DEFAULT_TIMEOUT * 3 }); } async getStatus(): Promise<"success" | "failed"> { diff --git a/e2e/tests/arbitrum.spec.ts b/e2e/tests/arbitrum.spec.ts index c9890f7..a93eb4c 100644 --- a/e2e/tests/arbitrum.spec.ts +++ b/e2e/tests/arbitrum.spec.ts @@ -3,7 +3,12 @@ import { BlockPage } from "../pages/block.page"; import { AddressPage } from "../pages/address.page"; import { TransactionPage } from "../pages/transaction.page"; import { ARBITRUM } from "../fixtures/arbitrum"; -import { waitForBlockContent, waitForTxContent, waitForAddressContent } from "../helpers/wait"; +import { + waitForBlockContent, + waitForTxContent, + waitForAddressContent, + DEFAULT_TIMEOUT, +} from "../helpers/wait"; const CHAIN_ID = ARBITRUM.chainId; @@ -235,7 +240,7 @@ test.describe("Arbitrum One - Block Page", () => { .or(blockPage.container) .or(page.locator("text=Something went wrong")) .first() - ).toBeVisible({ timeout: 30000 }); + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); }); }); @@ -384,7 +389,7 @@ test.describe("Arbitrum One - Transaction Page", () => { .or(txPage.container) .or(page.locator("text=Something went wrong")) .first() - ).toBeVisible({ timeout: 30000 }); + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); }); }); diff --git a/e2e/tests/base.spec.ts b/e2e/tests/base.spec.ts index bea7f4e..ac0a69e 100644 --- a/e2e/tests/base.spec.ts +++ b/e2e/tests/base.spec.ts @@ -3,7 +3,12 @@ import { BlockPage } from "../pages/block.page"; import { AddressPage } from "../pages/address.page"; import { TransactionPage } from "../pages/transaction.page"; import { BASE } from "../fixtures/base"; -import { waitForBlockContent, waitForTxContent, waitForAddressContent } from "../helpers/wait"; +import { + waitForBlockContent, + waitForTxContent, + waitForAddressContent, + DEFAULT_TIMEOUT, +} from "../helpers/wait"; const CHAIN_ID = BASE.chainId; @@ -173,7 +178,7 @@ test.describe("Base Network - Block Page", () => { .or(blockPage.container) .or(page.locator("text=Something went wrong")) .first() - ).toBeVisible({ timeout: 30000 }); + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); }); }); @@ -287,7 +292,7 @@ test.describe("Base Network - Transaction Page", () => { .or(txPage.container) .or(page.locator("text=Something went wrong")) .first() - ).toBeVisible({ timeout: 30000 }); + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); }); }); diff --git a/e2e/tests/bsc.spec.ts b/e2e/tests/bsc.spec.ts index 57f6019..aaf35c4 100644 --- a/e2e/tests/bsc.spec.ts +++ b/e2e/tests/bsc.spec.ts @@ -3,7 +3,12 @@ import { BlockPage } from "../pages/block.page"; import { TransactionPage } from "../pages/transaction.page"; import { AddressPage } from "../pages/address.page"; import { BSC } from "../fixtures/bsc"; -import { waitForBlockContent, waitForTxContent, waitForAddressContent } from "../helpers/wait"; +import { + waitForBlockContent, + waitForTxContent, + waitForAddressContent, + DEFAULT_TIMEOUT, +} from "../helpers/wait"; const CHAIN_ID = BSC.chainId; @@ -311,7 +316,7 @@ test.describe("BSC Block Page", () => { .or(blockPage.container) .or(page.locator("text=Something went wrong")) .first() - ).toBeVisible({ timeout: 30000 }); + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); }); }); @@ -527,7 +532,7 @@ test.describe("BSC Transaction Page", () => { .or(txPage.container) .or(page.locator("text=Something went wrong")) .first() - ).toBeVisible({ timeout: 30000 }); + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); }); }); @@ -782,7 +787,7 @@ test.describe("BSC Address Page - System Contracts", () => { .or(addressPage.container) .or(page.locator("text=Something went wrong")) .first() - ).toBeVisible({ timeout: 30000 }); + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); }); }); diff --git a/e2e/tests/mainnet/address.spec.ts b/e2e/tests/mainnet/address.spec.ts index 484a8b0..741c7d1 100644 --- a/e2e/tests/mainnet/address.spec.ts +++ b/e2e/tests/mainnet/address.spec.ts @@ -1,7 +1,7 @@ import { test, expect } from "../../fixtures/test"; import { AddressPage } from "../../pages/address.page"; import { MAINNET } from "../../fixtures/mainnet"; -import { waitForAddressContent } from "../../helpers/wait"; +import { waitForAddressContent, DEFAULT_TIMEOUT } from "../../helpers/wait"; test.describe("Address Page", () => { test("displays address with balance and transaction count", async ({ page }, testInfo) => { @@ -89,7 +89,7 @@ test.describe("Address Page", () => { await addressPage.goto(addr.address); // Verify address is displayed in header (at least partial) - await expect(page.locator(`text=${addr.address.slice(0, 10)}`)).toBeVisible({ timeout: 30000 }); + await expect(page.locator(`text=${addr.address.slice(0, 10)}`)).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); }); test("handles invalid address gracefully", async ({ page }) => { @@ -101,7 +101,7 @@ test.describe("Address Page", () => { .or(addressPage.container) .or(page.locator("text=Something went wrong")) .first() - ).toBeVisible({ timeout: 30000 }); + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); }); test("displays ERC721 NFT collection (BAYC) with collection details", async ({ page }, testInfo) => { @@ -153,7 +153,7 @@ test.describe("Address Page", () => { await contractDetailsHeader.click(); // Wait for expansion and verify details - await expect(page.locator("text=Verified At")).toBeVisible({ timeout: 5000 }); + await expect(page.locator("text=Verified At")).toBeVisible({ timeout: DEFAULT_TIMEOUT }); // Verify match type badge await expect(page.locator(`text=${addr.matchType}`)).toBeVisible(); @@ -181,7 +181,7 @@ test.describe("Address Page", () => { await contractDetailsHeader.click(); // Wait for Read Functions section to be visible - await expect(page.locator("text=/Read Functions \\(\\d+\\)/")).toBeVisible({ timeout: 5000 }); + await expect(page.locator("text=/Read Functions \\(\\d+\\)/")).toBeVisible({ timeout: DEFAULT_TIMEOUT }); // Verify some key read functions are displayed const keyReadFunctions = ["name", "symbol", "totalSupply", "balanceOf", "ownerOf", "tokenURI"]; @@ -204,7 +204,7 @@ test.describe("Address Page", () => { await contractDetailsHeader.click(); // Wait for Write Functions section to be visible - await expect(page.locator("text=/Write Functions \\(\\d+\\)/")).toBeVisible({ timeout: 5000 }); + await expect(page.locator("text=/Write Functions \\(\\d+\\)/")).toBeVisible({ timeout: DEFAULT_TIMEOUT }); // Verify some key write functions are displayed const keyWriteFunctions = ["approve", "transferFrom", "safeTransferFrom"]; @@ -227,7 +227,7 @@ test.describe("Address Page", () => { await contractDetailsHeader.click(); // Wait for Events section to be visible - await expect(page.locator("text=/Events \\(\\d+\\)/")).toBeVisible({ timeout: 5000 }); + await expect(page.locator("text=/Events \\(\\d+\\)/")).toBeVisible({ timeout: DEFAULT_TIMEOUT }); // Verify the events are displayed for (const event of addr.events) { @@ -284,7 +284,7 @@ test.describe("Address Page", () => { await contractDetailsHeader.click(); // Wait for expansion and verify details - await expect(page.locator("text=Verified At")).toBeVisible({ timeout: 5000 }); + await expect(page.locator("text=Verified At")).toBeVisible({ timeout: DEFAULT_TIMEOUT }); // Verify match type badge (MATCH for Rarible) await expect(page.locator(`text=${addr.matchType}`)).toBeVisible(); @@ -312,7 +312,7 @@ test.describe("Address Page", () => { await contractDetailsHeader.click(); // Wait for Read Functions section to be visible - await expect(page.locator("text=/Read Functions \\(\\d+\\)/")).toBeVisible({ timeout: 5000 }); + await expect(page.locator("text=/Read Functions \\(\\d+\\)/")).toBeVisible({ timeout: DEFAULT_TIMEOUT }); // Verify some key read functions are displayed const keyReadFunctions = ["balanceOf", "name", "symbol", "uri", "supportsInterface"]; @@ -335,7 +335,7 @@ test.describe("Address Page", () => { await contractDetailsHeader.click(); // Wait for Write Functions section to be visible - await expect(page.locator("text=/Write Functions \\(\\d+\\)/")).toBeVisible({ timeout: 5000 }); + await expect(page.locator("text=/Write Functions \\(\\d+\\)/")).toBeVisible({ timeout: DEFAULT_TIMEOUT }); // Verify some key write functions are displayed const keyWriteFunctions = ["mint", "burn", "safeTransferFrom", "setApprovalForAll"]; @@ -358,7 +358,7 @@ test.describe("Address Page", () => { await contractDetailsHeader.click(); // Wait for Events section to be visible - await expect(page.locator("text=/Events \\(\\d+\\)/")).toBeVisible({ timeout: 5000 }); + await expect(page.locator("text=/Events \\(\\d+\\)/")).toBeVisible({ timeout: DEFAULT_TIMEOUT }); // Verify some key events are displayed const keyEvents = ["TransferSingle", "TransferBatch", "ApprovalForAll", "URI"]; diff --git a/e2e/tests/mainnet/block.spec.ts b/e2e/tests/mainnet/block.spec.ts index 6ab427a..e3e098d 100644 --- a/e2e/tests/mainnet/block.spec.ts +++ b/e2e/tests/mainnet/block.spec.ts @@ -1,7 +1,7 @@ import { test, expect } from "../../fixtures/test"; import { BlockPage } from "../../pages/block.page"; import { MAINNET } from "../../fixtures/mainnet"; -import { waitForBlockContent } from "../../helpers/wait"; +import { waitForBlockContent, DEFAULT_TIMEOUT } from "../../helpers/wait"; test.describe("Block Page", () => { test("block #10,000 - pre-London block with no transactions", async ({ page }, testInfo) => { @@ -249,6 +249,6 @@ test.describe("Block Page", () => { .or(blockPage.container) .or(page.locator("text=Something went wrong")) .first() - ).toBeVisible({ timeout: 30000 }); + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); }); }); diff --git a/e2e/tests/mainnet/token.spec.ts b/e2e/tests/mainnet/token.spec.ts index 79b8165..06c5dca 100644 --- a/e2e/tests/mainnet/token.spec.ts +++ b/e2e/tests/mainnet/token.spec.ts @@ -1,6 +1,6 @@ import { test, expect } from "../../fixtures/test"; import { MAINNET } from "../../fixtures/mainnet"; -import { waitForTokenContent } from "../../helpers/wait"; +import { waitForTokenContent, DEFAULT_TIMEOUT } from "../../helpers/wait"; test.describe("ERC721 Token Details", () => { test("displays BAYC #1 NFT details section", async ({ page }, testInfo) => { @@ -138,7 +138,7 @@ test.describe("ERC721 Token Details", () => { .locator(".erc721-token-input") .or(page.locator("text=Error:")) .or(page.locator("text=Failed to fetch")) - ).toBeVisible({ timeout: 45000 }); + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 5 }); // Only proceed if the token input is visible (page loaded successfully) if (await page.locator(".erc721-token-input").isVisible()) { @@ -220,7 +220,7 @@ test.describe("ERC1155 Token Details", () => { .or(page.locator("text=Error:")) .or(page.locator("text=Failed to fetch")) .first() - ).toBeVisible({ timeout: 45000 }); + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 5 }); // Only proceed if the ERC1155 token input is visible (contract detected as ERC1155) if (await page.locator(".erc1155-token-input").isVisible()) { @@ -236,7 +236,7 @@ test.describe("ERC1155 Token Details", () => { // Verify token details page loaded (may be ERC1155 or show loading/error for invalid token) await expect( page.locator(".erc1155-header").or(page.locator("text=Error:")).or(page.locator(".erc1155-detail-content")) - ).toBeVisible({ timeout: 30000 }); + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); } // If not detected as ERC1155, test passes (RPC may not support interface detection) }); @@ -256,7 +256,7 @@ test.describe("Token Details - Error Handling", () => { .or(page.locator("text=Something went wrong")) .or(page.locator(".erc721-header")) .or(page.locator(".erc721-detail-content")) - ).toBeVisible({ timeout: 30000 }); + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); }); test("handles invalid contract for token view", async ({ page }, testInfo) => { @@ -271,6 +271,6 @@ test.describe("Token Details - Error Handling", () => { .locator("text=Error:") .or(page.locator("text=Something went wrong")) .or(page.locator(".container-wide")) - ).toBeVisible({ timeout: 30000 }); + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); }); }); diff --git a/e2e/tests/mainnet/transaction.spec.ts b/e2e/tests/mainnet/transaction.spec.ts index 8a7d303..e1441c6 100644 --- a/e2e/tests/mainnet/transaction.spec.ts +++ b/e2e/tests/mainnet/transaction.spec.ts @@ -1,7 +1,7 @@ import { test, expect } from "../../fixtures/test"; import { TransactionPage } from "../../pages/transaction.page"; import { MAINNET } from "../../fixtures/mainnet"; -import { waitForTxContent } from "../../helpers/wait"; +import { waitForTxContent, DEFAULT_TIMEOUT } from "../../helpers/wait"; // Transaction hash constants for readability const FIRST_ETH_TRANSFER = "0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060"; @@ -115,6 +115,6 @@ test.describe("Transaction Page", () => { .or(txPage.container) .or(page.locator("text=Something went wrong")) .first() - ).toBeVisible({ timeout: 30000 }); + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); }); }); diff --git a/e2e/tests/optimism.spec.ts b/e2e/tests/optimism.spec.ts index f6b5986..49ad7fd 100644 --- a/e2e/tests/optimism.spec.ts +++ b/e2e/tests/optimism.spec.ts @@ -3,7 +3,12 @@ import { BlockPage } from "../pages/block.page"; import { AddressPage } from "../pages/address.page"; import { TransactionPage } from "../pages/transaction.page"; import { OPTIMISM } from "../fixtures/optimism"; -import { waitForBlockContent, waitForTxContent, waitForAddressContent } from "../helpers/wait"; +import { + waitForBlockContent, + waitForTxContent, + waitForAddressContent, + DEFAULT_TIMEOUT, +} from "../helpers/wait"; const CHAIN_ID = OPTIMISM.chainId; @@ -279,7 +284,7 @@ test.describe("Optimism - Block Page", () => { .or(blockPage.container) .or(page.locator("text=Something went wrong")) .first() - ).toBeVisible({ timeout: 30000 }); + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); }); }); @@ -481,7 +486,7 @@ test.describe("Optimism - Transaction Page", () => { .or(txPage.container) .or(page.locator("text=Something went wrong")) .first() - ).toBeVisible({ timeout: 30000 }); + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); }); }); diff --git a/e2e/tests/polygon.spec.ts b/e2e/tests/polygon.spec.ts index 903a30c..3d2e960 100644 --- a/e2e/tests/polygon.spec.ts +++ b/e2e/tests/polygon.spec.ts @@ -3,7 +3,12 @@ import { BlockPage } from "../pages/block.page"; import { TransactionPage } from "../pages/transaction.page"; import { AddressPage } from "../pages/address.page"; import { POLYGON } from "../fixtures/polygon"; -import { waitForBlockContent, waitForTxContent, waitForAddressContent } from "../helpers/wait"; +import { + waitForBlockContent, + waitForTxContent, + waitForAddressContent, + DEFAULT_TIMEOUT, +} from "../helpers/wait"; const CHAIN_ID = POLYGON.chainId; @@ -332,7 +337,7 @@ test.describe("Polygon Block Page", () => { .or(blockPage.container) .or(page.locator("text=Something went wrong")) .first() - ).toBeVisible({ timeout: 30000 }); + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); }); }); @@ -525,7 +530,7 @@ test.describe("Polygon Transaction Page", () => { .or(txPage.container) .or(page.locator("text=Something went wrong")) .first() - ).toBeVisible({ timeout: 30000 }); + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); }); }); @@ -734,6 +739,6 @@ test.describe("Polygon Address Page - System Contracts", () => { .or(page.locator("text=Something went wrong")) .or(page.locator("text=Invalid")) .first() - ).toBeVisible({ timeout: 30000 }); + ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); }); }); From 1756d3cbf1943e66f7a55fffa3abdf57fce23558 Mon Sep 17 00:00:00 2001 From: Augusto Lemble Date: Sun, 4 Jan 2026 12:23:05 -0300 Subject: [PATCH 9/9] fix(e2e): resolve strict mode violations in locators - Add .first() to token error handling locator (matches 2 elements) - Add .first() to Contract Details locator in Optimism test --- e2e/tests/mainnet/token.spec.ts | 1 + e2e/tests/optimism.spec.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/e2e/tests/mainnet/token.spec.ts b/e2e/tests/mainnet/token.spec.ts index 06c5dca..7d05de1 100644 --- a/e2e/tests/mainnet/token.spec.ts +++ b/e2e/tests/mainnet/token.spec.ts @@ -271,6 +271,7 @@ test.describe("Token Details - Error Handling", () => { .locator("text=Error:") .or(page.locator("text=Something went wrong")) .or(page.locator(".container-wide")) + .first() ).toBeVisible({ timeout: DEFAULT_TIMEOUT * 3 }); }); }); diff --git a/e2e/tests/optimism.spec.ts b/e2e/tests/optimism.spec.ts index 49ad7fd..779e584 100644 --- a/e2e/tests/optimism.spec.ts +++ b/e2e/tests/optimism.spec.ts @@ -586,7 +586,7 @@ test.describe("Optimism - Address Page", () => { const isContract = await addressPage.isContract(); expect(isContract).toBe(true); - await expect(page.locator("text=Contract Details")).toBeVisible(); + await expect(page.locator("text=Contract Details").first()).toBeVisible(); } });