From 070d20f4ff4456c97e8d2cc9bc6028ecbe740973 Mon Sep 17 00:00:00 2001 From: Pavel Lesyuk Date: Sat, 11 Jul 2026 13:27:45 +0800 Subject: [PATCH 1/6] test(appkit-minter): pin stable Allure IDs on gasless e2e tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Append a native `@allure.id=` token to every gasless e2e test title so each result links to its existing TestOps case by a stable DB-level id instead of the default file-path + describe + title match. The case and its history now survive future renames, file moves, and title/describe edits — cases that the default binding silently orphans. Only the id token is appended: no test title, describe, or file was renamed or moved. The two `@real-send` tests stay unpinned until their first upload assigns a case id (bootstrap). --- .../e2e/specs/gasless-availability.spec.ts | 6 ++--- .../e2e/specs/gasless-error-scenarios.spec.ts | 4 ++-- .../e2e/specs/gasless-input-edges.spec.ts | 4 ++-- .../e2e/specs/gasless-mint-extra.spec.ts | 10 ++++----- .../e2e/specs/gasless-mint.spec.ts | 6 ++--- .../e2e/specs/gasless-races.spec.ts | 4 ++-- .../e2e/specs/gasless-security-extra.spec.ts | 2 +- .../e2e/specs/gasless-security.spec.ts | 14 ++++++------ .../e2e/specs/gasless-transfer-form.spec.ts | 22 +++++++++---------- .../e2e/specs/gasless-transfer.spec.ts | 6 ++--- 10 files changed, 39 insertions(+), 39 deletions(-) diff --git a/apps/appkit-minter/e2e/specs/gasless-availability.spec.ts b/apps/appkit-minter/e2e/specs/gasless-availability.spec.ts index 7119c3705..46362947c 100644 --- a/apps/appkit-minter/e2e/specs/gasless-availability.spec.ts +++ b/apps/appkit-minter/e2e/specs/gasless-availability.spec.ts @@ -23,7 +23,7 @@ import { USDT_MASTER } from '../mocks/gaslessRelayer'; // --- wallet-less: Assets empty state renders without crashing --- base.describe('Availability (no wallet)', () => { - base('Assets list shows empty state without a connected wallet (no crash)', async ({ page }) => { + base('Assets list shows empty state without a connected wallet (no crash) @allure.id=9849', async ({ page }) => { await gaslessMeta('Availability'); const pageErrors: string[] = []; await base.step('Open the jettons list without a connected wallet', async () => { @@ -46,7 +46,7 @@ const test = testWithGaslessFixture({ }); test.describe('Availability (two-tab wallet)', () => { - test('No Gasless block in the GRAM transfer modal', async ({ minter, widget, wallet }) => { + test('No Gasless block in the GRAM transfer modal @allure.id=9983', async ({ minter, widget, wallet }) => { await gaslessMeta('Availability'); await test.step('Connect wallet and open GRAM transfer', async () => { await connectWallet({ widget, wallet }); @@ -57,7 +57,7 @@ test.describe('Availability (two-tab wallet)', () => { }); }); - test('Jetton transfer — Gasless checkbox enabled for a SignMessage wallet', async ({ minter, widget, wallet }) => { + test('Jetton transfer — Gasless checkbox enabled for a SignMessage wallet @allure.id=9869', async ({ minter, widget, wallet }) => { await gaslessMeta('Availability'); await test.step('Connect wallet and open USDT transfer', async () => { await connectWallet({ widget, wallet }); diff --git a/apps/appkit-minter/e2e/specs/gasless-error-scenarios.spec.ts b/apps/appkit-minter/e2e/specs/gasless-error-scenarios.spec.ts index 92fa975ed..25e22edd6 100644 --- a/apps/appkit-minter/e2e/specs/gasless-error-scenarios.spec.ts +++ b/apps/appkit-minter/e2e/specs/gasless-error-scenarios.spec.ts @@ -23,7 +23,7 @@ test.describe('Relayer error rendering (no wallet)', () => { // Load-time facet of hostile config handling: a hostile config response must not // crash the app or execute script on boot. The render-sink facet (relayer error // shown as escaped text) is covered with a connected wallet in gasless-security.spec.ts. - test('Hostile config response (HTTP 400) does not crash the app or execute script (load without wallet)', async ({ + test('Hostile config response (HTTP 400) does not crash the app or execute script (load without wallet) @allure.id=9856', async ({ page, }) => { await gaslessMeta('Config errors'); @@ -61,7 +61,7 @@ test.describe('Relayer error rendering (no wallet)', () => { }); }); - test('App loads with a config that has no supported assets (no crash)', async ({ page }) => { + test('App loads with a config that has no supported assets (no crash) @allure.id=9859', async ({ page }) => { await gaslessMeta('Config errors'); await test.step('Mock config with a valid relay address and empty gas_jettons', async () => { // NB: relay_address must be a checksum-valid friendly address, else the diff --git a/apps/appkit-minter/e2e/specs/gasless-input-edges.spec.ts b/apps/appkit-minter/e2e/specs/gasless-input-edges.spec.ts index db4704f0a..7fb441e74 100644 --- a/apps/appkit-minter/e2e/specs/gasless-input-edges.spec.ts +++ b/apps/appkit-minter/e2e/specs/gasless-input-edges.spec.ts @@ -22,7 +22,7 @@ const test = testWithGaslessFixture({ }); test.describe('Transfer-form input edge cases (two-tab wallet, mocked relayer)', () => { - test('Invalid recipient address is rejected — error shown, send blocked', async ({ + test('Invalid recipient address is rejected — error shown, send blocked @allure.id=9873', async ({ app, minter, widget, @@ -46,7 +46,7 @@ test.describe('Transfer-form input edge cases (two-tab wallet, mocked relayer)', }); }); - test('Empty amount blocks Send; filling a valid amount unblocks it (comment with special characters does not interfere)', async ({ + test('Empty amount blocks Send; filling a valid amount unblocks it (comment with special characters does not interfere) @allure.id=9857', async ({ app, minter, widget, diff --git a/apps/appkit-minter/e2e/specs/gasless-mint-extra.spec.ts b/apps/appkit-minter/e2e/specs/gasless-mint-extra.spec.ts index b9aee72f7..49c15bc8b 100644 --- a/apps/appkit-minter/e2e/specs/gasless-mint-extra.spec.ts +++ b/apps/appkit-minter/e2e/specs/gasless-mint-extra.spec.ts @@ -23,7 +23,7 @@ const test = testWithGaslessFixture({ }); test.describe('Gasless Mint — additional states', () => { - test('With Gasless disabled the Confirm dialog shows only the Owner row', async ({ + test('With Gasless disabled the Confirm dialog shows only the Owner row @allure.id=9852', async ({ app, minter, widget, @@ -43,7 +43,7 @@ test.describe('Gasless Mint — additional states', () => { }); }); - test('Gasless setting persists when mint settings are reopened', async ({ app, minter, widget, wallet }) => { + test('Gasless setting persists when mint settings are reopened @allure.id=9866', async ({ app, minter, widget, wallet }) => { await gaslessMeta('Mint'); await mockGaslessConfig(app, { assets: [USDT_MASTER] }); await mockGaslessEstimateOk(app); @@ -58,7 +58,7 @@ test.describe('Gasless Mint — additional states', () => { }); }); - test('Changing the fee asset in Confirm updates the selected value', async ({ app, minter, widget, wallet }) => { + test('Changing the fee asset in Confirm updates the selected value @allure.id=9847', async ({ app, minter, widget, wallet }) => { await gaslessMeta('Mint'); await mockGaslessConfig(app, { assets: [USDT_MASTER, NOT_MASTER] }); await mockGaslessEstimateOk(app); @@ -75,7 +75,7 @@ test.describe('Gasless Mint — additional states', () => { }); }); - test('Cancelling Mint settings without Save keeps the previous state', async ({ app, minter, widget, wallet }) => { + test('Cancelling Mint settings without Save keeps the previous state @allure.id=9917', async ({ app, minter, widget, wallet }) => { await gaslessMeta('Mint'); await mockGaslessConfig(app, { assets: [USDT_MASTER] }); await test.step('Connect wallet, generate card, open Mint settings', async () => { @@ -94,7 +94,7 @@ test.describe('Gasless Mint — additional states', () => { }); }); - test('Confirm is blocked while the gas-fee quote is loading', async ({ app, minter, widget, wallet }) => { + test('Confirm is blocked while the gas-fee quote is loading @allure.id=9913', async ({ app, minter, widget, wallet }) => { await gaslessMeta('Mint'); await mockGaslessConfig(app, { assets: [USDT_MASTER] }); await mockGaslessEstimateOk(app, { delayMs: 4000 }); // slow quote → confirm stays disabled while loading diff --git a/apps/appkit-minter/e2e/specs/gasless-mint.spec.ts b/apps/appkit-minter/e2e/specs/gasless-mint.spec.ts index a95094d65..8fb307eda 100644 --- a/apps/appkit-minter/e2e/specs/gasless-mint.spec.ts +++ b/apps/appkit-minter/e2e/specs/gasless-mint.spec.ts @@ -25,7 +25,7 @@ import { gaslessMeta } from '../qa/allure-meta'; // --- no wallet: gasless toggle is disabled with a reason --- base.describe('Mint settings (no wallet)', () => { - base('Without a SignMessage wallet the Gasless toggle is disabled and a reason is shown', async ({ page }) => { + base('Without a SignMessage wallet the Gasless toggle is disabled and a reason is shown @allure.id=9876', async ({ page }) => { await gaslessMeta('Mint'); const minter = new MinterPage(page); await base.step('Generate a card and open Mint settings', async () => { @@ -47,7 +47,7 @@ const test = testWithGaslessFixture({ }); test.describe('Gasless mint (two-tab wallet, mocked relayer)', () => { - test('Enabling Gasless shows Provider / Fee asset / Gas fee in the Confirm dialog', async ({ + test('Enabling Gasless shows Provider / Fee asset / Gas fee in the Confirm dialog @allure.id=9877', async ({ app, minter, widget, @@ -75,7 +75,7 @@ test.describe('Gasless mint (two-tab wallet, mocked relayer)', () => { }); }); - test('Rejecting in the wallet keeps the mint recoverable (error shown, does not hang)', async ({ + test('Rejecting in the wallet keeps the mint recoverable (error shown, does not hang) @allure.id=9862', async ({ app, minter, widget, diff --git a/apps/appkit-minter/e2e/specs/gasless-races.spec.ts b/apps/appkit-minter/e2e/specs/gasless-races.spec.ts index b0a6abdc2..3bec73a5a 100644 --- a/apps/appkit-minter/e2e/specs/gasless-races.spec.ts +++ b/apps/appkit-minter/e2e/specs/gasless-races.spec.ts @@ -26,7 +26,7 @@ const test = testWithGaslessFixture({ }); test.describe('Gasless transfer races (two-tab wallet, mocked relayer)', () => { - test('Double-send protection — one click yields exactly one relayer send', async ({ + test('Double-send protection — one click yields exactly one relayer send @allure.id=9874', async ({ app, minter, widget, @@ -62,7 +62,7 @@ test.describe('Gasless transfer races (two-tab wallet, mocked relayer)', () => { }); }); - test('Switching the fee asset re-requests the quote — the form follows the new selection', async ({ + test('Switching the fee asset re-requests the quote — the form follows the new selection @allure.id=9848', async ({ app, minter, widget, diff --git a/apps/appkit-minter/e2e/specs/gasless-security-extra.spec.ts b/apps/appkit-minter/e2e/specs/gasless-security-extra.spec.ts index 468ceffae..2e17b80ce 100644 --- a/apps/appkit-minter/e2e/specs/gasless-security-extra.spec.ts +++ b/apps/appkit-minter/e2e/specs/gasless-security-extra.spec.ts @@ -29,7 +29,7 @@ const test = testWithGaslessFixture({ }); test.describe('Gasless transfer — resilience to relayer errors', () => { - test('Transient send error (503) triggers a retry request', async ({ app, minter, widget, wallet }) => { + test('Transient send error (503) triggers a retry request @allure.id=9853', async ({ app, minter, widget, wallet }) => { await gaslessMeta('Relayer errors'); const capture: SendCapture = { requests: [] }; await mockGaslessConfig(app, { assets: [USDT_MASTER] }); diff --git a/apps/appkit-minter/e2e/specs/gasless-security.spec.ts b/apps/appkit-minter/e2e/specs/gasless-security.spec.ts index 198776fdf..22f24e86a 100644 --- a/apps/appkit-minter/e2e/specs/gasless-security.spec.ts +++ b/apps/appkit-minter/e2e/specs/gasless-security.spec.ts @@ -43,7 +43,7 @@ async function openGaslessTransfer(minter: MinterPage) { } test.describe('Relayer error handling (two-tab wallet, mocked relayer)', () => { - test('Hostile relayer error string renders safely (no XSS) at the quote error display location', async ({ + test('Hostile relayer error string renders safely (no XSS) at the quote error display location @allure.id=9858', async ({ app, minter, widget, @@ -74,7 +74,7 @@ test.describe('Relayer error handling (two-tab wallet, mocked relayer)', () => { }); }); - test('Estimate HTTP 400 — quote error shown, send stays blocked', async ({ app, minter, widget, wallet }) => { + test('Estimate HTTP 400 — quote error shown, send stays blocked @allure.id=9855', async ({ app, minter, widget, wallet }) => { await gaslessMeta('Relayer errors'); await test.step('Mock config and estimate error (HTTP 400)', async () => { await mockGaslessConfig(app, { assets: [USDT_MASTER] }); @@ -95,7 +95,7 @@ test.describe('Relayer error handling (two-tab wallet, mocked relayer)', () => { }); }); - test('Estimate returned non-JSON on 200 — handled as a typed error, no crash', async ({ + test('Estimate returned non-JSON on 200 — handled as a typed error, no crash @allure.id=9864', async ({ app, minter, widget, @@ -118,7 +118,7 @@ test.describe('Relayer error handling (two-tab wallet, mocked relayer)', () => { }); }); - test('Send HTTP 500 — error shown, form recovers (quote re-requested)', async ({ app, minter, widget, wallet }) => { + test('Send HTTP 500 — error shown, form recovers (quote re-requested) @allure.id=9875', async ({ app, minter, widget, wallet }) => { await gaslessMeta('Relayer errors'); const capture: SendCapture = { requests: [] }; await test.step('Mock config, a successful estimate and a send error (HTTP 500)', async () => { @@ -145,7 +145,7 @@ test.describe('Relayer error handling (two-tab wallet, mocked relayer)', () => { }); }); - test('WALLET_MISMATCH — a quote bound to a different wallet is rejected before signing', async ({ + test('WALLET_MISMATCH — a quote bound to a different wallet is rejected before signing @allure.id=9865', async ({ app, minter, widget, @@ -169,7 +169,7 @@ test.describe('Relayer error handling (two-tab wallet, mocked relayer)', () => { }); }); - test('QUOTE_EXPIRED — a stale quote is rejected before signing', async ({ app, minter, widget, wallet }) => { + test('QUOTE_EXPIRED — a stale quote is rejected before signing @allure.id=9860', async ({ app, minter, widget, wallet }) => { await gaslessMeta('Relayer errors'); await test.step('Mock config and an already expired estimate quote', async () => { await mockGaslessConfig(app, { assets: [USDT_MASTER] }); @@ -186,7 +186,7 @@ test.describe('Relayer error handling (two-tab wallet, mocked relayer)', () => { }); }); - test('Malformed `from` in the quote is rejected (error shown, send blocked)', async ({ + test('Malformed `from` in the quote is rejected (error shown, send blocked) @allure.id=9854', async ({ app, minter, widget, diff --git a/apps/appkit-minter/e2e/specs/gasless-transfer-form.spec.ts b/apps/appkit-minter/e2e/specs/gasless-transfer-form.spec.ts index eed9e3329..f0911c4c7 100644 --- a/apps/appkit-minter/e2e/specs/gasless-transfer-form.spec.ts +++ b/apps/appkit-minter/e2e/specs/gasless-transfer-form.spec.ts @@ -22,7 +22,7 @@ const test = testWithGaslessFixture({ }); test.describe('Gasless transfer form — states', () => { - test('Fee asset selector is hidden while Gasless is off', async ({ app, minter, widget, wallet }) => { + test('Fee asset selector is hidden while Gasless is off @allure.id=9870', async ({ app, minter, widget, wallet }) => { await gaslessMeta('Transfer'); await mockGaslessConfig(app, { assets: [USDT_MASTER] }); await test.step('Connect Wallet and open USDT transfer (Gasless off)', async () => { @@ -35,7 +35,7 @@ test.describe('Gasless transfer form — states', () => { }); }); - test('Fee asset selector appears when Gasless is enabled', async ({ app, minter, widget, wallet }) => { + test('Fee asset selector appears when Gasless is enabled @allure.id=9871', async ({ app, minter, widget, wallet }) => { await gaslessMeta('Transfer'); await mockGaslessConfig(app, { assets: [USDT_MASTER] }); await test.step('Connect Wallet, open USDT transfer and enable Gasless', async () => { @@ -48,7 +48,7 @@ test.describe('Gasless transfer form — states', () => { }); }); - test('With Gasless off the send button is the regular one — not "Send Gasless"', async ({ + test('With Gasless off the send button is the regular one — not "Send Gasless" @allure.id=9851', async ({ app, minter, widget, @@ -67,7 +67,7 @@ test.describe('Gasless transfer form — states', () => { }); }); - test('Recipient and amount are preserved when Gasless is enabled', async ({ app, minter, widget, wallet }) => { + test('Recipient and amount are preserved when Gasless is enabled @allure.id=9880', async ({ app, minter, widget, wallet }) => { await gaslessMeta('Transfer'); await mockGaslessConfig(app, { assets: [USDT_MASTER] }); await mockGaslessEstimateOk(app); @@ -83,7 +83,7 @@ test.describe('Gasless transfer form — states', () => { }); }); - test('Toggling Gasless back and forth does not break the form', async ({ app, minter, widget, wallet }) => { + test('Toggling Gasless back and forth does not break the form @allure.id=9861', async ({ app, minter, widget, wallet }) => { await gaslessMeta('Transfer'); await mockGaslessConfig(app, { assets: [USDT_MASTER] }); await mockGaslessEstimateOk(app); @@ -106,7 +106,7 @@ test.describe('Gasless transfer form — states', () => { }); }); - test('Closing via "Cancel" resets the form on reopen', async ({ app, minter, widget, wallet }) => { + test('Closing via "Cancel" resets the form on reopen @allure.id=9868', async ({ app, minter, widget, wallet }) => { await gaslessMeta('Transfer'); await mockGaslessConfig(app, { assets: [USDT_MASTER] }); await test.step('Connect Wallet, open USDT transfer and fill the recipient', async () => { @@ -127,7 +127,7 @@ test.describe('Gasless transfer form — states', () => { // includes USDT). That requires a pre-navigation route hook in the fixture — tracked // in the backlog; the post-goto mock here can't override the cached config. - test('No quote is requested while inputs are incomplete', async ({ app, minter, widget, wallet }) => { + test('No quote is requested while inputs are incomplete @allure.id=9879', async ({ app, minter, widget, wallet }) => { await gaslessMeta('Transfer'); const estimates: string[] = []; await mockGaslessConfig(app, { assets: [USDT_MASTER] }); @@ -145,7 +145,7 @@ test.describe('Gasless transfer form — states', () => { }); }); - test('Send shows "Quoting…" while the estimate is in flight, then settles', async ({ + test('Send shows "Quoting…" while the estimate is in flight, then settles @allure.id=9863', async ({ app, minter, widget, @@ -169,7 +169,7 @@ test.describe('Gasless transfer form — states', () => { }); }); - test('Changing the amount re-requests the quote', async ({ app, minter, widget, wallet }) => { + test('Changing the amount re-requests the quote @allure.id=9915', async ({ app, minter, widget, wallet }) => { await gaslessMeta('Transfer'); const estimates: string[] = []; await mockGaslessConfig(app, { assets: [USDT_MASTER] }); @@ -189,7 +189,7 @@ test.describe('Gasless transfer form — states', () => { }); }); - test('Changing the recipient re-requests the quote', async ({ app, minter, widget, wallet }) => { + test('Changing the recipient re-requests the quote @allure.id=9916', async ({ app, minter, widget, wallet }) => { await gaslessMeta('Transfer'); const estimates: string[] = []; await mockGaslessConfig(app, { assets: [USDT_MASTER] }); @@ -210,7 +210,7 @@ test.describe('Gasless transfer form — states', () => { }); }); - test('Closing via Escape resets the form on reopen', async ({ app, minter, widget, wallet }) => { + test('Closing via Escape resets the form on reopen @allure.id=9914', async ({ app, minter, widget, wallet }) => { await gaslessMeta('Transfer'); await mockGaslessConfig(app, { assets: [USDT_MASTER] }); await test.step('Connect Wallet, open USDT transfer and fill the fields', async () => { diff --git a/apps/appkit-minter/e2e/specs/gasless-transfer.spec.ts b/apps/appkit-minter/e2e/specs/gasless-transfer.spec.ts index c756e28dd..3f16a0856 100644 --- a/apps/appkit-minter/e2e/specs/gasless-transfer.spec.ts +++ b/apps/appkit-minter/e2e/specs/gasless-transfer.spec.ts @@ -25,7 +25,7 @@ const test = testWithGaslessFixture({ }); test.describe('Gasless jetton transfer', () => { - test('USDT is selected automatically, quote loads, Send Gasless button is enabled', async ({ + test('USDT is selected automatically, quote loads, Send Gasless button is enabled @allure.id=9878', async ({ app, minter, widget, @@ -47,7 +47,7 @@ test.describe('Gasless jetton transfer', () => { }); }); - test('Relayer request is built correctly (without a real send)', async ({ app, minter, widget, wallet }) => { + test('Relayer request is built correctly (without a real send) @allure.id=9850', async ({ app, minter, widget, wallet }) => { await gaslessMeta('Transfer'); const capture: SendCapture = { requests: [] }; await mockGaslessConfig(app, { assets: [USDT_MASTER] }); @@ -72,7 +72,7 @@ test.describe('Gasless jetton transfer', () => { }); }); - test('Sign rejection in the wallet — form recovers, error is shown', async ({ app, minter, widget, wallet }) => { + test('Sign rejection in the wallet — form recovers, error is shown @allure.id=9872', async ({ app, minter, widget, wallet }) => { await gaslessMeta('Transfer'); await mockGaslessConfig(app, { assets: [USDT_MASTER] }); await mockGaslessEstimateOk(app); From 0f69561f1aebcedddf62e9bc649b77cc47bbea1a Mon Sep 17 00:00:00 2001 From: Pavel Lesyuk Date: Sat, 11 Jul 2026 13:30:03 +0800 Subject: [PATCH 2/6] test(appkit-minter): fence real-send tests with a native Playwright tag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the `@real-send` marker from a substring in the describe title to a native Playwright tag: `test.describe('…', { tag: '@real-send' }, …)`. The CI gate already excludes these via `--grep-invert "@real-send"`, which matches native tags too, so selection is unchanged (still 39 on the gate, 2 selected by `--grep "@real-send"`). The fence no longer depends on the human-readable title keeping the literal string — editing a title can no longer silently unfence the mainnet-broadcasting tests. --- apps/appkit-minter/e2e/specs/gasless-mint.spec.ts | 2 +- apps/appkit-minter/e2e/specs/gasless-transfer.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/appkit-minter/e2e/specs/gasless-mint.spec.ts b/apps/appkit-minter/e2e/specs/gasless-mint.spec.ts index 8fb307eda..40cb2a7a2 100644 --- a/apps/appkit-minter/e2e/specs/gasless-mint.spec.ts +++ b/apps/appkit-minter/e2e/specs/gasless-mint.spec.ts @@ -107,7 +107,7 @@ test.describe('Gasless mint (two-tab wallet, mocked relayer)', () => { }); // --- real on-chain mint (mainnet) — monitor only, broadcasts funds --- -test.describe('Gasless mint (real send) @real-send', () => { +test.describe('Gasless mint (real send)', { tag: '@real-send' }, () => { test('Successful Gasless mint lands on-chain', async ({ app, minter, widget, wallet }) => { await gaslessMeta('Mint'); await test.step('Connect Wallet, generate a card and open Confirm with Gasless', async () => { diff --git a/apps/appkit-minter/e2e/specs/gasless-transfer.spec.ts b/apps/appkit-minter/e2e/specs/gasless-transfer.spec.ts index 3f16a0856..5e5be1835 100644 --- a/apps/appkit-minter/e2e/specs/gasless-transfer.spec.ts +++ b/apps/appkit-minter/e2e/specs/gasless-transfer.spec.ts @@ -98,7 +98,7 @@ test.describe('Gasless jetton transfer', () => { }); // --- real on-chain send (mainnet) — manual run only, broadcasts funds --- -test.describe('Gasless jetton transfer (real send) @real-send', () => { +test.describe('Gasless jetton transfer (real send)', { tag: '@real-send' }, () => { test('Successful gasless send goes through on-chain', async ({ app, minter, widget, wallet }) => { await gaslessMeta('Transfer'); await test.step('Connect wallet and fill the gasless transfer', async () => { From 6cd94ce236a857323dd569b8323db6b86abb8485 Mon Sep 17 00:00:00 2001 From: Pavel Lesyuk Date: Sat, 11 Jul 2026 14:05:15 +0800 Subject: [PATCH 3/6] test(appkit-minter): mock account/jetton balances so the gate needs no funded wallet The wallet-based specs read the connected wallet's TON balance and jetton list from a live indexer (Toncenter by default, TonAPI when configured), so the gate depended on a real, funded wallet actually holding USDT. Add route mocks for the account-state and jetton-balance endpoints of both backends, returning a healthy GRAM balance and a spendable USDT jetton, so the transfer/mint flows are deterministic and need no on-chain funds. The gasless relayer (/v2/gasless/*) was already mocked. Installed in the two-tab fixture for every spec except `@real-send`, which broadcasts on-chain and must read the wallet's real balances. The full mocked gate (39 specs) stays green; `@real-send` selection is unchanged. --- .../e2e/fixtures/gaslessFixture.ts | 7 + apps/appkit-minter/e2e/mocks/tonBalances.ts | 137 ++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 apps/appkit-minter/e2e/mocks/tonBalances.ts diff --git a/apps/appkit-minter/e2e/fixtures/gaslessFixture.ts b/apps/appkit-minter/e2e/fixtures/gaslessFixture.ts index d9050e7ca..45274855f 100644 --- a/apps/appkit-minter/e2e/fixtures/gaslessFixture.ts +++ b/apps/appkit-minter/e2e/fixtures/gaslessFixture.ts @@ -12,6 +12,7 @@ import type { ConfigFixture, TestFixture } from '../qa'; import { launchPersistentContext, TonConnectWidget, testWith } from '../qa'; import { captureConsole, attachConsoleOnFailure } from '../qa/diagnostics'; import { MinterPage } from '../pages/MinterPage'; +import { mockTonBalances } from '../mocks/tonBalances'; import { DemoWallet } from '../wallet'; const DEFAULT_WALLET_SOURCE = process.env.E2E_WALLET_SOURCE ?? 'http://localhost:5173/'; @@ -40,6 +41,12 @@ export function gaslessFixture(config: ConfigFixture, slowMo = 0) { }, app: async ({ context }, use, testInfo) => { const app = await context.newPage(); + // Hermetic account/jetton balances so the mocked gate needs no funded + // wallet. `@real-send` specs broadcast on-chain and must read the wallet's + // real balances, so they opt out. + if (!testInfo.tags.includes('@real-send')) { + await mockTonBalances(app); + } const logs = captureConsole(app); await app.goto(config.appUrl, { waitUntil: 'load' }); await use(app); diff --git a/apps/appkit-minter/e2e/mocks/tonBalances.ts b/apps/appkit-minter/e2e/mocks/tonBalances.ts new file mode 100644 index 000000000..4a7f901c3 --- /dev/null +++ b/apps/appkit-minter/e2e/mocks/tonBalances.ts @@ -0,0 +1,137 @@ +/** + * Copyright (c) TonTech. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import type { Page, Route } from '@playwright/test'; + +/** + * Route mocks for the account-state + jetton-balance reads the minter issues for + * the connected wallet. Without these the gate depends on a live, *funded* wallet + * (real USDT) and the live indexer to populate the transfer asset list — the same + * class of external dependency the relayer mocks remove for `/v2/gasless/*`. + * + * The minter picks its indexer backend from `VITE_TON_API_PROVIDER` (default + * `toncenter`; `tonapi` when set), so both contracts are mocked and the gate is + * hermetic either way: + * - Toncenter: `GET /api/v3/addressInformation`, `GET /api/v3/jetton/wallets` + * - TonAPI: `GET /v2/blockchain/accounts/{a}`, `GET /v2/accounts/{a}/jettons` + * + * Not installed for `@real-send` specs — those broadcast on-chain and must read the + * wallet's real balances (see the fixture). + */ + +/** USDT (Tether) mainnet master. The Toncenter jetton mapper keys metadata by the + * raw master and the minter's asset-row testid uses the friendly master, so both + * forms are pinned here and must resolve to the same address. */ +const USDT_MASTER_FRIENDLY = 'EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs'; +const USDT_MASTER_RAW = '0:B113A994B5024A16719F69139328EB759596C38A25F59028B146FECDC3621DFE'; +/** A valid, parseable stand-in jetton-wallet address. Never broadcast (the relayer + * send is mocked); used only so the address mappers can normalize it. */ +const JETTON_WALLET_RAW = '0:7C873E096984BCEB6F2169BE1C99FD6614C1C5C86B39E2A8A66C764648DC81F9'; + +const IMAGE = 'https://tether.to/images/logoCircle.png'; + +const json = (route: Route, body: unknown) => + route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(body) }); + +export interface BalancesOpts { + /** Raw TON/GRAM balance in nanotons. Default 5 GRAM — comfortably above any gas floor. */ + tonBalanceNano?: string; + /** Raw USDT balance (6 decimals). Default 5 USDT. `'0'` ⇒ no spendable USDT. */ + usdtBalanceRaw?: string; +} + +/** + * Mock account state + jetton balances so a connected wallet always shows a funded + * TON balance and a spendable USDT jetton, regardless of the wallet's real holdings. + */ +export async function mockTonBalances(page: Page, opts: BalancesOpts = {}): Promise { + const ton = opts.tonBalanceNano ?? '5000000000'; + const usdt = opts.usdtBalanceRaw ?? '5000000'; + + // --- Toncenter (default backend) --- + await page.route(/\/api\/v3\/addressInformation/, (route) => + json(route, { + balance: ton, + status: 'active', + code: null, + data: null, + // Non-null tx id: the mapper parses these, so mirror the wire shape + // (base64 hash + numeric-string lt) rather than nulls. + last_transaction_hash: 'uToBwqQdeCqUK79AwRofY+YhWt/CjPRxm0UN2bxrJeA=', + last_transaction_lt: '1', + frozen_hash: null, + extra_currencies: [], + }), + ); + await page.route(/\/api\/v3\/jetton\/wallets/, (route) => + json(route, { + jetton_wallets: [ + { + address: JETTON_WALLET_RAW, + balance: usdt, + owner: JETTON_WALLET_RAW, + jetton: USDT_MASTER_RAW, + last_transaction_lt: '0', + code_hash: '', + data_hash: '', + }, + ], + address_book: {}, + // Toncenter carries jetton name/symbol/decimals in `metadata`, not on the + // wallet row — the mapper reads them from here, so it must be present. + metadata: { + [USDT_MASTER_RAW]: { + is_indexed: true, + token_info: [ + { + type: 'jetton_masters', + name: 'Tether USD', + symbol: 'USDT', + description: '', + image: IMAGE, + extra: { decimals: '6' }, + }, + ], + }, + }, + }), + ); + + // --- TonAPI (only reached when VITE_TON_API_PROVIDER=tonapi) --- + await page.route(/\/v2\/blockchain\/accounts\/[^/]+$/, (route) => + json(route, { + address: USDT_MASTER_FRIENDLY, + balance: Number(ton), + status: 'active', + code: null, + data: null, + last_transaction_lt: 0, + last_transaction_hash: null, + frozen_hash: null, + }), + ); + await page.route(/\/v2\/accounts\/[^/]+\/jettons/, (route) => + json(route, { + balances: [ + { + balance: usdt, + wallet_address: { address: JETTON_WALLET_RAW, is_scam: false, is_wallet: false }, + jetton: { + address: USDT_MASTER_FRIENDLY, + name: 'Tether USD', + symbol: 'USDT', + decimals: 6, + image: IMAGE, + verification: 'whitelist', + score: 100, + }, + }, + ], + }), + ); +} From 08279b9453d5bde3965e9d72d022326e05b5f8fa Mon Sep 17 00:00:00 2001 From: Pavel Lesyuk Date: Sat, 11 Jul 2026 14:12:24 +0800 Subject: [PATCH 4/6] test(appkit-minter): drop dead code and refresh the e2e README - Remove a commented-out constant, a redundant readonly re-assignment in the WalletApp constructor, and an unreachable `?? slowMo` fallback (parseInt never returns null; also guards against a NaN leak from a malformed E2E_SLOW_MO). - README: document the now-native `@allure.id=` case binding (replacing the stale `@allureId(N)` / `fullName` procedure) and note that account/jetton balances are mocked, so the gate needs no funded wallet. --- apps/appkit-minter/e2e/README.md | 24 +++++++++++++-------- apps/appkit-minter/e2e/qa/WalletApp.ts | 6 +----- apps/appkit-minter/e2e/qa/test.ts | 2 +- apps/appkit-minter/e2e/wallet/DemoWallet.ts | 2 -- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/apps/appkit-minter/e2e/README.md b/apps/appkit-minter/e2e/README.md index 6e1ff2cbc..482aadc55 100644 --- a/apps/appkit-minter/e2e/README.md +++ b/apps/appkit-minter/e2e/README.md @@ -140,11 +140,15 @@ they exist for manual/local real-send verification (`pnpm e2e --grep "@real-send A scheduled-monitor workflow that would run them against the live relayer was considered out of scope here (kept as a QA-side snippet, not in this repo). -Results upload to Allure TestOps (project 368). Cases are **auto-created/updated by -`fullName`** (path:line) on upload — no manual mapping. To harden the link so a -rename/move can't orphan a case, pin the AllureID **after the first run** with a -code call `await allureId()` (from `allure-js-commons`) — the title-only -`@allureId(N)` tag is decorative in allure-playwright v3 and does not link. +Results upload to Allure TestOps (project 368). Every test pins a stable case id +with a native `@allure.id=` token in its title — the adapter parses it, links +the result to that exact case, and strips the token from the display name +(`await allure.id("")` in code is equivalent). Because the id is a DB-level +identifier, a result stays linked to the same case across renames, file moves, +and describe/title edits — unlike the default path + describe + title match, +which orphans a case on any such change. A new test bootstraps by uploading once +(the first run auto-creates the case and TestOps assigns an id); pin that id back +in the title before any rename. ## Notes @@ -155,7 +159,9 @@ code call `await allureId()` (from `allure-js-commons`) — the title-only - The appkit-minter Assets list is **empty without a connected wallet**, so the transfer modal is only reachable in two-tab specs; truly wallet-less coverage is limited to page-load / mocked-config / mint-settings-disabled checks. -- `LowBalanceModal` (regular mint, insufficient TON) is **not yet automated**: it - needs either a real 0-TON wallet or a mocked account balance. Since CI must not - rely on a specially-funded wallet, the plan is to mock the balance endpoint - rather than add a second mnemonic — see the QA test plan. +- The account and jetton balances are mocked (`mocks/tonBalances.ts`, installed by + the two-tab fixture for every non-`@real-send` spec), so the gate needs no + specially-funded wallet and reads no live indexer for balances. +- `LowBalanceModal` (regular mint, insufficient TON) is **not yet automated**, but + the balance mock makes it reachable — a spec can drive it by mocking a low TON + balance instead of needing a real 0-TON wallet. diff --git a/apps/appkit-minter/e2e/qa/WalletApp.ts b/apps/appkit-minter/e2e/qa/WalletApp.ts index 472644d18..0425cfe9c 100644 --- a/apps/appkit-minter/e2e/qa/WalletApp.ts +++ b/apps/appkit-minter/e2e/qa/WalletApp.ts @@ -29,11 +29,7 @@ export abstract class WalletApp { readonly context: BrowserContext, readonly source: string, readonly password: string = TEST_PASSWORD, - ) { - this.context = context; - this.source = source; - this.password = password; - } + ) {} get isExtension(): boolean { return isExtensionWalletSource(this.source); diff --git a/apps/appkit-minter/e2e/qa/test.ts b/apps/appkit-minter/e2e/qa/test.ts index 1fe42e28c..2cd701596 100644 --- a/apps/appkit-minter/e2e/qa/test.ts +++ b/apps/appkit-minter/e2e/qa/test.ts @@ -35,7 +35,7 @@ export async function launchPersistentContext(extensionPath: string, slowMo = 0) args.push('--headless=new'); } - slowMo = isCi ? 0 : (parseInt(process.env.E2E_SLOW_MO || '0') ?? slowMo); + slowMo = isCi ? 0 : parseInt(process.env.E2E_SLOW_MO || '0', 10) || slowMo; const browserContext = await chromium.launchPersistentContext('', { args, headless: false, diff --git a/apps/appkit-minter/e2e/wallet/DemoWallet.ts b/apps/appkit-minter/e2e/wallet/DemoWallet.ts index 1b5929ee7..7d428db2f 100644 --- a/apps/appkit-minter/e2e/wallet/DemoWallet.ts +++ b/apps/appkit-minter/e2e/wallet/DemoWallet.ts @@ -8,8 +8,6 @@ import { WalletApp } from '../qa'; -// const timeout = 20_000; - function delay(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } From 100fa89a1f12d868f00a5f048df795f8c904a11e Mon Sep 17 00:00:00 2001 From: Pavel Lesyuk Date: Sat, 11 Jul 2026 14:24:57 +0800 Subject: [PATCH 5/6] docs(appkit-minter): describe test behavior, drop process asides from shared code Reword shared README/comment prose to describe what the suite does, dropping internal process asides: the "we deliberately don't hand-author in TestOps" case table, the out-of-scope monitor-workflow note, a "constraint" attribution in the relayer mock, "monitor only" markers on the real-send mint, and a stale "matched by fullName" claim. Behavior and technical intent are unchanged. --- apps/appkit-minter/e2e/README.md | 27 ++++--------------- .../appkit-minter/e2e/mocks/gaslessRelayer.ts | 3 +-- apps/appkit-minter/e2e/qa/allure-meta.ts | 7 +++-- .../e2e/specs/gasless-mint.spec.ts | 4 +-- 4 files changed, 11 insertions(+), 30 deletions(-) diff --git a/apps/appkit-minter/e2e/README.md b/apps/appkit-minter/e2e/README.md index 482aadc55..dd9914304 100644 --- a/apps/appkit-minter/e2e/README.md +++ b/apps/appkit-minter/e2e/README.md @@ -48,26 +48,11 @@ apps/appkit-minter/ ## Test design: steps are defined in code -Each test name and its steps are authored **in code** (`test.step('…')` + the -`gaslessMeta()` labels), and the TestOps test case is auto-created/updated from the -uploaded run (matched by `fullName`). We deliberately do **not** hand-author the -case scenarios in TestOps. - -Why code-defined (vs. describing steps directly in TestOps): - -| | Code-defined steps (chosen) | TestOps-authored steps | -| --------------------------- | ------------------------------------------------------------------------ | ------------------------------------ | -| Source of truth | the spec — one place | the case — diverges from code | -| Drift | impossible (steps come from the run) | constant (manual sync) | -| New test | appears in TestOps after one run | must be hand-created first | -| Matches repo | yes — demo-wallet/walletkit do this (`allureId`/`suite`/`label` in code) | no | -| Cost for ~60 cases | zero extra | ~60 manual cases to write + maintain | -| Rich "expected result" rows | weaker (body steps) | richer | - -The only real downside — slightly less rich step formatting — is outweighed by -zero drift across a fast-moving feature. `detail: false` in the Allure reporter -keeps the TestOps scenario clean (only our `test.step` labels; no Playwright -hook/fixture noise). +Each test's name and steps are authored in code (`test.step('…')`, with the +`feature` / `sub-suite` labels from `gaslessMeta()`), so the spec is the single +source of truth and the Allure report mirrors it with no manual step-authoring and +no drift. `detail: false` in the reporter keeps the report to just those +`test.step` labels — no Playwright hook/fixture noise. ## Testing approaches @@ -137,8 +122,6 @@ key + non-empty signed BoC — see `gasless-transfer.spec.ts` and `gasless-races The `@real-send` specs (in `gasless-transfer.spec.ts` and `gasless-mint.spec.ts`) **do broadcast** real mainnet transactions and are therefore **not run in CI** — they exist for manual/local real-send verification (`pnpm e2e --grep "@real-send"`). -A scheduled-monitor workflow that would run them against the live relayer was -considered out of scope here (kept as a QA-side snippet, not in this repo). Results upload to Allure TestOps (project 368). Every test pins a stable case id with a native `@allure.id=` token in its title — the adapter parses it, links diff --git a/apps/appkit-minter/e2e/mocks/gaslessRelayer.ts b/apps/appkit-minter/e2e/mocks/gaslessRelayer.ts index d4ea9d359..04b9b9f35 100644 --- a/apps/appkit-minter/e2e/mocks/gaslessRelayer.ts +++ b/apps/appkit-minter/e2e/mocks/gaslessRelayer.ts @@ -15,8 +15,7 @@ import type { Page, Route } from '@playwright/test'; * them — the wallet tab is never touched. * * The point of mocking `/send` is twofold: - * 1. nothing is broadcast on-chain, so the test spends no funds (the lead's CI - * constraint), and + * 1. nothing is broadcast on-chain, so the gate spends no funds, and * 2. the captured request body lets a test assert the minter formed the gasless * message correctly — i.e. tx-formation is verified without a real send. * diff --git a/apps/appkit-minter/e2e/qa/allure-meta.ts b/apps/appkit-minter/e2e/qa/allure-meta.ts index e2946af53..0b98d0e3a 100644 --- a/apps/appkit-minter/e2e/qa/allure-meta.ts +++ b/apps/appkit-minter/e2e/qa/allure-meta.ts @@ -9,10 +9,9 @@ import { feature, label } from 'allure-js-commons'; /** - * Attach grouping metadata to the current test, mirroring the demo-wallet e2e - * convention (`feature` + `sub-suite`). The top-level `Suite` custom field is left - * to allure-playwright's file-path default, so the gasless specs sit in the TestOps - * tree the same way the existing minter specs do. + * Attach grouping metadata to the current test (`feature` + `sub-suite` labels), + * matching the demo-wallet e2e convention. The top-level `Suite` custom field is + * left to allure-playwright's file-path default. * * @param subSuite area name, e.g. "Transfer", "Mint", "Relayer errors". */ diff --git a/apps/appkit-minter/e2e/specs/gasless-mint.spec.ts b/apps/appkit-minter/e2e/specs/gasless-mint.spec.ts index 40cb2a7a2..2873f1d1a 100644 --- a/apps/appkit-minter/e2e/specs/gasless-mint.spec.ts +++ b/apps/appkit-minter/e2e/specs/gasless-mint.spec.ts @@ -20,7 +20,7 @@ import { gaslessMeta } from '../qa/allure-meta'; * - two-tab wallet + mocked relayer: enabling gasless surfaces the Provider / * Fee asset / Gas fee rows in the Confirm dialog; rejecting the signature * recovers cleanly. Nothing is broadcast. - * - @real-send (monitor only): a real gasless mint lands on mainnet. + * - @real-send: a real gasless mint is broadcast to mainnet (manual run only). */ // --- no wallet: gasless toggle is disabled with a reason --- @@ -106,7 +106,7 @@ test.describe('Gasless mint (two-tab wallet, mocked relayer)', () => { }); }); -// --- real on-chain mint (mainnet) — monitor only, broadcasts funds --- +// --- real on-chain mint (mainnet) — manual run only, broadcasts funds --- test.describe('Gasless mint (real send)', { tag: '@real-send' }, () => { test('Successful Gasless mint lands on-chain', async ({ app, minter, widget, wallet }) => { await gaslessMeta('Mint'); From 7962703bf6363ac070fda401e58f30d20827ccb8 Mon Sep 17 00:00:00 2001 From: Pavel Lesyuk Date: Sat, 11 Jul 2026 15:03:02 +0800 Subject: [PATCH 6/6] style(appkit-minter): reflow e2e specs to satisfy prettier after the id pins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Appending the `@allure.id=` tokens pushed several `test(...)` signatures over the print width, so prettier wants the destructured fixture params wrapped onto their own lines. Pure formatting (eslint --fix) — no titles, ids, or behavior changed. --- .../e2e/specs/gasless-availability.spec.ts | 6 +++- .../e2e/specs/gasless-mint-extra.spec.ts | 28 ++++++++++++--- .../e2e/specs/gasless-mint.spec.ts | 31 ++++++++-------- .../e2e/specs/gasless-security-extra.spec.ts | 7 +++- .../e2e/specs/gasless-security.spec.ts | 21 +++++++++-- .../e2e/specs/gasless-transfer-form.spec.ts | 35 ++++++++++++++++--- .../e2e/specs/gasless-transfer.spec.ts | 14 ++++++-- 7 files changed, 112 insertions(+), 30 deletions(-) diff --git a/apps/appkit-minter/e2e/specs/gasless-availability.spec.ts b/apps/appkit-minter/e2e/specs/gasless-availability.spec.ts index 46362947c..a968412a7 100644 --- a/apps/appkit-minter/e2e/specs/gasless-availability.spec.ts +++ b/apps/appkit-minter/e2e/specs/gasless-availability.spec.ts @@ -57,7 +57,11 @@ test.describe('Availability (two-tab wallet)', () => { }); }); - test('Jetton transfer — Gasless checkbox enabled for a SignMessage wallet @allure.id=9869', async ({ minter, widget, wallet }) => { + test('Jetton transfer — Gasless checkbox enabled for a SignMessage wallet @allure.id=9869', async ({ + minter, + widget, + wallet, + }) => { await gaslessMeta('Availability'); await test.step('Connect wallet and open USDT transfer', async () => { await connectWallet({ widget, wallet }); diff --git a/apps/appkit-minter/e2e/specs/gasless-mint-extra.spec.ts b/apps/appkit-minter/e2e/specs/gasless-mint-extra.spec.ts index 49c15bc8b..42b5e2bf3 100644 --- a/apps/appkit-minter/e2e/specs/gasless-mint-extra.spec.ts +++ b/apps/appkit-minter/e2e/specs/gasless-mint-extra.spec.ts @@ -43,7 +43,12 @@ test.describe('Gasless Mint — additional states', () => { }); }); - test('Gasless setting persists when mint settings are reopened @allure.id=9866', async ({ app, minter, widget, wallet }) => { + test('Gasless setting persists when mint settings are reopened @allure.id=9866', async ({ + app, + minter, + widget, + wallet, + }) => { await gaslessMeta('Mint'); await mockGaslessConfig(app, { assets: [USDT_MASTER] }); await mockGaslessEstimateOk(app); @@ -58,7 +63,12 @@ test.describe('Gasless Mint — additional states', () => { }); }); - test('Changing the fee asset in Confirm updates the selected value @allure.id=9847', async ({ app, minter, widget, wallet }) => { + test('Changing the fee asset in Confirm updates the selected value @allure.id=9847', async ({ + app, + minter, + widget, + wallet, + }) => { await gaslessMeta('Mint'); await mockGaslessConfig(app, { assets: [USDT_MASTER, NOT_MASTER] }); await mockGaslessEstimateOk(app); @@ -75,7 +85,12 @@ test.describe('Gasless Mint — additional states', () => { }); }); - test('Cancelling Mint settings without Save keeps the previous state @allure.id=9917', async ({ app, minter, widget, wallet }) => { + test('Cancelling Mint settings without Save keeps the previous state @allure.id=9917', async ({ + app, + minter, + widget, + wallet, + }) => { await gaslessMeta('Mint'); await mockGaslessConfig(app, { assets: [USDT_MASTER] }); await test.step('Connect wallet, generate card, open Mint settings', async () => { @@ -94,7 +109,12 @@ test.describe('Gasless Mint — additional states', () => { }); }); - test('Confirm is blocked while the gas-fee quote is loading @allure.id=9913', async ({ app, minter, widget, wallet }) => { + test('Confirm is blocked while the gas-fee quote is loading @allure.id=9913', async ({ + app, + minter, + widget, + wallet, + }) => { await gaslessMeta('Mint'); await mockGaslessConfig(app, { assets: [USDT_MASTER] }); await mockGaslessEstimateOk(app, { delayMs: 4000 }); // slow quote → confirm stays disabled while loading diff --git a/apps/appkit-minter/e2e/specs/gasless-mint.spec.ts b/apps/appkit-minter/e2e/specs/gasless-mint.spec.ts index 2873f1d1a..f8d721091 100644 --- a/apps/appkit-minter/e2e/specs/gasless-mint.spec.ts +++ b/apps/appkit-minter/e2e/specs/gasless-mint.spec.ts @@ -25,20 +25,23 @@ import { gaslessMeta } from '../qa/allure-meta'; // --- no wallet: gasless toggle is disabled with a reason --- base.describe('Mint settings (no wallet)', () => { - base('Without a SignMessage wallet the Gasless toggle is disabled and a reason is shown @allure.id=9876', async ({ page }) => { - await gaslessMeta('Mint'); - const minter = new MinterPage(page); - await base.step('Generate a card and open Mint settings', async () => { - await page.goto('/'); - await minter.generateCard(); - await minter.openMintSettings(); - }); - await base.step('Gasless toggle is disabled and the reason hint is visible', async () => { - await expect(page.getByText(/^Gasless$/).first()).toBeVisible(); - await expect(minter.gaslessSwitch).toBeDisabled(); - await expect(minter.noSignMessageHint).toBeVisible(); - }); - }); + base( + 'Without a SignMessage wallet the Gasless toggle is disabled and a reason is shown @allure.id=9876', + async ({ page }) => { + await gaslessMeta('Mint'); + const minter = new MinterPage(page); + await base.step('Generate a card and open Mint settings', async () => { + await page.goto('/'); + await minter.generateCard(); + await minter.openMintSettings(); + }); + await base.step('Gasless toggle is disabled and the reason hint is visible', async () => { + await expect(page.getByText(/^Gasless$/).first()).toBeVisible(); + await expect(minter.gaslessSwitch).toBeDisabled(); + await expect(minter.noSignMessageHint).toBeVisible(); + }); + }, + ); }); // --- two-tab wallet + mocked relayer --- diff --git a/apps/appkit-minter/e2e/specs/gasless-security-extra.spec.ts b/apps/appkit-minter/e2e/specs/gasless-security-extra.spec.ts index 2e17b80ce..0c1cb8042 100644 --- a/apps/appkit-minter/e2e/specs/gasless-security-extra.spec.ts +++ b/apps/appkit-minter/e2e/specs/gasless-security-extra.spec.ts @@ -29,7 +29,12 @@ const test = testWithGaslessFixture({ }); test.describe('Gasless transfer — resilience to relayer errors', () => { - test('Transient send error (503) triggers a retry request @allure.id=9853', async ({ app, minter, widget, wallet }) => { + test('Transient send error (503) triggers a retry request @allure.id=9853', async ({ + app, + minter, + widget, + wallet, + }) => { await gaslessMeta('Relayer errors'); const capture: SendCapture = { requests: [] }; await mockGaslessConfig(app, { assets: [USDT_MASTER] }); diff --git a/apps/appkit-minter/e2e/specs/gasless-security.spec.ts b/apps/appkit-minter/e2e/specs/gasless-security.spec.ts index 22f24e86a..3f963f040 100644 --- a/apps/appkit-minter/e2e/specs/gasless-security.spec.ts +++ b/apps/appkit-minter/e2e/specs/gasless-security.spec.ts @@ -74,7 +74,12 @@ test.describe('Relayer error handling (two-tab wallet, mocked relayer)', () => { }); }); - test('Estimate HTTP 400 — quote error shown, send stays blocked @allure.id=9855', async ({ app, minter, widget, wallet }) => { + test('Estimate HTTP 400 — quote error shown, send stays blocked @allure.id=9855', async ({ + app, + minter, + widget, + wallet, + }) => { await gaslessMeta('Relayer errors'); await test.step('Mock config and estimate error (HTTP 400)', async () => { await mockGaslessConfig(app, { assets: [USDT_MASTER] }); @@ -118,7 +123,12 @@ test.describe('Relayer error handling (two-tab wallet, mocked relayer)', () => { }); }); - test('Send HTTP 500 — error shown, form recovers (quote re-requested) @allure.id=9875', async ({ app, minter, widget, wallet }) => { + test('Send HTTP 500 — error shown, form recovers (quote re-requested) @allure.id=9875', async ({ + app, + minter, + widget, + wallet, + }) => { await gaslessMeta('Relayer errors'); const capture: SendCapture = { requests: [] }; await test.step('Mock config, a successful estimate and a send error (HTTP 500)', async () => { @@ -169,7 +179,12 @@ test.describe('Relayer error handling (two-tab wallet, mocked relayer)', () => { }); }); - test('QUOTE_EXPIRED — a stale quote is rejected before signing @allure.id=9860', async ({ app, minter, widget, wallet }) => { + test('QUOTE_EXPIRED — a stale quote is rejected before signing @allure.id=9860', async ({ + app, + minter, + widget, + wallet, + }) => { await gaslessMeta('Relayer errors'); await test.step('Mock config and an already expired estimate quote', async () => { await mockGaslessConfig(app, { assets: [USDT_MASTER] }); diff --git a/apps/appkit-minter/e2e/specs/gasless-transfer-form.spec.ts b/apps/appkit-minter/e2e/specs/gasless-transfer-form.spec.ts index f0911c4c7..a540fec7e 100644 --- a/apps/appkit-minter/e2e/specs/gasless-transfer-form.spec.ts +++ b/apps/appkit-minter/e2e/specs/gasless-transfer-form.spec.ts @@ -22,7 +22,12 @@ const test = testWithGaslessFixture({ }); test.describe('Gasless transfer form — states', () => { - test('Fee asset selector is hidden while Gasless is off @allure.id=9870', async ({ app, minter, widget, wallet }) => { + test('Fee asset selector is hidden while Gasless is off @allure.id=9870', async ({ + app, + minter, + widget, + wallet, + }) => { await gaslessMeta('Transfer'); await mockGaslessConfig(app, { assets: [USDT_MASTER] }); await test.step('Connect Wallet and open USDT transfer (Gasless off)', async () => { @@ -35,7 +40,12 @@ test.describe('Gasless transfer form — states', () => { }); }); - test('Fee asset selector appears when Gasless is enabled @allure.id=9871', async ({ app, minter, widget, wallet }) => { + test('Fee asset selector appears when Gasless is enabled @allure.id=9871', async ({ + app, + minter, + widget, + wallet, + }) => { await gaslessMeta('Transfer'); await mockGaslessConfig(app, { assets: [USDT_MASTER] }); await test.step('Connect Wallet, open USDT transfer and enable Gasless', async () => { @@ -67,7 +77,12 @@ test.describe('Gasless transfer form — states', () => { }); }); - test('Recipient and amount are preserved when Gasless is enabled @allure.id=9880', async ({ app, minter, widget, wallet }) => { + test('Recipient and amount are preserved when Gasless is enabled @allure.id=9880', async ({ + app, + minter, + widget, + wallet, + }) => { await gaslessMeta('Transfer'); await mockGaslessConfig(app, { assets: [USDT_MASTER] }); await mockGaslessEstimateOk(app); @@ -83,7 +98,12 @@ test.describe('Gasless transfer form — states', () => { }); }); - test('Toggling Gasless back and forth does not break the form @allure.id=9861', async ({ app, minter, widget, wallet }) => { + test('Toggling Gasless back and forth does not break the form @allure.id=9861', async ({ + app, + minter, + widget, + wallet, + }) => { await gaslessMeta('Transfer'); await mockGaslessConfig(app, { assets: [USDT_MASTER] }); await mockGaslessEstimateOk(app); @@ -127,7 +147,12 @@ test.describe('Gasless transfer form — states', () => { // includes USDT). That requires a pre-navigation route hook in the fixture — tracked // in the backlog; the post-goto mock here can't override the cached config. - test('No quote is requested while inputs are incomplete @allure.id=9879', async ({ app, minter, widget, wallet }) => { + test('No quote is requested while inputs are incomplete @allure.id=9879', async ({ + app, + minter, + widget, + wallet, + }) => { await gaslessMeta('Transfer'); const estimates: string[] = []; await mockGaslessConfig(app, { assets: [USDT_MASTER] }); diff --git a/apps/appkit-minter/e2e/specs/gasless-transfer.spec.ts b/apps/appkit-minter/e2e/specs/gasless-transfer.spec.ts index 5e5be1835..1b5470851 100644 --- a/apps/appkit-minter/e2e/specs/gasless-transfer.spec.ts +++ b/apps/appkit-minter/e2e/specs/gasless-transfer.spec.ts @@ -47,7 +47,12 @@ test.describe('Gasless jetton transfer', () => { }); }); - test('Relayer request is built correctly (without a real send) @allure.id=9850', async ({ app, minter, widget, wallet }) => { + test('Relayer request is built correctly (without a real send) @allure.id=9850', async ({ + app, + minter, + widget, + wallet, + }) => { await gaslessMeta('Transfer'); const capture: SendCapture = { requests: [] }; await mockGaslessConfig(app, { assets: [USDT_MASTER] }); @@ -72,7 +77,12 @@ test.describe('Gasless jetton transfer', () => { }); }); - test('Sign rejection in the wallet — form recovers, error is shown @allure.id=9872', async ({ app, minter, widget, wallet }) => { + test('Sign rejection in the wallet — form recovers, error is shown @allure.id=9872', async ({ + app, + minter, + widget, + wallet, + }) => { await gaslessMeta('Transfer'); await mockGaslessConfig(app, { assets: [USDT_MASTER] }); await mockGaslessEstimateOk(app);