From 5116a58571359a4975ed90a9a970d781aec9f037 Mon Sep 17 00:00:00 2001 From: MariuSZ <112616777+MariuszSzpyt@users.noreply.github.com> Date: Mon, 27 May 2024 09:36:49 +0200 Subject: [PATCH] Feat/fe rollup prod checks (#428) --- .github/workflows/gasp-prod-smoke-test.yml | 192 ++++++++++++++++++ package.json | 1 + reports/artifacts/readme.md | 2 +- test/rollup-test/rollup-prod.deposit.test.ts | 117 +++++++++++ test/rollup-test/rollup-prod.withdraw.test.ts | 137 +++++++++++++ utils/frontend/rollup-pages/WalletWrapper.ts | 34 +++- utils/frontend/rollup-utils/DepositModal.ts | 40 +++- utils/frontend/utils/Helper.ts | 1 + 8 files changed, 518 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/gasp-prod-smoke-test.yml create mode 100644 test/rollup-test/rollup-prod.deposit.test.ts create mode 100644 test/rollup-test/rollup-prod.withdraw.test.ts diff --git a/.github/workflows/gasp-prod-smoke-test.yml b/.github/workflows/gasp-prod-smoke-test.yml new file mode 100644 index 000000000..6463d6846 --- /dev/null +++ b/.github/workflows/gasp-prod-smoke-test.yml @@ -0,0 +1,192 @@ +name: GASP UI prod smoke tests + +on: + workflow_dispatch: + schedule: + - cron: "30 7 * * *" +jobs: + setup-report: + name: setup testmo report + runs-on: ubuntu-latest + outputs: + testmo-run-id: ${{ steps.setTestRun.outputs.testmo-run-id }} + + steps: + - name: Install testmo + run: yarn global add @testmo/testmo-cli + + - name: Add extra params + run: | + testmo automation:resources:add-field --name git --type string \ + --value ${GITHUB_SHA:0:7} --resources resources.json + RUN_URL="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" + testmo automation:resources:add-link --name build \ + --url $RUN_URL --resources resources.json + + - name: Create test run + run: | + testmo automation:run:create \ + --instance https://mangata-finance.testmo.net \ + --project-id 2 \ + --name "FE GASP prod smoke test run" \ + --resources resources.json \ + --source "gasp-prod-e2e-job" > testmo-run-id.txt + ID=$(cat testmo-run-id.txt) + echo "testmo-run-id=$ID" >> $GITHUB_OUTPUT + echo "ID=$ID" >> $GITHUB_ENV + + env: + TESTMO_URL: ${{ secrets.TESTMO_URL }} + TESTMO_TOKEN: ${{ secrets.TESTMO_TOKEN }} + id: setTestRun + + e2e-tm: + name: Run UI tests + needs: [setup-report] + strategy: + fail-fast: false + matrix: + command: ["first"] + runs-on: ubuntu-latest + env: + API_URL: "wss://collator-01-ws-rollup-testnet.mangata.online" + UI_URL: https://holesky.gasp.xyz/ + TEST_SUDO_NAME: ${{ secrets.DEV_SUDO_NAME }} + MNEMONIC_META: ${{ secrets.MNEMONIC_PROD_META }} + PRIVKEY_META: ${{ secrets.PRIVKEY_PROD_META }} + TEST_SCRIPT: "test-gasp-prod" + NODE_ENV: test + SELENIUM_REMOTE_URL: http://localhost:4444/wd/hub + + steps: + - name: Start Selenoid Server + uses: Xotabu4/selenoid-github-action@v2 + with: + selenoid-start-arguments: | + --args "-timeout 300s" --browsers 'chrome:110.0;chrome:112.0;chrome:114.0' + + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: yarn + - name: Install testmo + run: yarn global add @testmo/testmo-cli + + - name: Install dependencies with Yarn + run: yarn + - name: Tsc code quality check + run: yarn tsc-lint + - name: ESLint code quality check + run: yarn eslint + - name: installed packages + run: yarn list + + - name: is selenoid started? + run: curl http://localhost:4444/status + + - name: Run tests + run: yarn test-gasp-prod-${{ matrix.command }} + + - name: Test Report + uses: dorny/test-reporter@v1 + if: success() || failure() # run this step even if previous step failed + with: + name: JEST UI Tests ${{ matrix.command }} # Name of the check run which will be created + path: reports/*.xml # Path to test results + reporter: jest-junit # Format of test results + + - name: Install testmo + run: yarn global add @testmo/testmo-cli + + - name: Submit results to the testmo-run + if: always() + run: | + testmo automation:run:submit-thread \ + --instance https://mangata-finance.testmo.net \ + --run-id ${{needs.setup-report.outputs.testmo-run-id}} \ + --results reports/*.xml + env: + TESTMO_URL: ${{ secrets.TESTMO_URL }} + TESTMO_TOKEN: ${{ secrets.TESTMO_TOKEN }} + continue-on-error: true + + - name: Sleep for 5 seconds + run: sleep 10s #close buffers of videos. + + - name: Copy video files to reports folder + if: success() || failure() + run: "cp ~/.aerokube/selenoid/* -r reports/artifacts/" + + - name: Create reports zip + uses: vimtor/action-zip@v1 + if: success() || failure() + with: + files: reports/ + dest: reports.zip + + - name: Archive report files + uses: actions/upload-artifact@v2 + if: success() || failure() + with: + name: ReportData ${{ matrix.command }} + path: reports.zip + + - name: Archive report files + uses: actions/upload-artifact@v2 + if: success() || failure() + with: + name: TestReport ${{ matrix.command }} + path: reports/html-report/report.html + + test-complete: + needs: [setup-report, e2e-tm] + if: always() + runs-on: ubuntu-latest + + steps: + - name: Install testmo + run: yarn global add @testmo/testmo-cli + + - name: Complete test run + run: | + npx testmo automation:run:complete \ + --instance https://mangata-finance.testmo.net \ + --run-id ${{needs.setup-report.outputs.testmo-run-id}} \ + env: + TESTMO_URL: ${{ secrets.TESTMO_URL }} + TESTMO_TOKEN: ${{ secrets.TESTMO_TOKEN }} + continue-on-error: true + + slack-notify-ok: + needs: [e2e-tm, setup-report] + if: success() + runs-on: ubuntu-latest + + steps: + - name: Slack Notification - OK + if: success() + uses: bryannice/gitactions-slack-notification@2.0.0 + env: + SLACK_INCOMING_WEBHOOK: ${{ secrets.STG_SLACK_WEBHOOK }} + SLACK_TITLE: "PROD GASP smoke UI tests - results - OK" + SLACK_COLOR: "#00ff11" + SLACK_MESSAGE: "Test succeed in [ ${{ env.TEST_SCRIPT }} ] + testmo report: https://mangata-finance.testmo.net/automation/runs/view/${{needs.setup-report.outputs.testmo-run-id}}" + GITHUB_REF: "https://mangata-finance.github.io/mangata-e2e/${{ github.run_number }}" + + slack-notify-nook: + needs: [e2e-tm] + if: failure() + runs-on: ubuntu-latest + + steps: + - name: Slack Notification - Error + uses: bryannice/gitactions-slack-notification@2.0.0 + env: + SLACK_INCOMING_WEBHOOK: ${{ secrets.STG_SLACK_WEBHOOK }} + SLACK_TITLE: "PROD GASP smoke UI tests - results - NOOK" + SLACK_COLOR: "#ff0011" + SLACK_MESSAGE: "Test failures [ ${{ env.TEST_SCRIPT }} ] + testmo report: https://mangata-finance.testmo.net/automation/runs/view/${{needs.setup-report.outputs.testmo-run-id}}" + GITHUB_REF: "https://mangata-finance.github.io/mangata-e2e/${{ github.run_number }}" diff --git a/package.json b/package.json index 9a5d46714..5828b8d6f 100644 --- a/package.json +++ b/package.json @@ -126,6 +126,7 @@ "test-microapps-chops-fifth": "export CHOPSTICK_UI=true ; rm nonce.db ; rm sudo.lock; node --experimental-specifier-resolution=node --loader ts-node/esm --experimental-vm-modules node_modules/jest/bin/jest.js --verbose --runInBand --ci --group=microappsStaking", "test-microapps-prod-first": "export CHOPSTICK_UI=true ; rm nonce.db ; rm sudo.lock; node --experimental-specifier-resolution=node --loader ts-node/esm --experimental-vm-modules node_modules/jest/bin/jest.js --verbose --runInBand --ci --group=microappsProdDeposit --group=microappsProdSwap --group=microappsProdWallet --group=microappsProdWithdraw --group=microappsProdStaking --group=microappsProdPools", "test-gasp-ui": "export CHOPSTICK_UI=true ; rm nonce.db ; rm sudo.lock; node --experimental-specifier-resolution=node --loader ts-node/esm --experimental-vm-modules node_modules/jest/bin/jest.js --verbose --runInBand --ci --group=rollupDeposit --group=rollupWallet --group=rollupWithdraw", + "test-gasp-prod-first": "export CHOPSTICK_UI=true ; rm nonce.db ; rm sudo.lock; node --experimental-specifier-resolution=node --loader ts-node/esm --experimental-vm-modules node_modules/jest/bin/jest.js --verbose --runInBand --ci --group=rollupWithdrawProd --group=rollupDepositProd", "cliTool": "node --experimental-specifier-resolution=node --loader ts-node/esm --experimental-vm-modules cliTool/index.ts --runInBand", "runtimeUpgrade": " jest --verbose --ci --group=upgradeRuntime --runInBand --forceExit" }, diff --git a/reports/artifacts/readme.md b/reports/artifacts/readme.md index 59bbcad55..d1c99613d 100644 --- a/reports/artifacts/readme.md +++ b/reports/artifacts/readme.md @@ -1 +1 @@ -This folder will contain screenshots for Github actions artifacts \ No newline at end of file +- This folder will contain screenshots for Github actions artifacts \ No newline at end of file diff --git a/test/rollup-test/rollup-prod.deposit.test.ts b/test/rollup-test/rollup-prod.deposit.test.ts new file mode 100644 index 000000000..146d93e59 --- /dev/null +++ b/test/rollup-test/rollup-prod.deposit.test.ts @@ -0,0 +1,117 @@ +/* + * + * @group rollupDepositProd + */ +import { jest } from "@jest/globals"; +import { WebDriver } from "selenium-webdriver"; +import { getApi, initApi } from "../../utils/api"; +import { DriverBuilder } from "../../utils/frontend/utils/Driver"; +import { + acceptNetworkSwitchInNewWindow, + addExtraLogs, + importMetamaskExtension, + uiStringToNumber, +} from "../../utils/frontend/utils/Helper"; +import "dotenv/config"; +import { + connectWallet, + setupPage, + setupPageWithState, + waitForActionNotification, +} from "../../utils/frontend/rollup-utils/Handlers"; +import { WalletWrapper } from "../../utils/frontend/rollup-pages/WalletWrapper"; +import { + DepositActionType, + DepositModal, +} from "../../utils/frontend/rollup-utils/DepositModal"; +import { TransactionType } from "../../utils/frontend/rollup-pages/NotificationToast"; + +jest.spyOn(console, "log").mockImplementation(jest.fn()); + +jest.setTimeout(1500000); +let driver: WebDriver; + +let acc_addr = ""; +let acc_addr_short = ""; +const GETH_ASSET_NAME = "GETH"; + +describe("Gasp Prod UI deposit tests", () => { + beforeAll(async () => { + try { + getApi(); + } catch (e) { + await initApi(); + } + + driver = await DriverBuilder.getInstance(); + acc_addr = await importMetamaskExtension(driver, true); + acc_addr_short = acc_addr.slice(-4).toLowerCase(); + + await setupPage(driver); + await connectWallet(driver, "Metamask", acc_addr_short); + }); + + test("User can deposit GETH", async () => { + await setupPageWithState(driver, acc_addr_short); + + const walletWrapper = new WalletWrapper(driver); + await walletWrapper.openWalletConnectionInfo(); + const tokensAmountBefore = + await walletWrapper.getMyTokensRowAmount(GETH_ASSET_NAME); + await walletWrapper.openDeposit(); + const depositModal = new DepositModal(driver); + const isModalVisible = await depositModal.isModalVisible(); + expect(isModalVisible).toBeTruthy(); + + await depositModal.openChainList(); + await depositModal.selectChain("Ethereum"); + await depositModal.openTokensList(); + await depositModal.waitForTokenListElementsVisible(GETH_ASSET_NAME); + await depositModal.selectToken(GETH_ASSET_NAME); + + const randomNum = Math.floor(Math.random() * 99) + 1; + await depositModal.enterValue("1." + randomNum.toString()); + + await depositModal.waitForContinueState(true, 60000); + const isOriginFeeDisplayed = await depositModal.isOriginFeeDisplayed(); + expect(isOriginFeeDisplayed).toBeTruthy(); + + const isNetworkButtonEnabled = await depositModal.isNetworkButtonEnabled(); + expect(isNetworkButtonEnabled).toBeTruthy(); + + await depositModal.clickDepositButtonByText(DepositActionType.Network); + await acceptNetworkSwitchInNewWindow(driver); + + await depositModal.clickDepositButtonByText(DepositActionType.Approve); + await waitForActionNotification(driver, TransactionType.ApproveContract); + + await depositModal.clickDepositButtonByText(DepositActionType.Deposit); + await waitForActionNotification(driver, TransactionType.Deposit); + await depositModal.clickDepositButtonByText(DepositActionType.Done); + + await walletWrapper.waitTokenAmountChange( + GETH_ASSET_NAME, + tokensAmountBefore, + ); + const tokensAmountAfter = + await walletWrapper.getMyTokensRowAmount(GETH_ASSET_NAME); + expect(await uiStringToNumber(tokensAmountAfter)).toBeGreaterThan( + await uiStringToNumber(tokensAmountBefore), + ); + }); + + afterEach(async () => { + const session = await driver.getSession(); + await addExtraLogs( + driver, + expect.getState().currentTestName + " - " + session.getId(), + ); + }); + + afterAll(async () => { + const api = getApi(); + await api.disconnect(); + await driver.quit(); + DriverBuilder.destroy(); + }); +}); diff --git a/test/rollup-test/rollup-prod.withdraw.test.ts b/test/rollup-test/rollup-prod.withdraw.test.ts new file mode 100644 index 000000000..1c414fa29 --- /dev/null +++ b/test/rollup-test/rollup-prod.withdraw.test.ts @@ -0,0 +1,137 @@ +/* + * + * @group rollupWithdrawProd + */ +import { jest } from "@jest/globals"; +import { WebDriver } from "selenium-webdriver"; +import { getApi, initApi } from "../../utils/api"; +import { DriverBuilder } from "../../utils/frontend/utils/Driver"; +import { + acceptNetworkSwitchInNewWindow, + addExtraLogs, + importMetamaskExtension, + uiStringToNumber, +} from "../../utils/frontend/utils/Helper"; +import "dotenv/config"; +import { + connectWallet, + setupPage, + setupPageWithState, + waitForActionNotification, +} from "../../utils/frontend/rollup-utils/Handlers"; +import { WalletWrapper } from "../../utils/frontend/rollup-pages/WalletWrapper"; +import { TransactionType } from "../../utils/frontend/rollup-pages/NotificationToast"; +import { + WithdrawActionType, + WithdrawModal, +} from "../../utils/frontend/rollup-pages/WithdrawModal"; +import { DepositModal } from "../../utils/frontend/rollup-utils/DepositModal"; + +jest.spyOn(console, "log").mockImplementation(jest.fn()); + +jest.setTimeout(1500000); +let driver: WebDriver; + +let acc_addr = ""; +let acc_addr_short = ""; +const ASSET_NAME = "GASP"; + +describe("Gasp Prod UI withdraw tests", () => { + beforeAll(async () => { + try { + getApi(); + } catch (e) { + await initApi(); + } + + driver = await DriverBuilder.getInstance(); + acc_addr = await importMetamaskExtension(driver, true); + acc_addr_short = acc_addr.slice(-4).toLowerCase(); + + await setupPage(driver); + await connectWallet(driver, "Metamask", acc_addr_short); + }); + + test("User can withdraw GETH", async () => { + await setupPageWithState(driver, acc_addr_short); + + const walletWrapper = new WalletWrapper(driver); + await walletWrapper.openWalletConnectionInfo(); + const tokensAmountBefore = + await walletWrapper.getMyTokensRowAmount(ASSET_NAME); + + await walletWrapper.openDeposit(); + const depositModal = new DepositModal(driver); + let isDepositModalVisible = await depositModal.isModalVisible(); + expect(isDepositModalVisible).toBeTruthy(); + await depositModal.openChainList(); + await depositModal.selectChain("Ethereum"); + await depositModal.openTokensList(); + await depositModal.waitForTokenListElementsVisible(ASSET_NAME); + await depositModal.selectToken(ASSET_NAME); + const l1TokensAmountBefore = await depositModal.getTokenAmount(); + await depositModal.close(); + + await walletWrapper.openWithdraw(); + const withdrawModal = new WithdrawModal(driver); + const isModalVisible = await withdrawModal.isModalVisible(); + expect(isModalVisible).toBeTruthy(); + + await withdrawModal.openChainList(); + await withdrawModal.selectChain("Ethereum"); + await withdrawModal.openTokensList(); + await withdrawModal.waitForTokenListElementsVisible(ASSET_NAME); + await withdrawModal.selectToken(ASSET_NAME); + await withdrawModal.enterValue("1"); + + await withdrawModal.waitForContinueState(true, 60000); + const isOriginFeeDisplayed = + await withdrawModal.isDestinationFeeDisplayed(); + expect(isOriginFeeDisplayed).toBeTruthy(); + + const isNetworkButtonEnabled = await withdrawModal.isNetworkButtonEnabled(); + expect(isNetworkButtonEnabled).toBeTruthy(); + + await withdrawModal.clickWithdrawButtonByText(WithdrawActionType.Network); + await acceptNetworkSwitchInNewWindow(driver); + + await withdrawModal.clickWithdrawButtonByText(WithdrawActionType.Withdraw); + await waitForActionNotification(driver, TransactionType.Withdraw); + + const tokensAmountAfter = + await walletWrapper.getMyTokensRowAmount(ASSET_NAME); + expect(await uiStringToNumber(tokensAmountAfter)).toBeLessThan( + await uiStringToNumber(tokensAmountBefore), + ); + + await walletWrapper.openDeposit(); + isDepositModalVisible = await depositModal.isModalVisible(); + expect(isDepositModalVisible).toBeTruthy(); + await depositModal.openChainList(); + await depositModal.selectChain("Ethereum"); + await depositModal.openTokensList(); + await depositModal.waitForTokenListElementsVisible(ASSET_NAME); + await depositModal.selectToken(ASSET_NAME); + await depositModal.waitTokenAmountChange(l1TokensAmountBefore); + + const l1TokensAmountAfter = await depositModal.getTokenAmount(); + expect(await uiStringToNumber(l1TokensAmountAfter)).toBeGreaterThan( + await uiStringToNumber(l1TokensAmountBefore), + ); + }); + + afterEach(async () => { + const session = await driver.getSession(); + await addExtraLogs( + driver, + expect.getState().currentTestName + " - " + session.getId(), + ); + }); + + afterAll(async () => { + const api = getApi(); + await api.disconnect(); + await driver.quit(); + DriverBuilder.destroy(); + }); +}); diff --git a/utils/frontend/rollup-pages/WalletWrapper.ts b/utils/frontend/rollup-pages/WalletWrapper.ts index a22c6bf10..f640f7748 100644 --- a/utils/frontend/rollup-pages/WalletWrapper.ts +++ b/utils/frontend/rollup-pages/WalletWrapper.ts @@ -1,4 +1,5 @@ import { WebDriver } from "selenium-webdriver"; +import { FIVE_MIN } from "../../Constants"; import { buildDataTestIdXpath, buildXpathByElementText, @@ -45,7 +46,11 @@ export class WalletWrapper { async openWalletConnectionInfo() { const walletWrapper = buildDataTestIdXpath(DIV_WALLET_WRAPPER); - await clickElement(this.driver, walletWrapper); + const depositButton = buildXpathByElementText("button", "Deposit"); + const isDepositDisplayed = await isDisplayed(this.driver, depositButton); + if (!isDepositDisplayed) { + await clickElement(this.driver, walletWrapper); + } } async openDeposit() { @@ -110,9 +115,36 @@ export class WalletWrapper { buildDataTestIdXpath(MY_TOKENS) + buildXpathByText(tokenName) + buildDataTestIdXpath(MY_TOKENS_ROW_AMOUNT); + await waitForElementVisible(this.driver, tokenRowAmount); return await getText(this.driver, tokenRowAmount); } + async waitTokenAmountChange( + tokenName: string, + initValue: string, + timeout = FIVE_MIN, + ) { + const startTime = Date.now(); + const endTime = startTime + timeout; + + while (Date.now() < endTime) { + try { + const tokenAmount = await this.getMyTokensRowAmount(tokenName); + if (tokenAmount !== initValue) { + return; + } + } catch (error) { + // Element not found or other error occurred, continue waiting + } + + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + + throw new Error( + `Timeout: Element value not as desired after ${timeout} milliseconds`, + ); + } + async getMyTokensRowFiatValue(tokenName: string) { const tokenFiatValue = buildDataTestIdXpath(MY_TOKENS) + diff --git a/utils/frontend/rollup-utils/DepositModal.ts b/utils/frontend/rollup-utils/DepositModal.ts index 0f06c85f2..cda98ea32 100644 --- a/utils/frontend/rollup-utils/DepositModal.ts +++ b/utils/frontend/rollup-utils/DepositModal.ts @@ -1,4 +1,5 @@ import { By, WebDriver } from "selenium-webdriver"; +import { FIVE_MIN } from "../../Constants"; import { sleep } from "../../utils"; import { buildDataTestIdXpath, @@ -25,12 +26,15 @@ const ORIGIN_FEE = "origin-fee"; const DESTINATION_FEE = "destination-fee"; const FEE_VALUE = "fee-value"; const ERR_MESSAGE = "deposit-error-message"; +const AMOUNT_FIELD = "AmountTooltip-anchor"; +const CLOSE_MODAL = "deposit-modal-close"; export enum DepositActionType { Deposit, Approve, Network, Approving, + Done, } export class DepositModal { @@ -45,6 +49,7 @@ export class DepositModal { [DepositActionType.Deposit]: "Deposit", [DepositActionType.Network]: "Switch to Holesky", [DepositActionType.Approving]: "Enabling Deposit...", + [DepositActionType.Done]: "Ok, I understand", }; async isModalVisible() { @@ -68,6 +73,10 @@ export class DepositModal { await clickElement(this.driver, buildDataTestIdXpath(BTN_CHAIN_SELECT)); } + async close() { + await clickElement(this.driver, buildDataTestIdXpath(CLOSE_MODAL)); + } + async isErrorMessage() { const errMessageXpath = buildDataTestIdXpath(ERR_MESSAGE); return await isDisplayed(this.driver, errMessageXpath); @@ -99,10 +108,11 @@ export class DepositModal { await clickElement(this.driver, tokenLocator); } - async getTokenAmount(assetName: string) { - const assetTestId = `token-list-token-${assetName}-balance`; - const assetLocator = buildDataTestIdXpath(assetTestId); - return parseFloat(await getText(this.driver, assetLocator)); + async getTokenAmount() { + const amountLocator = + buildDataTestIdXpath(DEPOSIT_MODAL_CONTENT) + + buildDataTestIdXpath(AMOUNT_FIELD); + return await getText(this.driver, amountLocator); } async waitForTokenListElementsVisible(assetName: string) { @@ -157,4 +167,26 @@ export class DepositModal { const xpath = buildXpathByElementText("button", "Switch to Holesky"); return await (await this.driver.findElement(By.xpath(xpath))).isEnabled(); } + + async waitTokenAmountChange(initValue: string, timeout = FIVE_MIN) { + const startTime = Date.now(); + const endTime = startTime + timeout; + + while (Date.now() < endTime) { + try { + const tokenAmount = await this.getTokenAmount(); + if (tokenAmount !== initValue) { + return; + } + } catch (error) { + // Element not found or other error occurred, continue waiting + } + + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + + throw new Error( + `Timeout: Element value not as desired after ${timeout} milliseconds`, + ); + } } diff --git a/utils/frontend/utils/Helper.ts b/utils/frontend/utils/Helper.ts index 23c6f035b..56eced996 100644 --- a/utils/frontend/utils/Helper.ts +++ b/utils/frontend/utils/Helper.ts @@ -647,6 +647,7 @@ export async function uiStringToNumber(stringValue: string) { let partIntNum: number; let partDecNum: number; let numberValue: number = 0; + stringValue = stringValue.replaceAll(",", ""); const partInt = stringValue.split(".")[0]; const partDec = stringValue.split(".")[1]; const millions = await stringValue.includes("M");