Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions tests/govtool-frontend/playwright/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ npm run generate-wallets

### 🔁 Run All Tests

- **Pre-requisite**: Ensure the faucet address holds at least **412,000 ADA**.
- **Pre-requisite**: Ensure the faucet address holds at least **512,000 ADA**.

#### 🖥️ UI Mode

Expand Down Expand Up @@ -270,7 +270,7 @@ npm run test:headless:proposal-pillar

#### 5. **Proposal Discussion**

- **Pre-requisite**: Ensure the faucet address holds at least **401,000 ADA**.
- **Pre-requisite**: Ensure the faucet address holds at least **501,000 ADA**.

#### 🖥️ UI Mode

Expand Down
Binary file modified tests/govtool-frontend/playwright/docs/proposal-discussion.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions tests/govtool-frontend/playwright/lib/constants/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export const proposal07AuthFile = ".auth/proposal07.json";
export const proposal08AuthFile = ".auth/proposal08.json";
export const proposal09AuthFile = ".auth/proposal09.json";

export const proposalSubmissionAuthFile = ".auth/proposalSubmission.json";

export const budgetProposal01AuthFile = ".auth/budgetProposal01.json";
export const budgetProposal02AuthFile = ".auth/budgetProposal02.json";
export const budgetProposal03AuthFile = ".auth/budgetProposal03.json";
Expand Down
11 changes: 11 additions & 0 deletions tests/govtool-frontend/playwright/lib/helpers/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import loadDemosExtension from "@fixtures/loadExtension";
import { Browser, ConsoleMessage, Page } from "@playwright/test";
import { StaticWallet } from "@types";
import { Logger } from "./logger";
import kuberService from "@services/kuberService";

interface NewPageConfig {
storageState?: string;
Expand Down Expand Up @@ -48,3 +49,13 @@ export function injectLogger(page: Page) {
page.isLoggerInjected = true;
}
}

