From 26ba1b674cafa0406d10d21aabc071ef63d8094b Mon Sep 17 00:00:00 2001 From: Niraj Date: Mon, 28 Apr 2025 16:15:35 +0545 Subject: [PATCH] test: add outcome test on loggedIn state --- .../lib/pages/outcomeDetailsPage.ts | 233 +++++++- .../playwright/lib/pages/outcomesPage.ts | 333 ++++++++++- .../9-outcomes/outcomes.loggedin.spec.ts | 122 ++++ .../tests/9-outcomes/outcomes.spec.ts | 561 +++--------------- 4 files changed, 761 insertions(+), 488 deletions(-) create mode 100644 tests/govtool-frontend/playwright/tests/9-outcomes/outcomes.loggedin.spec.ts diff --git a/tests/govtool-frontend/playwright/lib/pages/outcomeDetailsPage.ts b/tests/govtool-frontend/playwright/lib/pages/outcomeDetailsPage.ts index 878d71b67..dd94a1c6b 100644 --- a/tests/govtool-frontend/playwright/lib/pages/outcomeDetailsPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/outcomeDetailsPage.ts @@ -1,7 +1,14 @@ import environments from "@constants/environments"; import { formatWithThousandSeparator } from "@helpers/adaFormat"; -import { Page, Response } from "@playwright/test"; -import { outcomeProposal, VoterType } from "@types"; +import { Browser, expect, Page, Response } from "@playwright/test"; +import { outcomeProposal, outcomeType } from "@types"; +import OutComesPage from "./outcomesPage"; +import { + areCCVoteTotalsDisplayed, + areDRepVoteTotalsDisplayed, + areSPOVoteTotalsDisplayed, +} from "@helpers/featureFlag"; +import { parseVotingPowerAndPercentage } from "@helpers/index"; export default class OutcomeDetailsPage { readonly dRepYesVotes = this.page.getByTestId("DReps-yes-votes-submitted"); @@ -81,4 +88,226 @@ export default class OutcomeDetailsPage { sPosNoConfidence, }; } + + async shouldDisplayCorrectVotingResults( + browser: Browser, + isLoggedIn = false + ) { + await Promise.all( + Object.keys(outcomeType).map(async (filterKey) => { + const outcomePage = new OutComesPage(this.page); + const { + govActionDetailsPage, + metricsResponsePromise, + outcomeResponsePromise, + } = await outcomePage.navigateToFilteredProposalDetail( + browser, + filterKey, + isLoggedIn + ); + + const outcomeResponse = await outcomeResponsePromise; + const proposalToCheck = (await outcomeResponse.json())[0]; + + const metricsResponse = await metricsResponsePromise; + + const { autoAbstain, noConfidence, sPosAutoAbstain, sPosNoConfidence } = + await govActionDetailsPage.getSposAndDRepAbstainNoConfidence( + metricsResponse + ); + + const currentPageUrl = govActionDetailsPage.currentPage.url(); + + // check dRep votes + if (await areDRepVoteTotalsDisplayed(proposalToCheck)) { + await govActionDetailsPage.dRepExpandButton.click(); + + await expect( + govActionDetailsPage.dRepResultData.getByRole("row", { + name: "Yes", + }), + { + message: `DRep "Yes" voting power checked for ${currentPageUrl}`, + } + ).toHaveText( + `Yes${formatWithThousandSeparator(proposalToCheck.yes_votes, false)}`, + { + timeout: 60_000, + } + ); //BUG missing testIds + + await expect( + govActionDetailsPage.dRepResultData.getByRole("row", { + name: "Auto-Abstain", + }), + { + message: `DRep "Auto-Abstain" voting power checked for ${currentPageUrl}`, + } + ).toHaveText(`Auto-Abstain${autoAbstain}`); //BUG missing testIds + await expect( + govActionDetailsPage.dRepResultData.getByRole("row", { + name: "No Confidence", + }), + { + message: `DRep "No Confidence" voting power checked for ${currentPageUrl}`, + } + ).toHaveText(`No Confidence${noConfidence}`); //BUG missing testIds + await expect( + govActionDetailsPage.dRepResultData.getByRole("row", { + name: "Explicit", + }), + { + message: `DRep "Explicit" voting power checked for ${currentPageUrl}`, + } + ).toHaveText( + `Explicit${formatWithThousandSeparator(proposalToCheck.abstain_votes, false)}` + ); + + await expect( + govActionDetailsPage.dRepResultData + .getByRole("row", { + name: "No", + }) + .first(), + { + message: `DRep "No" voting power checked for ${currentPageUrl}`, + } + ).toHaveText( + `No${formatWithThousandSeparator(proposalToCheck.no_votes, false)}` + ); //BUG missing testIds + } + + // check sPos votes + if (await areSPOVoteTotalsDisplayed(proposalToCheck)) { + await govActionDetailsPage.sPosExpandButton.click(); + const totalSposNoVotes = + filterKey === "NoConfidence" + ? proposalToCheck.pool_no_votes + : parseInt(sPosNoConfidence.replace(/,/g, "")) * 1000000 + + parseInt(proposalToCheck.pool_no_votes); + + const totalSposYesVotesForNoConfidence = + parseInt(sPosNoConfidence.replace(/,/g, "")) * 1000000 + + parseInt(proposalToCheck.pool_yes_votes); + + const totalSposYesVotes = + filterKey === "NoConfidence" + ? totalSposYesVotesForNoConfidence + : proposalToCheck.pool_yes_votes; + await expect( + govActionDetailsPage.sPosResultData.getByRole("row", { + name: "Yes", + }), + { + message: `SPos "Yes" voting power checked for ${currentPageUrl}`, + } + ).toHaveText( + `Yes${formatWithThousandSeparator(totalSposYesVotes, false)}`, + { + timeout: 60_000, + } + ); //BUG missing testIds + + await expect( + govActionDetailsPage.sPosResultData.getByRole("row", { + name: "Auto-Abstain", + }), + { + message: `SPos "Auto-Abstain" voting power checked for ${currentPageUrl}`, + } + ).toHaveText(`Auto-Abstain${sPosAutoAbstain}`); //BUG missing testIds + await expect( + govActionDetailsPage.sPosResultData.getByRole("row", { + name: "No Confidence", + }), + { + message: `SPos "No Confidence" voting power checked for ${currentPageUrl}`, + } + ).toHaveText(`No Confidence${sPosNoConfidence}`); //BUG missing testIds + await expect( + govActionDetailsPage.sPosResultData.getByRole("row", { + name: "Explicit", + }), + { + message: `SPos "Explicit" voting power checked for ${currentPageUrl}`, + } + ).toHaveText( + `Explicit${formatWithThousandSeparator(proposalToCheck.pool_abstain_votes, false)}` + ); //BUG missing testIds + await expect( + govActionDetailsPage.sPosResultData + .getByRole("row", { + name: "No", + }) + .first(), + { + message: `SPos "No" voting power checked for ${currentPageUrl}`, + } + ).toHaveText( + `No${formatWithThousandSeparator(totalSposNoVotes, false)}` + ); //BUG missing testIds + } + + // check ccCommittee votes + if (areCCVoteTotalsDisplayed(proposalToCheck)) { + const ccYesVoteSubmittedText = + await govActionDetailsPage.ccCommitteeYesVotes.textContent(); + + const { percentage: yesPercentage } = parseVotingPowerAndPercentage( + ccYesVoteSubmittedText + ); + + await expect(govActionDetailsPage.ccCommitteeYesVotes, { + message: `CC "Yes" vote count checked for ${currentPageUrl}`, + }).toHaveText(`${proposalToCheck.cc_yes_votes} - ${yesPercentage}`); + await expect( + govActionDetailsPage.cCResultData.getByRole("row", { + name: "Abstain Votes", + }), + { + message: `CC "Abstain" vote count checked for ${currentPageUrl}`, + } + ).toHaveText(`Abstain Votes${proposalToCheck.pool_abstain_votes}`); //BUG missing testIds + + const noPercentage = 100 - parseFloat(yesPercentage.replace("%", "")); + await expect(govActionDetailsPage.ccCommitteeNoVotes, { + message: `CC "No" vote count checked for ${currentPageUrl}`, + }).toHaveText( + `${proposalToCheck.cc_no_votes} - ${noPercentage.toFixed(2)}%` + ); + } + }) + ); + } + + async verifyInvalidOutcomeMetadata({ + outcomeResponse, + type, + url, + hash, + }: { + outcomeResponse: outcomeProposal; + type: string; + url: string; + hash: string; + }) { + await this.page.route(/.*\/governance-actions\/[a-f0-9]{64}\?.*/, (route) => + route.fulfill({ body: JSON.stringify([outcomeResponse]) }) + ); + + const outcomePage = new OutComesPage(this.page); + await outcomePage.goto(); + await outcomePage.viewFirstOutcomes(); + const outcomeTitle = await outcomePage.title.textContent(); + + await expect( + outcomePage.title, + outcomeTitle.toLowerCase() !== type.toLowerCase() && + `The URL "${url}" and hash "${hash}" do not match the expected properties for type "${type}".` + ).toHaveText(type, { + ignoreCase: true, + timeout: 60_000, + }); + await expect(outcomePage.metadataErrorLearnMoreBtn).toBeVisible(); + } } diff --git a/tests/govtool-frontend/playwright/lib/pages/outcomesPage.ts b/tests/govtool-frontend/playwright/lib/pages/outcomesPage.ts index 0d0d3e0f8..e8a72481a 100644 --- a/tests/govtool-frontend/playwright/lib/pages/outcomesPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/outcomesPage.ts @@ -2,9 +2,24 @@ import environments from "@constants/environments"; import { outcomeStatusType } from "@constants/index"; import { toCamelCase } from "@helpers/string"; import { functionWaitedAssert, waitedLoop } from "@helpers/waitedLoop"; -import { expect, Locator, Page } from "@playwright/test"; -import { outcomeProposal, outcomeType } from "@types"; +import { Browser, expect, Locator, Page } from "@playwright/test"; +import { outcomeMetadata, outcomeProposal, outcomeType } from "@types"; import OutcomeDetailsPage from "./outcomeDetailsPage"; +import { isMobile } from "@helpers/mobile"; +import extractExpiryDateFromText from "@helpers/extractExpiryDateFromText"; +import { createNewPageWithWallet, injectLogger } from "@helpers/page"; +import { createTempUserAuth } from "@datafactory/createAuth"; +import { user01Wallet } from "@constants/staticWallets"; +import { user01AuthFile } from "@constants/auth"; + +const status = ["Expired", "Ratified", "Enacted", "Live"]; + +enum SortOption { + SoonToExpire = "Soon to expire", + NewestFirst = "Newest first", + OldestFirst = "Oldest first", + HighestAmountYesVote = "Highest amount of yes votes", +} export default class OutComesPage { // Buttons @@ -236,4 +251,318 @@ export default class OutComesPage { }); return outcomeStatus.some((status) => filters.includes(status)); } + + async shouldAccessPage() { + await this.page.goto("/"); + + if (isMobile(this.page)) { + await this.page.getByTestId("open-drawer-button").click(); + } + await this.page.getByTestId("governance-actions-outcomes-link").click(); + + await expect(this.page.getByText(/outcomes/i)).toHaveCount(2); + } + + async filterOutcomes() { + await this.filterBtn.click(); + const filterOptionNames = Object.values(outcomeType); + + // proposal type filter + await this.applyAndValidateFilters( + filterOptionNames, + this._validateFiltersInOutcomeCard + ); + + // proposal status filter + await this.applyAndValidateFilters( + status, + this._validateStatusFiltersInOutcomeCard + ); + } + + async sortOutcomes() { + await this.sortBtn.click(); + + await this.sortAndValidate( + SortOption.NewestFirst, + (p1, p2) => p1.expiry_date >= p2.time + ); + + await this.sortAndValidate( + SortOption.OldestFirst, + (p1, p2) => p1.expiry_date <= p2.expiry_date + ); + + await this.sortAndValidate( + SortOption.HighestAmountYesVote, + (p1, p2) => parseInt(p1.yes_votes) >= parseInt(p2.yes_votes) + ); + } + + async filterAndSortOutcomes() { + const filterOptionKeys = Object.keys(outcomeType); + const filterOptionNames = Object.values(outcomeType); + + const choice = Math.floor(Math.random() * filterOptionKeys.length); + await this.goto({ filter: filterOptionKeys[choice] }); + await this.sortBtn.click(); + + await this.sortAndValidate( + SortOption.OldestFirst, + (p1, p2) => p1.expiry_date <= p2.expiry_date + ); + + await this.validateFilters( + [filterOptionNames[choice]], + this._validateFiltersInOutcomeCard + ); + } + + async verifyAllOutcomesAreExpired() { + const proposalCards = await this.getAllOutcomes(); + + for (const proposalCard of proposalCards) { + const expiryDateEl = proposalCard.locator( + '[data-testid$="-Expired-date"]' + ); + const expiryDateTxt = await expiryDateEl.innerText(); + const expiryDate = extractExpiryDateFromText(expiryDateTxt); + const today = new Date(); + expect(today >= expiryDate).toBeTruthy(); + } + } + + async VerifyLoadMoreOutcomes() { + const responsePromise = this.page.waitForResponse((response) => + response + .url() + .includes(`governance-actions?search=&filters=&sort=newestFirst&page=2`) + ); + await this.goto(); + + let governanceActionIdsBefore: String[]; + let governanceActionIdsAfter: String[]; + + await functionWaitedAssert( + async () => { + governanceActionIdsBefore = + await this.getAllListedCIP105GovernanceIds(); + await this.showMoreBtn.click(); + }, + { message: "Show more button not visible" } + ); + + const response = await responsePromise; + const governanceActionListAfter = await response.json(); + + await functionWaitedAssert( + async () => { + governanceActionIdsAfter = await this.getAllListedCIP105GovernanceIds(); + expect(governanceActionIdsAfter.length).toBeGreaterThan( + governanceActionIdsBefore.length + ); + }, + { message: "Outcomes not loaded after clicking show more" } + ); + + if (governanceActionListAfter.length >= governanceActionIdsBefore.length) { + await expect(this.showMoreBtn).toBeVisible(); + expect(true).toBeTruthy(); + } else { + await expect(this.showMoreBtn).not.toBeVisible(); + } + } + + async fetchOutcomeIdFromNetwork(governanceActionId: string) { + let updatedGovernanceActionId = governanceActionId; + await this.page.route( + "**/governance-actions?search=&filters=&sort=**", + async (route) => { + const response = await route.fetch(); + const data: outcomeProposal[] = await response.json(); + if (!governanceActionId) { + if (data.length > 0) { + const randomIndexForId = Math.floor(Math.random() * data.length); + updatedGovernanceActionId = + data[randomIndexForId].tx_hash + + "#" + + data[randomIndexForId].index; + } + } + await route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify(data), + }); + } + ); + + const responsePromise = this.page.waitForResponse( + "**/governance-actions?search=&filters=&sort=**" + ); + await this.goto(); + await responsePromise; + return updatedGovernanceActionId; + } + + async fetchOutcomeTitleFromNetwork(governanceActionTitle: string) { + let updatedGovernanceActionTitle = governanceActionTitle; + await this.page.route( + "**/governance-actions/metadata?**", + async (route) => { + try { + const response = await route.fetch(); + if (response.status() !== 200) { + await route.continue(); + return; + } + const data: outcomeMetadata = await response.json(); + if (!governanceActionTitle && data.data.title != null) { + updatedGovernanceActionTitle = data.data.title; + } + await route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify(data), + }); + } catch (error) { + return; + } + } + ); + await this.goto(); + const metadataResponsePromise = this.page.waitForResponse( + "**/governance-actions/metadata?**" + ); + await metadataResponsePromise; + return updatedGovernanceActionTitle; + } + + async searchOutcomesById(governanceActionId: string) { + await this.searchInput.fill(governanceActionId); + await expect( + this.page.getByRole("progressbar").getByRole("img") + ).toBeVisible(); + + await functionWaitedAssert( + async () => { + const idSearchOutcomeCards = await this.getAllOutcomes(); + expect(idSearchOutcomeCards.length, { + message: + idSearchOutcomeCards.length == 0 && "No governance actions found", + }).toBeGreaterThan(0); + for (const outcomeCard of idSearchOutcomeCards) { + const id = await outcomeCard + .locator('[data-testid$="-CIP-105-id"]') + .textContent(); + expect(id.replace(/^.*ID/, "")).toContain(governanceActionId); + } + }, + { name: "search by id" } + ); + } + + async searchOutcomesByTitle(governanceActionTitle: string) { + await this.searchInput.fill(governanceActionTitle); + await expect( + this.page.getByRole("progressbar").getByRole("img") + ).toBeVisible(); + + await functionWaitedAssert( + async () => { + const titleSearchOutcomeCards = await this.getAllOutcomes(); + expect(titleSearchOutcomeCards.length, { + message: + titleSearchOutcomeCards.length == 0 && + "No governance actions found", + }).toBeGreaterThan(0); + for (const outcomeCard of titleSearchOutcomeCards) { + const title = await outcomeCard + .locator('[data-testid$="-card-title"]') + .textContent(); + expect(title.toLowerCase()).toContain( + governanceActionTitle.toLowerCase() + ); + } + }, + { name: "search by title" } + ); + } + + async shouldCopyGovernanceActionId(governanceActionId: string) { + await this.searchInput.fill(governanceActionId); + + await this.page + .getByTestId(`${governanceActionId}-CIP-105-id`) + .getByTestId("copy-button") + .click(); + await expect(this.page.getByText("Copied to clipboard")).toBeVisible({ + timeout: 60_000, + }); + const copiedTextDRepDirectory = await this.page.evaluate(() => + navigator.clipboard.readText() + ); + expect(copiedTextDRepDirectory).toEqual(governanceActionId); + } + + async navigateToFilteredProposalDetail( + browser: Browser, + filterKey: string, + isLoggedIn: boolean + ) { + let page: Page; + if (!isLoggedIn) { + page = await browser.newPage(); + } else { + page = await createNewPageWithWallet(browser, { + storageState: user01AuthFile, + wallet: user01Wallet, + }); + } + injectLogger(page); + + const outcomeListResponsePromise = page.waitForResponse( + (response) => + response + .url() + .includes(`governance-actions?search=&filters=${filterKey}`), + { timeout: 60_000 } + ); + + const metricsResponsePromise = page.waitForResponse( + (response) => response.url().includes(`/misc/network/metrics?epoch`), + { timeout: 60_000 } + ); + + const outcomePage = new OutComesPage(page); + await outcomePage.goto({ filter: filterKey }); + + const outcomeListResponse = await outcomeListResponsePromise; + const proposals = await outcomeListResponse.json(); + + expect( + proposals.length, + proposals.length == 0 && "No proposals found!" + ).toBeGreaterThan(0); + + const { index: governanceActionIndex, tx_hash: governanceTransactionHash } = + proposals[0]; + + const outcomeResponsePromise = page.waitForResponse( + (response) => + response + .url() + .includes( + `governance-actions/${governanceTransactionHash}?index=${governanceActionIndex}` + ), + { timeout: 60_000 } + ); + + const govActionDetailsPage = await outcomePage.viewFirstOutcomes(); + return { + govActionDetailsPage, + outcomeResponsePromise, + metricsResponsePromise, + }; + } } diff --git a/tests/govtool-frontend/playwright/tests/9-outcomes/outcomes.loggedin.spec.ts b/tests/govtool-frontend/playwright/tests/9-outcomes/outcomes.loggedin.spec.ts new file mode 100644 index 000000000..864286c65 --- /dev/null +++ b/tests/govtool-frontend/playwright/tests/9-outcomes/outcomes.loggedin.spec.ts @@ -0,0 +1,122 @@ +import { user01AuthFile } from "@constants/auth"; +import { InvalidMetadata } from "@constants/index"; +import { user01Wallet } from "@constants/staticWallets"; +import { test } from "@fixtures/walletExtension"; +import { setAllureEpic } from "@helpers/allure"; +import OutcomeDetailsPage from "@pages/outcomeDetailsPage"; +import OutComesPage from "@pages/outcomesPage"; +import { Page } from "@playwright/test"; + +const invalidOutcomeProposals = require("../../lib/_mock/outcome.json"); + +test.beforeEach(async () => { + await setAllureEpic("9. Outcomes"); +}); + +test.use({ + storageState: user01AuthFile, + wallet: user01Wallet, +}); + +test.describe("Outcomes page", () => { + let outcomePage: OutComesPage; + test.beforeEach(async ({ page }) => { + outcomePage = new OutComesPage(page); + }); + + test("9A_2. Should access Outcomes page in connected state", async () => { + await outcomePage.shouldAccessPage(); + }); + test.describe("outcome sorting and filtering", () => { + test("9C_1B. Should filter Governance Action Type on governance actions page", async () => { + test.slow(); + await outcomePage.goto(); + + await outcomePage.filterOutcomes(); + }); + + test("9C_2B. Should sort Governance Action Type on outcomes page", async () => { + test.slow(); + + await outcomePage.goto({ sort: "oldestFirst" }); + + await outcomePage.sortOutcomes(); + }); + + test("9C_3B. Should filter and sort Governance Action Type on outcomes page", async () => { + await outcomePage.filterAndSortOutcomes(); + }); + }); + + test("9E_2. Should verify all of the displayed governance actions have expired", async () => { + await outcomePage.goto(); + + await outcomePage.verifyAllOutcomesAreExpired(); + }); + + test("9F_2. Should load more Outcomes on show more", async () => { + await outcomePage.VerifyLoadMoreOutcomes(); + }); + + test.describe("Outcome details dependent test", () => { + let governanceActionId: string | undefined; + let governanceActionTitle: string | undefined; + let currentPage: Page; + test.beforeEach(async ({ page }) => { + const outcomePage = new OutComesPage(page); + governanceActionId = + await outcomePage.fetchOutcomeIdFromNetwork(governanceActionId); + governanceActionTitle = await outcomePage.fetchOutcomeTitleFromNetwork( + governanceActionTitle + ); + currentPage = page; + }); + + test("9B_2. Should search outcomes proposal by title and id", async () => { + // search by id + await outcomePage.searchOutcomesById(governanceActionId); + + await outcomePage.searchOutcomesByTitle(governanceActionTitle); + }); + + test("9D_2. Should copy governanceActionId in disconnect state", async ({ + context, + }) => { + await context.grantPermissions(["clipboard-read", "clipboard-write"]); + await outcomePage.shouldCopyGovernanceActionId(governanceActionId); + }); + }); +}); + +test.describe("Outcome details", () => { + test("9G_2. Should display correct vote counts on outcome details page", async ({ + browser, + page, + }) => { + const outcomeDetailPage = new OutcomeDetailsPage(page); + + await outcomeDetailPage.shouldDisplayCorrectVotingResults(browser, true); + }); + + test.describe("Invalid Outcome Metadata", () => { + InvalidMetadata.forEach(({ type, reason, url, hash }, index) => { + test(`9H_${index + 1}B: Should display "${type}" message in outcomes when ${reason}`, async ({ + page, + }) => { + const outcomeResponse = { + ...invalidOutcomeProposals[0], + url, + data_hash: hash, + }; + + const outcomeDetailPage = new OutcomeDetailsPage(page); + await outcomeDetailPage.verifyInvalidOutcomeMetadata({ + outcomeResponse, + type, + url, + hash, + }); + }); + }); + }); +}); diff --git a/tests/govtool-frontend/playwright/tests/9-outcomes/outcomes.spec.ts b/tests/govtool-frontend/playwright/tests/9-outcomes/outcomes.spec.ts index 0976ebb49..6e67b8727 100644 --- a/tests/govtool-frontend/playwright/tests/9-outcomes/outcomes.spec.ts +++ b/tests/govtool-frontend/playwright/tests/9-outcomes/outcomes.spec.ts @@ -1,20 +1,9 @@ import { InvalidMetadata } from "@constants/index"; import { test } from "@fixtures/walletExtension"; -import { formatWithThousandSeparator } from "@helpers/adaFormat"; import { setAllureEpic } from "@helpers/allure"; -import extractExpiryDateFromText from "@helpers/extractExpiryDateFromText"; -import { - areCCVoteTotalsDisplayed, - areDRepVoteTotalsDisplayed, - areSPOVoteTotalsDisplayed, -} from "@helpers/featureFlag"; -import { parseVotingPowerAndPercentage } from "@helpers/index"; -import { isMobile } from "@helpers/mobile"; -import { injectLogger } from "@helpers/page"; -import { functionWaitedAssert } from "@helpers/waitedLoop"; +import OutcomeDetailsPage from "@pages/outcomeDetailsPage"; import OutComesPage from "@pages/outcomesPage"; -import { expect, Page } from "@playwright/test"; -import { outcomeMetadata, outcomeProposal, outcomeType } from "@types"; +import { Page } from "@playwright/test"; const invalidOutcomeProposals = require("../../lib/_mock/outcome.json"); @@ -22,501 +11,105 @@ test.beforeEach(async () => { await setAllureEpic("9. Outcomes"); }); -const status = ["Expired", "Ratified", "Enacted", "Live"]; - -enum SortOption { - SoonToExpire = "Soon to expire", - NewestFirst = "Newest first", - OldestFirst = "Oldest first", - HighestAmountYesVote = "Highest amount of yes votes", -} -test("9A. Should access Outcomes page in disconnect state", async ({ - page, -}) => { - await page.goto("/"); - - if (isMobile(page)) { - await page.getByTestId("open-drawer-button").click(); - } - await page.getByTestId("governance-actions-outcomes-link").click(); - - await expect(page.getByText(/outcomes/i)).toHaveCount(2); -}); - -test.describe("Outcome details dependent test", () => { - let governanceActionId: string | undefined; - let governanceActionTitle: string | undefined; - let currentPage: Page; +test.describe("Outcomes page", () => { + let outcomePage: OutComesPage; test.beforeEach(async ({ page }) => { - // intercept outcomes data for id - await page.route( - "**/governance-actions?search=&filters=&sort=**", - async (route) => { - const response = await route.fetch(); - const data: outcomeProposal[] = await response.json(); - if (!governanceActionId) { - if (data.length > 0) { - const randomIndexForId = Math.floor(Math.random() * data.length); - governanceActionId = - data[randomIndexForId].tx_hash + - "#" + - data[randomIndexForId].index; - } - } - await route.fulfill({ - status: 200, - contentType: "application/json", - body: JSON.stringify(data), - }); - } - ); - - // intercept metadata for title - await page.route("**/governance-actions/metadata?**", async (route) => { - try { - const response = await route.fetch(); - if (response.status() !== 200) { - await route.continue(); - return; - } - const data: outcomeMetadata = await response.json(); - if (!governanceActionTitle && data.data.title != null) { - governanceActionTitle = data.data.title; - } - await route.fulfill({ - status: 200, - contentType: "application/json", - body: JSON.stringify(data), - }); - } catch (error) { - return; - } - }); - - const responsePromise = page.waitForResponse( - "**/governance-actions?search=&filters=&sort=**" - ); - const metadataResponsePromise = page.waitForResponse( - "**/governance-actions/metadata?**" - ); - - const outcomesPage = new OutComesPage(page); - await outcomesPage.goto(); - - await responsePromise; - await metadataResponsePromise; - currentPage = page; + outcomePage = new OutComesPage(page); }); - test("9B. Should search outcomes proposal by title and id", async ({}) => { - const outcomesPage = new OutComesPage(currentPage); - // search by id - await outcomesPage.searchInput.fill(governanceActionId); - await expect( - currentPage.getByRole("progressbar").getByRole("img") - ).toBeVisible(); - - await functionWaitedAssert( - async () => { - const idSearchOutcomeCards = await outcomesPage.getAllOutcomes(); - expect(idSearchOutcomeCards.length, { - message: - idSearchOutcomeCards.length == 0 && "No governance actions found", - }).toBeGreaterThan(0); - for (const outcomeCard of idSearchOutcomeCards) { - const id = await outcomeCard - .locator('[data-testid$="-CIP-105-id"]') - .textContent(); - expect(id.replace(/^.*ID/, "")).toContain(governanceActionId); - } - }, - { name: "search by id" } - ); - - // search by title - await outcomesPage.searchInput.fill(governanceActionTitle); - await expect( - currentPage.getByRole("progressbar").getByRole("img") - ).toBeVisible(); - - await functionWaitedAssert( - async () => { - const titleSearchOutcomeCards = await outcomesPage.getAllOutcomes(); - expect(titleSearchOutcomeCards.length, { - message: - titleSearchOutcomeCards.length == 0 && - "No governance actions found", - }).toBeGreaterThan(0); - for (const outcomeCard of titleSearchOutcomeCards) { - const title = await outcomeCard - .locator('[data-testid$="-card-title"]') - .textContent(); - expect(title.toLowerCase()).toContain( - governanceActionTitle.toLowerCase() - ); - } - }, - { name: "search by title" } - ); + test("9A_1. Should access Outcomes page in disconnect state", async () => { + await outcomePage.shouldAccessPage(); }); - test("9D. Should copy governanceActionId", async ({ page, context }) => { - await context.grantPermissions(["clipboard-read", "clipboard-write"]); - const outcomesPage = new OutComesPage(currentPage); - - await outcomesPage.searchInput.fill(governanceActionId); + test.describe("outcome sorting and filtering", () => { + test("9C_1A. Should filter Governance Action Type on governance actions page in disconnect state", async () => { + test.slow(); + await outcomePage.goto(); - await page - .getByTestId(`${governanceActionId}-CIP-105-id`) - .getByTestId("copy-button") - .click(); - await expect(page.getByText("Copied to clipboard")).toBeVisible({ - timeout: 60_000, + await outcomePage.filterOutcomes(); }); - const copiedTextDRepDirectory = await page.evaluate(() => - navigator.clipboard.readText() - ); - expect(copiedTextDRepDirectory).toEqual(governanceActionId); - }); -}); - -test("9C_1. Should filter Governance Action Type on governance actions page", async ({ - page, -}) => { - test.slow(); - const outcomePage = new OutComesPage(page); - await outcomePage.goto(); - - await outcomePage.filterBtn.click(); - const filterOptionNames = Object.values(outcomeType); - - // proposal type filter - await outcomePage.applyAndValidateFilters( - filterOptionNames, - outcomePage._validateFiltersInOutcomeCard - ); - - // proposal status filter - await outcomePage.applyAndValidateFilters( - status, - outcomePage._validateStatusFiltersInOutcomeCard - ); -}); - -test("9C_2. Should sort Governance Action Type on outcomes page", async ({ - page, -}) => { - test.slow(); - - const outcomePage = new OutComesPage(page); - await outcomePage.goto({ sort: "oldestFirst" }); - - await outcomePage.sortBtn.click(); - - await outcomePage.sortAndValidate( - SortOption.NewestFirst, - (p1, p2) => p1.expiry_date >= p2.time - ); - - await outcomePage.sortAndValidate( - SortOption.OldestFirst, - (p1, p2) => p1.expiry_date <= p2.expiry_date - ); - - await outcomePage.sortAndValidate( - SortOption.HighestAmountYesVote, - (p1, p2) => parseInt(p1.yes_votes) >= parseInt(p2.yes_votes) - ); -}); - -test("9C_3. Should filter and sort Governance Action Type on outcomes page", async ({ - page, -}) => { - const outcomePage = new OutComesPage(page); - const filterOptionKeys = Object.keys(outcomeType); - const filterOptionNames = Object.values(outcomeType); - - const choice = Math.floor(Math.random() * filterOptionKeys.length); - await outcomePage.goto({ filter: filterOptionKeys[choice] }); - await outcomePage.sortBtn.click(); - - await outcomePage.sortAndValidate( - SortOption.OldestFirst, - (p1, p2) => p1.expiry_date <= p2.expiry_date - ); - - await outcomePage.validateFilters( - [filterOptionNames[choice]], - outcomePage._validateFiltersInOutcomeCard - ); -}); - -test("9E. Should verify all of the displayed governance actions have expired", async ({ - page, -}) => { - const outcomePage = new OutComesPage(page); - await outcomePage.goto(); - - const proposalCards = await outcomePage.getAllOutcomes(); - - for (const proposalCard of proposalCards) { - const expiryDateEl = proposalCard.locator('[data-testid$="-Expired-date"]'); - const expiryDateTxt = await expiryDateEl.innerText(); - const expiryDate = extractExpiryDateFromText(expiryDateTxt); - const today = new Date(); - expect(today >= expiryDate).toBeTruthy(); - } -}); + test("9C_2A. Should sort Governance Action Type on outcomes page in disconnect state", async () => { + test.slow(); -test("9F. Should load more Outcomes on show more", async ({ page }) => { - const responsePromise = page.waitForResponse((response) => - response - .url() - .includes(`governance-actions?search=&filters=&sort=newestFirst&page=2`) - ); - const outcomePage = new OutComesPage(page); - await outcomePage.goto(); + await outcomePage.goto({ sort: "oldestFirst" }); - let governanceActionIdsBefore: String[]; - let governanceActionIdsAfter: String[]; - - await functionWaitedAssert( - async () => { - governanceActionIdsBefore = - await outcomePage.getAllListedCIP105GovernanceIds(); - await outcomePage.showMoreBtn.click(); - }, - { message: "Show more button not visible" } - ); - - const response = await responsePromise; - const governanceActionListAfter = await response.json(); - - await functionWaitedAssert( - async () => { - governanceActionIdsAfter = - await outcomePage.getAllListedCIP105GovernanceIds(); - expect(governanceActionIdsAfter.length).toBeGreaterThan( - governanceActionIdsBefore.length - ); - }, - { message: "Outcomes not loaded after clicking show more" } - ); + await outcomePage.sortOutcomes(); + }); - if (governanceActionListAfter.length >= governanceActionIdsBefore.length) { - await expect(outcomePage.showMoreBtn).toBeVisible(); - expect(true).toBeTruthy(); - } else { - await expect(outcomePage.showMoreBtn).not.toBeVisible(); - } -}); + test("9C_3A. Should filter and sort Governance Action Type on outcomes page in disconnect state", async () => { + await outcomePage.filterAndSortOutcomes(); + }); + }); -test("9G. Should display correct vote counts on outcome details page", async ({ - browser, -}) => { - await Promise.all( - Object.keys(outcomeType).map(async (filterKey) => { - const page = await browser.newPage(); - injectLogger(page); + test("9E_1. Should verify all of the displayed governance actions have expired in disconnect state", async () => { + await outcomePage.goto(); - const outcomeListResponsePromise = page.waitForResponse( - (response) => - response - .url() - .includes(`governance-actions?search=&filters=${filterKey}`), - { timeout: 60_000 } - ); + await outcomePage.verifyAllOutcomesAreExpired(); + }); - const metricsResponsePromise = page.waitForResponse( - (response) => response.url().includes(`/misc/network/metrics?epoch`), - { timeout: 60_000 } - ); + test("9F_1. Should load more Outcomes on show more in disconnect state", async () => { + await outcomePage.VerifyLoadMoreOutcomes(); + }); + test.describe("Outcome details dependent test", () => { + let governanceActionId: string | undefined; + let governanceActionTitle: string | undefined; + let currentPage: Page; + test.beforeEach(async ({ page }) => { const outcomePage = new OutComesPage(page); - await outcomePage.goto({ filter: filterKey }); - - const outcomeListResponse = await outcomeListResponsePromise; - const proposals = await outcomeListResponse.json(); - - expect( - proposals.length, - proposals.length == 0 && "No proposals found!" - ).toBeGreaterThan(0); - - const { - index: governanceActionIndex, - tx_hash: governanceTransactionHash, - } = proposals[0]; - - const outcomeResponsePromise = page.waitForResponse( - (response) => - response - .url() - .includes( - `governance-actions/${governanceTransactionHash}?index=${governanceActionIndex}` - ), - { timeout: 60_000 } + governanceActionId = + await outcomePage.fetchOutcomeIdFromNetwork(governanceActionId); + governanceActionTitle = await outcomePage.fetchOutcomeTitleFromNetwork( + governanceActionTitle ); + currentPage = page; + }); - const govActionDetailsPage = await outcomePage.viewFirstOutcomes(); - - const outcomeResponse = await outcomeResponsePromise; - const proposalToCheck = (await outcomeResponse.json())[0]; - - const metricsResponse = await metricsResponsePromise; - - const { autoAbstain, noConfidence, sPosAutoAbstain, sPosNoConfidence } = - await govActionDetailsPage.getSposAndDRepAbstainNoConfidence( - metricsResponse - ); - - // check dRep votes - if (await areDRepVoteTotalsDisplayed(proposalToCheck)) { - await govActionDetailsPage.dRepExpandButton.click(); - - await expect( - govActionDetailsPage.dRepResultData.getByRole("row", { - name: "Yes", - }) - ).toHaveText( - `Yes${formatWithThousandSeparator(proposalToCheck.yes_votes, false)}`, - { - timeout: 60_000, - } - ); //BUG missing testIds - - await expect( - govActionDetailsPage.dRepResultData.getByRole("row", { - name: "Auto-Abstain", - }) - ).toHaveText(`Auto-Abstain${autoAbstain}`); //BUG missing testIds - await expect( - govActionDetailsPage.dRepResultData.getByRole("row", { - name: "No Confidence", - }) - ).toHaveText(`No Confidence${noConfidence}`); //BUG missing testIds - await expect( - govActionDetailsPage.dRepResultData.getByRole("row", { - name: "Explicit", - }) - ).toHaveText( - `Explicit${formatWithThousandSeparator(proposalToCheck.abstain_votes, false)}` - ); - - await expect( - govActionDetailsPage.dRepResultData - .getByRole("row", { - name: "No", - }) - .first() - ).toHaveText( - `No${formatWithThousandSeparator(proposalToCheck.no_votes, false)}` - ); //BUG missing testIds - } - - // check sPos votes - if (await areSPOVoteTotalsDisplayed(proposalToCheck)) { - await govActionDetailsPage.sPosExpandButton.click(); - const totalSposNoVotes = - parseInt(sPosNoConfidence.replace(/,/g, "")) + - proposalToCheck.pool_no_votes * 1000000; - - await expect( - govActionDetailsPage.sPosResultData.getByRole("row", { - name: "Yes", - }) - ).toHaveText( - `Yes${formatWithThousandSeparator(proposalToCheck.pool_yes_votes, false)}`, - { - timeout: 60_000, - } - ); //BUG missing testIds - - await expect( - govActionDetailsPage.sPosResultData.getByRole("row", { - name: "Auto-Abstain", - }) - ).toHaveText(`Auto-Abstain${sPosAutoAbstain}`); //BUG missing testIds - await expect( - govActionDetailsPage.sPosResultData.getByRole("row", { - name: "No Confidence", - }) - ).toHaveText(`No Confidence${sPosNoConfidence}`); //BUG missing testIds - await expect( - govActionDetailsPage.sPosResultData.getByRole("row", { - name: "Explicit", - }) - ).toHaveText( - `Explicit${formatWithThousandSeparator(proposalToCheck.pool_abstain_votes, false)}` - ); //BUG missing testIds - await expect( - govActionDetailsPage.sPosResultData - .getByRole("row", { - name: "No", - }) - .first() - ).toHaveText(`No${formatWithThousandSeparator(totalSposNoVotes)}`); //BUG missing testIds - } - - // check ccCommittee votes - if (areCCVoteTotalsDisplayed(proposalToCheck)) { - const ccYesVoteSubmittedText = - await govActionDetailsPage.ccCommitteeYesVotes.textContent(); - const ccNoVoteSubmittedText = - await govActionDetailsPage.ccCommitteeYesVotes.textContent(); - const { percentage: yesPercentage } = parseVotingPowerAndPercentage( - ccYesVoteSubmittedText - ); - const { percentage: noPercentage } = parseVotingPowerAndPercentage( - ccNoVoteSubmittedText - ); - await expect(govActionDetailsPage.ccCommitteeYesVotes).toHaveText( - `${proposalToCheck.cc_yes_votes} - ${yesPercentage}` - ); - await expect( - govActionDetailsPage.cCResultData.getByRole("row", { - name: "Abstain Votes", - }) - ).toHaveText(`Abstain Votes${proposalToCheck.pool_abstain_votes}`); //BUG missing testIds + test("9B_1. Should search outcomes proposal by title and id in disconnect state", async () => { + // search by id + await outcomePage.searchOutcomesById(governanceActionId); - await expect(govActionDetailsPage.ccCommitteeNoVotes).toHaveText( - `${proposalToCheck.cc_no_votes} - ${noPercentage}` - ); - } - }) - ); -}); + await outcomePage.searchOutcomesByTitle(governanceActionTitle); + }); -test.describe("Invalid Outcome Metadata", () => { - InvalidMetadata.forEach(({ type, reason, url, hash }, index) => { - test(`9H_${index + 1}: Should display "${type}" message in outcomes when ${reason}`, async ({ - page, + test("9D_1. Should copy governanceActionId in disconnect state", async ({ + context, }) => { - const outcomeResponse = { - ...invalidOutcomeProposals[0], - url, - data_hash: hash, - }; - - await page.route(/.*\/governance-actions\/[a-f0-9]{64}\?.*/, (route) => - route.fulfill({ body: JSON.stringify([outcomeResponse]) }) - ); + await context.grantPermissions(["clipboard-read", "clipboard-write"]); + await outcomePage.shouldCopyGovernanceActionId(governanceActionId); + }); + }); +}); - const outcomePage = new OutComesPage(page); - await outcomePage.goto(); - await outcomePage.viewFirstOutcomes(); - const outcomeTitle = await outcomePage.title.textContent(); +test.describe("Outcome details", () => { + test("9G_1. Should display correct vote counts on outcome details page in disconnect state", async ({ + browser, + page, + }) => { + const outcomeDetailPage = new OutcomeDetailsPage(page); + await outcomeDetailPage.shouldDisplayCorrectVotingResults(browser); + }); - await expect( - outcomePage.title, - outcomeTitle.toLowerCase() !== type.toLowerCase() && - `The URL "${url}" and hash "${hash}" do not match the expected properties for type "${type}".` - ).toHaveText(type, { - ignoreCase: true, - timeout: 60_000, + test.describe("Invalid Outcome Metadata", () => { + InvalidMetadata.forEach(({ type, reason, url, hash }, index) => { + test(`9H_${index + 1}A: Should display "${type}" message in outcomes when ${reason}`, async ({ + page, + }) => { + const outcomeResponse = { + ...invalidOutcomeProposals[0], + url, + data_hash: hash, + }; + + const outcomeDetailPage = new OutcomeDetailsPage(page); + await outcomeDetailPage.verifyInvalidOutcomeMetadata({ + outcomeResponse, + type, + url, + hash, + }); }); - await expect(outcomePage.metadataErrorLearnMoreBtn).toBeVisible(); }); }); });