Compare commits

...

1 Commits

Author SHA1 Message Date
WTW0313
5e06345363 feat: add Playwright testing framework and integrate into CI workflow 2025-09-22 17:53:37 +08:00
8 changed files with 269 additions and 10 deletions

View File

@@ -61,6 +61,12 @@ jobs:
if: needs.check-changes.outputs.web-changed == 'true'
uses: ./.github/workflows/web-tests.yml
playwright-tests:
name: Playwright Tests
needs: check-changes
if: needs.check-changes.outputs.web-changed == 'true'
uses: ./.github/workflows/playwright.yml
style-check:
name: Style Check
uses: ./.github/workflows/style.yml

58
.github/workflows/playwright.yml vendored Normal file
View File

@@ -0,0 +1,58 @@
name: Playwright Tests
on:
workflow_call:
concurrency:
group: playwright-tests-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
test:
name: Playwright Tests
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./web
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Check changed files
id: changed-files
uses: tj-actions/changed-files@v46
with:
files: web/**
- name: Install pnpm
if: steps.changed-files.outputs.any_changed == 'true'
uses: pnpm/action-setup@v4
with:
package_json_file: web/package.json
run_install: false
- name: Setup Node.js
uses: actions/setup-node@v4
if: steps.changed-files.outputs.any_changed == 'true'
with:
node-version: 22
cache: pnpm
cache-dependency-path: ./web/package.json
- name: Install dependencies
if: steps.changed-files.outputs.any_changed == 'true'
working-directory: ./web
run: pnpm install --frozen-lockfile
- name: Install Playwright Browsers
if: steps.changed-files.outputs.any_changed == 'true'
run: pnpm exec playwright install --with-deps
- name: Run Playwright tests
if: steps.changed-files.outputs.any_changed == 'true'
working-directory: ./web
run: pnpm e2e-test

7
web/.gitignore vendored
View File

@@ -54,3 +54,10 @@ package-lock.json
# mise
mise.toml
# Playwright
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
/tests-examples/

View File

@@ -0,0 +1,48 @@
/**
* This is a mock of the system features object for the login page
* It is used to test the login page when all methods are disabled
*/
const AllMethodsDisabled = {
sso_enforced_for_signin: false,
sso_enforced_for_signin_protocol: '',
enable_marketplace: true,
max_plugin_package_size: 52428800,
enable_email_code_login: false,
enable_email_password_login: false,
enable_social_oauth_login: false,
is_allow_register: true,
is_allow_create_workspace: true,
is_email_setup: true,
license: {
status: 'none',
expired_at: '',
workspaces: {
enabled: false,
size: 0,
limit: 0,
},
},
branding: {
enabled: false,
application_title: '',
login_page_logo: '',
workspace_logo: '',
favicon: '',
},
webapp_auth: {
enabled: false,
allow_sso: false,
sso_config: {
protocol: '',
},
allow_email_code_login: false,
allow_email_password_login: false,
},
plugin_installation_permission: {
plugin_installation_scope: 'all',
restrict_to_marketplace_only: false,
},
enable_change_email: true,
}
export default AllMethodsDisabled

View File

@@ -0,0 +1,19 @@
import { expect, test } from '@playwright/test'
import AllMethodsDisabled from './__mocks__/system-features/all-methods-disabled'
test.describe('Login Flow', () => {
test('has title', async ({ page }) => {
await page.route('**/console/api/system-features', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(AllMethodsDisabled),
})
})
await page.goto('/signin')
await expect(page).toHaveTitle('Dify')
await expect(page.getByText('Authentication method not configured')).toBeVisible()
})
})

View File

@@ -42,7 +42,9 @@
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"preinstall": "npx only-allow pnpm",
"analyze": "ANALYZE=true pnpm build"
"analyze": "ANALYZE=true pnpm build",
"e2e-test": "pnpm exec playwright test",
"e2e-test:ui": "pnpm exec playwright test --ui"
},
"dependencies": {
"@babel/runtime": "^7.22.3",
@@ -169,6 +171,7 @@
"@next/bundle-analyzer": "15.5.3",
"@next/eslint-plugin-next": "15.5.0",
"@next/mdx": "15.5.0",
"@playwright/test": "^1.55.0",
"@rgrove/parse-xml": "^4.1.0",
"@storybook/addon-essentials": "8.5.0",
"@storybook/addon-interactions": "8.5.0",

79
web/playwright.config.ts Normal file
View File

@@ -0,0 +1,79 @@
import { defineConfig, devices } from '@playwright/test'
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// import dotenv from 'dotenv';
// import path from 'path';
// dotenv.config({ path: path.resolve(__dirname, '.env') });
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './e2e',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://localhost:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],
/* Run your local dev server before starting the tests */
webServer: {
command: 'pnpm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
})