export async function logWalletDetails(address: string) {
try {
const balance = await kuberService.getBalance(address);
console.log("wallet balance", balance);
} catch (error) {
console.log("failed to get balance", error);
}
console.log("wallet address", address);
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ export default class ProposalSubmissionPage {

constructor(private readonly page: Page) {}

get currentPage(): Page {
return this.page;
}

async goto() {
await this.page.goto(`${environments.frontendUrl}/proposal_discussion`);

Expand Down
7 changes: 7 additions & 0 deletions tests/govtool-frontend/playwright/lib/walletManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ class WalletManager {
);
await this.writeWallets(updatedWallets, purpose);
}
async getFirstWalletByPurpose(purpose: Purpose): Promise<StaticWallet> {
const wallets = await this.readWallets(purpose);
if (wallets.length === 0) {
throw new Error(`No wallets found for purpose: ${purpose}`);
}
return wallets[0];
}

async popWallet(purpose: Purpose): Promise<StaticWallet> {
const popCb = async () => {
Expand Down
8 changes: 7 additions & 1 deletion tests/govtool-frontend/playwright/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ export default defineConfig({
testMatch: "**/proposal-budget.auth.setup.ts",
teardown: environments.ci && "cleanup faucet",
},
{
name: "proposal submission ga auth setup",
testMatch: "**/proposal-submission.ga.auth.setup.ts",
dependencies: environments.ci ? ["proposal setup"] : [],
teardown: environments.ci && "cleanup faucet",
},
{
name: "dRep setup",
testMatch: "**/dRep.setup.ts",
Expand Down Expand Up @@ -112,7 +118,7 @@ export default defineConfig({
use: { ...devices["Desktop Chrome"] },
testMatch: "**/*.ga.spec.ts",
dependencies: environments.ci
? ["proposal setup"]
? ["proposal submission ga auth setup"]
: [],
teardown: environments.ci && "cleanup artifacts",
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ test.describe("Budget proposal logged in state", () => {

await budgetDiscussionDetailsPage.replyComment(randReply);
const replyRendered = await budgetDiscussionDetailsPage.currentPage
.locator(`[data-testid^="reply-"][data-testid$="-content"]`)
.locator(`[data-testid^="subcomment-"][data-testid$="-content"]`)
.textContent();
expect(replyRendered).toContain(randReply);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import environments from "@constants/environments";
import { createTempUserAuth } from "@datafactory/createAuth";
import { test } from "@fixtures/proposal";
import { setAllureEpic } from "@helpers/allure";
import { createNewPageWithWallet } from "@helpers/page";
import { createNewPageWithWallet, logWalletDetails } from "@helpers/page";
import { waitForTxConfirmation } from "@helpers/transaction";
import ProposalDiscussionPage from "@pages/proposalDiscussionPage";
import ProposalSubmissionPage from "@pages/proposalSubmissionPage";
Expand All @@ -13,9 +13,12 @@ import {
} from "@helpers/cardano";
import { ProposalType } from "@types";
import walletManager from "lib/walletManager";
import { valid } from "@mock/index";
import { valid as mockValid, invalid as mockInvalid } from "@mock/index";
import { rewardAddressBech32 } from "@helpers/shellyWallet";
import { getWalletConfigForFaucet } from "@helpers/index";
import { faker } from "@faker-js/faker";
import { proposalSubmissionAuthFile } from "@constants/auth";
import ProposalDiscussionDetailsPage from "@pages/proposalDiscussionDetailsPage";

test.beforeEach(async () => {
await setAllureEpic("7. Proposal submission");
Expand All @@ -31,6 +34,7 @@ Object.values(ProposalType).forEach((proposalType, index) => {
test.setTimeout(testInfo.timeout + environments.txTimeOut);

const wallet = await walletManager.popWallet("proposalSubmission");
await logWalletDetails(wallet.address);

const tempUserAuth = await createTempUserAuth(page, wallet);

Expand All @@ -42,7 +46,7 @@ Object.values(ProposalType).forEach((proposalType, index) => {
const proposalDiscussionPage = new ProposalDiscussionPage(userPage);
await proposalDiscussionPage.goto();
await proposalDiscussionPage.verifyIdentityBtn.click();
await proposalDiscussionPage.setUsername(valid.username());
await proposalDiscussionPage.setUsername(mockValid.username());

const proposalSubmissionPage = new ProposalSubmissionPage(userPage);
await proposalSubmissionPage.proposalCreateBtn.click();
Expand Down Expand Up @@ -72,3 +76,136 @@ Object.values(ProposalType).forEach((proposalType, index) => {
await waitForTxConfirmation(userPage);
});
});

test.describe("Proposed as a governance action", async () => {
let proposalSubmissionPage: ProposalSubmissionPage;
let proposalDiscussionDetailPage: ProposalDiscussionDetailsPage;
let proposalId: number;

test.beforeEach(async ({ browser }) => {
const proposalSubmissionWallet =
await walletManager.getFirstWalletByPurpose("proposalSubmissionCopy");
await logWalletDetails(proposalSubmissionWallet.address);

const page = await createNewPageWithWallet(browser, {
storageState: proposalSubmissionAuthFile,
wallet: proposalSubmissionWallet,
});

proposalSubmissionPage = new ProposalSubmissionPage(page);
await proposalSubmissionPage.goto();

proposalDiscussionDetailPage = new ProposalDiscussionDetailsPage(page);

const rewardAddress = rewardAddressBech32(
environments.networkId,
getWalletConfigForFaucet().stake.pkh
);

proposalId = await proposalSubmissionPage.createProposal(rewardAddress);
await proposalDiscussionDetailPage.submitAsGABtn.click();
await proposalSubmissionPage.currentPage
.getByTestId("agree-checkbox")
.click();
await proposalSubmissionPage.continueBtn.click();
});

test.afterEach(async () => {
// cleanup
await proposalDiscussionDetailPage.goto(proposalId);

const isVerifyIdentityBtnVisible =
await proposalDiscussionDetailPage.verifyIdentityBtn.isVisible();

if (isVerifyIdentityBtnVisible) {
await proposalDiscussionDetailPage.verifyIdentityBtn.click();
}

await proposalDiscussionDetailPage.deleteProposal();
});

test.describe("Metadata anchor validation", () => {
test("7J_1. Should accept valid metadata anchor on proposal submission", async () => {
test.slow(); // Brute-force testing with 50 random data
for (let i = 0; i < 50; i++) {
await proposalSubmissionPage.metadataUrlInput.fill(mockValid.url());
await expect(
proposalSubmissionPage.currentPage.getByTestId("url-input-error-text")
).toBeHidden();
}
});

test("7J_2. Should reject invalid metadata anchor on proposal submission", async () => {
test.slow(); // Brute-force testing with 50 random data
for (let i = 0; i < 50; i++) {
await proposalSubmissionPage.metadataUrlInput.fill(
mockInvalid.url(false)
);
await expect(
proposalSubmissionPage.currentPage.getByTestId("url-input-error-text")
).toBeVisible();
}

const sentenceWithoutSpace = faker.lorem
.sentence(128)
.replace(/[\s.]/g, "");
const metadataAnchorGreaterThan128Bytes =
faker.internet.url({ appendSlash: true }) + sentenceWithoutSpace;

await proposalSubmissionPage.metadataUrlInput.fill(
metadataAnchorGreaterThan128Bytes
);

await expect(
proposalSubmissionPage.currentPage.getByTestId("url-input-error-text")
).toBeVisible(); // BUG better to add different test id compare to invalid url testid
});
});

test("7K. Should reject invalid proposal metadata", async () => {
await proposalSubmissionPage.metadataUrlInput.fill(faker.internet.url());
await proposalSubmissionPage.submitBtn.click();

await expect(
proposalSubmissionPage.currentPage.getByTestId("url-error-modal-title")
).toHaveText(/the url you entered cannot be found/i);
});

test("7P. Should navigate to the edit proposal page when 'goto data edit screen' is selected if data does not match the anchor URL", async () => {
const invalidMetadataAnchorUrl = "https://www.google.com";

await proposalSubmissionPage.metadataUrlInput.fill(
invalidMetadataAnchorUrl
);
await proposalSubmissionPage.submitBtn.click();

await expect(
proposalSubmissionPage.currentPage.getByTestId("data-not-match-modal")
).toBeVisible();
await expect(
proposalSubmissionPage.currentPage.getByTestId(
"data-not-match-modal-go-to-data-button"
)
).toBeVisible();

await proposalSubmissionPage.currentPage
.getByTestId("data-not-match-modal-go-to-data-button")
.click();

await expect(
proposalSubmissionPage.currentPage.getByTestId("governance-action-type")
).toBeVisible();
await expect(
proposalSubmissionPage.currentPage.getByTestId("title-input")
).toBeVisible();
await expect(
proposalSubmissionPage.currentPage.getByTestId("abstract-input")
).toBeVisible();
await expect(
proposalSubmissionPage.currentPage.getByTestId("motivation-input")
).toBeVisible();
await expect(
proposalSubmissionPage.currentPage.getByTestId("rationale-input")
).toBeVisible();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -306,66 +306,25 @@ test.describe("Proposal created logged state", () => {
});
});

test.describe("Proposed as a governance action", () => {
let proposalSubmissionPage: ProposalSubmissionPage;
test.beforeEach(async ({ page, proposalId }) => {
const proposalDiscussionDetailsPage = new ProposalDiscussionDetailsPage(
page
);
await proposalDiscussionDetailsPage.goto(proposalId);

await proposalDiscussionDetailsPage.verifyIdentityBtn.click();
await proposalDiscussionDetailsPage.submitAsGABtn.click();

proposalSubmissionPage = new ProposalSubmissionPage(page);
await page.getByTestId("agree-checkbox").click();
await proposalSubmissionPage.continueBtn.click();
});

test.describe("Metadata anchor validation", () => {
test("7J_1. Should accept valid metadata anchor on proposal submission", async ({
page,
}) => {
test.slow(); // Brute-force testing with 100 random data
for (let i = 0; i < 50; i++) {
await proposalSubmissionPage.metadataUrlInput.fill(mockValid.url());
await expect(page.getByTestId("url-input-error-text")).toBeHidden();
}
});

test("7J_2. Should reject invalid metadata anchor on proposal submission", async ({
page,
}) => {
test.slow(); // Brute-force testing with 100 random data
for (let i = 0; i < 50; i++) {
await proposalSubmissionPage.metadataUrlInput.fill(
invalid.url(false)
);
await expect(page.getByTestId("url-input-error-text")).toBeVisible();
}

const sentenceWithoutSpace = faker.lorem
.sentence(128)
.replace(/[\s.]/g, "");
const metadataAnchorGreaterThan128Bytes =
faker.internet.url({ appendSlash: true }) + sentenceWithoutSpace;

await proposalSubmissionPage.metadataUrlInput.fill(
metadataAnchorGreaterThan128Bytes
);

await expect(page.getByTestId("url-input-error-text")).toBeVisible(); // BUG better to add different test id compare to invalid url testid
});
});
test("7O. Should display insufficient balance modal when submitting proposal with insufficient funds", async ({
page,
proposalId,
}) => {
const proposalDiscussionDetailsPage = new ProposalDiscussionDetailsPage(
page
);
await proposalDiscussionDetailsPage.goto(proposalId);

test("7K. Should reject invalid proposal metadata", async ({ page }) => {
await proposalSubmissionPage.metadataUrlInput.fill(faker.internet.url());
await proposalSubmissionPage.submitBtn.click();
await proposalDiscussionDetailsPage.verifyIdentityBtn.click();
await proposalDiscussionDetailsPage.submitAsGABtn.click();

await expect(page.getByTestId("url-error-modal-title")).toHaveText(
/the url you entered cannot be found/i
);
});
const proposalSubmissionPage = new ProposalSubmissionPage(page);
await expect(
proposalSubmissionPage.currentPage.getByText(
"Insufficient wallet balance",
{ exact: true }
)
).toBeVisible(); // BUG missing test id
});
});

Expand Down
Loading
Loading