name: 'Test: E2E' on: workflow_call: inputs: branch: description: 'GitHub branch to test.' required: false type: string test-mode: description: 'Test mode: local or docker-artifact' required: false default: 'local' type: string test-command: description: 'Test command to run' required: false default: 'pnpm --filter=n8n-playwright test:local' type: string workers: description: 'Number of parallel workers' required: false default: '' type: string runner: description: 'GitHub runner to use' required: false default: 'blacksmith-2vcpu-ubuntu-2204' type: string timeout-minutes: description: 'Job timeout in minutes' required: false default: 30 type: number currents-project-id: description: 'Currents project ID for reporting' required: false default: 'nHHLA5' type: string pre-generated-matrix: description: 'Pre-generated shard matrix JSON. Defaults to 1 shard; multi-shard callers pass their own.' required: false default: '[{"shard":1,"images":""}]' type: string n8n-env: description: 'JSON string of n8n env vars to inject into test containers, e.g. {"N8N_EXPRESSION_ENGINE":"vm"}' required: false default: '' type: string artifact-prefix: description: 'Prefix for uploaded shard artifacts' required: false default: 'e2e' type: string build-variant: description: 'Build variant for docker-artifact mode. Must match the variant used by prepare-docker (standard or coverage).' required: false default: 'standard' type: string env: NODE_OPTIONS: ${{ contains(inputs.runner, '2vcpu') && '--max-old-space-size=6144' || '' }} PLAYWRIGHT_WORKERS: ${{ inputs.workers != '' && inputs.workers || '2' }} # Browser cache location - must match install-browsers script. # Absolute so nested pnpm invocations (e.g. coverage shard runner) don't # re-resolve a relative path against the package's INIT_CWD and double it. PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/packages/testing/playwright/.playwright-browsers # docker-artifact loads the image locally as n8nio/n8n:local; unused in local mode. TEST_IMAGE_N8N: 'n8nio/n8n:local' N8N_SKIP_LICENSES: 'true' CURRENTS_CI_BUILD_ID: ${{ github.repository }}-${{ github.run_id }}-${{ github.run_attempt }} CURRENTS_PROJECT_ID: ${{ inputs.currents-project-id }} jobs: test: runs-on: ${{ vars.RUNNER_PROVIDER == 'github' && 'ubuntu-latest' || inputs.runner }} timeout-minutes: ${{ inputs.timeout-minutes }} permissions: contents: read strategy: fail-fast: false matrix: include: ${{ fromJSON(inputs.pre-generated-matrix) }} name: Shard ${{ matrix.shard }} steps: - name: Checkout uses: useblacksmith/checkout@41cdeedae8edb2e684ba22896a5fd2a3cb85db6b # v1 with: fetch-depth: 1 ref: ${{ inputs.branch || github.ref }} - name: Setup Environment uses: ./.github/actions/setup-nodejs env: QA_METRICS_WEBHOOK_URL: ${{ secrets.QA_METRICS_WEBHOOK_URL }} QA_METRICS_WEBHOOK_USER: ${{ secrets.QA_METRICS_WEBHOOK_USER }} QA_METRICS_WEBHOOK_PASSWORD: ${{ secrets.QA_METRICS_WEBHOOK_PASSWORD }} - name: Install Browsers run: pnpm turbo run install-browsers --filter=n8n-playwright - name: Load n8n image from cache if: ${{ inputs.test-mode == 'docker-artifact' }} uses: ./.github/actions/load-n8n-docker with: build-variant: ${{ inputs.build-variant }} - name: Pre-pull Test Container Images if: ${{ !contains(inputs.test-command, 'test:local') }} run: npx tsx packages/testing/containers/pull-test-images.ts ${{ matrix.images }} || true - name: Filter specs to previous-attempt failures if: github.run_attempt > 1 && matrix.specs != '' id: filter-shard env: CANDIDATES: ${{ matrix.specs }} GITHUB_RUN_ID: ${{ github.run_id }} GITHUB_RUN_ATTEMPT: ${{ github.run_attempt }} run: | FILTERED=$(echo "$CANDIDATES" | node packages/testing/janitor/dist/cli.js filter-shard | tr '\n' ' ' | sed 's/ *$//') echo "specs=$FILTERED" >> "$GITHUB_OUTPUT" - name: Run Tests run: | # --pass-with-no-tests makes Playwright exit 0 when the selected specs # resolve to zero runnable tests. Covers the impact-map vs current-suite # skew window: a spec the committed coverage map points at may have been # quarantined (test.skip/fixme) since the map was built, or a capability # --grep on the test-command may filter every test on a shard. The # selector is doing the right thing; the run shouldn't treat "nothing # to run" as failure. # shellcheck disable=SC2086 $TEST_COMMAND --workers="$WORKERS" $SHARD_ARGS --pass-with-no-tests env: # Protect args from template injections TEST_COMMAND: ${{ inputs.test-command }} # Uses pre-distributed specs if orchestration enabled, otherwise falls back to Playwright sharding WORKERS: ${{ env.PLAYWRIGHT_WORKERS }} # On retry, prefer the filtered subset (failed specs from previous attempt) SHARD_ARGS: ${{ steps.filter-shard.outputs.specs || matrix.specs || format('--shard={0}/{1}', matrix.shard, strategy.job-total) }} # Args for actual test command runner CURRENTS_RECORD_KEY: ${{ secrets.CURRENTS_RECORD_KEY }} QA_METRICS_WEBHOOK_URL: ${{ secrets.QA_METRICS_WEBHOOK_URL }} QA_METRICS_WEBHOOK_USER: ${{ secrets.QA_METRICS_WEBHOOK_USER }} QA_METRICS_WEBHOOK_PASSWORD: ${{ secrets.QA_METRICS_WEBHOOK_PASSWORD }} N8N_LICENSE_ACTIVATION_KEY: ${{ secrets.N8N_LICENSE_ACTIVATION_KEY }} N8N_LICENSE_CERT: ${{ secrets.N8N_LICENSE_CERT }} N8N_ENCRYPTION_KEY: ${{ secrets.N8N_ENCRYPTION_KEY }} N8N_TEST_ENV: ${{ inputs.n8n-env }} - name: Upload Shard Artifacts if: always() uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: ${{ inputs.artifact-prefix }}-shard-${{ matrix.shard }} path: | packages/testing/playwright/test-results/ packages/testing/playwright/test-results.json packages/testing/playwright/playwright-report/ packages/testing/playwright/coverage/lcov.info packages/testing/playwright/coverage/by-spec/ packages/testing/playwright/coverage/index.html packages/testing/playwright/coverage/assets/ retention-days: 1 if-no-files-found: ignore - name: Cancel Currents run if workflow is cancelled if: ${{ cancelled() }} env: CURRENTS_API_KEY: ${{ secrets.CURRENTS_API_KEY }} run: | if [ -n "$CURRENTS_API_KEY" ]; then curl --location --request PUT \ "https://api.currents.dev/v1/runs/cancel-ci/github" \ --header "Authorization: Bearer $CURRENTS_API_KEY" \ --header "Content-Type: application/json" \ --data "{\"githubRunId\": \"${{ github.run_id }}\", \"githubRunAttempt\": \"${{ github.run_attempt }}\"}" fi