harb/scripts/harb-evaluator/helpers/wallet.ts
openhands 77a229018a fix: address review feedback on holdout helpers
- Extract rpcCall into helpers/rpc.ts to eliminate the duplicate copy
  in wallet.ts and assertions.ts (warning: code duplication)

- Fix waitForReceipt() in swap.ts to assert receipt.status === '0x1':
  reverted transactions (status 0x0) now throw immediately with a clear
  message instead of letting sellAllKrk silently succeed and fail later
  at the balance assertion (bug)

- Add screen.width debug log to connectWallet() before the isVisible
  check, restoring the regression signal from always-leave.spec.ts (warning)

- Fix expectPoolHasLiquidity() to only assert sqrtPriceX96 > 0 (pool
  is initialised); drop the active-tick liquidity() check which gives
  false negatives when price moves outside all LiquidityManager ranges
  after a sovereign exit (warning)

- Add WETH balance snapshot before/after the swap in sellAllKrk() and
  log a warning when WETH output is 0, making pool health degradation
  visible despite amountOutMinimum: 0n (warning)

- Add before/after screenshots in buyKrk() (holdout-before-buy.png,
  holdout-after-buy.png) to restore CI debugging artefacts (nit)

- Move waitForTimeout(2_000) settle buffer in buyKrk() to the catch
  path only; when the Submitting→idle transition is observed the extra
  wait is redundant (nit)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 05:59:21 +00:00

96 lines
4.3 KiB
TypeScript

/**
* Shared wallet helpers for holdout scenarios.
*
* Functions here operate in the Playwright Node.js context (not the browser).
* UI interactions use Playwright locators; on-chain reads use direct RPC calls
* so that tests do not depend on the app's wallet state for balance queries.
*/
import type { Page } from '@playwright/test';
import { expect } from '@playwright/test';
import { rpcCall } from './rpc';
// ── Balance readers ──────────────────────────────────────────────────────────
/** Return the native ETH balance (in wei) of `address`. */
export async function getEthBalance(rpcUrl: string, address: string): Promise<bigint> {
const result = (await rpcCall(rpcUrl, 'eth_getBalance', [address, 'latest'])) as string;
return BigInt(result);
}
/** Return the ERC-20 KRK balance (in wei) of `address` on the given token contract. */
export async function getKrkBalance(rpcUrl: string, krkAddress: string, address: string): Promise<bigint> {
const selector = '0x70a08231'; // balanceOf(address)
const data = selector + address.slice(2).padStart(64, '0');
const result = (await rpcCall(rpcUrl, 'eth_call', [{ to: krkAddress, data }, 'latest'])) as string;
return BigInt(result);
}
// ── UI helpers ───────────────────────────────────────────────────────────────
/**
* Connect the test wallet via the desktop UI flow.
*
* Expects the page to already be on the web app with the navbar visible.
* The injected wallet provider must already be set up by createWalletContext.
* Verifies connection by waiting for the connected button to appear in the navbar.
*/
export async function connectWallet(page: Page): Promise<void> {
// Trigger resize so Vue's useMobile composable re-evaluates with screen.width=1280.
await page.evaluate(() => window.dispatchEvent(new Event('resize')));
await page.waitForTimeout(2_000);
const screenWidth = await page.evaluate(() => window.screen.width);
console.log(`[wallet] screen.width = ${screenWidth}`);
let panelOpened = false;
const connectButton = page.locator('.connect-button--disconnected').first();
if (await connectButton.isVisible({ timeout: 5_000 })) {
console.log('[wallet] Found desktop Connect button, clicking...');
await connectButton.click();
panelOpened = true;
} else {
// Fallback: mobile login icon. Dead code when screen.width=1280 but kept for safety.
const mobileLoginIcon = page.locator('.navbar-end svg').first();
if (await mobileLoginIcon.isVisible({ timeout: 2_000 })) {
console.log('[wallet] Found mobile login icon, clicking...');
await mobileLoginIcon.click();
panelOpened = true;
}
}
if (panelOpened) {
await page.waitForTimeout(1_000);
const injectedConnector = page.locator('.connectors-element').first();
if (await injectedConnector.isVisible({ timeout: 5_000 })) {
console.log('[wallet] Clicking wallet connector...');
await injectedConnector.click();
await page.waitForTimeout(2_000);
} else {
console.log('[wallet] WARNING: No wallet connector found in panel');
}
}
// The navbar shows .connect-button--connected once wagmi reports status=connected.
await expect(page.locator('.connect-button--connected').first()).toBeVisible({ timeout: 15_000 });
console.log('[wallet] Wallet connected');
}
/**
* Disconnect the wallet by opening the connected panel and clicking the logout icon.
*
* Verifies disconnection by waiting for the Connect button to reappear.
*/
export async function disconnectWallet(page: Page): Promise<void> {
const connectedButton = page.locator('.connect-button--connected').first();
await expect(connectedButton).toBeVisible({ timeout: 5_000 });
await connectedButton.click();
// Panel opens showing .connected-header-logout (img alt="Logout").
const logoutIcon = page.locator('.connected-header-logout').first();
await expect(logoutIcon).toBeVisible({ timeout: 5_000 });
await logoutIcon.click();
await expect(page.locator('.connect-button--disconnected').first()).toBeVisible({ timeout: 10_000 });
console.log('[wallet] Wallet disconnected');
}