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
17 changes: 17 additions & 0 deletions .github/workflows/goodwallet-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,20 @@ jobs:
uses: borales/actions-yarn@v5
with:
cmd: test

- name: Install Playwright Chromium
run: yarn playwright install --with-deps chromium

- name: Run Playwright screenshots
uses: borales/actions-yarn@v5
with:
cmd: test:e2e
- name: Upload Playwright screenshots and failure artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-artifacts
path: |
tests-playwright/screenshots/
tests-playwright/artifacts/
if-no-files-found: warn
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ node_modules

# testing
coverage
/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",
Comment thread
L03TJ3 marked this conversation as resolved.
"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
45 changes: 45 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { defineConfig, devices } from "@playwright/test"

const testWalletSeed =
"4c4d6a7e5f6a8b9c0d1e2f30415263748596a7b8c9d0e1f2031425364758697a"

export default defineConfig({
testDir: "./tests-playwright",
testMatch: "**/*.e2e.ts",
outputDir: "tests-playwright/artifacts",
snapshotPathTemplate: "{testDir}/screenshots/{projectName}/{arg}{ext}",
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: "/icon.svg",
},
},
})
10 changes: 1 addition & 9 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
import type { Metadata, Viewport } from "next"
import { Inter } from "next/font/google"

import "@/ui/styles.css"

import { Dialog, Toast } from "ui"

import { PreloadResources } from "./preload-resources.tsx"

const inter = Inter({
subsets: ["latin"],
display: "swap",
variable: "--font-inter",
})

export const viewport: Viewport = {
themeColor: "#000",
width: "device-width",
Expand Down Expand Up @@ -53,7 +45,7 @@ export default function RootLayout({
children: React.ReactNode
}) {
return (
<html lang="en" className={inter.variable}>
<html lang="en">
<head>
<link rel="shortcut icon" href="/favicon.goodwallet.ico" />
</head>
Expand Down
9 changes: 9 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 @@ -105,6 +106,14 @@ const getDailyStats = (claims: Record<ChainId, UbiClaim>) => {
}

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

return <ClaimViewContent />
}

function ClaimViewContent() {
const invitedChainId = useInvitedChainId()

const { locale, translations } = useTranslation()
Expand Down
14 changes: 11 additions & 3 deletions src/sections/Home/components/WalletSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -258,15 +258,20 @@ 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
ref={actionButtonsRef}
className={styles.buttonsContainer}
style={
isActionsCollapsed ? { maxHeight: actionRowHeight } : undefined
}
data-testid="wallet-actions"
>
{goodDollarLink}
{sendLink}
Expand All @@ -276,7 +281,10 @@ export default function WalletSection({
{walletConnectLink}
</div>
{hasMultipleActionRows ? (
<div className={styles.actionsToggle}>
<div
className={styles.actionsToggle}
data-testid="wallet-actions-toggle"
>
<Button
variant="icon"
icon={isActionsCollapsed ? "BsChevronDown" : "BsChevronUp"}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
107 changes: 107 additions & 0 deletions tests-playwright/wallet-flows.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { expect, type Page, type TestInfo, test } from "@playwright/test"

const captureScreenshot = async (
page: Page,
testInfo: TestInfo,
name: string,
) => {
await page.evaluate(() => document.fonts.ready)
await page.screenshot({
animations: "disabled",
caret: "hide",
path: testInfo.snapshotPath(name),
fullPage: true,
})
}

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: {} }),
})
}
if (hostname === "fonts.googleapis.com") {
return route.fulfill({
contentType: "text/css",
body: '@font-face { font-family: "Inter"; src: local("Arial"); }',
})
}
return hostname === "localhost" ? route.continue() : route.abort()
})
Comment on lines +18 to +33

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aborting all non-localhost requests also blocks Google Fonts

await page.addInitScript(
({ showOnboarding }) => {
localStorage.setItem("ShowWelcomeDialog", String(showOnboarding))
localStorage.setItem("defaultLoginMethod", JSON.stringify("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 captureScreenshot(page, testInfo, "login-onboarding-da.png")

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 responsive action grid", 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 actionsToggle = page.getByTestId("wallet-actions-toggle")
await expect(actionsToggle).toBeVisible()
await actionsToggle.getByRole("button").click()
}

await captureScreenshot(page, testInfo, "home-balances-overflow-en.png")
})

test("captures the claim verification requirement", async ({
page,
}, testInfo) => {
await preparePage(page, false)
await login(page)
await expect(page.getByRole("link", { name: "GoodDollar" })).toHaveAttribute(
"href",
"/en/gooddollar",
)
await page.goto("/en/gooddollar")

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 captureScreenshot(page, testInfo, "claim-requires-verification-en.png")
})
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
Loading