Parcourir la source

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
chenjiawei.inizio il y a 7 mois
Parent
commit
9aca28063e

+ 32 - 0
.github/workflows/e2e.yml

@@ -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 - 0
.github/workflows/sync-screenshot.yml

@@ -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 - 0
.gitignore

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

+ 1 - 0
apps/demo-free-layout/src/components/add-node/index.tsx

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

+ 1 - 0
apps/demo-free-layout/src/components/node-panel/node-list.tsx

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

+ 4 - 18
apps/docs/rspress.config.ts

@@ -8,6 +8,9 @@ export default defineConfig({
   base: '/',
   title: 'FlowGram.AI',
   globalStyles: path.join(__dirname, './global.less'),
+  route: {
+    exclude: ['./global.d.ts'],
+  },
   builderConfig: {
     source: {
       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)
           experiments: {
             outputModule: false,
@@ -58,7 +44,7 @@ export default defineConfig({
       },
     },
   },
-  ssg: false,
+  ssg: true,
   // locales 为一个对象数组
   locales: [
     {

+ 18 - 0
common/config/rush/command-line.json

@@ -267,6 +267,24 @@
           "enableParallelism": 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",
           "commandKind": "global",

+ 58 - 0
common/config/rush/pnpm-lock.yaml

@@ -869,6 +869,32 @@ importers:
         specifier: ^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:
     dependencies:
       '@flowgram.ai/command':
@@ -6820,6 +6846,14 @@ packages:
     engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
     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):
     resolution: {integrity: sha512-mWU3BMkmmzyYMSuhO9wu3eJVP21N8TcgYm9bZnTrMwuM818bEk+0NRM3hP+c/TqA9Ln5C7qE53p1H0QMtzYdvQ==}
     peerDependencies:
@@ -11220,6 +11254,14 @@ 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]
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /fsevents@2.3.3:
     resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
     engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -14299,6 +14341,22 @@ packages:
       pathe: 1.1.2
     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:
     resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==}
     engines: {node: '>= 0.4'}

+ 2 - 1
cspell.json

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

+ 6 - 0
e2e/fixed-layout/.eslintrc.js

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

+ 19 - 0
e2e/fixed-layout/package.json

@@ -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"
+  }
+}

+ 17 - 0
e2e/fixed-layout/playwright.config.ts

@@ -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,
+  },
+});

+ 6 - 0
e2e/fixed-layout/tests/layout.spec.ts

@@ -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');
+});

BIN
e2e/fixed-layout/tests/layout.spec.ts-snapshots/homepage-linux.png


+ 22 - 0
e2e/fixed-layout/tsconfig.json

@@ -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"]
+}

+ 15 - 0
e2e/free-layout/.eslintrc.js

@@ -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.',
+      },
+    ],
+  },
+});

+ 20 - 0
e2e/free-layout/package.json

@@ -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"
+  }
+}

+ 17 - 0
e2e/free-layout/playwright.config.ts

@@ -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,
+  },
+});

+ 8 - 0
e2e/free-layout/tests/layout.spec.ts

@@ -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');
+  });
+});

BIN
e2e/free-layout/tests/layout.spec.ts-snapshots/homepage-linux.png


+ 36 - 0
e2e/free-layout/tests/models/index.ts

@@ -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;

+ 23 - 0
e2e/free-layout/tests/node.spec.ts

@@ -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);
+  });
+});

+ 22 - 0
e2e/free-layout/tsconfig.json

@@ -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"]
+}

+ 10 - 0
rush.json

@@ -428,6 +428,16 @@
         //   "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 通用配置
         {
             "packageName": "@flowgram.ai/eslint-config",