diff --git a/test/rollup-test/rollup-main.staking-list.test.ts b/test/rollup-test/rollup-main.staking-list.test.ts new file mode 100644 index 000000000..973f1d71f --- /dev/null +++ b/test/rollup-test/rollup-main.staking-list.test.ts @@ -0,0 +1,112 @@ +/* + * + * @group rollupStakingListDev + */ +import { jest } from "@jest/globals"; +import { WebDriver } from "selenium-webdriver"; +import { getApi, initApi } from "../../utils/api"; +import { DriverBuilder } from "../../utils/frontend/utils/Driver"; +import { + addExtraLogs, + extractNumberFromText, + importMetamaskExtension, +} from "../../utils/frontend/utils/Helper"; +import "dotenv/config"; +import { + connectWallet, + setupPage, + setupPageWithState, +} from "../../utils/frontend/rollup-utils/Handlers"; +import { Sidebar } from "../../utils/frontend/rollup-pages/Sidebar"; +import { StakingPage } from "../../utils/frontend/rollup-pages/StakingPage"; +import { StakingCollatorPage } from "../../utils/frontend/rollup-pages/StakingCollatorPage"; + +jest.spyOn(console, "log").mockImplementation(jest.fn()); + +jest.setTimeout(1500000); +let driver: WebDriver; +let stakingPage: StakingPage; +let sidebar: Sidebar; +let stakingCollatorPage: StakingCollatorPage; + +let acc_addr = ""; +let acc_addr_short = ""; + +describe("Gasp UI staking list tests", () => { + beforeAll(async () => { + try { + getApi(); + } catch (e) { + await initApi(); + } + + driver = await DriverBuilder.getInstance(true, true); + acc_addr = await importMetamaskExtension(driver, true); + acc_addr_short = acc_addr.slice(-4); + stakingPage = new StakingPage(driver); + sidebar = new Sidebar(driver); + stakingCollatorPage = new StakingCollatorPage(driver); + + await setupPage(driver); + await connectWallet(driver, "MetaMask", acc_addr_short); + }); + + it("User can enter staking page with list of collators", async () => { + await setupPageWithState(driver, acc_addr_short); + await sidebar.clickNavStaking(); + + await stakingPage.waitForCollatorsListVisible(); + const isCollatorsListVisible = await stakingPage.isCollatorsListDisplayed(); + expect(isCollatorsListVisible).toBeTruthy(); + }); + + it("In staking page user can see active collators with details (staked token, min stake, etc..)", async () => { + await setupPageWithState(driver, acc_addr_short); + await sidebar.clickNavStaking(); + + await stakingPage.waitForCollatorsVisible(); + const collatorInfo = await stakingPage.getCollatorInfo("active"); + expect(collatorInfo.collatorAddress).not.toBeEmpty(); + expect(collatorInfo.totalStake).not.toBeEmpty(); + expect(collatorInfo.minBond).toBeGreaterThan(0); + }); + + it("User can enter active collator details and see its stats and go back", async () => { + await setupPageWithState(driver, acc_addr_short); + await sidebar.clickNavStaking(); + const STAKED_TOKEN = "GASPV2"; + + await stakingPage.waitForCollatorsVisible(); + await stakingPage.chooseCollatorRow(); + const isCollatorsDetailCardVisible = + await stakingCollatorPage.isCollatorsDetailCardDisplayed(); + expect(isCollatorsDetailCardVisible).toBeTruthy(); + + const stakigDetails = await stakingCollatorPage.getStakingStats(); + expect(stakigDetails.rewards).toBeGreaterThan(0); + expect(stakigDetails.minStake).toContain(STAKED_TOKEN); + expect(extractNumberFromText(stakigDetails.delegators)).toBeGreaterThan(0); + expect(stakigDetails.totalStake).toContain(STAKED_TOKEN); + expect(stakigDetails.stakedToken).toContain(STAKED_TOKEN); + + await stakingCollatorPage.clickBack(); + await stakingPage.waitForCollatorsVisible(); + const isCollatorsListVisible = await stakingPage.isCollatorsListDisplayed(); + expect(isCollatorsListVisible).toBeTruthy(); + }); + + 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/StakingCollatorPage.ts b/utils/frontend/rollup-pages/StakingCollatorPage.ts new file mode 100644 index 000000000..9aaed75b0 --- /dev/null +++ b/utils/frontend/rollup-pages/StakingCollatorPage.ts @@ -0,0 +1,93 @@ +import { WebDriver } from "selenium-webdriver"; +import { + buildDataTestIdXpath, + clickElement, + getText, + isDisplayed, + textNumberToFloat, + waitForElementVisible, +} from "../utils/Helper"; + +const MIN_STAKE = "min-stake"; +const STAKED_TOKEN = "token"; +const TOTAL_STAKE = "total-stake"; +const DELEGATORS = "delegators"; +const REWARDS_AMOUNT = + '//div[@data-testid="rewards"]//span[contains(@class, "text-highlight")]//span'; + +export class StakingCollatorPage { + driver: WebDriver; + + constructor(driver: WebDriver) { + this.driver = driver; + } + + async isCollatorsDetailCardDisplayed() { + const itemXpath = buildDataTestIdXpath("collator-details"); + return isDisplayed(this.driver, itemXpath); + } + + async getRewards() { + await waitForElementVisible(this.driver, REWARDS_AMOUNT); + const rewards = textNumberToFloat( + await getText(this.driver, REWARDS_AMOUNT), + ); + return rewards; + } + + async getMinStake() { + const xpath = buildDataTestIdXpath(MIN_STAKE); + await waitForElementVisible(this.driver, xpath); + const minStake = await getText(this.driver, xpath); + return minStake; + } + + async getStakedToken() { + const xpath = buildDataTestIdXpath(STAKED_TOKEN); + await waitForElementVisible(this.driver, xpath); + const token = await getText(this.driver, xpath); + return token; + } + + async getTotalStake() { + const xpath = buildDataTestIdXpath(TOTAL_STAKE); + await waitForElementVisible(this.driver, xpath); + const stake = await getText(this.driver, xpath); + return stake; + } + + async getDelegators() { + const xpath = buildDataTestIdXpath(DELEGATORS); + await waitForElementVisible(this.driver, xpath); + const delegators = await getText(this.driver, xpath); + return delegators; + } + + async getStakingStats() { + try { + const [rewards, minStake, stakedToken, totalStake, delegators] = + await Promise.all([ + this.getRewards(), + this.getMinStake(), + this.getStakedToken(), + this.getTotalStake(), + this.getDelegators(), + ]); + + return { + rewards, + minStake, + stakedToken, + totalStake, + delegators, + }; + } catch (error) { + throw new Error(`Failed to fetch staking stats: ${error}`); + } + } + + async clickBack() { + const itemXpath = buildDataTestIdXpath("back-button"); + return clickElement(this.driver, itemXpath); + } +} diff --git a/utils/frontend/rollup-pages/StakingPage.ts b/utils/frontend/rollup-pages/StakingPage.ts new file mode 100644 index 000000000..07e940095 --- /dev/null +++ b/utils/frontend/rollup-pages/StakingPage.ts @@ -0,0 +1,200 @@ +import { By, WebDriver } from "selenium-webdriver"; +import { + buildClassXpath, + buildDataTestIdXpath, + buildXpathByText, + clickElement, + isDisplayed, + scrollElementIntoView, + scrollIntoView, + uiStringToNumber, + waitForElementVisible, + writeText, +} from "../utils/Helper"; +import toNumber from "lodash-es/toNumber"; + +const COLLATOR_ROW_ITEM = "collator-row-item"; + +export class StakingPage { + driver: WebDriver; + + constructor(driver: WebDriver) { + this.driver = driver; + } + + async isCollatorsListDisplayed() { + const itemXpath = buildDataTestIdXpath("active-collators-list"); + return isDisplayed(this.driver, itemXpath); + } + + async waitForCollatorsListVisible() { + const collatorListLocator = buildDataTestIdXpath("active-collators-list"); + await waitForElementVisible(this.driver, collatorListLocator, 8000); + } + + async waitForCollatorsVisible() { + const collatorListLocator = buildDataTestIdXpath("collator-row-item"); + await waitForElementVisible(this.driver, collatorListLocator, 60000); + } + + async waitForStakeVisible() { + const collatorListLocator = + buildDataTestIdXpath("collator-row-item") + + buildDataTestIdXpath("total-stake"); + await waitForElementVisible(this.driver, collatorListLocator, 8000); + } + + async getCollatorInfo(collatorType: string) { + const collatorListXpath = + buildDataTestIdXpath(collatorType + "-collators-list") + + buildDataTestIdXpath("collator-row-item"); + const addressXpath = + collatorListXpath + buildDataTestIdXpath("collator-address"); + const totalStakeXpath = + collatorListXpath + buildDataTestIdXpath("total-stake"); + const minBondXpath = + collatorListXpath + buildDataTestIdXpath("min-bond-value"); + const collatorAddress = await this.driver.findElement( + By.xpath(addressXpath), + ); + const collatorAddressText = await collatorAddress.getText(); + if (collatorType === "active") { + const totalStake = await this.driver.findElement( + By.xpath(totalStakeXpath), + ); + const totalStakeText = await totalStake.getText(); + const minBond = await this.driver.findElement(By.xpath(minBondXpath)); + const minBondText = await minBond.getText(); + const minBondNumber = minBondText.replace(",", ""); + const minBondValue = toNumber(minBondNumber); + + return { + collatorAddress: collatorAddressText, + totalStake: totalStakeText, + minBond: minBondValue, + }; + } else { + return { + collatorAddress: collatorAddressText, + }; + } + } + + async getCollatorsStakes(collatorsType: string) { + const collatorsListXpath = + buildDataTestIdXpath(collatorsType + "-collators-list") + + buildDataTestIdXpath("collator-row-item") + + buildDataTestIdXpath("total-stake"); + await scrollIntoView(this.driver, collatorsListXpath); + const stakes = await this.driver.findElements(By.xpath(collatorsListXpath)); + const collatorsStakesNumber: number[] = []; + for (let i = 0; i < stakes.length; i++) { + await scrollElementIntoView(this.driver, stakes[i]); + const stakesText = await stakes[i].getText(); + const stakesNumber = await uiStringToNumber(stakesText); + collatorsStakesNumber.push(stakesNumber); + } + return collatorsStakesNumber; + } + + async getCollatorsAddresses(collatorsType: string) { + const collatorsListXpath = + buildDataTestIdXpath(collatorsType + "-collators-list") + + buildClassXpath("w-full"); + const collatorsContainer = await this.driver.findElements( + By.xpath(collatorsListXpath), + ); + const collatorsAddressesString: string[] = []; + for (let i = 0; i < collatorsContainer.length; i++) { + const collatorHref = await collatorsContainer[i].getAttribute("href"); + const collatorAddressText = collatorHref.split("staking/")[1]; + collatorsAddressesString.push(collatorAddressText); + } + return collatorsAddressesString; + } + + async chooseCollatorRow(index = 0) { + const collatorRowXpath = + buildDataTestIdXpath("active-collators-list") + + buildDataTestIdXpath("collator-row-item") + + buildDataTestIdXpath("collator-address"); + const collatorAddresses = await this.driver.findElements( + By.xpath(collatorRowXpath), + ); + const collatorAddress = await collatorAddresses[index].getText(); + const collatorAddressValueXpath = + buildDataTestIdXpath("active-collators-list") + + buildDataTestIdXpath("collator-row-item") + + buildXpathByText(collatorAddress); + await clickElement(this.driver, collatorAddressValueXpath); + } + + async chooseCollatorByAddress(address = "XKD3") { + const collatorAdderssXpath = buildXpathByText(address); + const collatorRowLocator = + buildDataTestIdXpath(COLLATOR_ROW_ITEM) + collatorAdderssXpath; + await clickElement(this.driver, collatorRowLocator); + } + + async isCollatorsDetailCardDisplayed() { + const itemXpath = buildDataTestIdXpath("collator-details"); + return isDisplayed(this.driver, itemXpath); + } + + async startStaking() { + const startStakingButton = buildDataTestIdXpath( + "stake-widget-banner-new-cta", + ); + await clickElement(this.driver, startStakingButton); + } + + async setStakingValue(value: any) { + const stakingValueXpath = buildDataTestIdXpath( + "new-stake-widget-tokenInput-input", + ); + await waitForElementVisible(this.driver, stakingValueXpath, 5000); + await writeText(this.driver, stakingValueXpath, value); + } + + async waitForStakingFeeVisible() { + const collatorListLocator = buildDataTestIdXpath( + "new-stake-widget-details-fee-value", + ); + await waitForElementVisible(this.driver, collatorListLocator, 10000); + } + + async submitStaking() { + const startStakingButton = buildDataTestIdXpath("new-stake-widget-submit"); + await clickElement(this.driver, startStakingButton); + } + + async waitStartStakingButtonVisible() { + const collatorListLocator = buildDataTestIdXpath( + "stake-widget-banner-new-cta", + ); + await waitForElementVisible(this.driver, collatorListLocator, 4000); + } + + async isStartStakingButtonDisplayed() { + const itemXpath = buildDataTestIdXpath("stake-widget-banner-new-cta"); + return isDisplayed(this.driver, itemXpath); + } + + async goToPositionInfo() { + const positionInfoButton = buildDataTestIdXpath( + "stake-widget-banner-manage-position", + ); + await clickElement(this.driver, positionInfoButton); + } + + async getStakingButtonText() { + const startStakingButtonXpath = buildDataTestIdXpath( + "new-stake-widget-submit", + ); + const startStakingButton = await this.driver.findElement( + By.xpath(startStakingButtonXpath), + ); + const startStakingButtonText = await startStakingButton.getText(); + return startStakingButtonText; + } +} diff --git a/utils/frontend/utils/Helper.ts b/utils/frontend/utils/Helper.ts index 80969b814..99f0f5dac 100644 --- a/utils/frontend/utils/Helper.ts +++ b/utils/frontend/utils/Helper.ts @@ -765,3 +765,24 @@ export async function comparePoolsLists( } expect(bePoolsList).toIncludeSameMembers(fePoolsList); } + +export function textNumberToFloat(text: string): number { + try { + // Remove all commas and convert to float + const value = parseFloat(text.replace(/,/g, "")); + + // Check if the result is a valid number + if (isNaN(value)) { + throw new Error("Invalid number format"); + } + + return value; + } catch (error) { + throw new Error(`Failed to convert "${text}" to number: ${error}`); + } +} + +export function extractNumberFromText(text: string): number { + const match = text.replace(/[^0-9.-]+/g, ""); + return parseFloat(match); +}