Skip to content

SITES-46557 - Add GitHub Actions CI/release workflows - #1080

Merged
LSantha merged 50 commits into
masterfrom
SITES-46234
Aug 10, 2026
Merged

SITES-46557 - Add GitHub Actions CI/release workflows#1080
LSantha merged 50 commits into
masterfrom
SITES-46234

Conversation

@alwinjoseph02

Copy link
Copy Markdown
Collaborator

No description provided.

@codecov

codecov Bot commented Jul 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…reactor resolution

mvn help:evaluate and mvn install:install-file both ran from the repo
root (a 28-module aggregator) without -N. Without it, Maven resolves
the entire reactor's effective POM graph before running the actual
goal - slow, and prone to failing on unrelated modules, when ~/.m2
starts empty as it does in test-aem's container (unlike CircleCI,
where restore_cache keeps it warm). Add -N so these only touch the
root project.
…on warning

v1 declares a node20 runtime, which GitHub Actions is deprecating.
v3 requires Node 24 (matching what the runner already defaults to)
with no input/output changes, so it's a drop-in bump across all five
usages.
…st-aem

Drop the pull_request trigger, keeping push only (matches CircleCI's
push-based model): push + pull_request together caused two independent
runs per commit on branches with an open PR, keyed by different
github.ref values so the existing concurrency group never deduped them.
GitHub still associates check runs with a PR by commit SHA regardless
of trigger type, so this loses no PR visibility.

Also add ARTIFACTORY_CLOUD_USER/PASS to test-aem's job-level env.
container.credentials/services.credentials only authenticate the
runner's docker pull of the private qp/aem images - they're not
exposed to job steps - so it-tests.js's Maven calls (via
settings.xml's ${env.ARTIFACTORY_CLOUD_USER}/_PASS) were failing to
authenticate against Artifactory.
test-aem's container starts with an empty ~/.m2 on every run - no cache
is shared with the build job (different container/runner), unlike
CircleCI's restore_cache/save_cache working off the same key. Without
it, it/site's standalone build and the install-file/download-maven-plugin
calls in it-tests.js re-download their entire dependency tree from
scratch every time, including old plugin chains like
content-package-maven-plugin's. Cache is keyed on pom.xml contents and
shared across all matrix.aem/type combinations, since they all need the
same underlying dependencies.
… always()

actions/cache@v4's save step only runs if the job succeeds. Since
test-aem was still failing for an unrelated reason, the cache never
got a chance to persist, even after re-downloading its entire
dependency tree - every retry stayed cold. Split into
actions/cache/restore@v4 (early) and actions/cache/save@v4 with
if: always() (near the end), so a failing run still saves whatever it
managed to download for the next attempt.
…tainer

The quick-provider RMI server started inside the aem service image
exports its remote object stub using its own self-view of its address,
which defaults to localhost when unset. That's harmless on CircleCI
(shared network namespace - localhost reaches every container in the
job), but on GitHub Actions this service is only reachable from the qp
container by its service name (aem), never localhost - so qp.sh start
was failing with "Connection refused to host: localhost" right after a
successful bind to aem:55555.

Set JAVA_TOOL_OPTIONS=-Djava.rmi.server.hostname=aem on the aem
service; every JVM started in that container picks it up silently,
including the quick-provider server's, so it advertises aem instead of
localhost.
…container:/services:

