From 4b74b1b8635a80f827678a780467500f303476d0 Mon Sep 17 00:00:00 2001 From: BrickheadJohnny <92519134+BrickheadJohnny@users.noreply.github.com> Date: Mon, 16 Sep 2024 08:50:19 +0200 Subject: [PATCH] test: add nft reward related e2e tests (#1489) * cleanup: rename dummy test to "auth-state" * chore: update e2e readme * fix(auth setup): increase the timeout * feat(playwright): add nft creation flow test * fix(playwright config): increase workers limit * fix(playwright config): update block number and set balance * test: add collect nft unhappy path test * fix: increase some timeouts * test: add collect nft happy path test * fix(nft reward spec): add some missing bits * rename the `set-auth-state` spec to `restore-auth-state` --- playwright.config.ts | 4 +- playwright/01-auth.setup.ts | 4 +- ....spec.ts => 02-restore-auth-state.spec.ts} | 6 +- playwright/03-guild-pin.spec.ts | 4 +- playwright/04-nft-reward.spec.ts | 294 ++++++++++++++++++ playwright/README.md | 6 +- playwright/files/nft-image.png | Bin 0 -> 21536 bytes .../CreateNftForm/CreateNftForm.tsx | 2 +- .../CreateNftForm/components/ImagePicker.tsx | 1 + .../components/MintPerAddressInput.tsx | 1 + .../CreateNftForm/components/SupplyInput.tsx | 1 + .../components/CollectNftButton.tsx | 2 +- src/components/common/SegmentedControl.tsx | 1 + .../components/AddSolutionsButton.tsx | 7 +- src/solutions/components/SolutionCard.tsx | 15 +- src/solutions/components/SolutionsPanel.tsx | 3 - src/v2/components/StickyAction.tsx | 1 + 17 files changed, 329 insertions(+), 23 deletions(-) rename playwright/{02-dummy.spec.ts => 02-restore-auth-state.spec.ts} (67%) create mode 100644 playwright/04-nft-reward.spec.ts create mode 100644 playwright/files/nft-image.png diff --git a/playwright.config.ts b/playwright.config.ts index 4ad263307b..ebc547013d 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -11,7 +11,7 @@ export default defineConfig({ fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, - workers: process.env.CI ? 1 : undefined, + workers: process.env.CI ? 3 : undefined, reporter: "html", outputDir: "playwright/results/", @@ -41,7 +41,7 @@ export default defineConfig({ webServer: [ { - command: `anvil --fork-url=${process.env.ANVIL_FORK_URL} --fork-block-number=6373425 -m='${process.env.NEXT_PUBLIC_E2E_WALLET_MNEMONIC}' --fork-header='Authorization: ${process.env.ANVIL_FORK_KEY}'`, + command: `anvil --fork-url=${process.env.ANVIL_FORK_URL} --fork-block-number=6697000 -m='${process.env.NEXT_PUBLIC_E2E_WALLET_MNEMONIC}' --balance=1 --fork-header='Authorization: ${process.env.ANVIL_FORK_KEY}'`, port: 8545, }, { diff --git a/playwright/01-auth.setup.ts b/playwright/01-auth.setup.ts index 8ea52212c7..5d225cd137 100644 --- a/playwright/01-auth.setup.ts +++ b/playwright/01-auth.setup.ts @@ -34,7 +34,9 @@ setup("authenticate", async ({ page }) => { await page.getByTestId("verify-address-button").click() const publicKeyResponse = await page - .waitForResponse(`**/v2/users/${TEST_USER.id}/public-key`) + .waitForResponse(`**/v2/users/${TEST_USER.id}/public-key`, { + timeout: 60_000, + }) .then((res) => res.json()) const storedKeyPairToSave = await page.evaluate( diff --git a/playwright/02-dummy.spec.ts b/playwright/02-restore-auth-state.spec.ts similarity index 67% rename from playwright/02-dummy.spec.ts rename to playwright/02-restore-auth-state.spec.ts index 0ec5e69c7d..239097c6c2 100644 --- a/playwright/02-dummy.spec.ts +++ b/playwright/02-restore-auth-state.spec.ts @@ -2,11 +2,13 @@ import { expect } from "@playwright/test" import { TEST_USER } from "./constants" import { test } from "./fixtures" -test("dummy", async ({ pageWithKeyPair: { page } }) => { +test("restore auth state from json", async ({ pageWithKeyPair: { page } }) => { await page.goto("/explorer") const accountCard = await page.getByTestId("account-card") - await expect(accountCard).toBeVisible() + await expect(accountCard).toBeVisible({ + timeout: 30_000, + }) accountCard.click() await page.waitForResponse(`**/v2/users/${TEST_USER.id}/profile`) diff --git a/playwright/03-guild-pin.spec.ts b/playwright/03-guild-pin.spec.ts index 1e89e1656c..1f23cedf98 100644 --- a/playwright/03-guild-pin.spec.ts +++ b/playwright/03-guild-pin.spec.ts @@ -5,7 +5,9 @@ import { test } from "./fixtures" test("can mint guild pin", async ({ pageWithKeyPair: { page } }) => { await page.goto(GUILD_CHECKOUT_TEST_GUILD_URL_NAME) - await page.waitForResponse("**/v2/users/*/memberships?guildId=*") + await page.waitForResponse("**/v2/users/*/memberships?guildId=*", { + timeout: 30_000, + }) const mintGuildPinButton = await page.getByTestId("mint-guild-pin-button") await mintGuildPinButton.click() diff --git a/playwright/04-nft-reward.spec.ts b/playwright/04-nft-reward.spec.ts new file mode 100644 index 0000000000..b5260bd63a --- /dev/null +++ b/playwright/04-nft-reward.spec.ts @@ -0,0 +1,294 @@ +import path from "path" +import { Locator, expect } from "@playwright/test" +import { GUILD_CHECKOUT_TEST_GUILD_URL_NAME, TEST_USER } from "./constants" +import { test } from "./fixtures" + +test("fill nft form and deploy a contract", async ({ + pageWithKeyPair: { page }, +}) => { + await page.goto(GUILD_CHECKOUT_TEST_GUILD_URL_NAME) + + await page.waitForResponse(`**/v2/users/${TEST_USER.id}/profile`, { + timeout: 30_000, + }) + + // Open the "Guild solutions" modal + const addSolutionButton = await page + .getByTestId("layout-action") + .getByTestId("add-solutions-button") + await addSolutionButton.click() + const addSolutionsModal = await page.getByRole("dialog", { + name: "Guild Solutions", + }) + await expect(addSolutionsModal).toBeVisible() + + // Open the "Add NFT reward" modal + const addContractCallRewardCard = await page.getByTestId("contract-call-solution") + await addContractCallRewardCard.click() + + const addNFTModal = await page.getByRole("dialog", { + name: "Add NFT reward", + }) + await expect(addNFTModal).toBeVisible() + + // Test every basic input field inside the NFT form + const nameInput = await addNFTModal.locator("input[name='name']") + await nameInput.focus() + await nameInput.blur() + await expect(nameInput).toHaveAttribute("aria-invalid", "true") + await nameInput.fill("E2E test NFT") + + const metadataDescriptionTextarea = await addNFTModal.getByLabel( + "Metadata description" + ) + await metadataDescriptionTextarea.fill("E2E test NFT metadata description") + + const payoutAddressInput = await addNFTModal.getByLabel("Payout address") + await expect(payoutAddressInput).toHaveValue(new RegExp(TEST_USER.address, "i")) + await payoutAddressInput.fill("") + await expect(payoutAddressInput).toHaveAttribute("aria-invalid", "true") + await payoutAddressInput.fill(TEST_USER.address) + + // TODO: Seems like the chain select doesn't work inside Playwright, make sure to write tests for that once we migrate to the Radix UI select + + // Test the metadata attribute fieldArray + const addAttributeButton = await addNFTModal.getByText("Add attribute") + addAttributeButton.click() + const attributeNameInput = await addNFTModal.locator( + "input[name='attributes.0.name']" + ) + await expect(attributeNameInput).toBeVisible() + const attributeValueInput = await addNFTModal.locator( + "input[name='attributes.0.value']" + ) + await expect(attributeValueInput).toBeVisible() + const removeAttributeButton = await addNFTModal.getByLabel("Remove attribute") + await removeAttributeButton.click() + await expect(attributeNameInput).toBeHidden() + + // Test the "Limit supply" panel + const limitSupplyPanel = await addNFTModal.getByText("Limit supply") + await limitSupplyPanel.click() + + const supplyTypeSegmentedControl = await addNFTModal.getByTestId( + "supply-type-segmented-control" + ) + await expect(supplyTypeSegmentedControl).toBeVisible() + await testSegmentedControlWithNumberInput( + addNFTModal, + supplyTypeSegmentedControl, + "maxSupply" + ) + + const mintLimitTypeSegmentedControl = await addNFTModal.getByTestId( + "mint-limit-type-segmented-control" + ) + await expect(mintLimitTypeSegmentedControl).toBeVisible() + await testSegmentedControlWithNumberInput( + addNFTModal, + mintLimitTypeSegmentedControl, + "mintableAmountPerUser" + ) + + await limitSupplyPanel.click() + await expect(supplyTypeSegmentedControl).toBeHidden() + await expect(mintLimitTypeSegmentedControl).toBeHidden() + + await limitSupplyPanel.click() + await testSegmentedControlWithNumberInputReset( + addNFTModal, + supplyTypeSegmentedControl, + "maxSupply" + ) + await testSegmentedControlWithNumberInputReset( + addNFTModal, + mintLimitTypeSegmentedControl, + "mintableAmountPerUser" + ) + await limitSupplyPanel.click() + + // Test the "Limit claiming time" panel + const limitClaimingTimePanel = await addNFTModal.getByText("Limit claiming time") + await limitClaimingTimePanel.click() + const startTimeInput = await addNFTModal.locator("input[name='startTime']") + await expect(startTimeInput).toBeVisible() + const endTimeInput = await addNFTModal.locator("input[name='endTime']") + await expect(endTimeInput).toBeVisible() + await limitClaimingTimePanel.click() + await expect(startTimeInput).toBeHidden() + await expect(endTimeInput).toBeHidden() + + /** + * Media input - Playwright doesn't work with react-dropzone, so we just set the file input's value manually here + * + * GitHub issue: https://github.com/microsoft/playwright/issues/8850 + */ + const imagePicker = await addNFTModal.getByTestId("nft-image-picker") + const fileInput = await imagePicker.locator("input[type='file']") + await fileInput.setInputFiles(path.join(__dirname, "files/nft-image.png")) + const uploadedImage = await imagePicker.locator("img[alt='NFT image']") + await expect(uploadedImage).toBeVisible({ + timeout: 30_000, + }) + await expect(uploadedImage).toHaveAttribute("src", /^https:\/\/*/) + + // Finally, submit the transaction + const createNFTButton = await addNFTModal.getByTestId("create-nft-button") + await createNFTButton.click() + + const successToast = await page.getByText("Successfully deployed NFT contract", { + exact: true, + }) + await expect(successToast).toBeVisible({ timeout: 30_000 }) +}) + +test("user is not eligible - can't mint nft", async ({ + pageWithKeyPair: { page }, +}) => { + await page.goto(GUILD_CHECKOUT_TEST_GUILD_URL_NAME) + + await page.waitForResponse("**/v2/users/*/memberships?guildId=*", { + timeout: 30_000, + }) + + const roleCard = await page.locator(`#role-${UNHAPPY_PATH_ROLE_ID}`) + const nftRewardCardButton = await roleCard.locator("a", { + hasText: NFT_REWARD_CARD_BUTTON_TEXT, + }) + const collectNFTPageURL = await nftRewardCardButton.getAttribute("href") + await nftRewardCardButton.click() + await page.waitForURL(collectNFTPageURL ?? "") + + const title = await page.locator("h2") + await expect(title).toHaveText(NFT_1_NAME) + + const collectNFTButton = await page.getByTestId("collect-nft-button") + await expect(collectNFTButton).toBeEnabled({ + timeout: 30_000, + }) + const whaleButton = await page.locator("button", { + hasText: "whale", + }) + await whaleButton.click() + await expect(collectNFTButton).toBeDisabled() + await expect(collectNFTButton).toHaveText("Insufficient balance") + const shrimpButton = await page.locator("button", { + hasText: "Shrimp", + }) + await shrimpButton.click() + await expect(collectNFTButton).toBeEnabled() + await expect(collectNFTButton).toHaveText(COLLECT_BUTTON_TEXT_REGEX) + + await collectNFTButton.click() + await page.waitForResponse("**/v2/guilds/*/roles/*/role-platforms/*/claim") + + const errorToast = await page.getByText(NOT_ELIGIBLE_TOAST_TEXT) + await expect(errorToast).toBeVisible({ + timeout: 30_000, + }) +}) + +test("user is eligible - can mint nft", async ({ pageWithKeyPair: { page } }) => { + await page.goto(GUILD_CHECKOUT_TEST_GUILD_URL_NAME) + + await page.waitForResponse("**/v2/users/*/memberships?guildId=*", { + timeout: 30_000, + }) + + const roleCard = await page.locator(`#role-${HAPPY_PATH_ROLE_ID}`) + const nftRewardCardButton = await roleCard.locator("a", { + hasText: NFT_REWARD_CARD_BUTTON_TEXT, + }) + const collectNFTPageURL = await nftRewardCardButton.getAttribute("href") + await nftRewardCardButton.click() + await page.waitForURL(collectNFTPageURL ?? "") + + const title = await page.locator("h2") + await expect(title).toHaveText(NFT_2_NAME) + + const collectNFTButton = await page.getByTestId("collect-nft-button") + await expect(collectNFTButton).toBeEnabled({ + timeout: 30_000, + }) + + await collectNFTButton.click() + await page.waitForResponse("**/v2/guilds/*/roles/*/role-platforms/*/claim") + + const successToast = await page.locator("li[role='status']", { + hasText: SUCCESS_TOAST_TEXT, + }) + await expect(successToast).toBeVisible({ + timeout: 30_000, + }) + + const successModal = await page.getByRole("dialog", { + name: "Success", + }) + await expect(successModal).toBeVisible({ + timeout: 30_000, + }) + const modalCloseButton = await successModal.getByText("Close") + await modalCloseButton.click() +}) + +// Utils, constants + +const testSegmentedControlWithNumberInput = async ( + addNFTModal: Locator, + segmentedControlLocator: Locator, + numberInputName: string +) => { + const unlimitedSegment = await segmentedControlLocator.locator( + UNLIMITED_SEGMENT_LOCATOR + ) + const numberInput = await addNFTModal.locator( + getNumberInputLocator(numberInputName) + ) + const limitedSegment = await segmentedControlLocator.locator( + "> label:last-child > div:last-child" + ) + + await testSegmentedControlWithNumberInputReset( + addNFTModal, + segmentedControlLocator, + numberInputName + ) + + await limitedSegment.click() + await expect(limitedSegment).toHaveAttribute("data-checked") + await expect(numberInput).toHaveValue("1") + await unlimitedSegment.click() + await expect(numberInput).toHaveValue("0") + await numberInput.fill("2") + await expect(limitedSegment).toHaveAttribute("data-checked") + await numberInput.fill("0") + await expect(unlimitedSegment).toHaveAttribute("data-checked") +} + +const testSegmentedControlWithNumberInputReset = async ( + addNFTModal: Locator, + segmentedControlLocator: Locator, + numberInputName: string +) => { + const unlimitedSegment = await segmentedControlLocator.locator( + UNLIMITED_SEGMENT_LOCATOR + ) + await expect(unlimitedSegment).toHaveAttribute("data-checked") + const numberInput = await addNFTModal.locator( + getNumberInputLocator(numberInputName) + ) + await expect(numberInput).toHaveValue("0") +} + +const UNLIMITED_SEGMENT_LOCATOR = "> label:first-child > div:last-child" +const getNumberInputLocator = (numberInputName: string) => + `input[name='${numberInputName}']` + +const NFT_REWARD_CARD_BUTTON_TEXT = new RegExp("(Collect NFT|View NFT details)") +const NFT_1_NAME = "Cypress Gang #1" +const UNHAPPY_PATH_ROLE_ID = 91062 +const NOT_ELIGIBLE_TOAST_TEXT = "You're not eligible for claiming this reward" +const NFT_2_NAME = "Cypress Gang #2" +const HAPPY_PATH_ROLE_ID = 91063 +const COLLECT_BUTTON_TEXT_REGEX = new RegExp("(Check access & collect|Collect now)") +const SUCCESS_TOAST_TEXT = "Successfully collected NFT!" diff --git a/playwright/README.md b/playwright/README.md index 9e966a29f1..a52ca8475c 100644 --- a/playwright/README.md +++ b/playwright/README.md @@ -10,7 +10,11 @@ We use Playwright for E2E testing. When running our tests, we create a viem [Tes curl -L https://foundry.paradigm.xyz | bash foundryup ``` -3. Either run the frontend in dev mode (`npm run dev`) or build it (`npm run build`) and run the tests. You can pick between several different modes, e.g. `npm run test` will just run the tests and print the output, `npm run test:ui` will open the Playwright runner, where you can see the console, network tab, etc., and `npm run test:debug` will open a Chromium browser where you can inspect the page during the test. +3. You might also need to install a browser which you can use for testing: +```sh +npx playwright install chromium +``` +4. Either run the frontend in dev mode (`npm run dev`) or build it (`npm run build`) and run the tests. You can pick between several different modes, e.g. `npm run test` will just run the tests and print the output, `npm run test:ui` will open the Playwright runner, where you can see the console, network tab, etc., and `npm run test:debug` will open a Chromium browser where you can inspect the page during the test. ## Writing tests diff --git a/playwright/files/nft-image.png b/playwright/files/nft-image.png new file mode 100644 index 0000000000000000000000000000000000000000..11aa220855cabb0dadf7a03cb390c1a776381480 GIT binary patch literal 21536 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 GC+t5kgb!2z literal 0 HcmV?d00001 diff --git a/src/components/[guild]/RolePlatforms/components/AddRoleRewardModal/components/AddContractCallPanel/components/CreateNftForm/CreateNftForm.tsx b/src/components/[guild]/RolePlatforms/components/AddRoleRewardModal/components/AddContractCallPanel/components/CreateNftForm/CreateNftForm.tsx index 878a1d2f1a..95b64f1ea2 100644 --- a/src/components/[guild]/RolePlatforms/components/AddRoleRewardModal/components/AddContractCallPanel/components/CreateNftForm/CreateNftForm.tsx +++ b/src/components/[guild]/RolePlatforms/components/AddRoleRewardModal/components/AddContractCallPanel/components/CreateNftForm/CreateNftForm.tsx @@ -63,7 +63,7 @@ const CreateNftForm = ({ onSuccess }: Props) => { { diff --git a/src/solutions/components/SolutionCard.tsx b/src/solutions/components/SolutionCard.tsx index 8fd4a54a32..74159a8986 100644 --- a/src/solutions/components/SolutionCard.tsx +++ b/src/solutions/components/SolutionCard.tsx @@ -8,24 +8,18 @@ import { } from "@chakra-ui/react" import DisplayCard from "components/common/DisplayCard" import Image from "next/image" - -export type Props = { - title: string - imageUrl: string - description: string - bgImageUrl: string - onClick?: (data?: any) => void - children?: JSX.Element -} +import { PropsWithChildren } from "react" +import { SolutionCardData } from "solutions" const SolutionCard = ({ title, description, imageUrl, bgImageUrl, + handlerParam, onClick, children, -}: Props) => { +}: PropsWithChildren void }>) => { const circleBgColor = useColorModeValue("whiteAlpha.300", "blackAlpha.300") const borderColor = useColorModeValue("blackAlpha.300", "whiteAlpha.200") const cardBg = useColorModeValue("white", "var(--chakra-colors-gray-800)") @@ -38,6 +32,7 @@ const SolutionCard = ({ return ( { if (platform === "CONTRACT_CALL") startSessionRecording() setSelection(platform) diff --git a/src/v2/components/StickyAction.tsx b/src/v2/components/StickyAction.tsx index 97553e66f7..c2eeb4a8c5 100644 --- a/src/v2/components/StickyAction.tsx +++ b/src/v2/components/StickyAction.tsx @@ -15,6 +15,7 @@ const StickyAction = ({ children }: PropsWithChildren) => { <> {children}