Fix TypeScript errors in products page and Harvest Reach components
This commit is contained in:
@@ -69,7 +69,7 @@ export default async function AdminProductsPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-[var(--admin-bg)]">
|
<div className="min-h-screen bg-[var(--admin-bg)]">
|
||||||
<ProductsClient products={products ?? []} brandId={brandId} />
|
<ProductsClient products={products ?? []} brandId={brandId ?? undefined} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Icon components
|
// Icon components
|
||||||
const LayersIcon = (className: string) => (
|
const LayersIcon = ({ className }: { className: string }) => (
|
||||||
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
<polygon points="12 2 2 7 12 12 22 7 12 2"/>
|
<polygon points="12 2 2 7 12 12 22 7 12 2"/>
|
||||||
<polyline points="2 17 12 22 22 17"/>
|
<polyline points="2 17 12 22 22 17"/>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { AdminButton, AdminIconButton, AdminSearchInput } from "@/components/adm
|
|||||||
|
|
||||||
// Icon components
|
// Icon components
|
||||||
const Icons = {
|
const Icons = {
|
||||||
plus: (className: string) => (
|
Plus: ({ className }: { className: string }) => (
|
||||||
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
<path d="M12 5v14M5 12h14" />
|
<path d="M12 5v14M5 12h14" />
|
||||||
</svg>
|
</svg>
|
||||||
@@ -47,7 +47,7 @@ export default function SegmentListSidebar({ segments, activeSegmentId, onSelect
|
|||||||
variant="primary"
|
variant="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
<Icons.plus className="w-4 h-4" />
|
<Icons.Plus className="w-4 h-4" />
|
||||||
</AdminIconButton>
|
</AdminIconButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ const PackageIconHeader = () => (
|
|||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default function ProductsClient({ products, brandId }: { products: Product[]; brandId: string }) {
|
export default function ProductsClient({ products, brandId }: { products: Product[]; brandId?: string }) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [, startTransition] = useTransition();
|
const [, startTransition] = useTransition();
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
@@ -234,6 +234,12 @@ export default function ProductsClient({ products, brandId }: { products: Produc
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (!brandId) {
|
||||||
|
setError("Brand ID is required");
|
||||||
|
setSaving(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let result;
|
let result;
|
||||||
const imageUrl = pendingImageUrl || formData.image_url || null;
|
const imageUrl = pendingImageUrl || formData.image_url || null;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
{
|
{
|
||||||
"status": "passed",
|
"status": "failed",
|
||||||
"failedTests": []
|
"failedTests": [
|
||||||
|
"9baefabce98ea24ee76d-2f6f670dfe942637faf7",
|
||||||
|
"9baefabce98ea24ee76d-f58eca1309366ec2998a",
|
||||||
|
"9baefabce98ea24ee76d-4a32d345869f5c6ceb12"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
+152
@@ -0,0 +1,152 @@
|
|||||||
|
# Instructions
|
||||||
|
|
||||||
|
- Following Playwright test failed.
|
||||||
|
- Explain why, be concise, respect Playwright best practices.
|
||||||
|
- Provide a snippet of code with the fix, if possible.
|
||||||
|
|
||||||
|
# Test info
|
||||||
|
|
||||||
|
- Name: login/login-flow.spec.ts >> admin session persists across page reloads
|
||||||
|
- Location: tests/login/login-flow.spec.ts:65:5
|
||||||
|
|
||||||
|
# Error details
|
||||||
|
|
||||||
|
```
|
||||||
|
Error: page.fill: value: expected string, got undefined
|
||||||
|
```
|
||||||
|
|
||||||
|
# Page snapshot
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- generic [active] [ref=e1]:
|
||||||
|
- banner [ref=e2]:
|
||||||
|
- generic [ref=e3]:
|
||||||
|
- link "Route Commerce" [ref=e4] [cursor=pointer]:
|
||||||
|
- /url: /
|
||||||
|
- navigation [ref=e5]:
|
||||||
|
- link "Tuxedo" [ref=e6] [cursor=pointer]:
|
||||||
|
- /url: /tuxedo
|
||||||
|
- link "IRD" [ref=e7] [cursor=pointer]:
|
||||||
|
- /url: /indian-river-direct
|
||||||
|
- link "Admin" [ref=e8] [cursor=pointer]:
|
||||||
|
- /url: /admin
|
||||||
|
- button "Switch to dark mode" [ref=e9]:
|
||||||
|
- img [ref=e10]
|
||||||
|
- link "Cart (0)" [ref=e12] [cursor=pointer]:
|
||||||
|
- /url: /cart
|
||||||
|
- main [ref=e13]:
|
||||||
|
- generic [ref=e14]:
|
||||||
|
- generic [ref=e15]:
|
||||||
|
- heading "Admin Login" [level=1] [ref=e16]
|
||||||
|
- paragraph [ref=e17]: Sign in with your account.
|
||||||
|
- generic [ref=e18]:
|
||||||
|
- generic [ref=e19]:
|
||||||
|
- generic [ref=e20]: Email
|
||||||
|
- textbox "Email" [ref=e21]:
|
||||||
|
- /placeholder: admin@example.com
|
||||||
|
- generic [ref=e22]:
|
||||||
|
- generic [ref=e23]: Password
|
||||||
|
- textbox "Password" [ref=e24]:
|
||||||
|
- /placeholder: ••••••••
|
||||||
|
- button "Sign In" [ref=e25]
|
||||||
|
- button "Forgot password?" [ref=e26]
|
||||||
|
- link "← Back to storefront" [ref=e27] [cursor=pointer]:
|
||||||
|
- /url: /
|
||||||
|
- contentinfo [ref=e28]:
|
||||||
|
- generic [ref=e30]:
|
||||||
|
- paragraph [ref=e31]: © 2026 Route Commerce. All rights reserved.
|
||||||
|
- navigation [ref=e32]:
|
||||||
|
- link "Privacy Policy" [ref=e33] [cursor=pointer]:
|
||||||
|
- /url: /privacy-policy
|
||||||
|
- link "Terms & Conditions" [ref=e34] [cursor=pointer]:
|
||||||
|
- /url: /terms-and-conditions
|
||||||
|
- link "Powered by Route Commerce" [ref=e35] [cursor=pointer]:
|
||||||
|
- /url: https://cielohermosa.com
|
||||||
|
- alert [ref=e36]
|
||||||
|
```
|
||||||
|
|
||||||
|
# Test source
|
||||||
|
|
||||||
|
```ts
|
||||||
|
1 | import { test, expect } from "@playwright/test";
|
||||||
|
2 |
|
||||||
|
3 | // ─────────────────────────────────────────────────────────────
|
||||||
|
4 | // Login flow: credentials → /api/login JSON → redirect to /admin
|
||||||
|
5 | // ─────────────────────────────────────────────────────────────
|
||||||
|
6 | test("login with valid credentials redirects to /admin and shows admin dashboard", async ({
|
||||||
|
7 | page,
|
||||||
|
8 | }) => {
|
||||||
|
9 | // Navigate to login page
|
||||||
|
10 | await page.goto("/login");
|
||||||
|
11 |
|
||||||
|
12 | // Fill credentials
|
||||||
|
13 | await page.fill("#email", process.env.TEST_ADMIN_EMAIL!);
|
||||||
|
14 | await page.fill("#password", process.env.TEST_ADMIN_PASSWORD!);
|
||||||
|
15 |
|
||||||
|
16 | // Submit form
|
||||||
|
17 | await page.click('button[type="submit"]');
|
||||||
|
18 |
|
||||||
|
19 | // Wait for navigation to /admin — the JSON response + client nav must happen
|
||||||
|
20 | await page.waitForURL("**/admin", { timeout: 10000 });
|
||||||
|
21 |
|
||||||
|
22 | // Admin dashboard must be rendered (Control Center heading or admin layout)
|
||||||
|
23 | await expect(page.locator("body")).not.toContainText("Access Denied");
|
||||||
|
24 | await expect(page.locator("body")).not.toContainText("Login failed");
|
||||||
|
25 | });
|
||||||
|
26 |
|
||||||
|
27 | // ─────────────────────────────────────────────────────────────
|
||||||
|
28 | // Login failure: bad password shows error, stays on /login
|
||||||
|
29 | // ─────────────────────────────────────────────────────────────
|
||||||
|
30 | test("login with wrong password shows error and stays on /login", async ({
|
||||||
|
31 | page,
|
||||||
|
32 | }) => {
|
||||||
|
33 | await page.goto("/login");
|
||||||
|
34 |
|
||||||
|
35 | await page.fill("#email", process.env.TEST_ADMIN_EMAIL!);
|
||||||
|
36 | await page.fill("#password", "wrongpassword123!");
|
||||||
|
37 |
|
||||||
|
38 | await page.click('button[type="submit"]');
|
||||||
|
39 |
|
||||||
|
40 | // Error message should appear
|
||||||
|
41 | await expect(page.locator('[role="alert"]')).toBeVisible({ timeout: 5000 });
|
||||||
|
42 | // Should NOT navigate away from login
|
||||||
|
43 | await expect(page).toHaveURL(/\/login/);
|
||||||
|
44 | });
|
||||||
|
45 |
|
||||||
|
46 | // ─────────────────────────────────────────────────────────────
|
||||||
|
47 | // Login with missing fields shows validation error
|
||||||
|
48 | // ─────────────────────────────────────────────────────────────
|
||||||
|
49 | test("login with missing email shows validation error", async ({ page }) => {
|
||||||
|
50 | await page.goto("/login");
|
||||||
|
51 |
|
||||||
|
52 | // Don't fill email, only password
|
||||||
|
53 | await page.fill("#password", "something");
|
||||||
|
54 |
|
||||||
|
55 | await page.click('button[type="submit"]');
|
||||||
|
56 |
|
||||||
|
57 | // Browser validation should fire (email required)
|
||||||
|
58 | await expect(page.locator("#email")).toHaveAttribute("required", "");
|
||||||
|
59 | });
|
||||||
|
60 |
|
||||||
|
61 | // ─────────────────────────────────────────────────────────────
|
||||||
|
62 | // Session persistence: after login, navigating to /admin
|
||||||
|
63 | // should load without re-authenticating
|
||||||
|
64 | // ─────────────────────────────────────────────────────────────
|
||||||
|
65 | test("admin session persists across page reloads", async ({ page }) => {
|
||||||
|
66 | // Login first
|
||||||
|
67 | await page.goto("/login");
|
||||||
|
> 68 | await page.fill("#email", process.env.TEST_ADMIN_EMAIL!);
|
||||||
|
| ^ Error: page.fill: value: expected string, got undefined
|
||||||
|
69 | await page.fill("#password", process.env.TEST_ADMIN_PASSWORD!);
|
||||||
|
70 | await page.click('button[type="submit"]');
|
||||||
|
71 | await page.waitForURL("**/admin", { timeout: 10000 });
|
||||||
|
72 |
|
||||||
|
73 | // Reload the page
|
||||||
|
74 | await page.reload();
|
||||||
|
75 |
|
||||||
|
76 | // Should still be on /admin (session cookie keeps user logged in)
|
||||||
|
77 | await expect(page).toHaveURL(/\/admin/);
|
||||||
|
78 | await expect(page.locator("body")).not.toContainText("Access Denied");
|
||||||
|
79 | });
|
||||||
|
80 |
|
||||||
|
```
|
||||||
+152
@@ -0,0 +1,152 @@
|
|||||||
|
# Instructions
|
||||||
|
|
||||||
|
- Following Playwright test failed.
|
||||||
|
- Explain why, be concise, respect Playwright best practices.
|
||||||
|
- Provide a snippet of code with the fix, if possible.
|
||||||
|
|
||||||
|
# Test info
|
||||||
|
|
||||||
|
- Name: login/login-flow.spec.ts >> login with wrong password shows error and stays on /login
|
||||||
|
- Location: tests/login/login-flow.spec.ts:30:5
|
||||||
|
|
||||||
|
# Error details
|
||||||
|
|
||||||
|
```
|
||||||
|
Error: page.fill: value: expected string, got undefined
|
||||||
|
```
|
||||||
|
|
||||||
|
# Page snapshot
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- generic [active] [ref=e1]:
|
||||||
|
- banner [ref=e2]:
|
||||||
|
- generic [ref=e3]:
|
||||||
|
- link "Route Commerce" [ref=e4] [cursor=pointer]:
|
||||||
|
- /url: /
|
||||||
|
- navigation [ref=e5]:
|
||||||
|
- link "Tuxedo" [ref=e6] [cursor=pointer]:
|
||||||
|
- /url: /tuxedo
|
||||||
|
- link "IRD" [ref=e7] [cursor=pointer]:
|
||||||
|
- /url: /indian-river-direct
|
||||||
|
- link "Admin" [ref=e8] [cursor=pointer]:
|
||||||
|
- /url: /admin
|
||||||
|
- button "Switch to dark mode" [ref=e9]:
|
||||||
|
- img [ref=e10]
|
||||||
|
- link "Cart (0)" [ref=e12] [cursor=pointer]:
|
||||||
|
- /url: /cart
|
||||||
|
- main [ref=e13]:
|
||||||
|
- generic [ref=e14]:
|
||||||
|
- generic [ref=e15]:
|
||||||
|
- heading "Admin Login" [level=1] [ref=e16]
|
||||||
|
- paragraph [ref=e17]: Sign in with your account.
|
||||||
|
- generic [ref=e18]:
|
||||||
|
- generic [ref=e19]:
|
||||||
|
- generic [ref=e20]: Email
|
||||||
|
- textbox "Email" [ref=e21]:
|
||||||
|
- /placeholder: admin@example.com
|
||||||
|
- generic [ref=e22]:
|
||||||
|
- generic [ref=e23]: Password
|
||||||
|
- textbox "Password" [ref=e24]:
|
||||||
|
- /placeholder: ••••••••
|
||||||
|
- button "Sign In" [ref=e25]
|
||||||
|
- button "Forgot password?" [ref=e26]
|
||||||
|
- link "← Back to storefront" [ref=e27] [cursor=pointer]:
|
||||||
|
- /url: /
|
||||||
|
- contentinfo [ref=e28]:
|
||||||
|
- generic [ref=e30]:
|
||||||
|
- paragraph [ref=e31]: © 2026 Route Commerce. All rights reserved.
|
||||||
|
- navigation [ref=e32]:
|
||||||
|
- link "Privacy Policy" [ref=e33] [cursor=pointer]:
|
||||||
|
- /url: /privacy-policy
|
||||||
|
- link "Terms & Conditions" [ref=e34] [cursor=pointer]:
|
||||||
|
- /url: /terms-and-conditions
|
||||||
|
- link "Powered by Route Commerce" [ref=e35] [cursor=pointer]:
|
||||||
|
- /url: https://cielohermosa.com
|
||||||
|
- alert [ref=e36]
|
||||||
|
```
|
||||||
|
|
||||||
|
# Test source
|
||||||
|
|
||||||
|
```ts
|
||||||
|
1 | import { test, expect } from "@playwright/test";
|
||||||
|
2 |
|
||||||
|
3 | // ─────────────────────────────────────────────────────────────
|
||||||
|
4 | // Login flow: credentials → /api/login JSON → redirect to /admin
|
||||||
|
5 | // ─────────────────────────────────────────────────────────────
|
||||||
|
6 | test("login with valid credentials redirects to /admin and shows admin dashboard", async ({
|
||||||
|
7 | page,
|
||||||
|
8 | }) => {
|
||||||
|
9 | // Navigate to login page
|
||||||
|
10 | await page.goto("/login");
|
||||||
|
11 |
|
||||||
|
12 | // Fill credentials
|
||||||
|
13 | await page.fill("#email", process.env.TEST_ADMIN_EMAIL!);
|
||||||
|
14 | await page.fill("#password", process.env.TEST_ADMIN_PASSWORD!);
|
||||||
|
15 |
|
||||||
|
16 | // Submit form
|
||||||
|
17 | await page.click('button[type="submit"]');
|
||||||
|
18 |
|
||||||
|
19 | // Wait for navigation to /admin — the JSON response + client nav must happen
|
||||||
|
20 | await page.waitForURL("**/admin", { timeout: 10000 });
|
||||||
|
21 |
|
||||||
|
22 | // Admin dashboard must be rendered (Control Center heading or admin layout)
|
||||||
|
23 | await expect(page.locator("body")).not.toContainText("Access Denied");
|
||||||
|
24 | await expect(page.locator("body")).not.toContainText("Login failed");
|
||||||
|
25 | });
|
||||||
|
26 |
|
||||||
|
27 | // ─────────────────────────────────────────────────────────────
|
||||||
|
28 | // Login failure: bad password shows error, stays on /login
|
||||||
|
29 | // ─────────────────────────────────────────────────────────────
|
||||||
|
30 | test("login with wrong password shows error and stays on /login", async ({
|
||||||
|
31 | page,
|
||||||
|
32 | }) => {
|
||||||
|
33 | await page.goto("/login");
|
||||||
|
34 |
|
||||||
|
> 35 | await page.fill("#email", process.env.TEST_ADMIN_EMAIL!);
|
||||||
|
| ^ Error: page.fill: value: expected string, got undefined
|
||||||
|
36 | await page.fill("#password", "wrongpassword123!");
|
||||||
|
37 |
|
||||||
|
38 | await page.click('button[type="submit"]');
|
||||||
|
39 |
|
||||||
|
40 | // Error message should appear
|
||||||
|
41 | await expect(page.locator('[role="alert"]')).toBeVisible({ timeout: 5000 });
|
||||||
|
42 | // Should NOT navigate away from login
|
||||||
|
43 | await expect(page).toHaveURL(/\/login/);
|
||||||
|
44 | });
|
||||||
|
45 |
|
||||||
|
46 | // ─────────────────────────────────────────────────────────────
|
||||||
|
47 | // Login with missing fields shows validation error
|
||||||
|
48 | // ─────────────────────────────────────────────────────────────
|
||||||
|
49 | test("login with missing email shows validation error", async ({ page }) => {
|
||||||
|
50 | await page.goto("/login");
|
||||||
|
51 |
|
||||||
|
52 | // Don't fill email, only password
|
||||||
|
53 | await page.fill("#password", "something");
|
||||||
|
54 |
|
||||||
|
55 | await page.click('button[type="submit"]');
|
||||||
|
56 |
|
||||||
|
57 | // Browser validation should fire (email required)
|
||||||
|
58 | await expect(page.locator("#email")).toHaveAttribute("required", "");
|
||||||
|
59 | });
|
||||||
|
60 |
|
||||||
|
61 | // ─────────────────────────────────────────────────────────────
|
||||||
|
62 | // Session persistence: after login, navigating to /admin
|
||||||
|
63 | // should load without re-authenticating
|
||||||
|
64 | // ─────────────────────────────────────────────────────────────
|
||||||
|
65 | test("admin session persists across page reloads", async ({ page }) => {
|
||||||
|
66 | // Login first
|
||||||
|
67 | await page.goto("/login");
|
||||||
|
68 | await page.fill("#email", process.env.TEST_ADMIN_EMAIL!);
|
||||||
|
69 | await page.fill("#password", process.env.TEST_ADMIN_PASSWORD!);
|
||||||
|
70 | await page.click('button[type="submit"]');
|
||||||
|
71 | await page.waitForURL("**/admin", { timeout: 10000 });
|
||||||
|
72 |
|
||||||
|
73 | // Reload the page
|
||||||
|
74 | await page.reload();
|
||||||
|
75 |
|
||||||
|
76 | // Should still be on /admin (session cookie keeps user logged in)
|
||||||
|
77 | await expect(page).toHaveURL(/\/admin/);
|
||||||
|
78 | await expect(page.locator("body")).not.toContainText("Access Denied");
|
||||||
|
79 | });
|
||||||
|
80 |
|
||||||
|
```
|
||||||
+152
@@ -0,0 +1,152 @@
|
|||||||
|
# Instructions
|
||||||
|
|
||||||
|
- Following Playwright test failed.
|
||||||
|
- Explain why, be concise, respect Playwright best practices.
|
||||||
|
- Provide a snippet of code with the fix, if possible.
|
||||||
|
|
||||||
|
# Test info
|
||||||
|
|
||||||
|
- Name: login/login-flow.spec.ts >> login with valid credentials redirects to /admin and shows admin dashboard
|
||||||
|
- Location: tests/login/login-flow.spec.ts:6:5
|
||||||
|
|
||||||
|
# Error details
|
||||||
|
|
||||||
|
```
|
||||||
|
Error: page.fill: value: expected string, got undefined
|
||||||
|
```
|
||||||
|
|
||||||
|
# Page snapshot
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- generic [active] [ref=e1]:
|
||||||
|
- banner [ref=e2]:
|
||||||
|
- generic [ref=e3]:
|
||||||
|
- link "Route Commerce" [ref=e4] [cursor=pointer]:
|
||||||
|
- /url: /
|
||||||
|
- navigation [ref=e5]:
|
||||||
|
- link "Tuxedo" [ref=e6] [cursor=pointer]:
|
||||||
|
- /url: /tuxedo
|
||||||
|
- link "IRD" [ref=e7] [cursor=pointer]:
|
||||||
|
- /url: /indian-river-direct
|
||||||
|
- link "Admin" [ref=e8] [cursor=pointer]:
|
||||||
|
- /url: /admin
|
||||||
|
- button "Switch to dark mode" [ref=e9]:
|
||||||
|
- img [ref=e10]
|
||||||
|
- link "Cart (0)" [ref=e12] [cursor=pointer]:
|
||||||
|
- /url: /cart
|
||||||
|
- main [ref=e13]:
|
||||||
|
- generic [ref=e14]:
|
||||||
|
- generic [ref=e15]:
|
||||||
|
- heading "Admin Login" [level=1] [ref=e16]
|
||||||
|
- paragraph [ref=e17]: Sign in with your account.
|
||||||
|
- generic [ref=e18]:
|
||||||
|
- generic [ref=e19]:
|
||||||
|
- generic [ref=e20]: Email
|
||||||
|
- textbox "Email" [ref=e21]:
|
||||||
|
- /placeholder: admin@example.com
|
||||||
|
- generic [ref=e22]:
|
||||||
|
- generic [ref=e23]: Password
|
||||||
|
- textbox "Password" [ref=e24]:
|
||||||
|
- /placeholder: ••••••••
|
||||||
|
- button "Sign In" [ref=e25]
|
||||||
|
- button "Forgot password?" [ref=e26]
|
||||||
|
- link "← Back to storefront" [ref=e27] [cursor=pointer]:
|
||||||
|
- /url: /
|
||||||
|
- contentinfo [ref=e28]:
|
||||||
|
- generic [ref=e30]:
|
||||||
|
- paragraph [ref=e31]: © 2026 Route Commerce. All rights reserved.
|
||||||
|
- navigation [ref=e32]:
|
||||||
|
- link "Privacy Policy" [ref=e33] [cursor=pointer]:
|
||||||
|
- /url: /privacy-policy
|
||||||
|
- link "Terms & Conditions" [ref=e34] [cursor=pointer]:
|
||||||
|
- /url: /terms-and-conditions
|
||||||
|
- link "Powered by Route Commerce" [ref=e35] [cursor=pointer]:
|
||||||
|
- /url: https://cielohermosa.com
|
||||||
|
- alert [ref=e36]
|
||||||
|
```
|
||||||
|
|
||||||
|
# Test source
|
||||||
|
|
||||||
|
```ts
|
||||||
|
1 | import { test, expect } from "@playwright/test";
|
||||||
|
2 |
|
||||||
|
3 | // ─────────────────────────────────────────────────────────────
|
||||||
|
4 | // Login flow: credentials → /api/login JSON → redirect to /admin
|
||||||
|
5 | // ─────────────────────────────────────────────────────────────
|
||||||
|
6 | test("login with valid credentials redirects to /admin and shows admin dashboard", async ({
|
||||||
|
7 | page,
|
||||||
|
8 | }) => {
|
||||||
|
9 | // Navigate to login page
|
||||||
|
10 | await page.goto("/login");
|
||||||
|
11 |
|
||||||
|
12 | // Fill credentials
|
||||||
|
> 13 | await page.fill("#email", process.env.TEST_ADMIN_EMAIL!);
|
||||||
|
| ^ Error: page.fill: value: expected string, got undefined
|
||||||
|
14 | await page.fill("#password", process.env.TEST_ADMIN_PASSWORD!);
|
||||||
|
15 |
|
||||||
|
16 | // Submit form
|
||||||
|
17 | await page.click('button[type="submit"]');
|
||||||
|
18 |
|
||||||
|
19 | // Wait for navigation to /admin — the JSON response + client nav must happen
|
||||||
|
20 | await page.waitForURL("**/admin", { timeout: 10000 });
|
||||||
|
21 |
|
||||||
|
22 | // Admin dashboard must be rendered (Control Center heading or admin layout)
|
||||||
|
23 | await expect(page.locator("body")).not.toContainText("Access Denied");
|
||||||
|
24 | await expect(page.locator("body")).not.toContainText("Login failed");
|
||||||
|
25 | });
|
||||||
|
26 |
|
||||||
|
27 | // ─────────────────────────────────────────────────────────────
|
||||||
|
28 | // Login failure: bad password shows error, stays on /login
|
||||||
|
29 | // ─────────────────────────────────────────────────────────────
|
||||||
|
30 | test("login with wrong password shows error and stays on /login", async ({
|
||||||
|
31 | page,
|
||||||
|
32 | }) => {
|
||||||
|
33 | await page.goto("/login");
|
||||||
|
34 |
|
||||||
|
35 | await page.fill("#email", process.env.TEST_ADMIN_EMAIL!);
|
||||||
|
36 | await page.fill("#password", "wrongpassword123!");
|
||||||
|
37 |
|
||||||
|
38 | await page.click('button[type="submit"]');
|
||||||
|
39 |
|
||||||
|
40 | // Error message should appear
|
||||||
|
41 | await expect(page.locator('[role="alert"]')).toBeVisible({ timeout: 5000 });
|
||||||
|
42 | // Should NOT navigate away from login
|
||||||
|
43 | await expect(page).toHaveURL(/\/login/);
|
||||||
|
44 | });
|
||||||
|
45 |
|
||||||
|
46 | // ─────────────────────────────────────────────────────────────
|
||||||
|
47 | // Login with missing fields shows validation error
|
||||||
|
48 | // ─────────────────────────────────────────────────────────────
|
||||||
|
49 | test("login with missing email shows validation error", async ({ page }) => {
|
||||||
|
50 | await page.goto("/login");
|
||||||
|
51 |
|
||||||
|
52 | // Don't fill email, only password
|
||||||
|
53 | await page.fill("#password", "something");
|
||||||
|
54 |
|
||||||
|
55 | await page.click('button[type="submit"]');
|
||||||
|
56 |
|
||||||
|
57 | // Browser validation should fire (email required)
|
||||||
|
58 | await expect(page.locator("#email")).toHaveAttribute("required", "");
|
||||||
|
59 | });
|
||||||
|
60 |
|
||||||
|
61 | // ─────────────────────────────────────────────────────────────
|
||||||
|
62 | // Session persistence: after login, navigating to /admin
|
||||||
|
63 | // should load without re-authenticating
|
||||||
|
64 | // ─────────────────────────────────────────────────────────────
|
||||||
|
65 | test("admin session persists across page reloads", async ({ page }) => {
|
||||||
|
66 | // Login first
|
||||||
|
67 | await page.goto("/login");
|
||||||
|
68 | await page.fill("#email", process.env.TEST_ADMIN_EMAIL!);
|
||||||
|
69 | await page.fill("#password", process.env.TEST_ADMIN_PASSWORD!);
|
||||||
|
70 | await page.click('button[type="submit"]');
|
||||||
|
71 | await page.waitForURL("**/admin", { timeout: 10000 });
|
||||||
|
72 |
|
||||||
|
73 | // Reload the page
|
||||||
|
74 | await page.reload();
|
||||||
|
75 |
|
||||||
|
76 | // Should still be on /admin (session cookie keeps user logged in)
|
||||||
|
77 | await expect(page).toHaveURL(/\/admin/);
|
||||||
|
78 | await expect(page.locator("body")).not.toContainText("Access Denied");
|
||||||
|
79 | });
|
||||||
|
80 |
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user