fix: address review findings in sellKrk helper
- Fix max-button race: wait for input to be non-empty after clicking Max (setMax is async, composable calls loadKrkBalance() before setting value) - Add on-chain confirmation via WETH Transfer event polling (mirrors buyKrk) so balance query happens after the swap is mined, not just UI-idle - Use Pick<SellConfig, 'rpcUrl' | 'accountAddress'> since krkAddress is unused - Add page heading assertion after navigate (consistent with buyKrk) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
912c7e4eee
commit
c891b3c617
1 changed files with 49 additions and 6 deletions
|
|
@ -174,35 +174,40 @@ export async function buyKrk(page: Page, ethAmount: string, opts?: BuyKrkOptions
|
|||
*
|
||||
* Drives the real sell widget UI (requires the #456 sell tab).
|
||||
*
|
||||
* If config is provided, queries WETH balance before and after the sell and
|
||||
* returns the delta. Otherwise returns 0n (caller is responsible for verification).
|
||||
* If config is provided, creates an eth_newFilter for WETH Transfer events to the
|
||||
* account before clicking Sell, polls eth_getFilterLogs until the event arrives
|
||||
* (confirming the swap is mined), then returns the WETH balance delta. Otherwise
|
||||
* returns 0n (caller is responsible for verification).
|
||||
*
|
||||
* @param page - Playwright page with injected wallet
|
||||
* @param amount - KRK amount to sell (as string). Use 'max' to click the Max button.
|
||||
* @param screenshotPrefix - Optional prefix for screenshot filenames
|
||||
* @param config - Optional config to query WETH received after sell
|
||||
* @param config - Optional config for on-chain WETH receipt confirmation
|
||||
* @returns WETH received (balance diff) or 0n if config is not provided
|
||||
*/
|
||||
export async function sellKrk(
|
||||
page: Page,
|
||||
amount: string,
|
||||
screenshotPrefix?: string,
|
||||
config?: SellConfig,
|
||||
config?: Pick<SellConfig, 'rpcUrl' | 'accountAddress'>,
|
||||
): Promise<bigint> {
|
||||
console.log(`[swap] Selling ${amount} KRK via get-krk page sell widget...`);
|
||||
await navigateSPA(page, '/app/get-krk');
|
||||
await expect(page.getByRole('heading', { name: 'Get $KRK Tokens' })).toBeVisible({ timeout: 10_000 });
|
||||
|
||||
const sellTab = page.getByTestId('swap-mode-sell');
|
||||
await expect(sellTab).toBeVisible({ timeout: 10_000 });
|
||||
await sellTab.click();
|
||||
|
||||
const sellInput = page.getByTestId('swap-sell-amount-input');
|
||||
if (amount === 'max') {
|
||||
const maxButton = page.locator('.max-button');
|
||||
await expect(maxButton).toBeVisible({ timeout: 5_000 });
|
||||
await maxButton.click();
|
||||
// setMax() is async — wait for the composable to populate the input via loadKrkBalance()
|
||||
await expect(sellInput).not.toHaveValue('', { timeout: 10_000 });
|
||||
console.log('[swap] Clicked Max button');
|
||||
} else {
|
||||
const sellInput = page.getByTestId('swap-sell-amount-input');
|
||||
await expect(sellInput).toBeVisible({ timeout: 5_000 });
|
||||
await sellInput.fill(amount);
|
||||
console.log(`[swap] Filled sell amount: ${amount}`);
|
||||
|
|
@ -210,6 +215,23 @@ export async function sellKrk(
|
|||
|
||||
const wethBefore = config ? await erc20BalanceOf(config.rpcUrl, WETH, config.accountAddress) : 0n;
|
||||
|
||||
// Create WETH Transfer event filter BEFORE the sell (if config provided)
|
||||
let filterId: string | undefined;
|
||||
if (config) {
|
||||
filterId = (await rpcCall(config.rpcUrl, 'eth_newFilter', [
|
||||
{
|
||||
address: WETH,
|
||||
topics: [
|
||||
TRANSFER_TOPIC,
|
||||
null, // any sender (pool/router)
|
||||
'0x' + config.accountAddress.slice(2).padStart(64, '0'), // to our account
|
||||
],
|
||||
fromBlock: 'latest',
|
||||
},
|
||||
])) as string;
|
||||
console.log(`[swap] WETH Transfer filter created: ${filterId}`);
|
||||
}
|
||||
|
||||
if (screenshotPrefix) {
|
||||
await page.screenshot({ path: `test-results/${screenshotPrefix}-before-sell.png` });
|
||||
}
|
||||
|
|
@ -228,7 +250,28 @@ export async function sellKrk(
|
|||
console.log('[swap] Button state not observed (sell may have completed instantly)');
|
||||
}
|
||||
await expect(sellButton).toHaveText('Sell KRK', { timeout: 60_000 });
|
||||
console.log('[swap] Sell completed');
|
||||
console.log('[swap] Sell completed (UI idle)');
|
||||
|
||||
// Wait for on-chain confirmation via WETH Transfer event
|
||||
if (config && filterId) {
|
||||
console.log('[swap] Waiting for WETH Transfer event...');
|
||||
const deadline = Date.now() + 15_000;
|
||||
let received = false;
|
||||
while (Date.now() < deadline) {
|
||||
const logs = (await rpcCall(config.rpcUrl, 'eth_getFilterLogs', [filterId])) as unknown[];
|
||||
if (logs && logs.length > 0) {
|
||||
received = true;
|
||||
console.log(`[swap] WETH Transfer event received (${logs.length} log(s))`);
|
||||
break;
|
||||
}
|
||||
// eslint-disable-next-line no-restricted-syntax -- Polling with timeout: eth_getFilterLogs is HTTP-only polling (not push). See AGENTS.md #Engineering Principles.
|
||||
await new Promise(r => setTimeout(r, 200));
|
||||
}
|
||||
await rpcCall(config.rpcUrl, 'eth_uninstallFilter', [filterId]).catch(() => {});
|
||||
if (!received) {
|
||||
throw new Error(`No WETH Transfer event received within 15s after selling ${amount} KRK`);
|
||||
}
|
||||
}
|
||||
|
||||
if (screenshotPrefix) {
|
||||
await page.screenshot({ path: `test-results/${screenshotPrefix}-after-sell.png` });
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue