Cut the e2e pipeline cost: environments by trigger, weekly bootstrap sweep, less setup per leg - #129
Merged
Merged
Conversation
The docker env runs two Kibana replicas behind kbn-proxy. The upstream block had no connect timeout and no failover, so while one replica restarted - which Kibana-config.cy.ts makes it do, and which `restart: always` on kbn-ror exists for - half of every request went to a container that was not listening. A stopped container drops the packets rather than refusing them, so those requests hung instead of failing. nginx waited its default 60s connect timeout, which is longer than every Cypress timeout, so the browser gave up first, the failure never counted against the peer, and the peer stayed in the rotation. Cypress retries did not help: all three attempts hit the same alternation. Measured on a two-replica reproduction with one replica stopped. before: 000 200 000 200 000 200 000 200 000 200 (50% lost, each hanging to the client's limit) after: 200/2.00s 200/0.0005s 200/0.0005s ... (one failover, then the peer is out) Sustained for 30s with the peer down: 110 requests, 0 failures, 3 over 0.5s (the 10s re-probe), worst 2.0s - inside every Cypress timeout. The replica returns to the rotation on its own when it answers again. This fits the evidence: the failures only ever appear on the `docker` legs, never on `eck`, and eck runs a single Kibana (kind-cluster/ror/base/kbn.yml, count: 1) with no proxy in front. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The matrix is 34 released versions x 2 envs, so it is 68 jobs. docs/dev/branching.md case 4 sends every workflow change to master. Three of them moved there today, and each queued its own 68 bootstrap jobs behind itself: approved, no failures, unmergeable for hours, while sweeping plugins that were released weeks ago. Every version in that matrix tests a plugin that is already out, so nothing a pull request changes can change the result. The nightly and a push to master still run it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
coutoPL's point on the review: a stale run can be cancelled instead. That is true and worth having, so it is here. It covers a different case, though. Cancelling helps when the same pull request is pushed twice. It does nothing for three open pull requests, which still queue their own bootstrap sweep each. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deleting /usr/share/dotnet and /usr/local/lib/android costs 79s on average and 184s at worst, measured over 10 jobs of nightly 34547123638. Every leg pays it, including the 68 bootstrap legs that boot a stack and stop. That is 32% of a bootstrap leg spent making room nothing asks for. The step now takes the space the job needs and returns early when df already reports it. Bootstrap passes 8G; a suite leg keeps the 25G default, so it still cleans. The deletes that do run are independent trees, so they run in parallel instead of in sequence. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two fixed costs every leg pays, measured over 10 legs of nightly 34547123638: 30s for `yarn --frozen-lockfile install` and 5-22s for the Cypress binary download. Both caches key on e2e-tests/yarn.lock, which is the file that decides their content, so a stale entry cannot be served. The Cypress version is pinned in that lockfile too, so one key covers both. paths-ignore keeps a prose change off the matrix. PR #125 was five markdown files and cost 1391 runner-minutes. The list holds only files nothing reads at runtime — e2e-tests/, .github/ and environments/ all still trigger the full run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
prod-e2e and dev-e2e ran 4 ELK versions against 3 environments. Over 75 nightly runs the two ECK operator versions disagreed on 8 of 300 paired observations, every one a singleton that did not repeat the next night. The operator version is orthogonal to the ELK version, so the third column bought a third more legs and no information. docker is not like that. It disagreed with ECK 23 times, 19 of them in the same direction, because it runs two Kibana replicas behind a proxy where ECK runs one. Every version keeps its docker leg. 12 legs become 8. Every ELK version still meets both operators, because the nightly asks for the flipped pairing, so no cell goes more than 24 hours unseen. Pull requests always get the same pairing, so their check names do not move between runs. ci/e2e-matrix.sh is now the one place the version list lives. It was in the workflow twice before, once for the prod matrix and once for ELK_VERSIONS, under a comment asking the reader to keep them in sync. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`--run bootstrap` runs no tests. runner.sh skips run-tests.sh entirely and the only assertion is that the stack came up. What it can catch is a newly published <elk>-ror-latest image that does not start on an old ELK version. Those tags move when ROR releases, about twice a month, and nothing in this repository can change them. So the sweep now runs on a repository_dispatch from the release pipelines, on a weekly cron as the safety net, and on a push to master, where the version list itself can change. The yield agrees with the cadence. Over 75 nightly runs, 3317 executed legs produced 7 failures, 6 of them the 7.10.0 that is already out of the list. It also no longer gates on prod-e2e-tests. That coupling meant one flaky suite leg skipped all 68 legs, and the sweep ran on 14 of the last 30 nightlies. The eck column goes: over those 75 nightlies docker and eck failed on exactly the same versions, and this job starts a stack rather than exercising the proxy, which is where the two differ. Per-trigger legs, before -> after: PR to master 80 -> 9, nightly 80 -> 9, push master 80 -> 43, plus 35 weekly and 35 per release. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Kibana-config.cy.ts is describe.skip on the whole file, and its own header explains why it cannot be otherwise: it rewrites kibana.yml and restarts Kibana, which the docker env cannot do because two replicas disagree about the active config, and the eck envs cannot do because kibana.yml is mounted read-only from a ConfigMap. ror-config.cy.ts is describe.skip behind a TODO waiting on a feature. Neither has executed an assertion in this repository. A skipped spec file still costs Cypress 3-6 seconds to load, on every leg, to run nothing. Kibana-config belongs on the kbn repo's IT stack, which is a single Kibana node with a writable kibana.yml — the one place it could actually run. The copy there is untouched by this commit. The six kibana.yml fixtures go with them. Nothing else in the repository reads any of them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Settings.cy.ts saved the config `readonlyrest:` and its only live assertion was clickSaveButton's `expect(statusCode).to.eq(200)`. So it asserted that saving malformed settings SUCCEEDS. It would have stayed green if ROR silently swallowed a broken config, and it would have gone red the day ROR started rejecting one. The toast assertion that carried the real meaning has been commented out since the step was written. Rejecting malformed config is covered by JsonSchemaValidator and rorApi tests in the ROR KBN repo. Test-settings.cy.ts keeps its live assertions and loses 28 lines of commented-out steps that were parked behind an ES plugin fix. The file now says what it tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
runTests() is invoked three times, and the three passes differ only in the callbacks they hand it: nothing, a history-back, and a second tab. Two of its five tests read neither callback, so the second and third passes executed byte-identical copies of them. They move to the top level of the describe. 17 test bodies become 13, and each one carries a full Login.initialization, which is about 13 seconds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
openKibanaNavigation sends ESC as a keydown and a keyup with cy.wait(200) between them. They are one key press; nothing has to happen in the middle. openPage routes every navigation in the suite through this method, so the 200ms was paid about fifty times a run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This comment was marked as spam.
This comment was marked as spam.
retries.runMode is 2, so a test can fail twice, pass on the third attempt, and leave a green leg with nothing in the output to say so. Job 103514840079 hid one: Sanity-check-ro-kibana-access ran 134s against its twin's 62s for identical work, and the difference was an invisible retry. after:spec already receives the per-attempt data. This writes one row per retried test to the run summary, so a flaky test can be named instead of inferred from a duration gap. It reports and never acts: GITHUB_STEP_SUMMARY is unset outside Actions, and a failure to write the report cannot fail a suite that passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The copy-link test is the most expensive in the suite: it installs the ecommerce sample dataset, and runTests ran it three times. Unlike the two tests moved in the previous commit, it does read both callbacks, so the three passes were not identical — they asked whether the copied link still carries the tenancy after a history-back and after a cross-tab switch. That property is the URL tenancy surviving the variant, and the first test in runTests already asserts it under all three passes, through checkTenancyNameInBadge and verifyKibanaNavigationLinkItemHref. What is left here is the share panel, which the variants do not touch. It keeps its own afterEach, because outside runTests it no longer inherits the sample-data cleanup. Tenancy now runs 9 test bodies where it ran 17. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Registering a second `on('after:spec')` does not add a listener, it replaces the first one.
EventRegistrar holds one function per event name:
private _registeredEvents: Record<string, Function> = {}
registerEvent (event, callback) { this._registeredEvents[event] = callback }
and executeNodeEvent looks the handler up by name and calls exactly that one
(packages/data-context/src/data/EventRegistrar.ts at v15.20.1). Only `task` is merged across
registrations, in run_plugins.ts.
So the retry report added in the previous commit never ran: the video-discard handler below it
registered second and won. Both jobs now live in the one handler.
Found while mirroring these changes into the ROR KBN repo's copy of the suite.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…f/e2e-cost-reduction
This comment was marked as outdated.
This comment was marked as outdated.
A bare `wait` returns 0 whatever the background jobs did, so parallelising the deletions removed the error propagation the sequential version had from `set -e`: $ bash -c 'set -e; (exit 7) & (exit 0) & wait; echo continued' continued A deletion that failed would have left the disk full and let the suite run into it later, with an error that points anywhere but at this step. Each job's pid is now kept and waited on by name. Raised by CodeRabbit on this PR. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This comment was marked as outdated.
This comment was marked as outdated.
Dzuming
reviewed
Sep 13, 2026
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
Dzuming
requested changes
Sep 14, 2026
| @@ -1,221 +0,0 @@ | |||
| import * as semver from 'semver'; | |||
Collaborator
There was a problem hiding this comment.
What do we do with this reload Kibana config logic? Currently, it's blocked because:
- It's not working in the case of multiple Kibana instances
- It's not working in the case of ECK
So, it should be diagnosed and fixed on the ReadonlyREST Kbn plugin side. We need a follow-up task to decide if we will fix it or drop it because of the cost of changes in this logic, which is only for testing purposes.
Contributor
Author
There was a problem hiding this comment.
The spec is deleted here because it cannot run in either environment of this repo: two replicas behind the proxy on docker, read-only kibana.yml on ECK. The decision to fix the reload logic on the plugin side or drop it belongs in a follow-up ticket. Can you open it, or shall I?
Collaborator
There was a problem hiding this comment.
Please, create a Jira task for it
coutoPL
requested changes
Sep 14, 2026
dev-e2e-tests reads its matrix from the setup job. Nothing reads this output. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The comments this branch added told the story of the lines they replaced. That story is in the commit messages. Each comment now says only what a reader cannot get from the code, or is gone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A named method replaces the comment that explained the two events. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
run-e2e-tests uses .github/cypress-cache, so the cache steps sit next to the other composite actions and do not crowd the retry wrapper. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
bootstrap-tests.yml runs the sweep on docker for all 34 ELK versions: against the released plugins on the Sunday cron, against dev images built from develop on the Wednesday cron, and both on a manual dispatch. The version list lives in one setup job and feeds both sweeps and the dev image pre-build. all-e2e-tests.yml keeps the e2e legs only. The repository_dispatch trigger goes: the plugin release pipelines do not send it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
daily cron, manual dispatch prod + dev docker, eck-2.16.1, eck-3.5.0 push to master / develop prod / dev docker, eck-3.5.0 pull request prod / dev docker, eck-3.5.0 draft pull request prod / dev docker The daily cron now runs the dev legs too, against plugins built from develop. ready_for_review is a trigger, so a draft that becomes a pull request gets its ECK leg. ci/e2e-matrix.sh builds a plain cross product of the version list and the environments the setup job names. The --flip pairing goes with it: the daily cron covers every (version, environment) cell. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
No step in the workflow writes through GITHUB_TOKEN; the pre-build dispatch uses ROR_GH_TOKEN. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
coutoPL
requested changes
Sep 15, 2026
… workflow The plan job now decides both the images (prod, dev) and the environments for every trigger, and the test jobs only ask it. The comment table at the top described the same thing in prose and would rot. ELK_VERSIONS, ECK_3X and ECK_2X are workflow env vars, so a bump touches one line. ci/e2e-matrix.sh is gone: with the list in the workflow, the matrix is one jq call. bootstrap-tests.yml gets the same plan job, and the cron-to-images choice moves out of the job conditions into it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
coutoPL
requested changes
Sep 15, 2026
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Makes the e2e pipeline cheaper to run. No assertion is weakened, and no ELK version stops being tested. The e2e legs run on the environments each trigger needs, the bootstrap sweep moves to its own scheduled workflow, and every leg carries less fixed setup.
What each trigger runs
The scheduling model @coutoPL asked for in review.
all-e2e-tests.ymldevelop)masterdevelopmasterdevelopready_for_reviewis a trigger, so a draft that becomes a pull request gets its ECK leg. Bootstrap tests do not run on pushes or pull requests.bootstrap-tests.yml(new)develop)Before this PR: 80 legs on every PR to
master, every push tomasterand every night; 13 on a PR todevelop.Coverage: what still runs
prod-e2e-tests, so a flaky suite leg cannot skip it (it ran on 14 of the last 30 nightlies).describe.skipspec deletedKibana-confighas never executed an assertion in this repository and cannot run in either environment here; the ROR KBN copy is untouched.runTests()runTests()already covers the variants it dropped.Settings.cy.tsexpect(statusCode).to.eq(200)on saving a broken config.cy.wait(200)between ESC keydown and keyupKibanaNavigation.closeOpenOverlays().paths-ignoreNet effect on the suite: same specs, same assertions, 13 test bodies instead of 17 in
Tenancy.cy.ts.The changes
Pick the e2e environments by trigger— aplanjob decides the images (prod, dev) and the environments for the trigger, and the test jobs only ask it.ELK_VERSIONS,ECK_3XandECK_2Xare workflow env vars at the top, one line each to bump. The earlier ECK "pairing" and the--fliptrick are gone: the daily cron covers every (version, environment) cell, so a PR does not have to.Move the bootstrap sweep to its own workflow—--run bootstrapruns no tests; the only assertion is that the stack came up. Its input is the plugin images, which a PR to this repo cannot change. Yield over 75 nightlies: 7 failures in 3,317 executed legs, 6 of them the7.10.0already removed from the list. The dev sweep dispatches the plugin pre-build workflows for all 34 versions fromdevelop; the wait timeouts are 4 hours per plugin, which is not measured, because this run has never existed.Move the yarn and Cypress caches into their own action—.github/cypress-cache, used byrun-e2e-tests. Measured over 10 legs of nightly 34547123638: 30s for the install and 5-22s for the Cypress binary on every leg.Skip the disk cleanup when the runner already has room— deleting the preinstalled toolchains costs about 80s. A bootstrap leg asks for 8 GB, a suite leg for 25. Eachrm -rfruns in the background and is waited on by pid, so a failed deletion fails the step.Delete the spec that runs in no environment—Kibana-config.cy.tsisdescribe.skipon the whole file: two replicas disagree about the active config on docker, andkibana.ymlis read-only on ECK. Four unreferencedkibana.ymlfixtures go with it.ror-config.cy.tsstays: #133 kept it with the reason on the skip (the suite lifts it when #116 merges), together with the two fixtures it reads.Drop an assertion that could only fail if ROR got it right—Settings.cy.tssavedreadonlyrest:and asserted a 200.Run Tenancy's two callback-free tests once, not three times— and the share-link test once, because the tenancy-survives-the-variant property is already asserted by the first test inrunTests().Name the retried tests on the run summary— a retried test that passed leaves no trace in the run output. Theafter:spechandler lists them on the GitHub run summary. Cypress keeps one handler per event name, so the report shares the handler with the video discard.Numbers
Measured before the review rework and still true: the per-spec timings, the bootstrap yield, the cache savings, and the 80 legs per PR baseline. Not re-measured: the "about 60%" saving of the first version. The legs per trigger above are simulated against the
if:expressions; the daily cron costs more than before (24 suite legs plus two plugin builds, against 12), pushes and pull requests cost less.Checked before pushing
yarn tsCheckclean andyarn lintat 0 errors.actionlintclean on both workflows.planstep run locally for the seven trigger shapes (cron, dispatch, push master/develop, PR to master/develop, draft, fork) gives the expected images and matrix.if:expression simulated for the nine trigger shapes, withneedspropagation: the leg tables above.Deliberately left alone
Test-settings.cy.tsandSettings.cy.tstest 1 (~53s). Their page objects assert real responses, so removing them drops the "the button is wired to the endpoint" coverage.RorMenu.ts:49's 300ms settle.cy.get().should()retries the assertion but never re-runs the click.Related pull requests
automatic-tests/.ro_strictreal coverage:roStrictSettings.yamlis a whitespace-only copy ofroSettings.yaml.🤖 Generated with Claude Code
Summary by CodeRabbit
CI Improvements
Test Maintenance
Documentation