Skip to content
Draft
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ node_modules

# testing
coverage
/tests-playwright/*.png
/tests-playwright/artifacts/

# next.js
.next/
Expand Down
5 changes: 5 additions & 0 deletions config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ const enabledLoginOptions: EnabledLoginOptions = {
const includeTestnetTokens =
process.env.NEXT_PUBLIC_INCLUDE_TESTNET_TOKENS === "true"

// Enables deterministic UI states exclusively for the Playwright test server.
const playwrightTestMode =
process.env.NEXT_PUBLIC_PLAYWRIGHT_TEST_MODE === "true"

const lifiConfig = {
apiUrl: "https://li.quest/v1",
chainTypes: ["EVM", "UTXO", "SVM"],
Expand Down Expand Up @@ -215,6 +219,7 @@ export const config = Object.freeze({
vercelConfig,
env,
includeTestnetTokens,
playwrightTestMode,
enabledLoginOptions,
lifiConfig,
alchemyConfig,
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
"start": "next start -p 80",
"lint": "next lint",
"test": "vitest run",
"test:e2e": "playwright test",
"test:e2e:screenshots": "playwright test --update-snapshots",
"biome:verify:all": "biome check",
"biome:fix:staged": "biome check --write --no-errors-on-unmatched --staged",
"biome:fix:all": "biome check --write --no-errors-on-unmatched",
Expand Down Expand Up @@ -69,6 +71,7 @@
},
"devDependencies": {
"@biomejs/biome": "^2.5.3",
"@playwright/test": "1.61.1",
"@types/async-retry": "^1.4.9",
"@types/hdkey": "^2.1.0",
"@types/node": "26.1.1",
Expand Down
44 changes: 44 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { randomBytes } from "node:crypto"
import { defineConfig, devices } from "@playwright/test"

const testWalletSeed = randomBytes(32).toString("hex")

export default defineConfig({
testDir: "./tests-playwright",
testMatch: "**/*.e2e.ts",
outputDir: "tests-playwright/artifacts",
fullyParallel: false,
workers: 1,
reporter: "list",
use: {
baseURL: "http://localhost:3000",
trace: "retain-on-failure",
video: "retain-on-failure",
},
projects: [
{
name: "desktop",
use: { ...devices["Desktop Chrome"] },
},
{
name: "mobile",
use: { ...devices["iPhone 13"], browserName: "chromium" },
},
],
webServer: {
command: "yarn next dev --turbo -p 3000",
url: "http://localhost:3000",
reuseExistingServer: false,
env: {
...process.env,
NEXT_PUBLIC_PLAYWRIGHT_TEST_MODE: "true",
NEXT_PUBLIC_LOGIN_GOOGLE_ENABLED: "false",
NEXT_PUBLIC_LOGIN_FACEBOOK_ENABLED: "false",
NEXT_PUBLIC_LOGIN_PWLESS_ENABLED: "false",
NEXT_PUBLIC_TEST_LOGIN_ENABLED: "true",
NEXT_PUBLIC_TEST_LOGIN_MASTER_SEED: testWalletSeed,
NEXT_PUBLIC_TEST_LOGIN_USER_NAME: "Playwright test wallet",
NEXT_PUBLIC_TEST_LOGIN_PROFILE_IMAGE: "",
},
},
})
5 changes: 5 additions & 0 deletions src/sections/GoodDollar/components/Claim/ClaimView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { getChainName } from "@/chain/chains"
import { setBottomSheetProps } from "@/components/Snippet/BottomSheet/bottomSheetStore"
import { LoadingSpinner } from "@/components/Snippet/LoadingSpinner"
import { formatTokenAmount } from "@/components/Utils/tokenFormat"
import { config } from "@/config"
import { topWallet } from "@/gooddollar/methods/faucet"
import { collectBountyStore } from "@/gooddollar/stores/collectBountyStore"
import {
Expand Down Expand Up @@ -396,6 +397,10 @@ export default function ClaimView() {
const goHome = useRouteTransition(`/${locale}`)
const addPropagatingTx = useSnapshot(activityHistoryStore).addPropagatingTx

if (config.playwrightTestMode) {
return <RequireWhitelist onWhitelist={() => undefined} />
}

const onClaim = () => {
const ongoingClaims = new Map<ChainId, Promise<void>>()

Expand Down
10 changes: 7 additions & 3 deletions src/sections/Home/components/WalletSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -225,10 +225,14 @@ export default function WalletSection({
<ProfileCard
userName={userName}
profileImage={profileImage}
aggregatedUsdValue={balances?.aggregatedUsdValue}
isLoadingValue={isValidating}
aggregatedUsdValue={
config.playwrightTestMode
? "124.68"
: balances?.aggregatedUsdValue
}
isLoadingValue={config.playwrightTestMode ? false : isValidating}
/>
<div className={styles.buttonsContainer}>
<div className={styles.buttonsContainer} data-testid="wallet-actions">
{goodDollarLink}
{sendLink}
{receiveLink}
Expand Down
102 changes: 102 additions & 0 deletions tests-playwright/wallet-flows.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { expect, type Page, test } from "@playwright/test"

const screenshotPath = (name: string, project: string) =>
`tests-playwright/${name}-${project}.png`

const preparePage = async (page: Page, showOnboarding: boolean) => {
await page.context().route("**/*", (route) => {
const { hostname, pathname } = new URL(route.request().url())
if (pathname === "/api/tokens") {
return route.fulfill({
contentType: "application/json",
body: JSON.stringify({ tokens: {} }),
})
}
return hostname === "localhost" ? route.continue() : route.abort()
})
await page.addInitScript(
({ showOnboarding }) => {
localStorage.setItem("ShowWelcomeDialog", String(showOnboarding))
localStorage.setItem("defaultLoginMethod", "testlogin")
localStorage.setItem("Tracking_Sentry", "denied")
localStorage.setItem("Tracking_Amplitude", "denied")
sessionStorage.setItem("gd-claim-view-seen", "true")
sessionStorage.removeItem("deep_link_url")
},
{ showOnboarding },
)
}

const login = async (page: Page) => {
await page.goto("/en?login=master_seed")
await page.getByRole("button", { name: "Playwright test wallet" }).click()
await expect(page.getByText("Playwright test wallet")).toBeVisible()
}

test("captures the locale-aware onboarding and login entry", async ({
page,
}, testInfo) => {
await preparePage(page, true)
await page.goto("/da?login=master_seed")

await expect(
page.getByText("Velkommen til din nye og forbedrede GoodWallet!"),
).toBeVisible()
await page.screenshot({
path: screenshotPath("login-onboarding-da", testInfo.project.name),
fullPage: true,
})

await page.getByRole("button", { name: "Sign In" }).click()
await expect(
page.getByRole("button", { name: "Playwright test wallet" }),
).toBeVisible()
})

test("captures the authenticated home balance and mobile action overflow", async ({
page,
}, testInfo) => {
await preparePage(page, false)
await login(page)

await expect(page.getByText("$124.68")).toBeVisible()
const walletActions = page.getByTestId("wallet-actions")
await expect(walletActions).toBeVisible()

if (testInfo.project.name === "mobile") {
const hasOverflow = await walletActions.evaluate(
(element) => element.scrollWidth > element.clientWidth,
)
expect(hasOverflow).toBe(true)
await walletActions.evaluate((element) => {
element.scrollLeft = element.scrollWidth
})
}

await page.screenshot({
path: screenshotPath("home-balances-overflow-en", testInfo.project.name),
fullPage: true,
})
})

test("captures the claim verification requirement", async ({
page,
}, testInfo) => {
await preparePage(page, false)
await login(page)
await page.getByRole("link", { name: "GoodDollar" }).click()

await expect(
page.getByText(
"Before you can start to claim your GoodDollars you first need to pass face verification to whitelist your account.",
),
).toBeVisible()
await expect(page.getByRole("button", { name: "Verify" })).toBeVisible()
await page.screenshot({
path: screenshotPath(
"claim-requires-verification-en",
testInfo.project.name,
),
fullPage: true,
})
})
55 changes: 55 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1799,6 +1799,17 @@ __metadata:
languageName: node
linkType: hard

"@playwright/test@npm:1.61.1":
version: 1.61.1
resolution: "@playwright/test@npm:1.61.1"
dependencies:
playwright: "npm:1.61.1"
bin:
playwright: cli.js
checksum: 10c0/45004139eaa7c7ec8129e4ed77a9d734aa09ed40b69162b871e4123f1d70217b7b73651431a9343ed5090d5fed11c6df1bff2a56ff4b87dc7b0371b5da87cf9c
languageName: node
linkType: hard

"@polymarket/builder-abstract-signer@npm:0.0.1":
version: 0.0.1
resolution: "@polymarket/builder-abstract-signer@npm:0.0.1"
Expand Down Expand Up @@ -7043,6 +7054,16 @@ __metadata:
languageName: node
linkType: hard

"fsevents@npm:2.3.2":
version: 2.3.2
resolution: "fsevents@npm:2.3.2"
dependencies:
node-gyp: "npm:latest"
checksum: 10c0/be78a3efa3e181cda3cf7a4637cb527bcebb0bd0ea0440105a3bb45b86f9245b307dc10a2507e8f4498a7d4ec349d1910f4d73e4d4495b16103106e07eee735b
conditions: os=darwin
languageName: node
linkType: hard

"fsevents@npm:~2.3.2, fsevents@npm:~2.3.3":
version: 2.3.3
resolution: "fsevents@npm:2.3.3"
Expand All @@ -7053,6 +7074,15 @@ __metadata:
languageName: node
linkType: hard

"fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin<compat/fsevents>":
version: 2.3.2
resolution: "fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin<compat/fsevents>::version=2.3.2&hash=df0bf1"
dependencies:
node-gyp: "npm:latest"
conditions: os=darwin
languageName: node
linkType: hard

"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin<compat/fsevents>, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin<compat/fsevents>":
version: 2.3.3
resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin<compat/fsevents>::version=2.3.3&hash=df0bf1"
Expand Down Expand Up @@ -7189,6 +7219,7 @@ __metadata:
"@lifi/sdk-provider-solana": "npm:^4.0.2"
"@lottiefiles/react-lottie-player": "npm:^3.6.0"
"@neondatabase/serverless": "npm:^1.1.0"
"@playwright/test": "npm:1.61.1"
"@polymarket/builder-relayer-client": "npm:^0.0.10"
"@polymarket/clob-client": "npm:^5.8.1"
"@react-hookz/web": "npm:^25.2.0"
Expand Down Expand Up @@ -8542,6 +8573,30 @@ __metadata:
languageName: node
linkType: hard

"playwright-core@npm:1.61.1":
version: 1.61.1
resolution: "playwright-core@npm:1.61.1"
bin:
playwright-core: cli.js
checksum: 10c0/c28896ba82a602182e240ed4f9c467fb0dd9cb57d510f8aba9f25183fe1cde3a0105725a29baffa4157fe885bbb11e228032a2aad0424111584fde45303f07bd
languageName: node
linkType: hard

"playwright@npm:1.61.1":
version: 1.61.1
resolution: "playwright@npm:1.61.1"
dependencies:
fsevents: "npm:2.3.2"
playwright-core: "npm:1.61.1"
dependenciesMeta:
fsevents:
optional: true
bin:
playwright: cli.js
checksum: 10c0/cea1bc4d2a64ec3ef683891606774029da7b8a8036ed98332565cec2f8e89549ca1fab9a89fbfba4e08e27e0d7e7d148031e965af66d5ff97bb3c34058cfcad0
languageName: node
linkType: hard

"possible-typed-array-names@npm:^1.0.0":
version: 1.1.0
resolution: "possible-typed-array-names@npm:1.1.0"
Expand Down