57
web/pnpm-lock.yaml generated
View File

@@ -231,10 +231,10 @@ importers:
version: 1.0.0
next:
specifier: 15.5.0
version: 15.5.0(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1)
version: 15.5.0(@babel/core@7.28.3)(@playwright/test@1.55.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1)
next-pwa:
specifier: ^5.6.0
version: 5.6.0(@babel/core@7.28.3)(@types/babel__core@7.20.5)(esbuild@0.25.0)(next@15.5.0(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1))(uglify-js@3.19.3)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))
version: 5.6.0(@babel/core@7.28.3)(@types/babel__core@7.20.5)(esbuild@0.25.0)(next@15.5.0(@babel/core@7.28.3)(@playwright/test@1.55.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1))(uglify-js@3.19.3)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))
next-themes:
specifier: ^0.4.3
version: 0.4.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
@@ -416,6 +416,9 @@ importers:
'@next/mdx':
specifier: 15.5.0
version: 15.5.0(@mdx-js/loader@3.1.0(acorn@8.15.0)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3)))(@mdx-js/react@3.1.0(@types/react@19.1.11)(react@19.1.1))
'@playwright/test':
specifier: ^1.55.0
version: 1.55.0
'@rgrove/parse-xml':
specifier: ^4.1.0
version: 4.2.0
@@ -439,7 +442,7 @@ importers:
version: 8.5.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@8.5.0)
'@storybook/nextjs':
specifier: 8.5.0
version: 8.5.0(esbuild@0.25.0)(next@15.5.0(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1)(storybook@8.5.0)(type-fest@2.19.0)(typescript@5.8.3)(uglify-js@3.19.3)(webpack-hot-middleware@2.26.1)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))
version: 8.5.0(esbuild@0.25.0)(next@15.5.0(@babel/core@7.28.3)(@playwright/test@1.55.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1)(storybook@8.5.0)(type-fest@2.19.0)(typescript@5.8.3)(uglify-js@3.19.3)(webpack-hot-middleware@2.26.1)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))
'@storybook/react':
specifier: 8.5.0
version: 8.5.0(@storybook/test@8.5.0(storybook@8.5.0))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@8.5.0)(typescript@5.8.3)
@@ -2527,6 +2530,11 @@ packages:
resolution: {integrity: sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
'@playwright/test@1.55.0':
resolution: {integrity: sha512-04IXzPwHrW69XusN/SIdDdKZBzMfOT9UNT/YiJit/xpy2VuAoB8NHc8Aplb96zsWDddLnbkPL3TsmrS04ZU2xQ==}
engines: {node: '>=18'}
hasBin: true
'@pmmmwh/react-refresh-webpack-plugin@0.5.17':
resolution: {integrity: sha512-tXDyE1/jzFsHXjhRZQ3hMl0IVhYe5qula43LDWIhVfjp9G/nT5OQY5AORVOrkEGAUltBJOfOWeETbmhm6kHhuQ==}
engines: {node: '>= 10.13'}
@@ -5653,6 +5661,11 @@ packages:
fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
fsevents@2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -7260,6 +7273,16 @@ packages:
pkg-types@2.3.0:
resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}
playwright-core@1.55.0:
resolution: {integrity: sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg==}
engines: {node: '>=18'}
hasBin: true
playwright@1.55.0:
resolution: {integrity: sha512-sdCWStblvV1YU909Xqx0DhOjPZE4/5lJsIS84IfN9dAZfcl/CIZ5O8l3o0j7hPMjDvqoTF8ZUcc+i/GL5erstA==}
engines: {node: '>=18'}
hasBin: true
pluralize@8.0.0:
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
engines: {node: '>=4'}
@@ -11272,6 +11295,10 @@ snapshots:
'@pkgr/core@0.2.7': {}
'@playwright/test@1.55.0':
dependencies:
playwright: 1.55.0
'@pmmmwh/react-refresh-webpack-plugin@0.5.17(react-refresh@0.14.2)(type-fest@2.19.0)(webpack-hot-middleware@2.26.1)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))':
dependencies:
ansi-html: 0.0.9
@@ -11877,7 +11904,7 @@ snapshots:
dependencies:
storybook: 8.5.0
'@storybook/nextjs@8.5.0(esbuild@0.25.0)(next@15.5.0(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1)(storybook@8.5.0)(type-fest@2.19.0)(typescript@5.8.3)(uglify-js@3.19.3)(webpack-hot-middleware@2.26.1)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))':
'@storybook/nextjs@8.5.0(esbuild@0.25.0)(next@15.5.0(@babel/core@7.28.3)(@playwright/test@1.55.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1)(storybook@8.5.0)(type-fest@2.19.0)(typescript@5.8.3)(uglify-js@3.19.3)(webpack-hot-middleware@2.26.1)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))':
dependencies:
'@babel/core': 7.28.3
'@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.3)
@@ -11903,7 +11930,7 @@ snapshots:
find-up: 5.0.0
image-size: 1.2.1
loader-utils: 3.3.1
next: 15.5.0(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1)
next: 15.5.0(@babel/core@7.28.3)(@playwright/test@1.55.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1)
node-polyfill-webpack-plugin: 2.0.1(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))
pnp-webpack-plugin: 1.7.0(typescript@5.8.3)
postcss: 8.5.6
@@ -15020,6 +15047,9 @@ snapshots:
fs.realpath@1.0.0: {}
fsevents@2.3.2:
optional: true
fsevents@2.3.3:
optional: true
@@ -16831,12 +16861,12 @@ snapshots:
neo-async@2.6.2: {}
next-pwa@5.6.0(@babel/core@7.28.3)(@types/babel__core@7.20.5)(esbuild@0.25.0)(next@15.5.0(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1))(uglify-js@3.19.3)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3)):
next-pwa@5.6.0(@babel/core@7.28.3)(@types/babel__core@7.20.5)(esbuild@0.25.0)(next@15.5.0(@babel/core@7.28.3)(@playwright/test@1.55.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1))(uglify-js@3.19.3)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3)):
dependencies:
babel-loader: 8.4.1(@babel/core@7.28.3)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))
clean-webpack-plugin: 4.0.0(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))
globby: 11.1.0
next: 15.5.0(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1)
next: 15.5.0(@babel/core@7.28.3)(@playwright/test@1.55.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1)
terser-webpack-plugin: 5.3.14(esbuild@0.25.0)(uglify-js@3.19.3)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))
workbox-webpack-plugin: 6.6.0(@types/babel__core@7.20.5)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))
workbox-window: 6.6.0
@@ -16854,7 +16884,7 @@ snapshots:
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
next@15.5.0(@babel/core@7.28.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1):
next@15.5.0(@babel/core@7.28.3)(@playwright/test@1.55.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(sass@1.92.1):
dependencies:
'@next/env': 15.5.0
'@swc/helpers': 0.5.15
@@ -16872,6 +16902,7 @@ snapshots:
'@next/swc-linux-x64-musl': 15.5.0
'@next/swc-win32-arm64-msvc': 15.5.0
'@next/swc-win32-x64-msvc': 15.5.0
'@playwright/test': 1.55.0
sass: 1.92.1
sharp: 0.34.3
transitivePeerDependencies:
@@ -17196,6 +17227,14 @@ snapshots:
exsolve: 1.0.7
pathe: 2.0.3
playwright-core@1.55.0: {}
playwright@1.55.0:
dependencies:
playwright-core: 1.55.0
optionalDependencies:
fsevents: 2.3.2
pluralize@8.0.0: {}
pnp-webpack-plugin@1.7.0(typescript@5.8.3):
@@ -18074,7 +18113,7 @@ snapshots:
sharp@0.34.3:
dependencies:
color: 4.2.3
detect-libc: 2.0.4
detect-libc: 2.1.0
semver: 7.7.2
optionalDependencies:
'@img/sharp-darwin-arm64': 0.34.3