diff --git a/.github/workflows/endform-experiment.yml b/.github/workflows/endform-experiment.yml
new file mode 100644
index 000000000000..2e04c3d0ecc5
--- /dev/null
+++ b/.github/workflows/endform-experiment.yml
@@ -0,0 +1,140 @@
+name: 'Endform: Incremental experiment'
+
+on:
+ push:
+ branches: [augustoccesar/endform-integration]
+
+permissions:
+ contents: read
+
+jobs:
+ baseline:
+ if: github.repository == 'endformdev/n8n'
+ name: '${{ matrix.runner }}: incremental suite'
+ runs-on: ubuntu-latest
+ timeout-minutes: 45
+ strategy:
+ fail-fast: false
+ max-parallel: 1
+ matrix:
+ runner: [playwright, endform]
+ env:
+ NODE_OPTIONS: '--max-old-space-size=6144'
+ N8N_BASE_URL: http://localhost:5680
+ RESET_E2E_DB: 'true'
+ N8N_SKIP_LICENSES: 'true'
+ PLAYWRIGHT_BROWSERS_PATH: '${{ github.workspace }}/packages/testing/playwright/.playwright-browsers'
+ steps:
+ - name: Checkout
+ uses: useblacksmith/checkout@bcec731f1eb1367240608d1c889a75b96db6ec53 # v1.5.0
+
+ - name: Install dependencies
+ uses: ./.github/actions/setup-nodejs
+ with:
+ build-command: ''
+
+ - name: Build
+ run: pnpm build > build.log 2>&1
+
+ - name: Record versions
+ working-directory: packages/testing/playwright
+ run: |
+ git rev-parse HEAD
+ node --version
+ pnpm --version
+ pnpm exec playwright --version
+ pnpm exec endform --version
+
+ - name: Check types and lint
+ working-directory: packages/testing/playwright
+ run: |
+ pnpm typecheck > typecheck.log 2>&1
+ pnpm lint > lint.log 2>&1
+
+ - name: Install Chromium
+ if: matrix.runner == 'playwright'
+ working-directory: packages/testing/playwright
+ run: pnpm exec playwright install chromium --with-deps
+
+ - name: Install resource monitor
+ run: |
+ sudo apt-get update -qq
+ sudo apt-get install -y sysstat procps iproute2
+
+ - name: Run incremental suite
+ working-directory: packages/testing/playwright
+ env:
+ TEST_RUNNER: '${{ matrix.runner }}'
+ E2E_NAVIGATION_TIMEOUT: '30000'
+ E2E_REMOTE_RUNNER: "${{ matrix.runner == 'endform' && 'true' || 'false' }}"
+ ENDFORM_API_KEY: "${{ matrix.runner == 'endform' && secrets.ENDFORM_API_KEY || '' }}"
+ run: |
+ export TZ=UTC
+ {
+ echo "Logical CPUs: $(nproc)"
+ free -m
+ echo 'The first vmstat sample contains averages since boot.'
+ } > host-resources.log
+ vmstat -w -t 5 >> host-resources.log &
+ host_monitor_pid=$!
+ pidstat -h -u -r -d -p ALL 5 > process-resources.log &
+ process_monitor_pid=$!
+ (
+ while true; do
+ date -u '+%Y-%m-%dT%H:%M:%SZ'
+ ss -ltnp 'sport = :5680'
+ sleep 5
+ done
+ ) > backend-listener.log &
+ listener_monitor_pid=$!
+ stop_monitors() {
+ kill "$host_monitor_pid" "$process_monitor_pid" "$listener_monitor_pid" 2>/dev/null || true
+ wait "$host_monitor_pid" "$process_monitor_pid" "$listener_monitor_pid" 2>/dev/null || true
+ }
+ trap stop_monitors EXIT
+
+ args=(
+ test --project=e2e
+ tests/e2e/building-blocks/workflow-entry-points.spec.ts
+ tests/e2e/credentials/crud.spec.ts
+ tests/e2e/building-blocks/canvas-actions.spec.ts
+ tests/e2e/building-blocks/node-details-configuration.spec.ts
+ tests/e2e/workflows/editor/execution/execution.spec.ts
+ tests/e2e/building-blocks/user-service.spec.ts
+ --retries=0
+ )
+ if [ "$TEST_RUNNER" = endform ]; then
+ args+=(--organization-id=2G1ZCj7X)
+ else
+ args+=(--workers=4)
+ fi
+ pnpm exec "$TEST_RUNNER" "${args[@]}" > experiment.log 2>&1
+
+ - name: Show log tails
+ if: always()
+ run: |
+ for log in build.log packages/testing/playwright/{typecheck,lint,experiment}.log; do
+ if [ -f "$log" ]; then
+ echo "::group::$log"
+ tail -n 50 "$log"
+ echo '::endgroup::'
+ fi
+ done
+
+ - name: Preserve evidence
+ if: always()
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ with:
+ name: 'endform-experiment-${{ matrix.runner }}-${{ github.run_attempt }}'
+ retention-days: 30
+ path: |
+ build.log
+ packages/testing/playwright/experiment.log
+ packages/testing/playwright/typecheck.log
+ packages/testing/playwright/lint.log
+ packages/testing/playwright/host-resources.log
+ packages/testing/playwright/process-resources.log
+ packages/testing/playwright/backend-listener.log
+ packages/testing/playwright/test-results/
+ packages/testing/playwright/test-results.json
+ packages/testing/playwright/playwright-report/
diff --git a/packages/testing/playwright/config/intercepts.ts b/packages/testing/playwright/config/intercepts.ts
index c67979285c08..50ec5fb5090d 100644
--- a/packages/testing/playwright/config/intercepts.ts
+++ b/packages/testing/playwright/config/intercepts.ts
@@ -116,8 +116,9 @@ export async function setupDefaultInterceptors(target: BrowserContext) {
});
// Pathname /api/health
+ // Use URL patterns so Playwright does not intercept unrelated asset requests.
await target.route(
- (url) => url.pathname.endsWith('/api/health'),
+ /^https?:\/\/[^/?#]+\/(?:[^?#]*\/)?api\/health(?:[?#].*)?$/,
async (route: Route) => {
await route.fulfill({
contentType: 'application/json',
@@ -127,37 +128,34 @@ export async function setupDefaultInterceptors(target: BrowserContext) {
);
// Pathname /api/versions/*
- await target.route(
- (url) => url.pathname.startsWith('/api/versions/'),
- async (route: Route) => {
- await route.fulfill({
- contentType: 'application/json',
- body: JSON.stringify([
- {
- name: '1.45.1',
- createdAt: '2023-08-18T11:53:12.857Z',
- hasSecurityIssue: null,
- hasSecurityFix: null,
- securityIssueFixVersion: null,
- hasBreakingChange: null,
- documentationUrl: 'https://docs.n8n.io/release-notes/#n8n131',
- nodes: [],
- description: 'Includes bug fixes',
- },
- {
- name: '1.0.5',
- createdAt: '2023-07-24T10:54:56.097Z',
- hasSecurityIssue: false,
- hasSecurityFix: null,
- securityIssueFixVersion: null,
- hasBreakingChange: true,
- documentationUrl: 'https://docs.n8n.io/release-notes/#n8n104',
- nodes: [],
- description:
- 'Includes core functionality and bug fixes',
- },
- ]),
- });
- },
- );
+ await target.route(/^https?:\/\/[^/?#]+\/api\/versions\//, async (route: Route) => {
+ await route.fulfill({
+ contentType: 'application/json',
+ body: JSON.stringify([
+ {
+ name: '1.45.1',
+ createdAt: '2023-08-18T11:53:12.857Z',
+ hasSecurityIssue: null,
+ hasSecurityFix: null,
+ securityIssueFixVersion: null,
+ hasBreakingChange: null,
+ documentationUrl: 'https://docs.n8n.io/release-notes/#n8n131',
+ nodes: [],
+ description: 'Includes bug fixes',
+ },
+ {
+ name: '1.0.5',
+ createdAt: '2023-07-24T10:54:56.097Z',
+ hasSecurityIssue: false,
+ hasSecurityFix: null,
+ securityIssueFixVersion: null,
+ hasBreakingChange: true,
+ documentationUrl: 'https://docs.n8n.io/release-notes/#n8n104',
+ nodes: [],
+ description:
+ 'Includes core functionality and bug fixes',
+ },
+ ]),
+ });
+ });
}
diff --git a/packages/testing/playwright/endform.config.ts b/packages/testing/playwright/endform.config.ts
new file mode 100644
index 000000000000..5a8b07c14d23
--- /dev/null
+++ b/packages/testing/playwright/endform.config.ts
@@ -0,0 +1,8 @@
+/* eslint-disable import-x/no-default-export */
+import { defineEndformConfig } from 'endform';
+
+export default defineEndformConfig({
+ additionalFiles: ['workflows/**/*'],
+ concurrentTestLimits: [{ scope: 'within-suite-run', limit: 4 }],
+ environmentVariables: ['N8N_BASE_URL', 'RESET_E2E_DB', 'E2E_REMOTE_RUNNER'],
+});
diff --git a/packages/testing/playwright/package.json b/packages/testing/playwright/package.json
index bf4d533742c6..f52f30b15ea1 100644
--- a/packages/testing/playwright/package.json
+++ b/packages/testing/playwright/package.json
@@ -75,6 +75,7 @@
"autocannon": "^8.0.0",
"axe-core": "^4.13.0",
"axe-html-reporter": "^2.2.11",
+ "endform": "catalog:",
"eslint-plugin-playwright": "catalog:e2e",
"flatted": "catalog:",
"generate-schema": "2.6.0",
diff --git a/packages/testing/playwright/playwright.config.ts b/packages/testing/playwright/playwright.config.ts
index 66f1395cedd2..6cdb881a650b 100644
--- a/packages/testing/playwright/playwright.config.ts
+++ b/packages/testing/playwright/playwright.config.ts
@@ -70,7 +70,7 @@ if (BACKEND_URL && !SKIP_WEB_SERVER) {
}
webServer.push({
command: 'pnpm --dir ../../.. start',
- url: `${BACKEND_URL}/favicon.ico`,
+ url: `${BACKEND_URL}/healthz/readiness`,
timeout: 30000,
reuseExistingServer: IS_DEV ? false : true,
env: {
@@ -112,7 +112,7 @@ export default defineConfig({
forbidOnly: IS_CI,
retries: IS_CI ? 2 : 0,
workers: WORKERS,
- timeout: 60000,
+ timeout: process.env.E2E_REMOTE_RUNNER === 'true' ? 90000 : 60000,
expect: {
timeout: EXPECT_TIMEOUT,
},
@@ -130,7 +130,10 @@ export default defineConfig({
headless: process.env.SHOW_BROWSER !== 'true',
viewport: MACBOOK_WINDOW_SIZE,
actionTimeout: 20000, // TODO: We might need to make this dynamic for container tests if we have low resource containers etc
- navigationTimeout: 10000,
+ navigationTimeout: Number(
+ process.env.E2E_NAVIGATION_TIMEOUT ??
+ (process.env.E2E_REMOTE_RUNNER === 'true' ? 30000 : 10000),
+ ),
currentsFixturesEnabled: !!process.env.CI,
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e733578946fe..97f6f4171976 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -500,6 +500,9 @@ catalogs:
electron:
specifier: 41.10.4
version: 41.10.4
+ endform:
+ specifier: 0.76.0
+ version: 0.76.0
eslint:
specifier: 9.29.0
version: 9.29.0
@@ -7647,6 +7650,9 @@ importers:
axe-html-reporter:
specifier: ^2.2.11
version: 2.2.11(axe-core@4.13.0)
+ endform:
+ specifier: 'catalog:'
+ version: 0.76.0
eslint-plugin-playwright:
specifier: catalog:e2e
version: 2.2.2(eslint@9.29.0(jiti@2.6.1)(supports-color@8.1.1))
@@ -17582,6 +17588,40 @@ packages:
end-of-stream@1.4.4:
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
+ endform-darwin-arm64@0.76.0:
+ resolution: {integrity: sha512-uspKKhg6K9u01d3gETnrENNB42s+Oj0JmAgDuA/Ij+/H3g4rvLaShAwIUUf6MQgUwg7oHJGppYz0bgzlrLFcag==}
+ cpu: [arm64]
+ os: [darwin]
+
+ endform-darwin-x86@0.76.0:
+ resolution: {integrity: sha512-eGS8aCru2i7EVdCYmwcz414LPGstzo9bN2/C7cy56QIaIHi71HCccVMIAHWXxgCQcE2/8WoAtcB6OVUB450Rqg==}
+ cpu: [x64]
+ os: [darwin]
+
+ endform-linux-arm64@0.76.0:
+ resolution: {integrity: sha512-V0naw5K33GmQW7yDsZvqCvTi9n5jT1gJpCeYGu70tB7NiOZVGFuOlepqSOZ0ZBzE0KJ2t85eMZj8ocdSuVVQJA==}
+ cpu: [arm64]
+ os: [linux]
+
+ endform-linux-x86@0.76.0:
+ resolution: {integrity: sha512-y4qt59+1rG/H1F7tZZ90fiTPbujHwVzpZE5UynpY/ffxxBkN2mU40y4BricSKX/mfyG0vey8Vp12A+u3evNrYQ==}
+ cpu: [x64]
+ os: [linux]
+
+ endform-win32-arm64@0.76.0:
+ resolution: {integrity: sha512-x+YXkZqmeCW9gcABctk7L7chYYoXA55+XOF56K6iEsiZh/dLpGF6ALpGmVmIlGo9kC5ADnvSqsKlxTO/kJZ+tQ==}
+ cpu: [arm64]
+ os: [win32]
+
+ endform-win32-x86@0.76.0:
+ resolution: {integrity: sha512-yCYi3kheL57pURP56QYl508oI1QK7rDWh1a7jy+YZ749MWKo4XlqJx/I04tMzYKAWYkKtfC2frTjoOY63m1CIQ==}
+ cpu: [x64]
+ os: [win32]
+
+ endform@0.76.0:
+ resolution: {integrity: sha512-qh5PWJ2CuFVEGObkt8OBvk5vmUNo0ET3MWst3GXLqgl/F6GEMW1sqgBdETmtJT8dUe4rIdVPzlpxobpq1Ch+4Q==}
+ hasBin: true
+
engine.io-client@6.6.6:
resolution: {integrity: sha512-iY6QdftLQ9pyiPoX082bpf/u1UewnOaJrtJIF9T0++QB34lZrj0uP+Q/bj8AlUsAxqhnkTV2BS8SBZSxOmoV5Q==}
@@ -37727,6 +37767,33 @@ snapshots:
dependencies:
once: 1.4.0
+ endform-darwin-arm64@0.76.0:
+ optional: true
+
+ endform-darwin-x86@0.76.0:
+ optional: true
+
+ endform-linux-arm64@0.76.0:
+ optional: true
+
+ endform-linux-x86@0.76.0:
+ optional: true
+
+ endform-win32-arm64@0.76.0:
+ optional: true
+
+ endform-win32-x86@0.76.0:
+ optional: true
+
+ endform@0.76.0:
+ optionalDependencies:
+ endform-darwin-arm64: 0.76.0
+ endform-darwin-x86: 0.76.0
+ endform-linux-arm64: 0.76.0
+ endform-linux-x86: 0.76.0
+ endform-win32-arm64: 0.76.0
+ endform-win32-x86: 0.76.0
+
engine.io-client@6.6.6(bufferutil@4.0.9)(supports-color@8.1.1)(utf-8-validate@5.0.10):
dependencies:
'@socket.io/component-emitter': 3.1.2
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 28c2ca3af7a4..19b5c69e54df 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -286,6 +286,7 @@ catalog:
yaml: 2.8.3
zod: 3.25.76
zod-to-json-schema: 3.23.3
+ endform: 0.76.0
catalogMode: strict