Skip to content

feat(e2e): add a reusable workflow for browser-based tests - #398

Closed
marcelmtz wants to merge 10 commits into
mage-os:mainfrom
marcelmtz:feat/reusable-e2e-workflow
Closed

marcelmtz wants to merge 10 commits into
mage-os:mainfrom
marcelmtz:feat/reusable-e2e-workflow

Conversation

@marcelmtz

Copy link
Copy Markdown
Contributor

Adds e2e.yaml, a workflow_call workflow that stands a Mage-OS store up, serves it, and points a browser-based test runner at it. This is the piece the release pipeline can call; _internal-e2e.yaml (#396) cannot be reused, because it references the actions through ./ and a relative path in a reusable workflow resolves against the caller's checkout.

Own-repo actions are therefore referenced by full ref, as integration.yaml and full-integration-tests.yaml already do.

Stacked

This cannot go green until #395 is on main, because warden/setup-environment@main does not yet have base_url, configure_host_resolution or run_static_content_deploy. Draft until then.

Verified

Run on a throwaway branch with the Warden action pointed at #395's branch:

  • store served and answering HTTP/2 200 on the resolved hostname
  • catalog seeded, Total execution time: 00:00:09
  • 2 passed — the storefront smoke test and the guest checkout
  • report, trace and video uploaded as an artifact

Total 6m31s, of which 2m26s was a runner apt mirror running at 145 kB/s during playwright install --with-deps. The equivalent canary run is 4m10s. Nothing in the workflow accounts for the difference — the Chromium download itself was 6 seconds — but it is a fair reason to start non-blocking.

test_command

test_command is an input rather than a hardcoded npx playwright test. The workflow's job is to stand a store up and hand over a URL; it should not know what runner points at it, and a caller wanting a grep filter, a shard or a different reporter should not need a new input for each.

Two details worth a reviewer's attention:

  • It arrives through the environment, not interpolated into the script, so its quoting is bash's problem and not the expression engine's.
  • The default carries no flags. Evidence policy (trace, video, screenshot) belongs in the suite's config, where it is reviewable — a CLI flag overrides the config, and --trace on records a trace for every passing test.

The test_command input and the idea of a setup phase are both from @digitalrisedorset's #313. The README documents the setup phase as Playwright project dependencies rather than two --grep invocations, because two invocations write to the same report directory and the second overwrites the first, so a failing setup phase loses its own evidence.

Notes for review

  • fixture_profile seeds a catalog with setup:performance:generate-fixtures before the run, because a bare setup:install has nothing to buy. Empty skips it.
  • No supported-version matrix. A release gate has to install the version being released, which by definition is not in supported.json yet — so magento_version and magento_repository are passed directly.
  • @rhoerr raised on ci(e2e): add Playwright-based E2E workflow for Mage-OS #313 that an e2e workflow will overlap integration.yaml and that the common parts should be extracted. That is still true and still worth doing separately; this does not make it worse.

PR Checklist

PR Type

  • Bugfix
  • Feature
  • Code style update
  • Refactoring
  • Build related changes
  • CI related changes
  • Documentation content changes

Does this PR introduce a breaking change?

  • Yes
  • No

The Warden environment already brings up nginx, Varnish and Traefik, but
nothing installed into it is reachable from the runner itself. Integration
tests never noticed, because they drive Magento's own bootstrap rather than
making HTTP requests.

Three things have to be true before a browser can reach the storefront, and
each is added here behind a default that preserves current behaviour:

- setup:install needs --base-url, or Magento redirects to its default base
  URL regardless of where the environment is served
- the Traefik hostname needs resolving on the runner, which has no dnsmasq
- static content needs deploying, or the storefront returns 200 and renders
  nothing

Also exposes the served URL as an output, so a caller does not have to
reconstruct it, and makes the admin frontName and the Warden environment
naming configurable rather than hardcoded -- a predictable admin URL is a
prerequisite for testing the admin at all.

Existing callers pass none of the new inputs and are unaffected.
Adds the canary for the browser-serving inputs on
warden/setup-environment: it installs Mage-OS, serves it, deploys Luma's
static content and drives a browser at the result, following the
_internal-* convention the other actions in this repository use.

The suite lives with the demo package, as the dummy e2e test already
did. It stays deliberately small: its job is to prove a storefront is
being served, not to cover a store's behaviour.

The assertions are chosen to fail against things that should not pass. A
200 is not evidence of a storefront -- the admin login page, a CMS
no-route page and a Magento error page all answer 200 with a header, a
footer and a title -- so the suite checks the resolved route in the body
class, Luma's minicart block and whether a stylesheet actually parsed
into rules. Without the last one it would pass against a store whose
pub/static was never deployed.

The suite now refuses to start without PLAYWRIGHT_BASE_URL rather than
falling back to a public demo store, since a suite that reports green
about a store nobody asked about is worse than one that will not run.
The first run failed in the post phase of the Warden step: the local
action could no longer be found. warden/setup-environment checks the
Warden CLI out into warden/ at the workspace root, which is exactly
where this repository keeps that action, so the checkout landed on top
of it.

Checking the repository out into actions/ leaves the workspace root free
for the Warden CLI, which the steps reach as
$(dirname $(pwd))/warden/bin/warden.
The README told the reader to point PLAYWRIGHT_BASE_URL at the admin URL
and watch the test go red. It does not go red: page.goto("/") resolves
against the origin and drops the path, so the run hits the homepage again
and passes -- the exact vacuous green this suite exists to refuse.

Replacing it with a throwaway spec that navigates to the admin path
directly, and the output it produces against a real store.
trace: on-first-retry only produces a trace once a test has failed and
been retried. A gate that fails is read by someone deciding whether to
ship, and "re-run it and watch" is not an answer at that point, so the
first failure now carries its own trace.

Report artifacts are kept for 30 days rather than 7, long enough to
outlive a release weekend.
The homepage check proves a store is served. It does not prove a store
can take money. This walks the deepest path a storefront check can:
category listing, product page, cart, shipping address, rate selection,
payment, order. It touches the price index, the quote, the shipping and
payment method pools, order placement, and the private-content sections
Varnish has to punch holes for.

That path needs a salable product, which a bare setup:install does not
have. fixtures/mageos-e2e.xml seeds the least data it takes: 20 simple
products with deterministic SKUs, one configurable, two categories in
the main navigation. 12s wall clock on Mage-OS 4.0.0 under Warden,
including the reindex the command runs itself, and idempotent.

Core ships no profile for functional seeding -- the smallest of the five
performance profiles generates 800 products, 100 images and 80 orders,
because those profiles exist to benchmark a store under catalog weight.
Sample data was the other option and is worse for a gate: its content
changes between releases, so an assertion on a product name is an
assertion about sample data rather than about Mage-OS.

Verified both directions against a Warden store: green with three
persisted guest orders carrying Flat Rate and a $6.00 grand total, and
red at the rate-selection step with carriers/flatrate/active set to 0.
The checkout spec needs something to buy, and the install the workflow
stands up has nothing. The profile is copied into the project directory
because the command runs inside the container, which sees only that.

It reindexes itself, so no indexer:reindex follows it.
The runner cannot write into the project directories the container has
written to, because those are owned by the container's user:

  cp: cannot create regular file 'var/mageos-e2e.xml': Permission denied

Piping the profile through exec avoids host permissions entirely and
leaves nothing behind in the project. Keeping the profile outside
setup/performance-toolkit means its di.xml is not loaded, which only
mocks the mail transport -- noted in the profile, and nothing here
generates customers or orders.
Which product a category lists first is not ours to decide, and on a
clean install it is the configurable. That will not go into the cart
until every attribute has a value, so the first CI run failed waiting
for a success message that was never coming -- it had a validation
error instead. It passed locally only because a leftover simple product
happened to sort ahead of it.

Selecting the first available value for whatever the page asks for
makes the step indifferent to catalog order, and lets the spec run
against a simple product, a configurable, or a merchant's own catalog.

Verified against a configurable locally: one dropdown, three values,
first selection adds to the cart.
_internal-e2e.yaml proves the actions work, but it cannot be reused: it
references them through "./", and a relative path in a reusable workflow
resolves against the caller's checkout, which is not this repository
when the caller is the release pipeline. So own-repo actions are
referenced by full ref here, as integration.yaml and
full-integration-tests.yaml already do.

test_command is an input rather than a hardcoded "npx playwright test".
The workflow's job is to stand a store up and hand over a URL; it should
not know what runner points at it, and a caller wanting a grep filter, a
shard or a different reporter should not need a new input for each. It
arrives through the environment rather than interpolated into the
script, so quoting is bash's problem and not the expression engine's.

Evidence policy stays in the suite's config, so the default command
carries no flags: a CLI flag overrides the config, and --trace on
records a trace for every passing test.

fixture_profile seeds a catalog before the run, because a bare
setup:install has nothing to buy. Empty skips it, which is right for a
suite that asserts only on what a bare install provides.

Verified green end to end on a throwaway branch, with the Warden action
pointed at the stacked serving branch: store served, catalog seeded in
9s, 2 specs passed. At @main it needs the serving inputs, so this cannot
go green until mage-os#395 is on main.
@marcelmtz marcelmtz closed this Nov 10, 2026
@marcelmtz
marcelmtz deleted the feat/reusable-e2e-workflow branch November 10, 2026 04:39
@marcelmtz

Copy link
Copy Markdown
Contributor Author

Folded into #396 instead.

Stacking this separately bought no review isolation: because the branch targeted main, #398's diff showed all 15 files and 10 commits from #395 and #396 anyway, while adding a third PR to track. And #396 was already blocked on #395, so there was nothing to keep green by splitting.

e2e.yaml and e2e-README.md are now on #396, unchanged.

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.

1 participant