Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/feat/proposal-submission-tests' …
Browse files Browse the repository at this point in the history
…into develop
  • Loading branch information
NabinKawan committed May 21, 2024
2 parents 4e5a17e + ab78837 commit 611085a
Show file tree
Hide file tree
Showing 11 changed files with 547 additions and 16 deletions.
49 changes: 49 additions & 0 deletions tests/govtool-frontend/playwright/lib/_mock/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { faker } from "@faker-js/faker";

export const invalid = {
url: () => {
const invalidSchemes = ["ftp", "unsupported", "unknown-scheme"];
const invalidCharacters = "<>@!#$%^&*()";
const invalidTlds = [".invalid", ".example", ".test"];

const scheme =
invalidSchemes[Math.floor(Math.random() * invalidSchemes.length)];
const invalidChar =
invalidCharacters[Math.floor(Math.random() * invalidCharacters.length)];
const invalidTld =
invalidTlds[Math.floor(Math.random() * invalidTlds.length)];

const randomDomain = `example${invalidChar}domain${invalidTld}`;
return `${scheme}://${randomDomain}`;
},

proposalTitle: () => {
const choice = faker.number.int({ min: 1, max: 2 });
if (choice === 1) {
// maximum 80 words invalid
return faker.lorem.paragraphs(4).replace(/\s+/g, "");
}
// empty invalid
return " ";
},

paragraph: () => {
const choice = faker.number.int({ min: 1, max: 2 });
if (choice === 1) {
// maximum 500 words
return faker.lorem.paragraphs(40);
}
// empty invalid
return " ";
},

amount: () => {
const choice = faker.number.int({ min: 1, max: 2 });
if (choice === 1) {
// only number is allowed
return faker.lorem.word();
}
// empty invalid
return " ";
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ export async function createTempAdaHolderAuth(
return tempAdaHolderAuth;
}

export async function createTempUserAuth(page: Page) {
export async function createTempUserAuth(page: Page, wallet: ShelleyWallet) {
await importWallet(page, wallet.json());

const loginPage = new LoginPage(page);
await loginPage.login();
await loginPage.isLoggedIn();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import environments from "@constants/environments";
import { faker } from "@faker-js/faker";
import { invalid } from "@mock/index";
import ProposalSubmission from "@pages/proposalSubmissionPage";
import { IGovernanceProposal, ProposalType } from "@types";
import { ShelleyWallet } from "./crypto";

export function generateValidProposalFormField(
proposalType: ProposalType,
receivingAddress?: string
) {
const proposal: IGovernanceProposal = {
title: faker.lorem.sentence(6),
abstract: faker.lorem.paragraph(2),
motivation: faker.lorem.paragraphs(2),
rationale: faker.lorem.paragraphs(2),

extraContentLinks: [faker.internet.url()],
type: proposalType,
};
if (proposalType === "Treasury") {
(proposal.receivingAddress = receivingAddress),
(proposal.amount = faker.number.int({ min: 100, max: 1000 }).toString());
}
return proposal;
}

export function generateInValidProposalFormField(proposalType: ProposalType) {
const proposal: IGovernanceProposal = {
title: invalid.proposalTitle(),
abstract: invalid.paragraph(),
motivation: invalid.paragraph(),
rationale: invalid.paragraph(),

extraContentLinks: [invalid.url()],
type: proposalType,
};
if (proposalType === "Treasury") {
(proposal.receivingAddress = faker.location.streetAddress()),
(proposal.amount = invalid.amount());
}
return proposal;
}

export async function submitInfoProposal(
proposalSubmissionPage: ProposalSubmission,
proposalType: ProposalType
) {
await proposalSubmissionPage.infoRadioButton.click();
await proposalSubmissionPage.continueBtn.click();

const infoProposal: IGovernanceProposal =
generateValidProposalFormField(proposalType);
await proposalSubmissionPage.register({ ...infoProposal });
}

export async function submitTreasuryProposal(
proposalSubmissionPage: ProposalSubmission,
proposalType: ProposalType,
wallet: ShelleyWallet
) {
await proposalSubmissionPage.treasuryRadioButton.click();
await proposalSubmissionPage.continueBtn.click();

const treasuryProposal: IGovernanceProposal = generateValidProposalFormField(
proposalType,
wallet.rewardAddressBech32(environments.networkId)
);

await proposalSubmissionPage.register({ ...treasuryProposal });
}
207 changes: 207 additions & 0 deletions tests/govtool-frontend/playwright/lib/pages/proposalSubmissionPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import { downloadMetadata } from "@helpers/metadata";
import { Download, Page, expect } from "@playwright/test";
import metadataBucketService from "@services/metadataBucketService";
import { IGovernanceProposal, ProposalType } from "@types";
import environments from "lib/constants/environments";
import { withTxConfirmation } from "lib/transaction.decorator";
const formErrors = {
proposalTitle: ["max-80-characters-error", "this-field-is-required-error"],
abstract: "this-field-is-required-error",
motivation: "this-field-is-required-error",
Rationale: "this-field-is-required-error",
receivingAddress: "invalid-bech32-address-error",
amount: ["only-number-is-allowed-error", "this-field-is-required-error"],
link: "invalid-url-error",
};

export default class ProposalSubmission {
// modals
readonly registrationSuccessModal = this.page.getByTestId(
"create-governance-action-submitted-modal"
);

// buttons
readonly registerBtn = this.page.getByTestId("register-button");
readonly skipBtn = this.page.getByTestId("skip-button");
readonly confirmBtn = this.page.getByTestId("confirm-modal-button");

readonly continueBtn = this.page.getByTestId("continue-button");
readonly addLinkBtn = this.page.getByRole("button", { name: "+ Add link" }); // BUG testid= add-link-button
readonly infoRadioButton = this.page.getByTestId("Info-radio");
readonly treasuryRadioButton = this.page.getByTestId("Treasury-radio");

// input fields
readonly titleInput = this.page.getByPlaceholder("A name for this Action"); // BUG testid = title-input
readonly abstractInput = this.page.getByPlaceholder("Summary"); // BUG testid = abstract-input
readonly motivationInput = this.page.getByPlaceholder(
"Problem this GA will solve"
); // BUG testid = motivation-input
readonly rationaleInput = this.page.getByPlaceholder(
"Content of Governance Action"
); // BUG testid = rationale-input
readonly linkInput = this.page.getByPlaceholder("https://website.com/"); // BUG testid = link-input
readonly receivingAddressInput = this.page.getByPlaceholder(
"The address to receive funds"
);
readonly amountInput = this.page.getByPlaceholder("e.g.");

constructor(private readonly page: Page) {}

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

@withTxConfirmation
async register(governanceProposal: IGovernanceProposal) {
await this.fillupForm(governanceProposal);

await this.continueBtn.click();
await this.continueBtn.click();
await this.page.getByRole("checkbox").click();
await this.continueBtn.click();

this.page
.getByRole("button", { name: `${governanceProposal.type}.jsonld` })
.click(); // BUG test id = metadata-download-button

const dRepMetadata = await this.downloadVoteMetadata();
const url = await metadataBucketService.uploadMetadata(
dRepMetadata.name,
dRepMetadata.data
);
await this.page.getByPlaceholder("URL").fill(url);
await this.continueBtn.click();
}

async downloadVoteMetadata() {
const download: Download = await this.page.waitForEvent("download");
return downloadMetadata(download);
}

async fillupForm(governanceProposal: IGovernanceProposal) {
await this.titleInput.fill(governanceProposal.title);
await this.abstractInput.fill(governanceProposal.abstract);
await this.motivationInput.fill(governanceProposal.motivation);
await this.rationaleInput.fill(governanceProposal.rationale);

if (governanceProposal.type === "Treasury") {
await this.receivingAddressInput.fill(
governanceProposal.receivingAddress
);
await this.amountInput.fill(governanceProposal.amount);
}

if (governanceProposal.extraContentLinks != null) {
for (let i = 0; i < governanceProposal.extraContentLinks.length; i++) {
if (i > 0) {
this.page
.getByRole("button", {
name: "+ Add link",
})
.click();
}
await this.linkInput
.nth(i)
.fill(governanceProposal.extraContentLinks[i]);
}
}
}

async validateForm(governanceProposal: IGovernanceProposal) {
await this.fillupForm(governanceProposal);

for (const err of formErrors.proposalTitle) {
await expect(
this.page.getByTestId(err),
`Invalid title: ${governanceProposal.title}`
).toBeHidden();
}

expect(await this.abstractInput.textContent()).toEqual(
governanceProposal.abstract
);

expect(await this.rationaleInput.textContent()).toEqual(
governanceProposal.rationale
);

expect(await this.motivationInput.textContent()).toEqual(
governanceProposal.motivation
);

if (governanceProposal.type === "Treasury") {
await expect(
this.page.getByTestId(formErrors.receivingAddress)
).toBeHidden();

for (const err of formErrors.amount) {
await expect(this.page.getByTestId(err)).toBeHidden();
}
}

await expect(this.page.getByTestId(formErrors.link)).toBeHidden();

await expect(this.continueBtn).toBeEnabled();
}

async inValidateForm(governanceProposal: IGovernanceProposal) {
await this.fillupForm(governanceProposal);

function convertTestIdToText(testId: string) {
let text = testId.replace("-error", "");
text = text.replace(/-/g, " ");
return text[0].toUpperCase() + text.substring(1);
}

// Helper function to generate regex pattern from form errors
function generateRegexPattern(errors: string[]) {
return new RegExp(errors.map(convertTestIdToText).join("|"));
}

// Helper function to get errors based on regex pattern
async function getErrorsByPattern(page: Page, regexPattern: RegExp) {
return await page
.locator('[data-testid$="-error"]')
.filter({ hasText: regexPattern })
.all();
}

const proposalTitlePattern = generateRegexPattern(formErrors.proposalTitle);
const proposalTitleErrors = await getErrorsByPattern(
this.page,
proposalTitlePattern
);
expect(proposalTitleErrors.length).toEqual(1);

if (governanceProposal.type === "Treasury") {
const receiverAddressErrors = await getErrorsByPattern(
this.page,
new RegExp(convertTestIdToText(formErrors.receivingAddress))
);
expect(receiverAddressErrors.length).toEqual(1);

const amountPattern = generateRegexPattern(formErrors.amount);
const amountErrors = await getErrorsByPattern(this.page, amountPattern);
expect(amountErrors.length).toEqual(1);
}

expect(await this.abstractInput.textContent()).not.toEqual(
governanceProposal.abstract
);

expect(await this.motivationInput.textContent()).not.toEqual(
governanceProposal.motivation
);

expect(await this.rationaleInput.textContent()).not.toEqual(
governanceProposal.rationale
);

await expect(this.page.getByTestId(formErrors.link)).toBeVisible();

await expect(this.continueBtn).toBeDisabled();
}
}
13 changes: 13 additions & 0 deletions tests/govtool-frontend/playwright/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,19 @@ export type IDRepInfo = {
extraContentLinks?: string[];
};

export type ProposalType = "Info" | "Treasury";

export type IGovernanceProposal = {
title: string;
abstract: string;
motivation: string;
rationale: string;
extraContentLinks?: string[];
type: ProposalType;
receivingAddress?: string;
amount?: string;
};

export enum FilterOption {
ProtocolParameterChange = "ParameterChange",
InfoAction = "InfoAction",
Expand Down
Loading

0 comments on commit 611085a

Please sign in to comment.