Skip to content

Commit 76cdca8

Browse files
Merge pull request #4 from testerxiaodong/e2e/plawright
feature: add e2d test with playwright in home auth and dashboard page
2 parents 465ca68 + 8cc939a commit 76cdca8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+428
-2
lines changed

Diff for: .eslintrc.json

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
11
{
2-
"extends": ["next/core-web-vitals", "next/typescript"]
2+
"extends": ["next/core-web-vitals", "next/typescript"],
3+
"overrides": [
4+
{
5+
"files": ["tests/**/*.ts", "tests/**/*.js"], // Playwright 测试文件路径
6+
"rules": {
7+
"react-hooks/rules-of-hooks": "off", // 禁用 React Hooks 规则
8+
"react-hooks/exhaustive-deps": "off" // 禁用 React 依赖检查规则
9+
}
10+
}
11+
]
312
}

Diff for: .github/workflows/playwright.yml

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Playwright Tests
2+
3+
on:
4+
push:
5+
branches: [main, master]
6+
pull_request:
7+
branches: [main, master]
8+
9+
jobs:
10+
test:
11+
timeout-minutes: 60
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- name: Checkout code
16+
uses: actions/checkout@v4
17+
18+
- name: Set up Node.js
19+
uses: actions/setup-node@v4
20+
with:
21+
node-version: lts/*
22+
23+
- name: Install dependencies
24+
run: npm ci
25+
26+
- name: Install Playwright Browsers
27+
run: npx playwright install --with-deps
28+
29+
- name: Run Playwright tests
30+
run: npx playwright test
31+
env:
32+
NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }}
33+
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }}
34+
35+
- uses: actions/upload-artifact@v4
36+
if: ${{ !cancelled() }}
37+
with:
38+
name: playwright-report
39+
path: playwright-report/
40+
retention-days: 30

Diff for: .gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,8 @@ yarn-error.log*
3838
# typescript
3939
*.tsbuildinfo
4040
next-env.d.ts
41+
node_modules/
42+
/test-results/
43+
/playwright-report/
44+
/blob-report/
45+
/playwright/.cache/

Diff for: package-lock.json

+64
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
"dev": "next dev --turbopack",
77
"build": "next build",
88
"start": "next start",
9-
"lint": "next lint"
9+
"lint": "next lint",
10+
"test:e2e": "playwright test",
11+
"test:e2e:ui": "playwright test --ui"
1012
},
1113
"dependencies": {
1214
"@hookform/resolvers": "^3.9.1",
@@ -37,6 +39,7 @@
3739
"zod": "^3.23.8"
3840
},
3941
"devDependencies": {
42+
"@playwright/test": "^1.49.0",
4043
"@types/node": "^20",
4144
"@types/react": "^18",
4245
"@types/react-dom": "^18",

Diff for: playwright.config.ts

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { defineConfig, devices } from '@playwright/test'
2+
3+
export default defineConfig({
4+
testDir: './tests',
5+
/* Run tests in files in parallel */
6+
fullyParallel: true,
7+
/* Fail the build on CI if you accidentally left test.only in the source code. */
8+
forbidOnly: !!process.env.CI,
9+
/* Retry on CI only */
10+
retries: process.env.CI ? 2 : 0,
11+
/* Opt out of parallel tests on CI. */
12+
workers: process.env.CI ? 1 : undefined,
13+
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
14+
reporter: 'html',
15+
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
16+
use: {
17+
/* Base URL to use in actions like `await page.goto('/')`. */
18+
baseURL: 'http://localhost:3000',
19+
20+
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
21+
trace: 'on-first-retry',
22+
},
23+
24+
/* Configure projects for major browsers */
25+
projects: [
26+
// Setup project
27+
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
28+
{
29+
name: 'chromium',
30+
use: {
31+
...devices['Desktop Chrome'],
32+
storageState: 'tests/.auth/user.json',
33+
},
34+
dependencies: ['setup'],
35+
},
36+
37+
{
38+
name: 'firefox',
39+
use: {
40+
...devices['Desktop Firefox'],
41+
storageState: 'tests/.auth/user.json',
42+
},
43+
dependencies: ['setup'],
44+
},
45+
46+
{
47+
name: 'webkit',
48+
use: {
49+
...devices['Desktop Safari'],
50+
storageState: 'tests/.auth/user.json',
51+
},
52+
dependencies: ['setup'],
53+
},
54+
],
55+
/* Run your local dev server before starting the tests */
56+
webServer: {
57+
command: 'npm run build && npm run start',
58+
port: 3000,
59+
reuseExistingServer: !process.env.CI,
60+
timeout: 120 * 1000,
61+
},
62+
})
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

