diff --git a/src/app/(sidebar)/transaction/simulate/page.tsx b/src/app/(sidebar)/transaction/simulate/page.tsx index 37f6fee2..e5ac528a 100644 --- a/src/app/(sidebar)/transaction/simulate/page.tsx +++ b/src/app/(sidebar)/transaction/simulate/page.tsx @@ -116,6 +116,7 @@ export default function SimulateTransaction() { /> {simulateTxData ? (
diff --git a/tests/simulateTransactionPage.test.ts b/tests/simulateTransactionPage.test.ts new file mode 100644 index 00000000..9ce013c8 --- /dev/null +++ b/tests/simulateTransactionPage.test.ts @@ -0,0 +1,258 @@ +import { test, expect } from "@playwright/test"; + +test.describe("Simulate Transaction Page", () => { + test.beforeEach(async ({ page }) => { + await page.goto("http://localhost:3000/transaction/simulate"); + }); + + test("Loads", async ({ page }) => { + await expect(page.locator("h1")).toHaveText("Simulate Transaction"); + }); + + test.describe("Instruction Leeway Input", () => { + test("error when inputting a non whole number", async ({ page }) => { + const instructionLeewayOn = page.getByTestId("simulate-tx-instr-leeway"); + await instructionLeewayOn.fill("aaa"); + await expect(page.getByText("Expected a whole number.")).toBeVisible(); + }); + + test("previous response should be removed when inputting a non whole number", async ({ + page, + }) => { + // Getting a success response + const xdrInput = page.getByLabel( + "Input a base-64 encoded TransactionEnvelope", + ); + const txResponseCard = page.getByTestId("simulate-tx-response"); + const simulateTxBtn = page.getByRole("button", { + name: "Simulate transaction", + }); + + await xdrInput.fill(MOCK_VALID_SUCCESS_TX_XDR_RPC); + + expect(simulateTxBtn).toBeEnabled(); + + // Mock the RPC submission api call + await page.route("https://soroban-testnet.stellar.org", async (route) => { + await route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify({ + jsonrpc: "2.0", + id: 1, + result: { + status: "SUCCESS", + }, + }), + }); + }); + + const responsePromise = page.waitForResponse( + (response) => + response.url().includes("soroban-testnet") && + response.status() === 200, + ); + + await simulateTxBtn.click(); + + await responsePromise; + + await expect(txResponseCard).toBeVisible(); + + // Typing in the wrong instruction leeway should remove the previous response + const instructionLeewayOn = page.getByTestId("simulate-tx-instr-leeway"); + await instructionLeewayOn.fill("aaa"); + await expect(page.getByText("Expected a whole number.")).toBeVisible(); + + await expect(txResponseCard).toBeHidden(); + }); + + test("simulate button to be enabled when inputting a whole number for instruction leeway and the correct format xdr", async ({ + page, + }) => { + const simulateTxBtn = page.getByRole("button", { + name: "Simulate transaction", + }); + const xdrInput = page.getByLabel( + "Input a base-64 encoded TransactionEnvelope", + ); + + await expect(simulateTxBtn).toBeDisabled(); + + const instructionLeewayOn = page.getByTestId("simulate-tx-instr-leeway"); + await instructionLeewayOn.fill("23423423"); + + // inputting the leeway alone isn't enough to enable the simulate tx button + await expect(simulateTxBtn).toBeDisabled(); + + await xdrInput.fill(MOCK_VALID_SUCCESS_TX_XDR_RPC); + + await expect(simulateTxBtn).toBeEnabled(); + }); + }); + + test.describe("Invalid XDR Flow", () => { + test("display an error with a random text", async ({ page }) => { + const invalidXdrMsg = page.getByText( + "Unable to parse input XDR into Transaction Envelope", + ); + const xdrInput = page.getByLabel( + "Input a base-64 encoded TransactionEnvelope", + ); + await xdrInput.fill("ssdfsdf"); + await expect(invalidXdrMsg).toBeVisible(); + + const simulateTxBtn = page.getByRole("button", { + name: "Simulate transaction", + }); + + await expect(simulateTxBtn).toBeDisabled(); + }); + + test("display an error with a valid XDR that is not TX Envelope", async ({ + page, + }) => { + const invalidXdrMsg = page.getByText( + "Unable to parse input XDR into Transaction Envelope", + ); + const xdrInput = page.getByLabel( + "Input a base-64 encoded TransactionEnvelope", + ); + const simulateTxBtn = page.getByRole("button", { + name: "Simulate transaction", + }); + + // Simulate transaction button to be disabled by default + await expect(simulateTxBtn).toBeDisabled(); + + // Input an XDR in correct format + await xdrInput.fill(MOCK_VALID_FAILED_TX_XDR); + + // Simulate transaction button to be enabled since the correct XDR input + await expect(simulateTxBtn).toBeEnabled(); + + // Input an XDR in unsupported format + await xdrInput.fill(MOCK_SC_VAL_XDR); + + // Error message to be visible + await expect(invalidXdrMsg).toBeVisible(); + + // Simulate transaction button to be disabled + await expect(simulateTxBtn).toBeDisabled(); + }); + }); + + test.describe("Valid XDR with a Failed 400 Submission Flow", () => { + test("Submit via RPC", async ({ page }) => { + const xdrInput = page.getByLabel( + "Input a base-64 encoded TransactionEnvelope", + ); + const txResponseCard = page.getByTestId("simulate-tx-response"); + + await xdrInput.fill(MOCK_VALID_FAILED_TX_XDR); + + const simulateTxBtn = page.getByRole("button", { + name: "Simulate transaction", + }); + + await expect(simulateTxBtn).toBeEnabled(); + + // Mock the RPC submission api call + await page.route("https://soroban-testnet.stellar.org", async (route) => { + await route.fulfill({ + status: 400, + contentType: "application/json", + body: JSON.stringify(MOCK_TX_XDR_FAILED_RPC_RESPONSE), + }); + }); + + const responsePromise = page.waitForResponse( + (response) => + response.url().includes("soroban") && response.status() === 400, + ); + + await simulateTxBtn.click(); + + await responsePromise; + + await expect(txResponseCard).toBeVisible(); + + const responseText = await txResponseCard.textContent(); + + expect(responseText).toContain("ERROR"); + }); + }); + + test.describe("Valid XDR with a Successful Submission Flow", () => { + test("Submit via RPC", async ({ page }) => { + const xdrInput = page.getByLabel( + "Input a base-64 encoded TransactionEnvelope", + ); + const txResponseCard = page.getByTestId("simulate-tx-response"); + const simulateTxBtn = page.getByRole("button", { + name: "Simulate transaction", + }); + + await xdrInput.fill(MOCK_VALID_SUCCESS_TX_XDR_RPC); + + expect(simulateTxBtn).toBeEnabled(); + + // Mock the RPC submission api call + await page.route("https://soroban-testnet.stellar.org", async (route) => { + await route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify({ + jsonrpc: "2.0", + id: 1, + result: { + status: "SUCCESS", + }, + }), + }); + }); + + const responsePromise = page.waitForResponse( + (response) => + response.url().includes("soroban-testnet") && + response.status() === 200, + ); + + await simulateTxBtn.click(); + + await responsePromise; + + await expect(txResponseCard).toBeVisible(); + + const responseText = await txResponseCard.textContent(); + + expect(responseText).toContain("SUCCESS"); + + // Omitting the API end result because the test gives inconsistenet results + }); + }); +}); + +// // ============================================================================= +// // Mock data +// // ============================================================================= +const MOCK_SC_VAL_XDR = + "AAAAEQAAAAEAAAAGAAAADwAAAAZhbW91bnQAAAAAAAoAAAAAAAAAAAAACRhOcqAAAAAADwAAAAxib290c3RyYXBwZXIAAAASAAAAARssFqxD/prgmYc9vGkaqslWrGlPINzMYTLc4yqRfO3AAAAADwAAAAxjbG9zZV9sZWRnZXIAAAADAz6ilAAAAA8AAAAIcGFpcl9taW4AAAAKAAAAAAAAAAAAAAARdlkuAAAAAA8AAAAEcG9vbAAAABIAAAABX/a7xfliM8nFgGel6pbCM6fT/kqrHAITNtWZQXgDlIIAAAAPAAAAC3Rva2VuX2luZGV4AAAAAAMAAAAA"; + +const MOCK_VALID_FAILED_TX_XDR = + "AAAAAgAAAADJrq4b4AopDZibkeBWpDxuWKUcY4FUUNQdIEF3Nm9dkQAAAGQAAAIiAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAACXlGN76T6NQcaUJxbEkH3mi1HHWsHnLqMDdlLl9NlJgQAAAAAAAAAABfXhAAAAAAAAAAAA"; + +const MOCK_VALID_SUCCESS_TX_XDR_RPC = + "AAAAAgAAAAAg4dbAxsGAGICfBG3iT2cKGYQ6hK4sJWzZ6or1C5v6GAAAAGQAJsOiAAAAEQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAGAAAAAAAAAABzAP+dP0PsNzYvFF1pv7a8RQXwH5eg3uZBbbWjE9PwAsAAAAJaW5jcmVtZW50AAAAAAAAAgAAABIAAAAAAAAAACDh1sDGwYAYgJ8EbeJPZwoZhDqEriwlbNnqivULm/oYAAAAAwAAAAMAAAAAAAAAAAAAAAA="; + +const MOCK_TX_XDR_FAILED_RPC_RESPONSE = { + jsonrpc: "2.0", + id: 1, + result: { + errorResultXdr: "AAAAAAAAAGT////7AAAAAA==", + status: "ERROR", + hash: "794e2073e130dc09d2b7e8b147b51f6ef75ff171c83c603bc8ab4cffa3f341a1", + latestLedger: 1305084, + latestLedgerCloseTime: "1733355115", + }, +};