Skip to content

Commit c2b17cf

Browse files
Merge pull request #6 from testerxiaodong/e2e/plawright
finish e2e test
2 parents b4887d6 + 35287c8 commit c2b17cf

26 files changed

+309
-54
lines changed

playwright.config.ts

+10-6
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,25 @@ export default defineConfig({
2424
/* Configure projects for major browsers */
2525
projects: [
2626
// Setup project
27-
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
27+
{ name: 'setup', testMatch: /setup-admin\.setup\.ts/ },
28+
// logout project
29+
{
30+
name: 'logout',
31+
testMatch: /logout\.spec\.ts/,
32+
},
2833
{
2934
name: 'chromium',
3035
use: {
3136
...devices['Desktop Chrome'],
32-
storageState: 'tests/.auth/user.json',
37+
storageState: 'tests/.auth/admin.json',
3338
},
3439
dependencies: ['setup'],
3540
},
36-
3741
{
3842
name: 'firefox',
3943
use: {
4044
...devices['Desktop Firefox'],
41-
storageState: 'tests/.auth/user.json',
45+
storageState: 'tests/.auth/admin.json',
4246
},
4347
dependencies: ['setup'],
4448
},
@@ -47,7 +51,7 @@ export default defineConfig({
4751
name: 'webkit',
4852
use: {
4953
...devices['Desktop Safari'],
50-
storageState: 'tests/.auth/user.json',
54+
storageState: 'tests/.auth/admin.json',
5155
},
5256
dependencies: ['setup'],
5357
},
@@ -57,6 +61,6 @@ export default defineConfig({
5761
command: 'npm run build && npm run start',
5862
port: 3000,
5963
reuseExistingServer: !process.env.CI,
60-
timeout: 120 * 1000,
64+
timeout: 200 * 1000,
6165
},
6266
})

src/actions/orders.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ export const updateOrderStatus = async (orderId: number, status: string) => {
2828

2929
// 查询用户信息:userId
3030
const {
31-
data: { session },
32-
} = await supabase.auth.getSession()
31+
data: { user },
32+
} = await supabase.auth.getUser()
3333

34-
const userId = session?.user.id
34+
const userId = user?.id
3535

3636
if (!userId) throw new Error('User not found')
3737

src/app/admin/categories/category-form.tsx

+9-3
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,9 @@ export const CategoryForm = ({
103103
try {
104104
return await imageUploadHandler(formData)
105105
} catch {
106-
toast.error('Image upload failed!')
106+
toast.error('Image upload failed!', {
107+
position: 'top-right',
108+
})
107109
return null
108110
}
109111
}
@@ -122,13 +124,17 @@ export const CategoryForm = ({
122124
imageUrl,
123125
slug: defaultValues?.slug || slug, // 如果已有 slug,则沿用;否则根据名称生成
124126
})
125-
toast.success('Category updated successfully')
127+
toast.success('Category updated successfully', {
128+
position: 'top-right',
129+
})
126130
} else {
127131
// 创建新分类
128132
const imageUrl = await handleImageUpload()
129133
if (imageUrl) {
130134
await createCategory({ imageUrl, name })
131-
toast.success('Category created successfully')
135+
toast.success('Category created successfully', {
136+
position: 'top-right',
137+
})
132138
}
133139
}
134140
form.reset()

src/app/admin/categories/category-table-row.tsx

+6-2
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,13 @@ export const CategoryTableRow = ({
6060
// 删除分类
6161
try {
6262
await deleteCategory(id)
63-
toast.success('Category deleted successfully')
63+
toast.success('Category deleted successfully', {
64+
position: 'top-right',
65+
})
6466
} catch {
65-
toast.error('Failed to delete category')
67+
toast.error('Failed to delete category', {
68+
position: 'top-right',
69+
})
6670
}
6771

6872
// 刷新页面

src/app/admin/products/page-component.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ export const ProductPageComponent: FC<Props> = ({
5353
if (currentProduct?.slug) {
5454
await deleteProduct(currentProduct.slug)
5555
router.refresh()
56-
toast.success('Product deleted successfully')
56+
toast.success('Product deleted successfully', {
57+
position: 'top-right',
58+
})
5759
setIsDeleteModalOpen(false)
5860
setCurrentProduct(null)
5961
}

src/app/admin/products/product-dialog-form.tsx

+13-4
Original file line numberDiff line numberDiff line change
@@ -167,13 +167,17 @@ export const ProductForm = ({
167167

168168
// 检查主图是否成功上传
169169
if (!uploadedHeroImage) {
170-
toast.error('Failed to upload the hero image. Please try again.')
170+
toast.error('Failed to upload the hero image. Please try again.', {
171+
position: 'top-right',
172+
})
171173
return
172174
}
173175

174176
// 检查是否所有产品图片都成功上传
175177
if (uploadedImages.includes(null)) {
176-
toast.error('Failed to upload some product images. Please try again.')
178+
toast.error('Failed to upload some product images. Please try again.', {
179+
position: 'top-right',
180+
})
177181
return
178182
}
179183

@@ -203,14 +207,19 @@ export const ProductForm = ({
203207
toast.success(
204208
isEditMode
205209
? 'Product updated successfully!'
206-
: 'Product created successfully!'
210+
: 'Product created successfully!',
211+
{
212+
position: 'top-right',
213+
}
207214
)
208215
form.reset() // 重置表单
209216
router.refresh() // 刷新页面
210217
setIsProductModalOpen(false) // 关闭模态框
211218
} catch (error) {
212219
console.error(error)
213-
toast.error('Something went wrong. Please try again.')
220+
toast.error('Something went wrong. Please try again.', {
221+
position: 'top-right',
222+
})
214223
}
215224
}
216225

src/app/admin/products/schema.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ export const createOrUpdateProductSchema = z.object({
55
price: z
66
.number()
77
.int({ message: 'Price must be an integer' })
8-
.min(0, { message: 'Price must be a positive integer' }),
8+
.positive({ message: 'Price is required' }),
99
maxQuantity: z
1010
.number()
1111
.int({ message: 'Max Quantity must be an integer' })
12-
.min(0, { message: 'Max Quantity must be a positive integer' }),
12+
.positive({ message: 'Max Quantity is required' }),
1313
category: z.string().min(1, { message: 'Category is required' }),
1414
heroImage: z
1515
.union([

src/app/auth/page.tsx

+6-2
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,13 @@ export default function Auth() {
5858
} catch (error) {
5959
// 捕获到异常,提示异常中的错误信息
6060
if (error instanceof Error) {
61-
toast.error(error.message)
61+
toast.error(error.message, {
62+
position: 'top-right',
63+
})
6264
} else {
63-
toast.error('An error occurred while authenticating')
65+
toast.error('An error occurred while authenticating', {
66+
position: 'top-right',
67+
})
6468
}
6569
} finally {
6670
setIsAuthenticating(false)

src/constants/constants.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export const ADMIN = 'admin'
2+
export const USER = 'user'

tests/.auth/admin.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"cookies": [
3+
{
4+
"name": "sb-binfgbgsoetsbeapeoks-auth-token",
5+
"value": "base64-eyJhY2Nlc3NfdG9rZW4iOiJleUpoYkdjaU9pSklVekkxTmlJc0ltdHBaQ0k2SWpsa1RXazVhSFJXWjFsNFRXMXpkSEVpTENKMGVYQWlPaUpLVjFRaWZRLmV5SnBjM01pT2lKb2RIUndjem92TDJKcGJtWm5ZbWR6YjJWMGMySmxZWEJsYjJ0ekxuTjFjR0ZpWVhObExtTnZMMkYxZEdndmRqRWlMQ0p6ZFdJaU9pSmhNREF6WXpBeE55MHlNV1F6TFRReFpqWXRPRE0yTkMwMVpUbGxaV0psWWprek5XUWlMQ0poZFdRaU9pSmhkWFJvWlc1MGFXTmhkR1ZrSWl3aVpYaHdJam94TnpNek5Ea3dOREV6TENKcFlYUWlPakUzTXpNME9EWTRNVE1zSW1WdFlXbHNJam9pZEdWemRFQmhaRzFwYmk1amIyMGlMQ0p3YUc5dVpTSTZJaUlzSW1Gd2NGOXRaWFJoWkdGMFlTSTZleUp3Y205MmFXUmxjaUk2SW1WdFlXbHNJaXdpY0hKdmRtbGtaWEp6SWpwYkltVnRZV2xzSWwxOUxDSjFjMlZ5WDIxbGRHRmtZWFJoSWpwN0ltVnRZV2xzSWpvaWRHVnpkRUJoWkcxcGJpNWpiMjBpTENKbGJXRnBiRjkyWlhKcFptbGxaQ0k2Wm1Gc2MyVXNJbkJvYjI1bFgzWmxjbWxtYVdWa0lqcG1ZV3h6WlN3aWMzVmlJam9pWVRBd00yTXdNVGN0TWpGa015MDBNV1kyTFRnek5qUXROV1U1WldWaVpXSTVNelZrSW4wc0luSnZiR1VpT2lKaGRYUm9aVzUwYVdOaGRHVmtJaXdpWVdGc0lqb2lZV0ZzTVNJc0ltRnRjaUk2VzNzaWJXVjBhRzlrSWpvaWNHRnpjM2R2Y21RaUxDSjBhVzFsYzNSaGJYQWlPakUzTXpNME9EWTRNVE45WFN3aWMyVnpjMmx2Ymw5cFpDSTZJbVZrWW1WaU1XSmxMVGMxTVRBdE5EUmtaUzA0T0RZMUxXSXdOMkV4T0RRMk5ESTVNeUlzSW1selgyRnViMjU1Ylc5MWN5STZabUZzYzJWOS5yNU9hZW8wckRvUk1CeFlZSWZZMFRIaUd0LVVDcFZaTjhmNmpiOW9SOG5nIiwidG9rZW5fdHlwZSI6ImJlYXJlciIsImV4cGlyZXNfaW4iOjM2MDAsImV4cGlyZXNfYXQiOjE3MzM0OTA0MTMsInJlZnJlc2hfdG9rZW4iOiJPb0NPT3lwdGVRdFNWZjFXMHlJZXd3IiwidXNlciI6eyJpZCI6ImEwMDNjMDE3LTIxZDMtNDFmNi04MzY0LTVlOWVlYmViOTM1ZCIsImF1ZCI6ImF1dGhlbnRpY2F0ZWQiLCJyb2xlIjoiYXV0aGVudGljYXRlZCIsImVtYWlsIjoidGVzdEBhZG1pbi5jb20iLCJlbWFpbF9jb25maXJtZWRfYXQiOiIyMDI0LTEyLTA2VDA4OjUwOjM0LjUzMDU2NVoiLCJwaG9uZSI6IiIsImNvbmZpcm1lZF9hdCI6IjIwMjQtMTItMDZUMDg6NTA6MzQuNTMwNTY1WiIsImxhc3Rfc2lnbl9pbl9hdCI6IjIwMjQtMTItMDZUMTI6MDY6NTMuMzQ0MTc2NzA0WiIsImFwcF9tZXRhZGF0YSI6eyJwcm92aWRlciI6ImVtYWlsIiwicHJvdmlkZXJzIjpbImVtYWlsIl19LCJ1c2VyX21ldGFkYXRhIjp7ImVtYWlsIjoidGVzdEBhZG1pbi5jb20iLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInBob25lX3ZlcmlmaWVkIjpmYWxzZSwic3ViIjoiYTAwM2MwMTctMjFkMy00MWY2LTgzNjQtNWU5ZWViZWI5MzVkIn0sImlkZW50aXRpZXMiOlt7ImlkZW50aXR5X2lkIjoiMGUyZGIzZTYtMWIzMi00MWI4LTlmNWMtMGZhMjdlOTExMWE2IiwiaWQiOiJhMDAzYzAxNy0yMWQzLTQxZjYtODM2NC01ZTllZWJlYjkzNWQiLCJ1c2VyX2lkIjoiYTAwM2MwMTctMjFkMy00MWY2LTgzNjQtNWU5ZWViZWI5MzVkIiwiaWRlbnRpdHlfZGF0YSI6eyJlbWFpbCI6InRlc3RAYWRtaW4uY29tIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwaG9uZV92ZXJpZmllZCI6ZmFsc2UsInN1YiI6ImEwMDNjMDE3LTIxZDMtNDFmNi04MzY0LTVlOWVlYmViOTM1ZCJ9LCJwcm92aWRlciI6ImVtYWlsIiwibGFzdF9zaWduX2luX2F0IjoiMjAyNC0xMi0wNlQwODo1MDozNC41MjQ2NVoiLCJjcmVhdGVkX2F0IjoiMjAyNC0xMi0wNlQwODo1MDozNC41MjQ3MDhaIiwidXBkYXRlZF9hdCI6IjIwMjQtMTItMDZUMDg6NTA6MzQuNTI0NzA4WiIsImVtYWlsIjoidGVzdEBhZG1pbi5jb20ifV0sImNyZWF0ZWRfYXQiOiIyMDI0LTEyLTA2VDA4OjUwOjM0LjQ4OTA2OFoiLCJ1cGRhdGVkX2F0IjoiMjAyNC0xMi0wNlQxMjowNjo1My4zNDcxMDJaIiwiaXNfYW5vbnltb3VzIjpmYWxzZX19",
6+
"domain": "localhost",
7+
"path": "/",
8+
"expires": 1768046813.496824,
9+
"httpOnly": false,
10+
"secure": false,
11+
"sameSite": "Lax"
12+
}
13+
],
14+
"origins": []
15+
}

tests/.auth/logout.json

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"cookies": [],
3+
"origins": []
4+
}

tests/.auth/user.json

-15
This file was deleted.

tests/e2e/adminLayout.spec.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import test from '../fixtures/pageFixtrue'
2+
3+
test.describe('admin layout common functions', () => {
4+
test('should toggle light theme', async ({ adminLayout }) => {
5+
await adminLayout.navigateToRadomPage()
6+
await adminLayout.toggleLightTheme()
7+
})
8+
9+
test('should toggle dark theme', async ({ adminLayout }) => {
10+
await adminLayout.navigateToRadomPage()
11+
await adminLayout.toggleDarkTheme()
12+
})
13+
14+
test('should toggle system with light theme', async ({ adminLayout }) => {
15+
await adminLayout.navigateToRadomPage()
16+
await adminLayout.toggleSystemWithLightTheme()
17+
})
18+
19+
test('should toggle system with dark theme', async ({ adminLayout }) => {
20+
await adminLayout.navigateToRadomPage()
21+
await adminLayout.toggleSystemWithDarkTheme()
22+
})
23+
})

tests/e2e/authPage.spec.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
import test from '../fixtures/pageFixtrue'
22

33
test.describe('Auth Page', () => {
4+
// 表单验证错误信息验证
5+
test('login with form validation error', async ({ authPage }) => {
6+
await authPage.navigate()
7+
await authPage.loginWithFormValidationError()
8+
})
9+
// 登录成功验证
410
test('should be able to login', async ({ authPage }) => {
511
await authPage.navigate()
612
await authPage.login('[email protected]', '123456')
713
})
8-
14+
// 登录失败验证
915
test('should not be able to login with invalid credentials', async ({
1016
authPage,
1117
}) => {

tests/e2e/categoriesPage.spec.ts

+5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ test.describe('Categories Page', () => {
1717
await categoriesPage.navigateToProducts()
1818
})
1919

20+
test('form validation error messages', async ({ categoriesPage }) => {
21+
await categoriesPage.navigate()
22+
await categoriesPage.formvalidationErrorMessages()
23+
})
24+
2025
test('add new category', async ({ categoriesPage }) => {
2126
const categoryName = faker.commerce.productName()
2227
await categoriesPage.navigate()

tests/e2e/dashboardPage.spec.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,31 @@
11
import test from '../fixtures/pageFixtrue'
22

33
test.describe('Dashboard Page', () => {
4-
test('ordersChartCheck', async ({ dashboardPage }) => {
4+
test('ordersc chart check', async ({ dashboardPage }) => {
55
await dashboardPage.navigate()
66
await dashboardPage.ordersChartCheck()
77
})
8-
test('productDistributionCheck', async ({ dashboardPage }) => {
8+
test('product distribution chart check', async ({ dashboardPage }) => {
99
await dashboardPage.navigate()
10-
await dashboardPage.productDistributionCheck()
10+
await dashboardPage.productDistributionChartCheck()
1111
})
12-
test('productsPerCategoryChartCheck', async ({ dashboardPage }) => {
12+
test('products per category chart check', async ({ dashboardPage }) => {
1313
await dashboardPage.navigate()
1414
await dashboardPage.productsPerCategoryChartCheck()
1515
})
16-
test('latestUsersChartCheck', async ({ dashboardPage }) => {
16+
test('latestUsers chart check', async ({ dashboardPage }) => {
1717
await dashboardPage.navigate()
1818
await dashboardPage.latestUsersChartCheck()
1919
})
20-
test('navigateToOrdersPage', async ({ dashboardPage }) => {
20+
test('navigate to orders page', async ({ dashboardPage }) => {
2121
await dashboardPage.navigate()
2222
await dashboardPage.navigateToOrdersPage()
2323
})
24-
test('navigateToProductsPage', async ({ dashboardPage }) => {
24+
test('navigate to products page', async ({ dashboardPage }) => {
2525
await dashboardPage.navigate()
2626
await dashboardPage.navigateToProductsPage()
2727
})
28-
test('navigateToCategoriesPage', async ({ dashboardPage }) => {
28+
test('navigate to categories page', async ({ dashboardPage }) => {
2929
await dashboardPage.navigate()
3030
await dashboardPage.navigateToCategoriesPage()
3131
})

tests/e2e/logout.spec.ts

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import test from '@playwright/test'
2+
import { AdminLayout } from '../pages/adminLayout'
3+
import { AuthPage } from '../pages/authPage'
4+
5+
test.describe('Logout functionality', () => {
6+
test('Logout', async ({ browser }) => {
7+
const context = await browser.newContext({
8+
storageState: 'tests/.auth/logout.json', // 使用 user 的状态文件
9+
})
10+
const page = await context.newPage()
11+
12+
const authPage = new AuthPage(page)
13+
await authPage.navigate()
14+
await authPage.login('[email protected]', '123456')
15+
16+
// 测试登出逻辑
17+
const adminLayout = new AdminLayout(page)
18+
await adminLayout.navigateToRadomPage() // 跳转到随机页面
19+
await adminLayout.logout()
20+
21+
// 验证已退出(比如应该重定向到登录页)
22+
23+
await context.close()
24+
})
25+
})

tests/e2e/productsPage.spec.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,26 @@ import test from '../fixtures/pageFixtrue'
22
import { faker } from '@faker-js/faker'
33

44
test.describe('Products Page', () => {
5-
test('navigateToDashboard', async ({ productsPage }) => {
5+
test('navigate to dashboard page', async ({ productsPage }) => {
66
await productsPage.navigate()
77
await productsPage.navigateToDashboard()
88
})
99

10-
test('navigateToOrdersPage', async ({ productsPage }) => {
10+
test('navigate to Orders page', async ({ productsPage }) => {
1111
await productsPage.navigate()
1212
await productsPage.navigateToOrdersPage()
1313
})
1414

15-
test('navigateToCategoriesPage', async ({ productsPage }) => {
15+
test('navigate to categories page', async ({ productsPage }) => {
1616
await productsPage.navigate()
1717
await productsPage.navigateToCategoriesPage()
1818
})
1919

20+
test('form validation error message', async ({ productsPage }) => {
21+
await productsPage.navigate()
22+
await productsPage.formValidationErrorMessages()
23+
})
24+
2025
test('add new product', async ({ productsPage }) => {
2126
// 生成一个随机产品名称
2227
const productTitle = faker.commerce.productName()

0 commit comments

Comments
 (0)