Diff for: tests/.auth/user.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-eyJhY2Nlc3NfdG9rZW4iOiJleUpoYkdjaU9pSklVekkxTmlJc0ltdHBaQ0k2SWpsa1RXazVhSFJXWjFsNFRXMXpkSEVpTENKMGVYQWlPaUpLVjFRaWZRLmV5SnBjM01pT2lKb2RIUndjem92TDJKcGJtWm5ZbWR6YjJWMGMySmxZWEJsYjJ0ekxuTjFjR0ZpWVhObExtTnZMMkYxZEdndmRqRWlMQ0p6ZFdJaU9pSmpNVGsyT1dOa1ppMWtNVFE0TFRReE1XUXRPRGMxTXkxaVpXSXdNR1ZoWkRRellUY2lMQ0poZFdRaU9pSmhkWFJvWlc1MGFXTmhkR1ZrSWl3aVpYaHdJam94TnpNek16TXdOVFkyTENKcFlYUWlPakUzTXpNek1qWTVOallzSW1WdFlXbHNJam9pZEdWemRFQjBaWE4wTG1OdmJTSXNJbkJvYjI1bElqb2lJaXdpWVhCd1gyMWxkR0ZrWVhSaElqcDdJbkJ5YjNacFpHVnlJam9pWlcxaGFXd2lMQ0p3Y205MmFXUmxjbk1pT2xzaVpXMWhhV3dpWFgwc0luVnpaWEpmYldWMFlXUmhkR0VpT25zaVpXMWhhV3dpT2lKMFpYTjBRSFJsYzNRdVkyOXRJaXdpWlcxaGFXeGZkbVZ5YVdacFpXUWlPbVpoYkhObExDSndhRzl1WlY5MlpYSnBabWxsWkNJNlptRnNjMlVzSW5OMVlpSTZJbU14T1RZNVkyUm1MV1F4TkRndE5ERXhaQzA0TnpVekxXSmxZakF3WldGa05ETmhOeUo5TENKeWIyeGxJam9pWVhWMGFHVnVkR2xqWVhSbFpDSXNJbUZoYkNJNkltRmhiREVpTENKaGJYSWlPbHQ3SW0xbGRHaHZaQ0k2SW5CaGMzTjNiM0prSWl3aWRHbHRaWE4wWVcxd0lqb3hOek16TXpJMk9UWTJmVjBzSW5ObGMzTnBiMjVmYVdRaU9pSTVOMkkwT1dVMU1pMWhNV013TFRReFptSXRZVGxtTUMweFpUVmlZMlF6WVRWak1EZ2lMQ0pwYzE5aGJtOXVlVzF2ZFhNaU9tWmhiSE5sZlEuLUFqRzU0WjJPUE90LXE1akdwZ09PSUtGMFRpS0N2cWNMbVFWX29rSnY2ZyIsInRva2VuX3R5cGUiOiJiZWFyZXIiLCJleHBpcmVzX2luIjozNjAwLCJleHBpcmVzX2F0IjoxNzMzMzMwNTY2LCJyZWZyZXNoX3Rva2VuIjoiWDVJVHZ6VDNwaEhvQ0h6bDRrYTFGZyIsInVzZXIiOnsiaWQiOiJjMTk2OWNkZi1kMTQ4LTQxMWQtODc1My1iZWIwMGVhZDQzYTciLCJhdWQiOiJhdXRoZW50aWNhdGVkIiwicm9sZSI6ImF1dGhlbnRpY2F0ZWQiLCJlbWFpbCI6InRlc3RAdGVzdC5jb20iLCJlbWFpbF9jb25maXJtZWRfYXQiOiIyMDI0LTExLTEzVDEyOjI3OjMxLjc5MDY0M1oiLCJwaG9uZSI6IiIsImNvbmZpcm1lZF9hdCI6IjIwMjQtMTEtMTNUMTI6Mjc6MzEuNzkwNjQzWiIsImxhc3Rfc2lnbl9pbl9hdCI6IjIwMjQtMTItMDRUMTU6NDI6NDYuMTE4Nzc0OTM2WiIsImFwcF9tZXRhZGF0YSI6eyJwcm92aWRlciI6ImVtYWlsIiwicHJvdmlkZXJzIjpbImVtYWlsIl19LCJ1c2VyX21ldGFkYXRhIjp7ImVtYWlsIjoidGVzdEB0ZXN0LmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicGhvbmVfdmVyaWZpZWQiOmZhbHNlLCJzdWIiOiJjMTk2OWNkZi1kMTQ4LTQxMWQtODc1My1iZWIwMGVhZDQzYTcifSwiaWRlbnRpdGllcyI6W3siaWRlbnRpdHlfaWQiOiJiODY0ZjYxNC0xMDM3LTRhNmMtOTVlNS1kNDZiMmM1MDI1OTEiLCJpZCI6ImMxOTY5Y2RmLWQxNDgtNDExZC04NzUzLWJlYjAwZWFkNDNhNyIsInVzZXJfaWQiOiJjMTk2OWNkZi1kMTQ4LTQxMWQtODc1My1iZWIwMGVhZDQzYTciLCJpZGVudGl0eV9kYXRhIjp7ImVtYWlsIjoidGVzdEB0ZXN0LmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicGhvbmVfdmVyaWZpZWQiOmZhbHNlLCJzdWIiOiJjMTk2OWNkZi1kMTQ4LTQxMWQtODc1My1iZWIwMGVhZDQzYTcifSwicHJvdmlkZXIiOiJlbWFpbCIsImxhc3Rfc2lnbl9pbl9hdCI6IjIwMjQtMTEtMTNUMTI6Mjc6MzEuNzgyNzg1WiIsImNyZWF0ZWRfYXQiOiIyMDI0LTExLTEzVDEyOjI3OjMxLjc4MjgzOVoiLCJ1cGRhdGVkX2F0IjoiMjAyNC0xMS0xM1QxMjoyNzozMS43ODI4MzlaIiwiZW1haWwiOiJ0ZXN0QHRlc3QuY29tIn1dLCJjcmVhdGVkX2F0IjoiMjAyNC0xMS0xM1QxMjoyNzozMS43NjU4NDlaIiwidXBkYXRlZF9hdCI6IjIwMjQtMTItMDRUMTU6NDI6NDYuMTIzODk5WiIsImlzX2Fub255bW91cyI6ZmFsc2V9fQ",
6+
"domain": "localhost",
7+
"path": "/",
8+
"expires": 1767886966.364104,
9+
"httpOnly": false,
10+
"secure": false,
11+
"sameSite": "Lax"
12+
}
13+
],
14+
"origins": []
15+
}

