chore: add e2e config (#280)

* chore: open ssg

* chore: add e2e base config case

* chore: sync screenshot

* chore: sync screenshot in linux

* chore: render layout
This commit is contained in:
chenjiawei.inizio 2025-05-27 14:52:20 +08:00 committed by GitHub
parent 83221a61b6
commit 9aca28063e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 389 additions and 19 deletions

32
.github/workflows/e2e.yml vendored Normal file
View File

@ -0,0 +1,32 @@
name: E2E Tests
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
merge_group:
branches: [ "main" ]
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Rush Install
run: node common/scripts/install-run-rush.js install
- name: Rush build
run: node common/scripts/install-run-rush.js build
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run E2E tests
run: node common/scripts/install-run-rush.js e2e:test --verbose

49
.github/workflows/sync-screenshot.yml vendored Normal file
View File

@ -0,0 +1,49 @@
name: Sync Screenshot
on:
workflow_dispatch
concurrency:
group: "manual-sync-screenshot"
cancel-in-progress: false
jobs:
e2e:
# can not update screenshot run on main.
if: github.ref_name != 'main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Set Git user.name and user.email from trigger actor
run: |
git config --global user.name "${{ github.actor }}"
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
- name: Rush Install
run: node common/scripts/install-run-rush.js install
- name: Rush build
run: node common/scripts/install-run-rush.js build
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run E2E tests
run: node common/scripts/install-run-rush.js e2e:update-screenshot --verbose
- name: Commit and push changes
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git add .
if git diff-index --quiet HEAD; then
echo "No changes to commit"
else
git commit -m "chore: sync screenshot"
git push https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }} HEAD:${{ github.ref_name }}
fi

3
.gitignore vendored
View File

@ -1,3 +1,6 @@
# e2e results
test-results/
# Logs # Logs
*.log *.log
npm-debug.log* npm-debug.log*

View File

