Skip to content

Commit

Permalink
test: add nft reward related e2e tests (#1489)
Browse files Browse the repository at this point in the history
* 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`
  • Loading branch information
BrickheadJohnny authored Sep 16, 2024
1 parent a1fa61d commit 4b74b1b
Show file tree
Hide file tree
Showing 17 changed files with 329 additions and 23 deletions.
4 changes: 2 additions & 2 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/",

Expand Down Expand Up @@ -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,
},
{
Expand Down
4 changes: 3 additions & 1 deletion playwright/01-auth.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand Down
4 changes: 3 additions & 1 deletion playwright/03-guild-pin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
294 changes: 294 additions & 0 deletions playwright/04-nft-reward.spec.ts
Original file line number Diff line number Diff line change
@@ -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!"
6 changes: 5 additions & 1 deletion playwright/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Binary file added playwright/files/nft-image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const CreateNftForm = ({ onSuccess }: Props) => {
<NftDataForm
submitButton={
<Button
data-test="create-nft-button"
data-testid="create-nft-button"
colorScheme="indigo"
isDisabled={!isEvmConnected || shouldSwitchChain || isLoading}
isLoading={isLoading}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const ImagePicker = () => {
<WrapperElement borderRadius="xl">
<AspectRatio ratio={1}>
<Button
data-testid="nft-image-picker"
p={0}
borderWidth={1}
variant="ghost"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ const MintPerAddressInput = () => {
<GridItem as={FormControl} colSpan={{ base: 3, md: 2 }}>
<FormLabel>Claiming per address</FormLabel>
<SegmentedControl
name="mint-limit-type"
options={options}
value={mintLimitType}
onChange={(newSupplyType: MintLimitType) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const SupplyInput = () => {
<GridItem as={FormControl} colSpan={{ base: 3, md: 2 }}>
<FormLabel>Supply</FormLabel>
<SegmentedControl
name="supply-type"
options={options}
value={supplyType}
onChange={(newSupplyType) =>
Expand Down
Loading

0 comments on commit 4b74b1b

Please sign in to comment.