Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
16ec2bc
add initial version of linearize send flow
leofelix077 May 11, 2026
b2246b5
fix input handling for decimal amounts
leofelix077 May 11, 2026
d151993
adjust layouting for send flow
leofelix077 May 11, 2026
119c0e8
rewrite sendpayment tests
leofelix077 May 11, 2026
309e590
fix UI according to figma design
leofelix077 May 11, 2026
e64eb2d
bring Ui in line with mobile
leofelix077 May 12, 2026
768acf9
fix amount formatting on base usd value
leofelix077 May 12, 2026
ba7d7cf
adjust navigation flows for send and collectible flows and add e2e tests
leofelix077 May 13, 2026
03e09be
keep recents on search and add debounce
leofelix077 May 13, 2026
4326a2c
preserve amounts between swap of fiat and token
leofelix077 May 13, 2026
2fec35e
update input validation and navigation on changes
leofelix077 May 13, 2026
87d6271
Merge branch 'master' into feature/linearize-send-flow
leofelix077 May 13, 2026
09fec92
fix debounce, unused strings and navigation flow for suggestions
leofelix077 May 13, 2026
cb9fc71
Merge branch 'feature/linearize-send-flow' of https://github.com/stel…
leofelix077 May 13, 2026
ae763e9
add tests for amount preservation toggle between fiat and token
leofelix077 May 13, 2026
bce720b
add tests for amount preservation toggle between fiat and token
leofelix077 May 13, 2026
7cdb65f
remove duplicated tests for memo editing context
leofelix077 May 14, 2026
41188cf
adjust e2e tests to new send flow and minor UI elements
leofelix077 May 14, 2026
c8c968f
adjust comments from agentic code review
leofelix077 May 14, 2026
d281820
update loading state for search
leofelix077 May 14, 2026
29c7d15
fix load account failing test
leofelix077 May 14, 2026
d11ef2f
wait for search to settle to allow continue
leofelix077 May 14, 2026
d115129
remove .slow from unit tests and extract formatter for values
leofelix077 May 18, 2026
75ddc9e
fix merge conflicts and memo type inference for federated address on …
leofelix077 May 18, 2026
d6d39f0
fix comments from code review and round of QA
leofelix077 May 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 156 additions & 52 deletions extension/e2e-tests/sendPayment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,8 @@ test("Send doesn't throw error when account is unfunded", async ({
await loginToTestAccount({ page, extensionId, context });
await page.getByTestId("nav-link-send").click({ force: true });

await expect(page.getByTestId("send-amount-amount-input")).toBeVisible();

await page.getByTestId("address-tile").click();

await expect(page.getByTestId("token-list")).toBeVisible();
await page.getByTestId("SendRow-native").click();
await page
.getByTestId("send-to-input")
.fill("GBTYAFHGNZSTE4VBWZYAGB3SRGJEPTI5I4Y22KZ4JTVAN56LESB6JZOF");
Expand Down Expand Up @@ -230,14 +228,11 @@ test("Send XLM below minimum to unfunded destination shows warning", async ({
});
await page.getByTestId("nav-link-send").click({ force: true });

await expect(page.getByTestId("send-amount-amount-input")).toBeVisible();

// Select address to send to
await page.getByTestId("address-tile").click();
await expect(page.getByTestId("token-list")).toBeVisible();
await page.getByTestId("SendRow-native").click();
await page.getByTestId("send-to-input").fill(UNFUNDED_DESTINATION);
await page.getByText("Continue").click({ force: true });

// Verify amount input is shown
await expect(page.getByTestId("send-amount-amount-input")).toBeVisible();

// Enter amount less than 1 XLM
Expand Down Expand Up @@ -283,14 +278,11 @@ test("Send XLM at minimum to unfunded destination proceeds without warning", asy
});
await page.getByTestId("nav-link-send").click({ force: true });

await expect(page.getByTestId("send-amount-amount-input")).toBeVisible();

// Select address to send to
await page.getByTestId("address-tile").click();
await expect(page.getByTestId("token-list")).toBeVisible();
await page.getByTestId("SendRow-native").click();
await page.getByTestId("send-to-input").fill(UNFUNDED_DESTINATION);
await page.getByText("Continue").click({ force: true });

// Verify amount input is shown
await expect(page.getByTestId("send-amount-amount-input")).toBeVisible();

// Enter exactly 1 XLM (minimum required)
Expand Down Expand Up @@ -341,18 +333,16 @@ test("Send non-native asset to unfunded destination shows destination missing wa
});

await page.getByTestId("nav-link-send").click({ force: true });
await expect(page.getByTestId("send-amount-amount-input")).toBeVisible();

// Change asset to USDC
await page.getByTestId("send-amount-edit-dest-asset").click();
await page.getByText("USDC").click();

// Select address to send to
await page.getByTestId("address-tile").click();
await expect(page.getByTestId("token-list")).toBeVisible();
// Select USDC directly from token picker
await page
.getByTestId(
"SendRow-USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
)
.click();
await page.getByTestId("send-to-input").fill(UNFUNDED_DESTINATION);
await page.getByText("Continue").click({ force: true });

// Verify amount input is shown
await expect(page.getByTestId("send-amount-amount-input")).toBeVisible();

// Enter USDC amount
Expand Down Expand Up @@ -385,14 +375,11 @@ test("Send XLM to funded destination does not show unfunded warning", async ({
// Don't stub unfunded balances - the default stub will return isFunded: true
await page.getByTestId("nav-link-send").click({ force: true });

await expect(page.getByTestId("send-amount-amount-input")).toBeVisible();

// Select address to send to (using funded destination)
await page.getByTestId("address-tile").click();
await expect(page.getByTestId("token-list")).toBeVisible();
await page.getByTestId("SendRow-native").click();
await page.getByTestId("send-to-input").fill(FUNDED_DESTINATION);
await page.getByText("Continue").click({ force: true });

// Verify amount input is shown
await expect(page.getByTestId("send-amount-amount-input")).toBeVisible();

// Enter amount
Expand Down Expand Up @@ -472,9 +459,8 @@ test("Send doesn't throw error when creating muxed account", async ({
});
await page.getByTestId("nav-link-send").click({ force: true });

await expect(page.getByTestId("send-amount-amount-input")).toBeVisible();

await page.getByTestId("address-tile").click();
await expect(page.getByTestId("token-list")).toBeVisible();
await page.getByTestId("SendRow-native").click();

await page.getByTestId("send-to-input").fill(MUXED_ACCOUNT_ADDRESS);
await expect(
Expand Down Expand Up @@ -556,9 +542,8 @@ test("Send can review formatted inputs", async ({
});
await page.getByTestId("nav-link-send").click({ force: true });

await expect(page.getByTestId("send-amount-amount-input")).toBeVisible();

await page.getByTestId("address-tile").click();
await expect(page.getByTestId("token-list")).toBeVisible();
await page.getByTestId("SendRow-native").click();

await page.getByTestId("send-to-input").fill(MUXED_ACCOUNT_ADDRESS);
await expect(
Expand Down Expand Up @@ -686,10 +671,16 @@ test("SendPayment persists amount and asset when navigating to choose address",
await loginToTestAccount({ page, extensionId, context });
await page.getByTestId("nav-link-send").click({ force: true });

await expect(page.getByTestId("token-list")).toBeVisible();
await page.getByTestId("SendRow-native").click();
// Fill an address to proceed to AMOUNT
await page.getByTestId("send-to-input").fill(FUNDED_DESTINATION);
await page.getByText("Continue").click({ force: true });
await expect(page.getByTestId("send-amount-amount-input")).toBeVisible();
await page.getByTestId("send-amount-amount-input").fill("100");
await expect(page.getByTestId("send-amount-amount-input")).toHaveValue("100");

// Navigate to address picker from AMOUNT and back
await page.getByTestId("address-tile").click();
await expect(page.getByTestId("send-to-input")).toBeVisible();

Expand All @@ -716,16 +707,21 @@ test("SendPayment resets amount when user selects new asset", async ({
});

await page.getByTestId("nav-link-send").click({ force: true });
await expect(page.getByTestId("token-list")).toBeVisible();
await page.getByTestId("SendRow-native").click();
await page.getByTestId("send-to-input").fill(FUNDED_DESTINATION);
await page.getByText("Continue").click({ force: true });
await expect(page.getByTestId("send-amount-amount-input")).toBeVisible();

await page.getByTestId("send-amount-amount-input").fill("50");
await expect(page.getByTestId("send-amount-amount-input")).toHaveValue("50");

// Change token via the token tile on the AMOUNT screen
await page.locator(".SendAmount__EditDestAsset").click();
await page.getByText("USDC").first().click({ force: true });

await expect(page.getByTestId("send-amount-amount-input")).toBeVisible();
await expect(page.getByTestId("send-amount-amount-input")).toHaveValue("0");
await expect(page.getByTestId("send-amount-amount-input")).toHaveValue("");
});

test("SendPayment resets state when navigating back to account", async ({
Expand All @@ -745,27 +741,36 @@ test("SendPayment resets state when navigating back to account", async ({
});

await page.getByTestId("nav-link-send").click({ force: true });
await expect(page.getByTestId("token-list")).toBeVisible();
await page.getByTestId("SendRow-native").click();
await page
.getByTestId("send-to-input")
.fill("GBTYAFHGNZSTE4VBWZYAGB3SRGJEPTI5I4Y22KZ4JTVAN56LESB6JZOF");
await page.getByText("Continue").click({ force: true });
await expect(page.getByTestId("send-amount-amount-input")).toBeVisible();

// Change to USDC and enter amount
await page.locator(".SendAmount__EditDestAsset").click();
await page.getByText("USDC").first().click({ force: true });
await page.getByTestId("send-amount-amount-input").fill("100");

await page.getByTestId("address-tile").click();
await page
.getByTestId("send-to-input")
.fill("GBTYAFHGNZSTE4VBWZYAGB3SRGJEPTI5I4Y22KZ4JTVAN56LESB6JZOF");
await page.getByText("Continue").click({ force: true });

await expect(page.getByTestId("send-amount-amount-input")).toBeVisible();
// Press BackButton (X) to exit the flow
await page.getByTestId("BackButton").click();

await expect(page.getByTestId("account-view")).toBeVisible();

// Re-enter send flow: should start fresh at token picker
await page.getByTestId("nav-link-send").click({ force: true });
await expect(page.getByTestId("token-list")).toBeVisible();
// Select XLM and navigate to AMOUNT to verify reset state
await page.getByTestId("SendRow-native").click();
await page
.getByTestId("send-to-input")
.fill("GBTYAFHGNZSTE4VBWZYAGB3SRGJEPTI5I4Y22KZ4JTVAN56LESB6JZOF");
await page.getByText("Continue").click({ force: true });
await expect(page.getByTestId("send-amount-amount-input")).toBeVisible();
await expect(page.getByTestId("send-amount-amount-input")).toHaveValue("0");
// Verify XLM is selected (more reliable than checking for "0 XLM" text)
await expect(page.getByTestId("send-amount-amount-input")).toHaveValue("");
// Verify XLM is selected (not USDC from the previous session)
await expect(page.locator(".SendAmount__EditDestAsset")).toContainText("XLM");
});

Expand Down Expand Up @@ -895,6 +900,108 @@ test("Swap resets state when navigating back to account", async ({
await expect(page.getByTestId("swap-src-asset-tile")).toContainText("XLM");
});

test("Send flow starts at token picker and proceeds to amount screen", async ({
page,
extensionId,
context,
}) => {
test.slow();
await loginToTestAccount({ page, extensionId, context });

await page.getByTestId("nav-link-send").click({ force: true });

// Step 1: token picker
await expect(page.getByTestId("token-list")).toBeVisible();
await expect(page.getByTestId("send-amount-amount-input")).toHaveCount(0);

// Step 2: select XLM → DESTINATION
await page.getByTestId("SendRow-native").click();
await expect(page.getByTestId("send-to-input")).toBeVisible();
await expect(page.getByTestId("send-amount-amount-input")).toHaveCount(0);

// Step 3: fill address and continue → AMOUNT
await page.getByTestId("send-to-input").fill(FUNDED_DESTINATION);
await page.getByText("Continue").click({ force: true });
await expect(page.getByTestId("send-amount-amount-input")).toBeVisible();
});

test("Send flow from asset detail starts at destination step", async ({
page,
extensionId,
context,
}) => {
test.slow();
const stubOverrides = async () => {
await stubAccountBalancesE2e(page);
};
await stubContractSpec(page, TEST_TOKEN_ADDRESS, true);
await loginToTestAccount({ page, extensionId, context, stubOverrides });

await page.getByText("E2E").click();
await page.getByTestId("asset-detail-send-button").click();

// Asset detail pre-selects asset via ?asset= param → starts at DESTINATION
await expect(page.getByTestId("send-to-input")).toBeVisible();
// Token picker must NOT be visible (asset was pre-selected)
await expect(page.getByTestId("token-list")).toHaveCount(0);
// Amount screen must NOT be visible yet
await expect(page.getByTestId("send-amount-amount-input")).toHaveCount(0);
});

test("Send flow change recipient from amount screen dismisses back", async ({
page,
extensionId,
context,
}) => {
test.slow();
await loginToTestAccount({ page, extensionId, context });

// Navigate to AMOUNT screen
await page.getByTestId("nav-link-send").click({ force: true });
await expect(page.getByTestId("token-list")).toBeVisible();
await page.getByTestId("SendRow-native").click();
await page.getByTestId("send-to-input").fill(FUNDED_DESTINATION);
await page.getByText("Continue").click({ force: true });
await expect(page.getByTestId("send-amount-amount-input")).toBeVisible();

// Click address tile to change recipient — DESTINATION slides in from bottom
await page.getByTestId("address-tile").click();
await expect(page.getByTestId("send-to-input")).toBeVisible();

// Back dismisses back to AMOUNT
await page.getByTestId("BackButton").click();
await expect(page.getByTestId("send-amount-amount-input")).toBeVisible();
});

test("Send flow change token from amount screen dismisses back", async ({
page,
extensionId,
context,
}) => {
test.slow();
const stubOverrides = async () => {
await stubAccountBalancesWithUSDC(page);
};
await loginToTestAccount({ page, extensionId, context, stubOverrides });

// Navigate to AMOUNT screen
await page.getByTestId("nav-link-send").click({ force: true });
await expect(page.getByTestId("token-list")).toBeVisible();
await page.getByTestId("SendRow-native").click();
await page.getByTestId("send-to-input").fill(FUNDED_DESTINATION);
await page.getByText("Continue").click({ force: true });
await expect(page.getByTestId("send-amount-amount-input")).toBeVisible();

// Click token tile to change asset — token picker slides in from bottom
await page.locator(".SendAmount__EditDestAsset").click();
// Should now show token list (no tabs, inline list)
await expect(page.getByTestId("token-list")).toBeVisible();

// Back dismisses back to AMOUNT
await page.getByTestId("BackButton").click();
await expect(page.getByTestId("send-amount-amount-input")).toBeVisible();
});

test.afterAll(async ({ page, extensionId, context }) => {
if (
test.info().status !== test.info().expectedStatus &&
Expand Down Expand Up @@ -935,16 +1042,15 @@ test("Send token payment from Asset Detail", async ({
await page.getByText("E2E").click();

await page.getByTestId("asset-detail-send-button").click();
await expect(page.getByTestId("send-amount-amount-input")).toBeVisible();
await page.getByTestId("send-amount-amount-input").fill("0.123");

await page.getByTestId("address-tile").click();
// Asset detail navigates with ?asset= param, so we land at DESTINATION (not token picker)
await expect(page.getByTestId("send-to-input")).toBeVisible();
await page
.getByTestId("send-to-input")
.fill("GDF32CQINROD3E2LMCGZUDVMWTXCJFR5SBYVRJ7WAAIAS3P7DCVWZEFY");
await page.getByText("Continue").click();

await expect(page.getByTestId("send-amount-amount-input")).toBeVisible();
await page.getByTestId("send-amount-amount-input").fill("0.123");
await expect(page.getByTestId("send-amount-amount-input")).toHaveValue(
"0.123",
);
Expand Down Expand Up @@ -987,16 +1093,14 @@ test("Send XLM payment from Asset Detail", async ({
await page.getByText("XLM").click();

await page.getByTestId("asset-detail-send-button").click();
await expect(page.getByTestId("send-amount-amount-input")).toBeVisible();
await page.getByTestId("send-amount-amount-input").fill("0.01");

await page.getByTestId("address-tile").click();
await expect(page.getByTestId("send-to-input")).toBeVisible();
await page
.getByTestId("send-to-input")
.fill("GDF32CQINROD3E2LMCGZUDVMWTXCJFR5SBYVRJ7WAAIAS3P7DCVWZEFY");
await page.getByText("Continue").click();

await expect(page.getByTestId("send-amount-amount-input")).toBeVisible();
await page.getByTestId("send-amount-amount-input").fill("0.01");
await expect(page.getByTestId("send-amount-amount-input")).toHaveValue(
"0.01",
);
Expand Down
5 changes: 4 additions & 1 deletion extension/e2e-tests/test-fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ export const test = base.extend<{
extensionId: string;
page: Page;
language: string;
viewportSize: { width: number; height: number } | null;
}>({
viewportSize: null,
language: "en",
context: async ({}, use) => {
context: async ({ viewportSize }, use) => {
const pathToExtension = path.join(__dirname, "../build");
const context = await chromium.launchPersistentContext("", {
headless: false,
Expand All @@ -25,6 +27,7 @@ export const test = base.extend<{
`--disable-extensions-except=${pathToExtension}`,
`--load-extension=${pathToExtension}`,
],
...(viewportSize ? { viewport: viewportSize } : {}),
});

// Mark every page (including popups opened by the extension) as a
Expand Down
Loading
Loading