@ -7,6 +7,7 @@ export const AddNode = (props: { disabled: boolean }) => {
const addNode = useAddNode(); const addNode = useAddNode();
return ( return (
<Button <Button
data-testid="demo.free-layout.add-node"
icon={<IconPlus />} icon={<IconPlus />}
color="highlight" color="highlight"
style={{ backgroundColor: 'rgba(171,181,255,0.3)', borderRadius: '8px' }} style={{ backgroundColor: 'rgba(171,181,255,0.3)', borderRadius: '8px' }}

View File

@ -37,6 +37,7 @@ interface NodeProps {
function Node(props: NodeProps) { function Node(props: NodeProps) {
return ( return (
<NodeWrap <NodeWrap
data-testid={`demo-free-node-list-${props.label}`}
onClick={props.disabled ? undefined : props.onClick} onClick={props.disabled ? undefined : props.onClick}
style={props.disabled ? { opacity: 0.3 } : {}} style={props.disabled ? { opacity: 0.3 } : {}}
> >

View File

@ -8,6 +8,9 @@ export default defineConfig({
base: '/', base: '/',
title: 'FlowGram.AI', title: 'FlowGram.AI',
globalStyles: path.join(__dirname, './global.less'), globalStyles: path.join(__dirname, './global.less'),
route: {
exclude: ['./global.d.ts'],
},
builderConfig: { builderConfig: {
source: { source: {
decorators: { decorators: {
@ -29,23 +32,6 @@ export default defineConfig({
}, },
], ],
}, },
optimization: {
splitChunks: {
chunks: 'all', // 拆分所有模块,包括异步和同步
minSize: 30 * 1024, // 30KB 以下不拆分
maxSize: 500 * 1024, // 500KB 以上强制拆分
minChunks: 1, // 最少被引用 1 次就可以拆分
automaticNameDelimiter: '-',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
priority: -10, // 优先级
},
},
},
},
// 禁用 ES 模块输出(启用 CommonJS // 禁用 ES 模块输出(启用 CommonJS
experiments: { experiments: {
outputModule: false, outputModule: false,
@ -58,7 +44,7 @@ export default defineConfig({
}, },
}, },
}, },
ssg: false, ssg: true,
// locales 为一个对象数组 // locales 为一个对象数组
locales: [ locales: [
{ {

View File

@ -266,6 +266,24 @@
"ignoreMissingScript": true, "ignoreMissingScript": true,
"enableParallelism": true, "enableParallelism": true,
"safeForSimultaneousRushProcesses": true "safeForSimultaneousRushProcesses": true
},
{
"name": "e2e:test",
"commandKind": "bulk",
"summary": "⭐️️ Run e2e cases in packages",
"ignoreMissingScript": true,
"enableParallelism": false,
"allowWarningsInSuccessfulBuild": true,
"safeForSimultaneousRushProcesses": false
},
{
"name": "e2e:update-screenshot",
"commandKind": "bulk",
"summary": "⭐️️ Update screenshots of e2e cases",
"ignoreMissingScript": true,
"enableParallelism": false,
"allowWarningsInSuccessfulBuild": true,
"safeForSimultaneousRushProcesses": false
}, },
{ {
"name": "dev:demo-fixed-layout", "name": "dev:demo-fixed-layout",

View File

@ -869,6 +869,32 @@ importers:
specifier: ^5.0.4 specifier: ^5.0.4
version: 5.0.4 version: 5.0.4
../../e2e/fixed-layout:
dependencies:
'@playwright/test':
specifier: ^1.52.0
version: 1.52.0
devDependencies:
'@flowgram.ai/eslint-config':
specifier: workspace:*
version: link:../../config/eslint-config
'@types/node':
specifier: ^18
version: 18.19.68
../../e2e/free-layout:
dependencies:
'@playwright/test':
specifier: ^1.52.0
version: 1.52.0
devDependencies:
'@flowgram.ai/eslint-config':
specifier: workspace:*
version: link:../../config/eslint-config
'@types/node':
specifier: ^18
version: 18.19.68
../../packages/canvas-engine/core: ../../packages/canvas-engine/core:
dependencies: dependencies:
'@flowgram.ai/command': '@flowgram.ai/command':
@ -6820,6 +6846,14 @@ packages:
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
dev: false dev: false
/@playwright/test@1.52.0:
resolution: {integrity: sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g==}
engines: {node: '>=18'}
hasBin: true
dependencies:
playwright: 1.52.0
dev: false
/@react-hook/intersection-observer@3.1.2(react@18.3.1): /@react-hook/intersection-observer@3.1.2(react@18.3.1):
resolution: {integrity: sha512-mWU3BMkmmzyYMSuhO9wu3eJVP21N8TcgYm9bZnTrMwuM818bEk+0NRM3hP+c/TqA9Ln5C7qE53p1H0QMtzYdvQ==} resolution: {integrity: sha512-mWU3BMkmmzyYMSuhO9wu3eJVP21N8TcgYm9bZnTrMwuM818bEk+0NRM3hP+c/TqA9Ln5C7qE53p1H0QMtzYdvQ==}
peerDependencies: peerDependencies:
@ -11220,6 +11254,14 @@ packages:
/fs.realpath@1.0.0: /fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} 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]
requiresBuild: true
dev: false
optional: true
/fsevents@2.3.3: /fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@ -14299,6 +14341,22 @@ packages:
pathe: 1.1.2 pathe: 1.1.2
dev: true dev: true
/playwright-core@1.52.0:
resolution: {integrity: sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==}
engines: {node: '>=18'}
hasBin: true
dev: false
/playwright@1.52.0:
resolution: {integrity: sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==}
engines: {node: '>=18'}
hasBin: true
dependencies:
playwright-core: 1.52.0
optionalDependencies:
fsevents: 2.3.2
dev: false
/possible-typed-array-names@1.0.0: /possible-typed-array-names@1.0.0:
resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}

View File

@ -16,7 +16,8 @@
"rspress", "rspress",
"Sandpack", "Sandpack",
"zoomin", "zoomin",
"zoomout" "zoomout",
"gedit"
], ],
"ignoreWords": [], "ignoreWords": [],
"import": [] "import": []

View File

@ -0,0 +1,6 @@
const { defineConfig } = require('@flowgram.ai/eslint-config');
module.exports = defineConfig({
preset: 'web',
packageRoot: __dirname,
});

View File

@ -0,0 +1,19 @@
{
"name": "@flowgram.ai/e2e-fixed-layout",
"version": "0.1.0",
"description": "",
"keywords": [],
"license": "MIT",
"scripts": {
"build": "exit",
"e2e:test": "npx playwright test",
"e2e:update-screenshot": "npx playwright test --update-snapshots"
},
"dependencies": {
"@playwright/test": "^1.52.0"
},
"devDependencies": {
"@flowgram.ai/eslint-config": "workspace:*",
"@types/node": "^18"
}
}

View File

@ -0,0 +1,17 @@
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30 * 1000,
retries: 1,
use: {
baseURL: 'http://localhost:3000',
headless: true,
},
webServer: {
command: 'rush dev:demo-fixed-layout',
port: 3000,
timeout: 120 * 1000,
reuseExistingServer: !process.env.GITHUB_ACTIONS,
},
});

View File

@ -0,0 +1,6 @@
import { test, expect } from '@playwright/test';
test('page render test', async ({ page }) => {
await page.goto('http://localhost:3000');
await expect(page).toHaveScreenshot('homepage.png');
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@ -0,0 +1,22 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"target": "es2020",
"module": "esnext",
"strictPropertyInitialization": false,
"strict": true,
"esModuleInterop": true,
"moduleResolution": "node",
"skipLibCheck": true,
"noUnusedLocals": true,
"noImplicitAny": true,
"allowJs": true,
"resolveJsonModule": true,
"types": ["node"],
"typeRoots": ["node_modules/@types"],
"jsx": "react",
"lib": ["es6", "dom", "es2020", "es2019.Array"]
},
"include": ["./tests", "playwright.config.ts"],
"exclude": ["node_modules"]
}

View File

@ -0,0 +1,15 @@
const { defineConfig } = require('@flowgram.ai/eslint-config');
module.exports = defineConfig({
preset: 'web',
packageRoot: __dirname,
rules: {
'no-restricted-syntax': [
'warn',
{
selector: "CallExpression[callee.property.name='waitForTimeout']",
message: 'Consider using waitForFunction instead of waitForTimeout.',
},
],
},
});

View File

@ -0,0 +1,20 @@
{
"name": "@flowgram.ai/e2e-free-layout",
"version": "0.1.0",
"description": "",
"keywords": [],
"license": "MIT",
"scripts": {
"build": "exit",
"e2e:debug": "npx playwright test --debug",
"e2e:test": "npx playwright test",
"e2e:update-screenshot": "npx playwright test --update-snapshots"
},
"dependencies": {
"@playwright/test": "^1.52.0"
},
"devDependencies": {
"@flowgram.ai/eslint-config": "workspace:*",
"@types/node": "^18"
}
}

View File

@ -0,0 +1,17 @@
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30 * 1000,
retries: 1,
use: {
baseURL: 'http://localhost:3000',
headless: true,
},
webServer: {
command: 'rush dev:demo-free-layout',
port: 3000,
timeout: 120 * 1000,
reuseExistingServer: !process.env.GITHUB_ACTIONS,
},
});

View File

@ -0,0 +1,8 @@
import { test, expect } from '@playwright/test';
test.describe('page render screen shot', () => {
test('screenshot', async ({ page }) => {
await page.goto('http://localhost:3000');
await expect(page).toHaveScreenshot('homepage.png');
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

View File

@ -0,0 +1,36 @@
import type { Page } from '@playwright/test';
class FreeLayoutModel {
public readonly page: Page;
constructor(page: Page) {
this.page = page;
}
// 获取节点数量
async getNodeCount() {
return await this.page.evaluate(
() => document.querySelectorAll('[data-testid="sdk.workflow.canvas.node"]').length
);
}
async addConditionNode() {
const preConditionNodes = await this.page.locator('.gedit-flow-activity-node');
const preCount = await preConditionNodes.count();
const button = this.page.locator('[data-testid="demo.free-layout.add-node"]');
// open add node panel
await button.click();
await this.page.waitForSelector('[data-testid="demo-free-node-list-condition"]');
// add condition
const conditionItem = this.page.locator('[data-testid="demo-free-node-list-condition"]');
await conditionItem.click();
// determine whether the node was successfully added
await this.page.waitForFunction(
(expectedCount) =>
document.querySelectorAll('.gedit-flow-activity-node').length === expectedCount,
preCount + 1
);
}
}
export default FreeLayoutModel;

View File

@ -0,0 +1,23 @@
import { test, expect } from '@playwright/test';
import PageModel from './models';
test.describe('node operations', () => {
let editorPage: PageModel;
test.beforeEach(async ({ page }) => {
editorPage = new PageModel(page);
await page.goto('http://localhost:3000');
});
test('node preview', async () => {
const defaultNodeCount = await editorPage.getNodeCount();
expect(defaultNodeCount).toEqual(10);
});
test('add node', async () => {
await editorPage.addConditionNode();
const defaultNodeCount = await editorPage.getNodeCount();
expect(defaultNodeCount).toEqual(11);
});
});

View File

@ -0,0 +1,22 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"target": "es2020",
"module": "esnext",
"strictPropertyInitialization": false,
"strict": true,
"esModuleInterop": true,
"moduleResolution": "node",
"skipLibCheck": true,
"noUnusedLocals": true,
"noImplicitAny": true,
"allowJs": true,
"resolveJsonModule": true,
"types": ["node"],
"typeRoots": ["node_modules/@types"],
"jsx": "react",
"lib": ["es6", "dom", "es2020", "es2019.Array"]
},
"include": ["./tests", "playwright.config.ts"],
"exclude": ["node_modules"]
}

View File

@ -428,6 +428,16 @@
// "tags": [ "frontend-team" ] // "tags": [ "frontend-team" ]
// }, // },
// //
{
"packageName": "@flowgram.ai/e2e-fixed-layout",
"projectFolder": "e2e/fixed-layout",
"tags": ["e2e"]
},
{
"packageName": "@flowgram.ai/e2e-free-layout",
"projectFolder": "e2e/free-layout",
"tags": ["e2e"]
},
// eslint // eslint
{ {
"packageName": "@flowgram.ai/eslint-config", "packageName": "@flowgram.ai/eslint-config",