Diff for: tests/e2e/authPage.spec.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import test from '../fixtures/pageFixtrue'
2+
3+
test.describe('Auth Page', () => {
4+
test('should be able to login', async ({ authPage }) => {
5+
await authPage.navigate()
6+
await authPage.login('[email protected]', '123456')
7+
})
8+
9+
test('should not be able to login with invalid credentials', async ({
10+
authPage,
11+
}) => {
12+
await authPage.navigate()
13+
await authPage.loginWithError('[email protected]', '1234567')
14+
})
15+
})

Diff for: tests/e2e/dashboardPage.spec.ts

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import test from '../fixtures/pageFixtrue'
2+
3+
test.describe('Dashboard Page', () => {
4+
test('ordersChartCheck', async ({ dashboardPage }) => {
5+
await dashboardPage.navigate()
6+
await dashboardPage.ordersChartCheck()
7+
})
8+
test('productDistributionCheck', async ({ dashboardPage }) => {
9+
await dashboardPage.navigate()
10+
await dashboardPage.productDistributionCheck()
11+
})
12+
test('productsPerCategoryChartCheck', async ({ dashboardPage }) => {
13+
await dashboardPage.navigate()
14+
await dashboardPage.productsPerCategoryChartCheck()
15+
})
16+
test('latestUsersChartCheck', async ({ dashboardPage }) => {
17+
await dashboardPage.navigate()
18+
await dashboardPage.latestUsersChartCheck()
19+
})
20+
})

