From 5e063453635986d07af5ae73cf861947be426b0f Mon Sep 17 00:00:00 2001 From: WTW0313 Date: Mon, 22 Sep 2025 17:53:37 +0800 Subject: [PATCH] feat: add Playwright testing framework and integrate into CI workflow --- .github/workflows/main-ci.yml | 6 ++ .github/workflows/playwright.yml | 58 ++++++++++++++ web/.gitignore | 7 ++ .../system-features/all-methods-disabled.ts | 48 +++++++++++ web/e2e/signin/index.spec.ts | 19 +++++ web/package.json | 5 +- web/playwright.config.ts | 79 +++++++++++++++++++ web/pnpm-lock.yaml | 57 ++++++++++--- 8 files changed, 269 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/playwright.yml create mode 100644 web/e2e/signin/__mocks__/system-features/all-methods-disabled.ts create mode 100644 web/e2e/signin/index.spec.ts create mode 100644 web/playwright.config.ts diff --git a/.github/workflows/main-ci.yml b/.github/workflows/main-ci.yml index 876ec23a3d..903b1505c1 100644 --- a/.github/workflows/main-ci.yml +++ b/.github/workflows/main-ci.yml @@ -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 diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 0000000000..6a9f1deb2d --- /dev/null +++ b/.github/workflows/playwright.yml @@ -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 + diff --git a/web/.gitignore b/web/.gitignore index 048c5f6485..2d99fe6c39 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -54,3 +54,10 @@ package-lock.json # mise mise.toml +# Playwright +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ +/tests-examples/ diff --git a/web/e2e/signin/__mocks__/system-features/all-methods-disabled.ts b/web/e2e/signin/__mocks__/system-features/all-methods-disabled.ts new file mode 100644 index 0000000000..bf08657545 --- /dev/null +++ b/web/e2e/signin/__mocks__/system-features/all-methods-disabled.ts @@ -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 diff --git a/web/e2e/signin/index.spec.ts b/web/e2e/signin/index.spec.ts new file mode 100644 index 0000000000..62f221c6c2 --- /dev/null +++ b/web/e2e/signin/index.spec.ts @@ -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() + }) +}) diff --git a/web/package.json b/web/package.json index d9bd413b0e..7d0fccb9f8 100644 --- a/web/package.json +++ b/web/package.json @@ -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", diff --git a/web/playwright.config.ts b/web/playwright.config.ts new file mode 100644 index 0000000000..5067ea76df --- /dev/null +++ b/web/playwright.config.ts @@ -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, + }, +}) diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 5d4308288c..1442cf6a9b 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -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