Skip to content

Commit 35dd8bd

Browse files
authored
Merge pull request #460 from edehvictor/feat/playwright-visual-regression-124
Add Playwright visual regression coverage
2 parents 11defea + 1061036 commit 35dd8bd

12 files changed

Lines changed: 202 additions & 158 deletions

File tree

frontend/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
"start": "next start",
99
"lint": "next lint",
1010
"test:e2e": "playwright test",
11-
"test:visual": "playwright test tests/e2e/vrt.spec.ts --project=chromium",
12-
"test:visual:update": "playwright test tests/e2e/vrt.spec.ts --project=chromium --update-snapshots"
11+
"test:e2e:update": "playwright test --update-snapshots",
12+
"test:visual": "playwright test tests/e2e/checkout.visual.spec.ts tests/e2e/create-payment.visual.spec.ts",
13+
"test:visual:update": "playwright test tests/e2e/checkout.visual.spec.ts tests/e2e/create-payment.visual.spec.ts --update-snapshots"
1314
},
1415
"dependencies": {
1516
"@ducanh2912/next-pwa": "^10.2.9",

frontend/playwright.config.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,48 @@
11
import { defineConfig, devices } from "@playwright/test";
22

3+
const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
4+
35
export default defineConfig({
46
testDir: "./tests/e2e",
7+
fullyParallel: true,
8+
retries: process.env.CI ? 2 : 0,
9+
reporter: process.env.CI ? "github" : "list",
10+
forbidOnly: !!process.env.CI,
11+
snapshotPathTemplate:
12+
"{testDir}/__screenshots__/{testFilePath}/{arg}-{projectName}{ext}",
513
expect: {
614
toHaveScreenshot: {
715
animations: "disabled",
816
caret: "hide",
917
scale: "css",
18+
maxDiffPixelRatio: 0.01,
1019
},
1120
},
1221
use: {
1322
baseURL: "http://127.0.0.1:3000",
1423
locale: "en-US",
1524
timezoneId: "UTC",
16-
viewport: {
17-
width: 1440,
18-
height: 1200,
19-
},
25+
trace: "on-first-retry",
2026
},
2127
webServer: {
22-
command: "npm run dev",
28+
command: `${npmCommand} run dev -- --hostname 127.0.0.1 --port 3000`,
2329
url: "http://127.0.0.1:3000",
2430
reuseExistingServer: true,
2531
timeout: 120 * 1000,
2632
},
2733
projects: [
28-
{ name: "chromium", use: { ...devices["Desktop Chrome"] } },
29-
{ name: "firefox", use: { ...devices["Desktop Firefox"] } },
30-
{ name: "webkit", use: { ...devices["Desktop Safari"] } },
34+
{
35+
name: "desktop-chrome",
36+
use: {
37+
...devices["Desktop Chrome"],
38+
viewport: { width: 1440, height: 1100 },
39+
},
40+
},
41+
{
42+
name: "mobile-chrome",
43+
use: {
44+
...devices["Pixel 7"],
45+
},
46+
},
3147
],
3248
});

frontend/src/app/(authenticated)/dashboard/create/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export default async function CreatePaymentPage() {
1212
const t = await getTranslations("createPaymentPage");
1313

1414
return (
15-
<main className="mx-auto flex min-h-screen max-w-lg flex-col justify-center gap-10 px-6 py-16">
15+
<main className="mx-auto flex min-h-screen w-full min-w-0 max-w-lg flex-col justify-center gap-10 px-6 py-16">
1616
<header className="flex flex-col gap-3 text-center">
1717
<p className="font-mono text-xs uppercase tracking-[0.3em] text-mint">
1818
{t("eyebrow")}
@@ -25,7 +25,7 @@ export default async function CreatePaymentPage() {
2525
</p>
2626
</header>
2727

28-
<div className="rounded-3xl border border-white/10 bg-white/5 p-8 shadow-2xl backdrop-blur">
28+
<div className="min-w-0 rounded-3xl border border-white/10 bg-white/5 p-8 shadow-2xl backdrop-blur">
2929
<CreatePaymentForm />
3030
</div>
3131

frontend/src/app/(authenticated)/layout.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ export default function AuthenticatedLayout({
1818

1919
return (
2020
<AuthGuard>
21-
<div className="flex min-h-screen bg-black">
21+
<div className="flex min-h-screen overflow-x-hidden bg-black">
2222
{/* Sidebar - fixed width for desktop layout offset */}
2323
<Sidebar />
2424
<MobileNav />
2525
<PaymentToastListener />
2626

2727
{/* Main Content Area */}
28-
<main className="flex-1 transition-all lg:pl-[260px]">
29-
<div className="mx-auto flex max-w-7xl flex-col p-6 lg:p-10">
28+
<main className="min-w-0 flex-1 overflow-x-hidden transition-all lg:pl-[260px]">
29+
<div className="mx-auto flex w-full min-w-0 max-w-7xl flex-col p-6 lg:p-10">
3030
{/* Header with Breadcrumbs */}
3131
<header className="mb-10 flex flex-col gap-6">
3232
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
@@ -41,7 +41,7 @@ export default function AuthenticatedLayout({
4141
initial={{ opacity: 0, y: 10 }}
4242
animate={{ opacity: 1, y: 0 }}
4343
transition={{ duration: 0.4, ease: "easeOut" }}
44-
className="pb-20 lg:pb-0"
44+
className="min-w-0 pb-20 lg:pb-0"
4545
>
4646
{children}
4747
</motion.section>

frontend/src/app/globals.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
:root {
66
color-scheme: light;
7+
--font-sans: "Segoe UI", "Helvetica Neue", Arial, sans-serif;
8+
--font-mono: "Cascadia Code", Consolas, "SFMono-Regular", monospace;
79
}
810

911
:root.dark {

frontend/src/app/layout.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,11 @@ import ToastProvider from "@/components/ToastProvider";
88
import CommandPalette from "@/components/CommandPalette";
99
import KeyboardShortcuts from "@/components/KeyboardShortcuts";
1010
import { WalletContextProvider } from "@/lib/wallet-context";
11+
import { Metadata, Viewport } from "next";
1112

1213
const spaceGrotesk = Space_Grotesk({ subsets: ["latin"], variable: "--font-sans", display: "swap" });
1314
const spaceMono = Space_Mono({ subsets: ["latin"], weight: ["400", "700"], variable: "--font-mono", display: "swap" });
1415

15-
import { Metadata, Viewport } from "next";
16-
1716
export const metadata: Metadata = {
1817
title: "Stellar Payment Dashboard",
1918
description: "Accept Stellar payments with simple links and status tracking.",

frontend/src/components/CreatePaymentForm.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -278,15 +278,19 @@ function SuccessCard({ created, onReset, t }: SuccessCardProps) {
278278

279279
export default function CreatePaymentForm() {
280280
const t = useTranslations("createPaymentForm");
281-
const [amount, setAmount] = useState("");
282-
const [asset, setAsset] = useState<"XLM" | "USDC">("XLM");
283-
const [recipient, setRecipient] = useState("");
284-
const [description, setDescription] = useState("");
281+
const [amount, setAmount] = useLocalStorage("payment_amount", "");
282+
const [asset, setAsset] = useLocalStorage<"XLM" | "USDC">(
283+
"payment_asset",
284+
"XLM",
285+
);
286+
const [recipient, setRecipient] = useLocalStorage("payment_recipient", "");
287+
const [description, setDescription] = useLocalStorage(
288+
"payment_description",
289+
"",
290+
);
285291
const [loading, setLoading] = useState(false);
286292
const [error, setError] = useState<string | null>(null);
287293
const [created, setCreated] = useState<CreatedPayment | null>(null);
288-
289-
290294
const apiKey = useMerchantApiKey();
291295
const hydrated = useMerchantHydrated();
292296
const trustedAddresses = useMerchantTrustedAddresses();
@@ -302,8 +306,6 @@ export default function CreatePaymentForm() {
302306
"payment_trusted_address",
303307
"",
304308
);
305-
306-
307309
useHydrateMerchantStore();
308310

309311
// ── Rate-limit countdown ──────────────────────────────────

frontend/tests/e2e/checkout-branding.spec.ts

Lines changed: 0 additions & 68 deletions
This file was deleted.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { expect, test } from "@playwright/test";
2+
import {
3+
checkoutPaymentId,
4+
expectNoHorizontalOverflow,
5+
mockCheckoutPayment,
6+
prepareVisualSnapshot,
7+
} from "./helpers/fixtures";
8+
9+
test.describe("Checkout Visual Regression", () => {
10+
test.beforeEach(async ({ page }) => {
11+
await mockCheckoutPayment(page);
12+
await page.goto(`/pay/${checkoutPaymentId}`);
13+
await prepareVisualSnapshot(page);
14+
});
15+
16+
test("checkout layout remains stable across viewports", async ({ page }) => {
17+
const checkoutMain = page.locator("main");
18+
await expect(checkoutMain).toBeVisible();
19+
await expect(page.getByText("Complete Payment")).toBeVisible();
20+
await expect(page.getByText("Styled payment")).toBeVisible();
21+
22+
const noOverflow = await expectNoHorizontalOverflow(page);
23+
expect(noOverflow).toBeTruthy();
24+
25+
await expect(checkoutMain).toHaveScreenshot("checkout-page.png");
26+
});
27+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { expect, test } from "@playwright/test";
2+
import {
3+
expectNoHorizontalOverflow,
4+
prepareVisualSnapshot,
5+
seedMerchantSession,
6+
} from "./helpers/fixtures";
7+
8+
test.describe("Create Payment Visual Regression", () => {
9+
test.beforeEach(async ({ page }) => {
10+
await seedMerchantSession(page);
11+
await page.goto("/dashboard/create");
12+
await prepareVisualSnapshot(page);
13+
});
14+
15+
test("create payment form remains visually stable", async ({ page }) => {
16+
const heading = page.getByRole("heading", { name: "Create Payment Link" });
17+
const formShell = heading.locator("xpath=ancestor::main[1]");
18+
19+
await expect(heading).toBeVisible();
20+
await expect(formShell).toBeVisible();
21+
await expect(page.locator("select#trusted-address")).toBeVisible();
22+
23+
const noOverflow = await expectNoHorizontalOverflow(page);
24+
expect(noOverflow).toBeTruthy();
25+
26+
await expect(formShell).toHaveScreenshot("create-payment-form.png");
27+
});
28+
});

0 commit comments

Comments
 (0)