Diff for: tests/e2e/homePage.spec.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import test from '../fixtures/pageFixtrue'
2+
3+
test.describe('Home page', () => {
4+
test('home page header show', async ({ homePage }) => {
5+
await homePage.navigate()
6+
await homePage.headerShow()
7+
})
8+
9+
test('go to github', async ({ homePage }) => {
10+
await homePage.navigate()
11+
await homePage.gotoGithub()
12+
})
13+
})

Diff for: tests/fixtures/pageFixtrue.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { test as baseTest } from '@playwright/test'
2+
import { HomePage } from '../pages/homePage'
3+
import { AuthPage } from '../pages/authPage'
4+
import { DashboardPage } from '../pages/dashboardPage'
5+
6+
const test = baseTest.extend<{
7+
homePage: HomePage
8+
authPage: AuthPage
9+
dashboardPage: DashboardPage
10+
}>({
11+
homePage: async ({ page }, use) => {
12+
await use(new HomePage(page))
13+
},
14+
authPage: async ({ page }, use) => {
15+
await use(new AuthPage(page))
16+
},
17+
dashboardPage: async ({ page }, use) => {
18+
await use(new DashboardPage(page))
19+
},
20+
})
21+
22+
export default test

Diff for: tests/pages/authPage.ts

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { expect, Page } from '@playwright/test'
2+
import { HelperBase } from './helperBase'
3+
4+
export class AuthPage extends HelperBase {
5+
readonly email = this.page.getByLabel('Email')
6+
readonly password = this.page.getByLabel('Password')
7+
readonly loginButton = this.page.getByText('Login')
8+
readonly successMessage = this.page.getByText('Logged in successfully')
9+
readonly errorMessage = this.page.getByText('Invalid email or password')
10+
11+
constructor(page: Page) {
12+
super(page)
13+
}
14+
15+
async navigate() {
16+
await this.page.goto('/auth')
17+
}
18+
19+
async login(email: string, password: string) {
20+
await this.email.fill(email)
21+
await this.password.fill(password)
22+
await this.loginButton.click()
23+
// 等待消息可见并验证内容
24+
await expect(this.successMessage).toHaveText('Logged in successfully', {
25+
timeout: 4000,
26+
})
27+
await this.page.waitForURL('http://localhost:3000/admin/dashboard', {
28+
waitUntil: 'load',
29+
})
30+
const dashboardHeader = this.page.locator('main h1', {
31+
hasText: 'Dashboard Overview',
32+
})
33+
await expect(dashboardHeader).toBeVisible()
34+
}
35+
36+
async loginWithError(email: string, password: string) {
37+
await this.email.fill(email)
38+
await this.password.fill(password)
39+
await this.loginButton.click()
40+
// 等待消息可见并验证内容
41+
await expect(this.errorMessage).toHaveText('Invalid email or password', {
42+
timeout: 4000,
43+
})
44+
}
45+
}

0 commit comments

Comments
 (0)