QuickProvider's RMI protocol advertises "localhost" as its own callback
address, which only works if the qp and aem containers share one
network namespace (--network host) - exactly like CircleCI's machine
executor. GitHub Actions' container:/services: keys cannot express
this: combining --network host with a services: container crashes the
runner before the job even starts ("Error: Value cannot be null.
(Parameter 'ContainerId')"). This is a GitHub Actions runner
limitation, not something fixable by adjusting JAVA_TOOL_OPTIONS or
similar - confirmed against aem-cif-guides-venia's
.github/ci/run-containerized-test.sh, which hit and solved this exact
problem first.

- Add run-containerized-test.sh: drives both containers directly via
  the Docker CLI on the bare runner, dumping the aem container's logs
  via a cleanup trap regardless of outcome (real visibility into it,
  finally)
- test-aem now runs on plain ubuntu-latest instead of container:/
  services:; cache path updated to /root/.m2/repository since the qp
  container now runs as --user root (needed to write into the
  bind-mounted workspace)
- Remove the now-moot JAVA_TOOL_OPTIONS RMI hostname override and the
  manual "dump aem logs" diagnostic step, both superseded by this
- it-tests.js needs no functional changes: AEM_HOST already defaulted
  to localhost, which is correct again now that networking matches
  CircleCI exactly. Updated its stale header comments accordingly.
…test-aem

it-tests.js already reads these from process.env (matching CircleCI's
script), and run-containerized-test.sh already forwards them into the
container - but ci.yml never sourced them from secrets, so the chain
was incomplete. Optional: it-tests.js already handles them being
unset, so this only takes effect once matching repo secrets exist with
real values.
maven-failsafe-plugin writes failsafe-summary.xml into
target/failsafe-reports/ alongside the real per-test TEST-*.xml
reports - a separate aggregate-counts file with no <testsuite>
structure. dorny/test-reporter's JUnit parser crashed trying to read
it as a per-test report ("Cannot read properties of undefined (reading
'$')") since the glob matched it too. Narrow the glob to TEST-*.xml.
… container

Selenium specs were failing at startup with "SevereServiceError:
Cannot find module 'chromedriver'". wdio.conf.local.js ("DO NOT
MODIFY") resolves chromedriver via `command -v chromedriver` - a
system binary lookup, not an npm module - and wdio-chromedriver-
service's own package.json doesn't depend on the chromedriver npm
package either. The qp image, unlike CircleCI's browser-tools/install-
browser-tools orb for this same job, doesn't ship Chrome/chromedriver
at all. Since no specs ever ran, the junit reporter never wrote any
report file either, which is why "Publish test results" separately
failed with "No test report files were found" - same root cause, not
a new issue.

Ported from aem-cif-guides-venia's .github/ci/install-chrome.sh, which
hit and solved this exact problem first (including a related root-
sandbox issue: our qp container also runs as --user root, and Chrome's
sandbox refuses to run as root).

- Add install-chrome.sh: installs a matched Chrome + chromedriver pair
  as system binaries, then wraps Chrome's launcher and leaf binary with
  --no-sandbox
- Add run-integration-test.sh: new entry point run inside the qp
  container - runs install-chrome.sh only when TYPE=selenium, then
  runs it-tests.js as before
- run-containerized-test.sh now invokes run-integration-test.sh instead
  of calling node it-tests.js directly
Same Node 20 deprecation fix as the earlier dorny/test-reporter bump.
Both v5/v6 bump the same underlying @actions/artifact dependency to
v4.0.0 (confirmed paired release), with no input/output changes.
…SION

Cache save was failing with "EACCES: permission denied, lstat
'/root/.m2/repository'". The path was never actually shared between
host and container: /root/.m2/repository only existed inside the qp
container's own ephemeral layer (gone the instant --rm removed it), or
was the host's real /root (mode 700), inaccessible to the unprivileged
default runner user actions/cache steps run as. Bind-mount the
runner's own ~/.m2 into the container's /root/.m2 in
run-containerized-test.sh, and cache ~/.m2/repository (the host path)
instead of /root/.m2/repository.

Also add two things surfaced while fixing this:
- actions/cache only ever warns on failure, never fails the step, so
  this exact bug was silently hiding as an easy-to-miss orange warning
  while the job stayed green. Add a "Verify Maven cache is populated"
  step before the save that fails loudly (::error::) if the directory
  is missing/empty, and a "Report cache restore outcome" step logging
  cache-hit after restore (informational only - a miss is normal on
  the first run or after a pom.xml change).
- CACHE_VERSION, mirroring CircleCI's `{{ .Environment.CACHE_VERSION }}`
  prefix in maven-repo-*: a repository/organization Variable (not
  defined here) that can be bumped later to force a fresh cache
  lineage without a code change. Defaults to '1' until that Variable
  exists.
Same Node 20 deprecation fix as the other actions bumped earlier. No
input/output changes, just the Node 24 runtime bump - confirmed
against v5.0.0's release notes.
Cache is confirmed working now (verified hitting on subsequent runs).
No longer need the informational cache-hit logging step.
test-aem: remove entirely - matrix/user info was mostly redundant
with the job name already shown in the Actions UI, and everything
depends on Docker being present anyway (if it weren't,
run-containerized-test.sh fails immediately with an equally clear
error).

build: drop the runner os/arch and git ref/sha echoes (never actually
used for debugging) and node/npm versions (frontend-maven-plugin
downloads its own pinned Node per module, so the system version isn't
actually in play here). Keep java -version/mvn -v, which do confirm
matrix.java resolved to the intended JDK.
Same Node 20 deprecation fix as the earlier bumps, extended to the
actions we hadn't touched yet:

- actions/checkout v4 -> v5 (ci.yml and release.yml)
- actions/setup-java v4 -> v5 (ci.yml and release.yml)
- actions/setup-node v4 -> v5 (ci.yml) - v5's new auto-caching-on-
  packageManager-field behavior doesn't affect us, confirmed no
  package.json in this repo declares that field
- codecov/codecov-action v4 -> v6 - v5 stayed on Node 20, v6 is the
  first Node 24 release; carries a bigger internal rewrite (a "Codecov
  Wrapper" + a token opt-out change) than the other bumps here, but no
  breaking changes to the token/flags/fail_ci_if_error inputs we use,
  and fail_ci_if_error: false everywhere means worst case is a silent
  non-upload, not a broken build
- webfactory/ssh-agent v0.9.0 -> v0.10.0 (release.yml)

mxschmitt/action-tmate stays on the floating @V3 tag: v3.24 (already
under the v3 line) added Node 24 support, so it's already covered -
no v4 tag actually exists despite what a search result claimed.
…onfiguration()

v5.5.0 generates a settings.xml that force-disables Maven's interactive
mode, with no opt-out. ci.js's collectConfiguration() (used by build.js)
and parsePom() (used by deploy-queries.js) both rely on that mode - they
pipe `printf '${project.groupId}|...'` into `mvn help:evaluate` (no
-Dexpression flag) specifically to grab it via the interactive prompt
reading from stdin. With that disabled, Maven never reads the prompt,
produces zero output on both stdout and stderr, and the build fails
immediately on the very first pom.xml processed.

CircleCI never hit this since it uses cimg/openjdk images with their own
Maven install, not actions/setup-java, so this is a GitHub-Actions-only
incompatibility. Reverting is safer than rewriting ci.js's shared
Maven-invocation mechanism just to silence a cosmetic Node 20 warning -
v4 was proven working through this entire migration.
GitHub has no native "Tests" tab. Ports aem-cif-guides-venia's
.github/ci/test-summary.py: parses JUnit XML from
it/http/target/failsafe-reports and ui.tests/test-module/reports, and
writes a CircleCI-style Markdown report to $GITHUB_STEP_SUMMARY - an
overview table plus, for each failed test, the stack trace and
captured server log in a collapsible block. Explicitly distinguishes
"no XML found" (environment/startup failure) from "tests ran and
failed", and skips failsafe-summary.xml by checking the XML root tag
rather than a filename glob.

Needs only contents: read, unlike dorny/test-reporter's checks: write.
Complementary to dorny/test-reporter, not a replacement - it still
gives the Checks-tab/PR status view this doesn't.

Report files are chmod'd first since the qp container writes them as
--user root.
Cache is confirmed working reliably now. Removing this in favor of
keeping the workflow lean, per user preference.
It wasn't reliably creating check runs (likely a GITHUB_TOKEN
permission restriction - e.g. fork PRs force checks: write down to
read regardless of this workflow's permissions block). Removed in
favor of the Job Summary report (test-summary.py) alone, matching
aem-cif-guides-venia's own integration/selenium jobs, which never had
a Checks-tab step to begin with. Left in place for build/karma/jest/
jest-extension-product-recs, which don't have an equivalent Job
Summary replacement yet.
Neither actually protects against a silent failure: setup-java hard-
fails if the requested matrix.java version isn't resolvable rather
than silently substituting a different one, and setup-java's own step
log already prints the installed JDK version. karma's browser-version
echo is informational only, same category as other steps already
removed this session. No Debug context steps remain anywhere in this
workflow now.
The tmate SSH step gave access to the runner, but run-containerized-
test.sh's cleanup trap always removed the aem container immediately on
exit - by the time anyone connected, there was nothing left to
docker exec into.

- run-containerized-test.sh: skip docker rm -f in the cleanup trap
  when KEEP_AEM_CONTAINER=true (still dumps logs either way); exports
  the container name via GITHUB_ENV for later steps to reference
- ci.yml: wire KEEP_AEM_CONTAINER from the existing debug_enabled
  workflow_dispatch input; move "Debug via SSH (tmate)" to the very
  last step, after cache save/artifact upload/coverage upload, so
  those always complete before this potentially-long, open-ended
  pause instead of risking never running if the session runs long or
  the job gets cancelled mid-session; add a final cleanup step that
  force-removes the container once the debug session ends
…ollapsed

- collapsed: auto -> always on all dorny/test-reporter steps: reports
  stay collapsed regardless of pass/fail, click-to-expand only,
  instead of auto-expanding on failure
- test-aem: remove the custom test-summary.py Job Summary step and
  re-add dorny/test-reporter (with the same list-tests: failed +
  collapsed: always config as every other job), for one consistent
  reporting mechanism across the whole workflow. Whether the check run
  itself reliably appears here depends on the same GITHUB_TOKEN
  permission conditions as the other jobs - re-added for consistency
  regardless.
- Delete .github/scripts/test-summary.py, no longer referenced
  anywhere
alwinjoseph02 and others added 8 commits July 20, 2026 19:05
…eporter

list-tests, list-suites, and list-files are independent controls at
each level of the report. Setting only list-tests: failed still listed
every fully-passing suite and result file in full, which is the
clutter that prompted this. Set all three to failed so a suite/file/
test that fully passed doesn't appear in the report at all - only
genuine failures do.
…report

dorny/test-reporter's list-tests/list-suites/list-files "failed"
settings don't suppress the always-present per-suite/file summary
table (a structural limitation of its report format, not a config
mistake), and list-tests itself has a long-standing unfixed upstream
bug (dorny/test-reporter#142) where it doesn't reliably filter passed
tests out at all.

mikepenz/action-junit-report's skip_success_summary does what those
couldn't: the summary table is skipped entirely when everything
passes, and only appears when there's a real failure to show. Already
proven for this exact purpose in aem-cif-guides-venia's own ci.yml.

- build/karma/jest/jest-extension-product-recs: straight swap, with
  skip_success_summary: true and fail_on_failure: false
- test-aem: split into two matrix.type-gated steps instead of one
  multi-path glob, since this action's report_paths multi-pattern
  syntax isn't clearly documented and only one path ever has matching
  files per matrix.type anyway
- Kept the TEST-*.xml glob (not *.xml) for the failsafe path, since
  this action's tolerance for failsafe-summary.xml is unverified
Changed if: always() to if: failure() on all six mikepenz/action-
junit-report steps (build, karma, jest, jest-extension-product-recs,
and both test-aem type-gated steps). On a passing run, the step is
now skipped entirely - no check run gets created at all - rather than
creating one that just shows a minimal passed state via
skip_success_summary. Only runs (and only then creates a check run)
when there's an actual failure to report.

failure() correctly reflects the accumulated job status rather than
just the immediately preceding step, so a prior failure (e.g.
Integration/UI tests) still triggers these even with an always()-gated
step (e.g. Fix report file permissions) running successfully in
between.
v5 of upload-artifact and v6 of download-artifact only had
"preliminary" Node 24 support - their action.yml still declares
runs.using: node20 by default, which is why the runner warning kept
naming them. v6/v7 respectively are the first versions that actually
declare runs.using: node24. Confirmed no input/output changes in
either, just the runtime bump.

actions/setup-java@v4 still shows the same warning and stays as-is -
that one is intentional (v5.5.0 breaks collectConfiguration(), see its
existing comment), not something this commit addresses.
…porting

Adds one deliberately failing jest test (react-components) to verify
the if: failure() -> mikepenz/action-junit-report -> Job Summary
reporting chain actually behaves correctly on a real failure.
Verified locally: this is the only failing test out of 90 suites/352
tests. TEMPORARY - to be reverted once verified.
Removes the deliberately failing test added to verify the
if: failure() -> mikepenz/action-junit-report -> Job Summary reporting
chain. Verified locally: back to 89 suites / 351 tests, all passing.
Rewrite the comments across the .github workflows and scripts to describe
their own behavior instead of referencing the CircleCI pipeline they were
ported from. No functional changes; .circleci/ is left untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@alwinjoseph02 alwinjoseph02 changed the title SITES-46234 - Add GitHub Actions CI/release workflows SITES-46557 - Add GitHub Actions CI/release workflows Jul 24, 2026
}
// Tag latest commit with component release
ci.sh(`git tag ${tagName} HEAD`);
ci.sh(`git push origin ${tagName}`);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this runs twice for the same version (e.g. a release retry), git tag ${tagName} fails because the tag already exists and aborts the step. Consider git tag -f or checking for the tag first so retries are idempotent.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same behaviour exists in circleci too. And from my understanding it shouldn't run on the same tag again will create other issues

Comment thread .github/workflows/ci.yml

# if: failure() (not always()): skip this step entirely on a passing run - only
# run it (and thus only create a check run) when there's an actual failure.
- name: Publish test results

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CircleCI never set an explicit timeout in config.yml — it relied on the built-in
per-step default of 10 minutes of no output (no_output_timeout: 10m), which
automatically killed a hung step (e.g. an AEM startup that never comes up).

but GitHub Actions has no no-output timeout and defaults to a 6-hour job limit,
so that safety net is gone after the migration. A hung test-aem job would sit for
6h before failing.

Suggest adding timeout-minutes to each job (especially the AEM integration/selenium
jobs in ci.yml) to restore the bound CircleCI gave us for free, e.g.:

jobs:
  test-aem:
    runs-on: ubuntu-latest
    timeout-minutes: 30

alwinjoseph02 and others added 13 commits July 29, 2026 13:53
GitHub Actions has no no-output timeout and defaults to a 6h job limit, so a
hung job (stuck mvn/docker pull, wedged selenium session, etc.) would sit for
6h before failing. Add a job-level timeout-minutes to each job to restore the
bound CircleCI provided implicitly via its 10m no-output default.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ialized before test suites run (#1083) (#1084)

* kama fix

Co-authored-by: satish <del60718@adobe.com>
…ad of 6

`aem` was only declared inside `include:`, not as a top-level matrix key, so
GitHub Actions treated it as an added (non-original) key: each include entry
overwrote the previous one's aem/qp_image/aem_image across every {type}
combination, leaving only the last entry (addon) applied to both type values.
The job silently ran 2 legs instead of 6, with classic and lts never executing
while still reporting green.

Declaring `aem: [classic, lts, addon]` alongside `type` makes it an original
matrix key, so each include entry now only merges into the combination whose
aem already matches - restoring all 6 legs with the correct image pairs,
matching CircleCI's integration-test-{655,660,cloudready-with-addon} and
selenium-chrome-{655,660,cloudready-with-addon} jobs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts:
#	it/site/pom.xml
Switch the six Publish test results steps from if: failure() to if: always()
so the Checks tab always shows a pass/fail/skip summary, matching CircleCI's
Tests insights tab. skip_success_summary keeps a passing run's check
lightweight - only the counts, with the per-test table appearing only when
there's an actual failure to report.
Replace skip_success_summary with detailed_summary + include_passed on all
six Publish test results steps, so the Checks tab lists every test case
(name, result, failure message/stack trace) on every run instead of just
counts on success.
Add annotate_notice: true to all six Publish test results steps. Without it,
only failed tests get an annotation - passed tests never reach the per-test
console log line printed during parsing (path:line | test name), so a
passing run showed nothing beyond the aggregate count.

With it, every test case (pass, fail, skip) gets logged directly in that
step's own console output - same job page, no separate tab, and not subject
to the Checks API's 50-annotation cap (that only limits what's attached to
the check's own Annotations list, not what's printed to the step log).
…orter

Add group_suite: true to all six Publish test results steps. detailed_summary
+ include_passed already list every test case in the job summary; on a job
with hundreds of tests (e.g. 1022 on build jdk 11) that table gets very long
to scroll. Grouping by test class/suite instead of one row per test method
keeps the same information available at a coarser, shorter grain.
Name the six test-aem legs like the CircleCI jobs they replace
(integration-test-655/660/cloudready-with-addon and
selenium-chrome-655/660/cloudready-with-addon) via a display-only 'label'
on each AEM include entry. The matrix values and AEM env var stay
classic/lts/addon so it-tests.js's branching is unaffected.
@alwinjoseph02
alwinjoseph02 requested a review from LSantha August 10, 2026 11:23
@LSantha
LSantha merged commit 6d707c1 into master Aug 10, 2026
36 of 37 checks passed
@LSantha
LSantha deleted the SITES-46234 branch August 10, 2026 13:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants