Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .github/actions/acceptance-lane/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Acceptance lane
description: >-
Boots a Shopware lane through bin/ci-write-compose.sh with the plugin installed and the
administration built, then installs the acceptance suite. Expects the plugin checked out at
agentic-commerce/.

inputs:
lane:
description: 'Lane for bin/ci-write-compose.sh: 6.5.x, 6.6.x, 6.7.14.x or trunk'
required: true
shopware-ref:
description: 'shopware/shopware ref to check out'
required: true

runs:
using: composite
steps:
- uses: actions/checkout@v6
with:
repository: shopware/shopware
ref: ${{ inputs.shopware-ref }}
path: shopware
- uses: actions/setup-node@v6
with:
node-version: '24'
cache: npm
cache-dependency-path: agentic-commerce/tests/acceptance/package-lock.json
- name: Write the lane's compose stack
shell: bash
env:
LANE: ${{ inputs.lane }}
run: agentic-commerce/bin/ci-write-compose.sh "${GITHUB_WORKSPACE}/shopware" "${LANE}"
# The Setup project logs into the administration, so the bootstrap builds it. "auto" keeps each
# lane's default build tool and leaves the 6.6 feature dump untouched after the script exits.
- name: Boot the shop with the administration built
shell: bash
env:
SHOPWARE_REF: ${{ inputs.lane }}
CI_ADMIN_BOOTSTRAP_MODE: cold
CI_ADMIN_BROWSER_VALIDATE: '0'
CI_ADMIN_KEEP_STACK: '1'
run: agentic-commerce/bin/ci-admin-smoke.sh "${GITHUB_WORKSPACE}/shopware" auto
# After the bootstrap: bin/ci-smoke.sh copies the plugin checkout into the shop.
- name: Install the acceptance suite
shell: bash
working-directory: agentic-commerce/tests/acceptance
run: |
npm ci --no-audit --no-fund
npx playwright install chromium --with-deps
35 changes: 35 additions & 0 deletions .github/actions/acceptance-report/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Acceptance report
description: >-
Merges every acceptance-blob-* artifact of the run into one Playwright report, writes the failure
table to the step summary and uploads the report as the acceptance-report artifact. Expects the
plugin checked out at the workspace root.

runs:
using: composite
steps:
- uses: actions/setup-node@v6
with:
node-version: '24'
cache: npm
cache-dependency-path: tests/acceptance/package-lock.json
- name: Install the acceptance suite
shell: bash
working-directory: tests/acceptance
run: npm ci --no-audit --no-fund
- name: Download the shard reports
uses: actions/download-artifact@v7
with:
pattern: acceptance-blob-*
path: ${{ runner.temp }}/acceptance-blobs
merge-multiple: true
- name: Merge the shard reports
shell: bash
run: bin/ci-acceptance-report.sh "${RUNNER_TEMP}/acceptance-blobs" "${RUNNER_TEMP}/acceptance-report"
- name: Upload the merged report
uses: actions/upload-artifact@v6
with:
name: acceptance-report
path: ${{ runner.temp }}/acceptance-report
if-no-files-found: error
overwrite: true
retention-days: 14
209 changes: 209 additions & 0 deletions .github/workflows/acceptance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
name: Acceptance

# The tests/acceptance suite: static checks, the lane x shard matrix and, on pull requests, the
# changed specs repeated ten times. The callers merge the shard reports once per run
# (.github/actions/acceptance-report).

on:
workflow_call:
inputs:
lanes:
description: 'JSON array of bin/ci-write-compose.sh lanes'
type: string
shards:
type: number
repeat_each:
type: number
fail_on_flaky:
description: 'Fail a lane when a test only passes on a retry'
type: boolean
run_changed:
description: 'On pull requests, repeat the changed specs ten times without retries'
type: boolean
skip_static:
description: 'Skip lint, typecheck and the Signer project (another call runs them)'
type: boolean
shopware_65_ref:
type: string
shopware_66_ref:
type: string
shopware_trunk_ref:
type: string
workflow_dispatch:

# A caller's env does not reach a called workflow, so every layer resolves the refs itself.
env:
SHOPWARE_65_REF: ${{ inputs.shopware_65_ref || vars.SHOPWARE_65_REF || '6.5.x' }}
SHOPWARE_66_REF: ${{ inputs.shopware_66_ref || vars.SHOPWARE_66_REF || '6.6.x' }}
SHOPWARE_TRUNK_REF: ${{ inputs.shopware_trunk_ref || vars.SHOPWARE_TRUNK_REF || 'trunk' }}
SHOPWARE_6714_REF: ${{ vars.SHOPWARE_6714_REF || '6.7.14.x' }}

permissions:
contents: read

jobs:
acceptance-static:
if: ${{ !inputs.skip_static }}
runs-on: ubuntu-latest
defaults:
run:
working-directory: tests/acceptance
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: '24'
cache: npm
cache-dependency-path: tests/acceptance/package-lock.json
- run: npm ci --no-audit --no-fund
- run: npm run lint
- run: npm run typecheck
# The RFC 9421 signer checks need no shop.
- run: npm run test:signer

acceptance-plan:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.plan.outputs.matrix }}
changed: ${{ steps.plan.outputs.changed }}
steps:
- uses: actions/checkout@v6
with:
# On pull_request the merge ref's first parent is the base branch.
fetch-depth: 2
- id: plan
env:
LANES: ${{ inputs.lanes || '["trunk"]' }}
SHARDS: ${{ inputs.shards || 1 }}
RUN_CHANGED: ${{ inputs.run_changed && github.event_name == 'pull_request' }}
run: |
set -euo pipefail
# jq -e fails on an empty lane list instead of planning an empty run.
matrix="$(jq -ce --argjson shards "${SHARDS}" \
'select(length > 0) | {include: [.[] as $lane | range(1; $shards + 1) | {lane: $lane, shard: ., shards: $shards}]}' <<<"${LANES}")"
changed='[]'
if [[ "${RUN_CHANGED}" == "true" ]]; then
# Setup and Signer are dependency projects that already run in every acceptance job.
# KnownBlocked specs are not gating, so they are not repeated either.
# quotePath=false: git would otherwise print non-ASCII paths quoted, matching no spec.
changed="$(git -c core.quotePath=false diff --name-only --diff-filter=d HEAD^1 HEAD -- \
':(glob)tests/acceptance/tests/**/*.spec.ts' \
Comment thread
dgrothaus-sw marked this conversation as resolved.
Resolved
':(exclude,glob)tests/acceptance/tests/Setup/**' \
':(exclude,glob)tests/acceptance/tests/Signer/**' \
':(exclude,glob)tests/acceptance/tests/KnownBlocked/**' \
| jq -Rnc '[inputs | select(length > 0) | ltrimstr("tests/acceptance/")]')"
fi
echo "matrix=${matrix}" >> "${GITHUB_OUTPUT}"
echo "changed=${changed}" >> "${GITHUB_OUTPUT}"
echo "Changed specs repeated ten times: ${changed}" >> "${GITHUB_STEP_SUMMARY}"

acceptance:
name: acceptance (${{ matrix.lane == '6.5.x' && '6.5.8' || matrix.lane }}, ${{ matrix.shard }}/${{ matrix.shards }})
needs: acceptance-plan
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.acceptance-plan.outputs.matrix) }}
steps:
- uses: actions/checkout@v6
with:
path: agentic-commerce
- uses: ./agentic-commerce/.github/actions/acceptance-lane
with:
lane: ${{ matrix.lane }}
shopware-ref: ${{ matrix.lane == '6.5.x' && env.SHOPWARE_65_REF || matrix.lane == '6.6.x' && env.SHOPWARE_66_REF || matrix.lane == '6.7.14.x' && env.SHOPWARE_6714_REF || env.SHOPWARE_TRUNK_REF }}
- id: suite
working-directory: agentic-commerce/tests/acceptance
env:
# bin/ci-write-compose.sh publishes the shop on this URL inside the container and on the
# runner. The agent-profile fixture writes into SHOPWARE_DIR/public and the shop fetches
# the profile back from this URL in development mode.
APP_URL: http://localhost:8000
SHOPWARE_DIR: ${{ github.workspace }}/shopware
# The lane's container runs with SHELL_VERBOSITY=-1 (bin/ci-write-compose.sh), which mutes
# the JSON the signing-key commands print.
UCP_CONSOLE: docker compose exec -T -e SHELL_VERBOSITY=0 web php bin/console
ACCEPTANCE_LANE: ${{ matrix.lane }}
PLAYWRIGHT_BLOB_OUTPUT_FILE: ${{ github.workspace }}/acceptance-blob/acceptance-${{ matrix.lane }}-${{ matrix.shard }}.zip
SHARD: ${{ matrix.shard }}/${{ matrix.shards }}
REPEAT_EACH: ${{ inputs.repeat_each || 1 }}
FAIL_ON_FLAKY: ${{ inputs.fail_on_flaky && '--fail-on-flaky-tests' || '' }}
# FAIL_ON_FLAKY stays unquoted, so an empty value passes no argument.
run: npx playwright test --grep-invert @UcpKnownBlocked --shard="${SHARD}" --repeat-each="${REPEAT_EACH}" ${FAIL_ON_FLAKY} --reporter=blob,list
- if: ${{ !cancelled() && steps.suite.outcome != 'skipped' }}
uses: actions/upload-artifact@v6
with:
name: acceptance-blob-${{ matrix.lane }}-${{ matrix.shard }}
path: acceptance-blob
if-no-files-found: error
overwrite: true
retention-days: 7

# A spec that passes once can still be flaky: every spec this pull request adds or changes runs
# ten times without retries on trunk.
acceptance-tests-changed:
needs: acceptance-plan
if: needs.acceptance-plan.outputs.changed != '[]'
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
with:
path: agentic-commerce
- uses: ./agentic-commerce/.github/actions/acceptance-lane
with:
lane: trunk
shopware-ref: ${{ env.SHOPWARE_TRUNK_REF }}
- working-directory: agentic-commerce/tests/acceptance
env:
APP_URL: http://localhost:8000
SHOPWARE_DIR: ${{ github.workspace }}/shopware
UCP_CONSOLE: docker compose exec -T -e SHELL_VERBOSITY=0 web php bin/console
# Paths chosen by the pull request: read as data, never interpolated into the script.
CHANGED_SPECS: ${{ needs.acceptance-plan.outputs.changed }}
run: |
mapfile -t specs < <(jq -r '.[]' <<<"${CHANGED_SPECS}")
# The plan leaves out KnownBlocked specs, so a changed spec that selects no test (no project
# tag, or a name Playwright reads as a regex) must fail the job. Playwright only fails when the
# whole run selects nothing, so each spec is listed on its own first, without Setup and Signer.
for spec in "${specs[@]}"; do
npx playwright test --list --no-deps --grep-invert @UcpKnownBlocked --reporter=list "${spec}" || exit 1
done
# One run for all specs, so Setup and Signer execute once.
# Two workers, the default on the 4-vCPU runner, so a smaller runner keeps the parallel load.
# No `--` before the specs: Playwright drops every argument after it from the test filter and
# runs the whole suite. The plan only emits paths starting with tests/, never an option.
# -x stops at the first failure, as core's job does: one failure already shows the flake.
npx playwright test --grep-invert @UcpKnownBlocked --repeat-each=10 --retries=0 --workers=2 -x --reporter=list "${specs[@]}"
# The failures' traces hold every Admin API response the ATS asserts without its body; the shop
# log holds the server-side errors. Both carry tokens of this throwaway shop, like the lane blobs.
- if: failure()
uses: actions/upload-artifact@v6
with:
name: acceptance-tests-changed-failure
path: |
agentic-commerce/tests/acceptance/test-results
shopware/var/log
if-no-files-found: ignore
overwrite: true
retention-days: 7

acceptance-check:
# always(), not !cancelled(): a skipped gate would count as passing.
if: always()
needs:
- acceptance-static
- acceptance-plan
- acceptance
- acceptance-tests-changed
runs-on: ubuntu-latest
steps:
- uses: re-actors/alls-green@b5b5b37504aa4183270bd3d855c52a67f212be35 # v1.3.0
with:
jobs: ${{ toJSON(needs) }}
# Only the two conditional jobs may be skipped, and only when their condition says so.
# alls-green drops the empty entries of this comma-separated list.
allowed-skips: >-
${{ inputs.skip_static && 'acceptance-static' || '' }},
${{ needs.acceptance-plan.outputs.changed == '[]' && 'acceptance-tests-changed' || '' }}
Loading
Loading