From d63e16d25bdd0a28a3d03d057d2a6644f65a8241 Mon Sep 17 00:00:00 2001 From: Simone Scarduzio Date: Sat, 12 Sep 2026 13:45:32 +0000 Subject: [PATCH 01/26] Take a restarting Kibana replica out of the proxy rotation 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 --- .../elk-ror/conf/kbn/kbn-proxy-nginx.conf | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/environments/elk-ror/conf/kbn/kbn-proxy-nginx.conf b/environments/elk-ror/conf/kbn/kbn-proxy-nginx.conf index ad5a9b3e..183fb4ad 100644 --- a/environments/elk-ror/conf/kbn/kbn-proxy-nginx.conf +++ b/environments/elk-ror/conf/kbn/kbn-proxy-nginx.conf @@ -1,8 +1,12 @@ events { } http { + # `kbn-ror` resolves to both Kibana replicas, so this one line becomes two round-robin peers. + # max_fails/fail_timeout are the defaults, written out because the whole point of this block is + # that a peer which stops answering leaves the rotation: one failure ejects it for 10 seconds, + # then nginx probes it again and takes it back when it answers. upstream kbn-ror { - server kbn-ror:5601; + server kbn-ror:5601 max_fails=1 fail_timeout=10s; } server { @@ -22,6 +26,21 @@ http { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_ssl_verify off; # Disable SSL verification for internal requests (only if Kibana uses self-signed certs) + + # A Kibana replica that is restarting drops the packets instead of refusing them, so a + # connection to it hangs rather than failing. Without a connect timeout nginx waits the + # default 60 seconds, which is longer than every Cypress timeout, so the browser gives up + # first and the failure never reaches nginx to be counted against the peer. Two seconds is + # far above a healthy connect on the container network and far below the 10s Cypress spends + # on cy.wait(). + proxy_connect_timeout 2s; + + # Send the request to the other replica when this one cannot take it. http_503 is here + # because a Kibana that is listening but still starting answers 503, which is not an error + # to nginx by default. Two tries, because there are two replicas. + proxy_next_upstream error timeout http_502 http_503 http_504; + proxy_next_upstream_tries 2; + proxy_next_upstream_timeout 5s; } } } \ No newline at end of file From 5a9f0ee3ca9f63957ed58ef60d9cec20cd3e3891 Mon Sep 17 00:00:00 2001 From: Simone Scarduzio Date: Thu, 10 Sep 2026 15:07:59 +0000 Subject: [PATCH 02/26] Take the bootstrap sweep off pull requests 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 --- .github/workflows/all-e2e-tests.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/all-e2e-tests.yml b/.github/workflows/all-e2e-tests.yml index a00c6155..237b1e91 100644 --- a/.github/workflows/all-e2e-tests.yml +++ b/.github/workflows/all-e2e-tests.yml @@ -84,11 +84,13 @@ jobs: # Bootstrap uses released images (no --mode), as it did before. master-bootstrap-tests: name: "๐Ÿš€ Bootstrap Tests" + # Not on a pull request. The matrix is 34 released versions x 2 envs, and every one of them + # tests plugins that are already out, so nothing a pull request changes can change the result. + # The nightly and a push to master still run it, because the released set moves there. if: > github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || - (github.event_name == 'push' && github.ref == 'refs/heads/master') || - (github.event_name == 'pull_request' && github.base_ref == 'master' && github.event.pull_request.head.repo.fork == false) + (github.event_name == 'push' && github.ref == 'refs/heads/master') needs: prod-e2e-tests runs-on: ubuntu-latest strategy: From e3e779ff115341dcd59dd850ddbaf5afc158a646 Mon Sep 17 00:00:00 2001 From: Simone Scarduzio Date: Sat, 12 Sep 2026 06:53:44 +0000 Subject: [PATCH 03/26] Also cancel a superseded pull-request run 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 --- .github/workflows/all-e2e-tests.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/all-e2e-tests.yml b/.github/workflows/all-e2e-tests.yml index 237b1e91..928ee333 100644 --- a/.github/workflows/all-e2e-tests.yml +++ b/.github/workflows/all-e2e-tests.yml @@ -11,6 +11,14 @@ on: pull_request: types: [opened, synchronize, reopened] +# A second push to the same pull request makes the first run's result worthless, and this suite +# holds runners for hours. Cancel it. Only for pull requests: on master, the nightly and a manual +# dispatch the group is the run id, which is unique, so those runs neither cancel nor queue behind +# each other. Grouping them by ref instead would park a nightly behind a master push for hours. +concurrency: + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + env: APPLY_RESOURCE_LIMITS: "auto" From 393b845e31fb07597f072e76ce5e8571d9d47bc4 Mon Sep 17 00:00:00 2001 From: Simone Scarduzio Date: Sat, 12 Sep 2026 14:28:27 +0000 Subject: [PATCH 04/26] Skip the disk cleanup when the runner already has room 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 --- .github/cleanup-disk-space/action.yml | 38 +++++++++++++++++++++++---- .github/workflows/all-e2e-tests.yml | 4 +++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/.github/cleanup-disk-space/action.yml b/.github/cleanup-disk-space/action.yml index 14c0886d..58119aec 100644 --- a/.github/cleanup-disk-space/action.yml +++ b/.github/cleanup-disk-space/action.yml @@ -1,16 +1,44 @@ name: 'Cleanup disk space' -description: 'Cleanup runner disk space' +description: 'Frees runner disk space, but only when the job needs more than the runner already has free.' + +inputs: + required_gb: + description: > + How much free space on / the job needs. The cleanup is skipped when the runner already has + this much, because deleting the preinstalled toolchains costs ~80 seconds and an e2e leg + that never fills the disk pays it for nothing. A bootstrap leg only boots a stack, so it + passes a small number here; a full suite leg keeps the default. + required: false + default: '25' runs: using: 'composite' steps: - name: Cleanup disk space shell: bash + env: + REQUIRED_GB: ${{ inputs.required_gb }} run: | - sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc - sudo rm -rf /usr/local/share/boost /usr/local/share/boost-build + set -euo pipefail + + FREE_GB=$(df -BG --output=avail / | tail -1 | tr -dc '0-9') + echo "Free space on /: ${FREE_GB}G. This job asks for ${REQUIRED_GB}G." + + if [ "$FREE_GB" -ge "$REQUIRED_GB" ]; then + echo "Enough free space already โ€” skipping the cleanup." + exit 0 + fi + + # Independent trees on the same disk. Deleting them in parallel finishes in about the time + # the largest one takes, instead of the sum. + sudo rm -rf /usr/share/dotnet & + sudo rm -rf /usr/local/lib/android & + sudo rm -rf /opt/ghc & + sudo rm -rf /usr/local/share/boost /usr/local/share/boost-build & + wait + sudo apt-get clean sudo rm -rf /var/lib/apt/lists/* - + echo "Available disk space after cleanup:" - df -h + df -h / diff --git a/.github/workflows/all-e2e-tests.yml b/.github/workflows/all-e2e-tests.yml index 928ee333..1f059e6e 100644 --- a/.github/workflows/all-e2e-tests.yml +++ b/.github/workflows/all-e2e-tests.yml @@ -116,6 +116,10 @@ jobs: run: source .github/scripts/docker-hub-mirror.sh - name: Clean up disk space uses: ./.github/cleanup-disk-space + with: + # A bootstrap leg starts the stack and stops. It pulls two images and never writes a + # video, a screenshot or a Cypress cache, so it does not need the toolchains deleted. + required_gb: '8' - name: Start Docker memory monitor uses: ./.github/docker-memory-monitor with: From 61ac6be33f1c13f86294a9d6646f9e1a868fe81e Mon Sep 17 00:00:00 2001 From: Simone Scarduzio Date: Sat, 12 Sep 2026 14:29:00 +0000 Subject: [PATCH 05/26] Cache the yarn and Cypress downloads, and skip the suite for prose MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .github/run-e2e-tests/action.yml | 18 ++++++++++++++++++ .github/workflows/all-e2e-tests.yml | 5 +++++ 2 files changed, 23 insertions(+) diff --git a/.github/run-e2e-tests/action.yml b/.github/run-e2e-tests/action.yml index 0ab8c404..9617a590 100644 --- a/.github/run-e2e-tests/action.yml +++ b/.github/run-e2e-tests/action.yml @@ -35,6 +35,24 @@ inputs: runs: using: 'composite' steps: + # run-tests.sh runs `yarn --frozen-lockfile install` and Cypress downloads its binary on first + # use. Measured over 10 legs of nightly 34547123638 that is 30s for the install and 5-22s for + # the binary, on every leg, for dependencies that change when yarn.lock does. Both caches are + # keyed on the file that decides their content, so a stale entry cannot be served. + - name: Cache the yarn download cache and node_modules + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 + with: + path: | + ~/.cache/yarn + e2e-tests/node_modules + key: e2e-yarn-${{ runner.os }}-${{ hashFiles('e2e-tests/yarn.lock') }} + - name: Cache the Cypress binary + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 + with: + # Keyed on yarn.lock as well: the binary version is pinned by the `cypress` entry in it, + # so one key covers both and cannot disagree with the installed package. + path: ~/.cache/Cypress + key: e2e-cypress-${{ runner.os }}-${{ hashFiles('e2e-tests/yarn.lock') }} - name: Run E2E tests uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 with: diff --git a/.github/workflows/all-e2e-tests.yml b/.github/workflows/all-e2e-tests.yml index 1f059e6e..f214b047 100644 --- a/.github/workflows/all-e2e-tests.yml +++ b/.github/workflows/all-e2e-tests.yml @@ -6,10 +6,15 @@ on: - cron: '0 0 * * *' # Only the two long-lived branches. Feature branches are covered by their pull request, and # listing them here would run everything twice for every push to an open PR. + # paths-ignore on both: a change to prose cannot change what the suite does, and a docs-only + # pull request costs the same 12 legs as a code one without it. Keep the list to files that are + # never read at runtime โ€” anything under e2e-tests/, .github/ or environments/ must still run. push: branches: [master, develop] + paths-ignore: ['docs/**', '**/*.md', 'LICENSE'] pull_request: types: [opened, synchronize, reopened] + paths-ignore: ['docs/**', '**/*.md', 'LICENSE'] # A second push to the same pull request makes the first run's result worthless, and this suite # holds runners for hours. Cancel it. Only for pull requests: on master, the nightly and a manual From 22a80a85886b9c7905c0df4604904f19556c72a4 Mon Sep 17 00:00:00 2001 From: Simone Scarduzio Date: Sat, 12 Sep 2026 14:30:20 +0000 Subject: [PATCH 06/26] Pair each ELK version with one ECK operator instead of both 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 --- .github/workflows/all-e2e-tests.yml | 52 ++++++++++++++++++++++------- ci/e2e-matrix.sh | 48 ++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 12 deletions(-) create mode 100755 ci/e2e-matrix.sh diff --git a/.github/workflows/all-e2e-tests.yml b/.github/workflows/all-e2e-tests.yml index f214b047..39ce152a 100644 --- a/.github/workflows/all-e2e-tests.yml +++ b/.github/workflows/all-e2e-tests.yml @@ -28,15 +28,44 @@ env: APPLY_RESOURCE_LIMITS: "auto" jobs: + # ========================================== + # MATRIX + # ========================================== + # One source for the ELK version list and the (version, environment) pairs. ci/e2e-matrix.sh + # explains why the pairs are not a cross product; the short form is that the two ECK operator + # versions agree with each other and the extra column buys no information. The nightly asks for + # the flipped pairing, so the pairs a pull request does not run are covered within 24 hours. + setup: + name: "๐Ÿงฎ Matrix" + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + versions: ${{ steps.matrix.outputs.versions }} + e2e: ${{ steps.matrix.outputs.e2e }} + steps: + - name: Checkout code + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + with: + persist-credentials: false + - name: Build the matrix + id: matrix + run: | + set -euo pipefail + FLIP="" + [ "${{ github.event_name }}" = "schedule" ] && FLIP="--flip" + { + echo "versions=$(./ci/e2e-matrix.sh versions)" + echo "e2e=$(./ci/e2e-matrix.sh matrix $FLIP)" + } >> "$GITHUB_OUTPUT" + # ========================================== # E2E TESTS - RELEASED (PROD) PLUGIN IMAGES # ========================================== # Runs against the RELEASED plugin images (`--mode prod`, ror-latest). The signal is "the shipped # plugins still pass the suite": master itself, PRs targeting it, and the nightly schedule. - # - # Keep the matrix in sync with ELK_VERSIONS in prepare-dev-images. prod-e2e-tests: name: "๐Ÿ”ฌ E2E Tests (released plugins)" + needs: setup if: > github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || @@ -45,9 +74,7 @@ jobs: runs-on: ubuntu-latest strategy: fail-fast: false - matrix: - version: ["9.5.3", "9.4.6", "8.19.21", "7.17.29"] - env: [docker, eck-2.16.1, eck-3.5.0] + matrix: ${{ fromJSON(needs.setup.outputs.e2e) }} steps: - name: Checkout code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 @@ -158,6 +185,7 @@ jobs: # Fork PRs are excluded: the dispatch needs secrets GitHub does not expose to them. prepare-dev-images: name: "๐Ÿ—๏ธ Prepare dev images" + needs: setup if: > (github.event_name == 'push' && github.ref == 'refs/heads/develop') || (github.event_name == 'pull_request' && github.base_ref != 'master' && github.event.pull_request.head.repo.fork == false) @@ -174,8 +202,8 @@ jobs: - name: Dispatch and await ROR plugin pre-builds id: prepare env: - # Mirrors the prod-e2e-tests matrix โ€” keep both in sync. - ELK_VERSIONS: "9.5.3 9.4.6 8.19.21 7.17.29" + # From ci/e2e-matrix.sh through the setup job, the same list prod-e2e-tests runs on. + ELK_VERSIONS_JSON: ${{ needs.setup.outputs.versions }} # On a PR, head_ref is the source branch โ€” the plugins must be built from the PR head, not # from the merge ref github.ref points at. On a push, head_ref is empty and ref_name is # the branch that was pushed (develop). @@ -188,9 +216,11 @@ jobs: # Unique per attempt: a re-run must not silently reuse the previous attempt's images. RUN_TAG="run-${{ github.run_id }}-${{ github.run_attempt }}" + # The pre-build helpers take a space-separated list; the matrix travels as JSON. + ELK_VERSIONS=$(jq -r 'join(" ")' <<< "$ELK_VERSIONS_JSON") { echo "run_tag=$RUN_TAG" - echo "versions=$(printf '%s\n' $ELK_VERSIONS | jq -Rcn '[inputs]')" + echo "versions=$ELK_VERSIONS_JSON" } >> "$GITHUB_OUTPUT" dispatch_prebuild_images "$ELK_VERSIONS" "$TARGET_BRANCH" "$RUN_TAG" @@ -204,13 +234,11 @@ jobs: # that job is skipped. dev-e2e-tests: name: "๐Ÿงช E2E Tests (pre-build plugins)" - needs: prepare-dev-images + needs: [setup, prepare-dev-images] runs-on: ubuntu-latest strategy: fail-fast: false - matrix: - version: ${{ fromJSON(needs.prepare-dev-images.outputs.versions) }} - env: [docker, eck-2.16.1, eck-3.5.0] + matrix: ${{ fromJSON(needs.setup.outputs.e2e) }} env: ROR_IMAGE_TAG: ${{ needs.prepare-dev-images.outputs.run_tag }} steps: diff --git a/ci/e2e-matrix.sh b/ci/e2e-matrix.sh new file mode 100755 index 00000000..22e2629f --- /dev/null +++ b/ci/e2e-matrix.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# The one place that decides which (ELK version, environment) pairs the e2e suite runs on. +# +# Before this script the version list lived twice in all-e2e-tests.yml โ€” once in the prod matrix +# and once in ELK_VERSIONS for the dev image pre-build โ€” under a comment asking the reader to keep +# them in sync. Now both read it from here. +# +# Why the pairs are not a cross product. 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 testing all four ELK versions +# against both operators buys a third more legs and no information. docker is different: 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. +# +# So every ELK version gets docker plus ONE operator, and the operators alternate down the list. +# `--flip` swaps which operator each version takes, so the nightly covers the pairs the pull +# requests do not. Every cell is therefore visited within 24 hours. +# +# Usage: e2e-matrix.sh versions|matrix [--flip] +set -euo pipefail + +# Keep newest first: a failure on the newest line is the one worth seeing first in the UI. +ELK_VERSIONS=("9.5.3" "9.4.6" "8.19.21" "7.17.29") +ECK_ENVS=("eck-3.5.0" "eck-2.16.1") + +MODE=${1:?Usage: e2e-matrix.sh versions|matrix [--flip]} +FLIP=0 +[ "${2:-}" = "--flip" ] && FLIP=1 + +case $MODE in + versions) + printf '%s\n' "${ELK_VERSIONS[@]}" | jq -Rcn '[inputs]' + ;; + matrix) + { + for v in "${ELK_VERSIONS[@]}"; do printf '%s docker\n' "$v"; done + i=0 + for v in "${ELK_VERSIONS[@]}"; do + printf '%s %s\n' "$v" "${ECK_ENVS[$(( (i + FLIP) % ${#ECK_ENVS[@]} ))]}" + i=$(( i + 1 )) + done + } | jq -Rcn '{include: [inputs | split(" ") | {version: .[0], env: .[1]}]}' + ;; + *) + echo "Usage: e2e-matrix.sh versions|matrix [--flip]" >&2 + exit 2 + ;; +esac From 089f645c8f95ab7cf90deefb788f50278d5b94a2 Mon Sep 17 00:00:00 2001 From: Simone Scarduzio Date: Sat, 12 Sep 2026 14:31:37 +0000 Subject: [PATCH 07/26] Run the bootstrap sweep on a release, not every night `--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 -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 --- .github/workflows/all-e2e-tests.yml | 40 ++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/.github/workflows/all-e2e-tests.yml b/.github/workflows/all-e2e-tests.yml index 39ce152a..d5e74ae0 100644 --- a/.github/workflows/all-e2e-tests.yml +++ b/.github/workflows/all-e2e-tests.yml @@ -3,7 +3,14 @@ name: End-to-end test workflow on: workflow_dispatch: {} schedule: + # Nightly: the suite against the released images. - cron: '0 0 * * *' + # Sunday: the bootstrap sweep as well. See master-bootstrap-tests for why it is not nightly. + - cron: '0 1 * * 0' + # Sent by the ROR plugin release pipelines when they publish new `-ror-latest` images. Those + # tags are the only input the bootstrap sweep has, so a release is the moment it is worth running. + repository_dispatch: + types: [ror-plugins-released] # Only the two long-lived branches. Feature branches are covered by their pull request, and # listing them here would run everything twice for every push to an open PR. # paths-ignore on both: a change to prose cannot change what the suite does, and a docs-only @@ -66,8 +73,10 @@ jobs: prod-e2e-tests: name: "๐Ÿ”ฌ E2E Tests (released plugins)" needs: setup + # The nightly cron only. The workflow carries a second, weekly cron for the bootstrap sweep, + # and this job has nothing to add on that run. if: > - github.event_name == 'schedule' || + (github.event_name == 'schedule' && github.event.schedule == '0 0 * * *') || github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/master') || (github.event_name == 'pull_request' && github.base_ref == 'master' && github.event.pull_request.head.repo.fork == false) @@ -120,25 +129,38 @@ jobs: # ========================================== # BOOTSTRAP TESTS # ========================================== - # Same condition as prod-e2e-tests, which this gates on โ€” kept identical so the two cannot drift. - # Bootstrap uses released images (no --mode), as it did before. + # Boots each released ELK version against the released plugin images and stops. `--run bootstrap` + # runs no tests: the only assertion is that `docker compose up --wait` returned 0. What it can + # catch is "a newly published -ror-latest image does not start on this old version". + # + # It runs on a release, weekly, and on a push to master. Not nightly, and not on pull requests: + # + # - Its input is the `-ror-latest` tags, which move when ROR releases, about twice a + # month. Nothing in this repository can change them, so a pull request cannot change the + # result, and a nightly asks the same question 30 times between two answers. + # - The yield says the same. Over 75 nightly runs, 3317 executed legs produced 7 failures, 6 of + # them the 7.10.0 below that is already out of the list. + # - It used to gate on prod-e2e-tests, which meant one flaky suite leg skipped all 68. It ran on + # 14 of the last 30 nightlies. A release dispatch is a better signal than a run that the thing + # it does not depend on can cancel. + # + # Only docker. Over those 75 nightlies the docker and eck columns failed on exactly the same + # versions, and this job starts a stack rather than exercising the proxy, which is where docker + # and ECK actually differ. master-bootstrap-tests: name: "๐Ÿš€ Bootstrap Tests" - # Not on a pull request. The matrix is 34 released versions x 2 envs, and every one of them - # tests plugins that are already out, so nothing a pull request changes can change the result. - # The nightly and a push to master still run it, because the released set moves there. if: > - github.event_name == 'schedule' || + github.event_name == 'repository_dispatch' || github.event_name == 'workflow_dispatch' || + (github.event_name == 'schedule' && github.event.schedule == '0 1 * * 0') || (github.event_name == 'push' && github.ref == 'refs/heads/master') - needs: prod-e2e-tests runs-on: ubuntu-latest strategy: fail-fast: false matrix: # 7.10.0 was temporarily removed due to this issue: https://github.com/sscarduzio/elasticsearch-readonlyrest-plugin/pull/1362. Add it again when the fix is released. version: ["9.5.0", "9.4.0", "9.3.0", "9.2.0", "9.1.0", "9.0.0", "8.19.0", "8.18.0", "8.17.0", "8.16.0", "8.15.0", "8.14.0", "8.13.0", "8.12.0", "8.11.0", "8.10.1", "8.9.0", "8.8.0", "8.7.0", "8.6.0", "8.5.0", "8.4.0", "8.3.0", "8.2.0", "8.1.0", "8.0.0", "7.17.0", "7.16.0", "7.15.0", "7.14.0", "7.13.0", "7.12.0", "7.11.2", "7.9.0"] - env: [docker, eck-3.5.0] + env: [docker] steps: - name: Checkout code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 From a753c6be13b460cb944ad85506929a7016fbbbd2 Mon Sep 17 00:00:00 2001 From: Simone Scarduzio Date: Sat, 12 Sep 2026 14:33:07 +0000 Subject: [PATCH 08/26] Delete the two specs that run in no environment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- e2e-tests/cypress/e2e/Kibana-config.cy.ts | 221 ------------------ e2e-tests/cypress/e2e/ror-config.cy.ts | 36 --- .../cypress/fixtures/customKibanaConfig.yml | 69 ------ ...customKibanaConfigMultitenancyDisabled.yml | 31 --- .../customKibanaConfigXpackReportingIndex.yml | 30 --- ...tomMiddlewareDefaultTenantKibanaConfig.yml | 61 ----- .../cypress/fixtures/defaultKibanaConfig.yml | 31 --- .../fixtures/settingsWithReadonlyRestKbn.yml | 76 ------ 8 files changed, 555 deletions(-) delete mode 100644 e2e-tests/cypress/e2e/Kibana-config.cy.ts delete mode 100644 e2e-tests/cypress/e2e/ror-config.cy.ts delete mode 100644 e2e-tests/cypress/fixtures/customKibanaConfig.yml delete mode 100644 e2e-tests/cypress/fixtures/customKibanaConfigMultitenancyDisabled.yml delete mode 100644 e2e-tests/cypress/fixtures/customKibanaConfigXpackReportingIndex.yml delete mode 100644 e2e-tests/cypress/fixtures/customMiddlewareDefaultTenantKibanaConfig.yml delete mode 100644 e2e-tests/cypress/fixtures/defaultKibanaConfig.yml delete mode 100644 e2e-tests/cypress/fixtures/settingsWithReadonlyRestKbn.yml diff --git a/e2e-tests/cypress/e2e/Kibana-config.cy.ts b/e2e-tests/cypress/e2e/Kibana-config.cy.ts deleted file mode 100644 index 9fb25fb9..00000000 --- a/e2e-tests/cypress/e2e/Kibana-config.cy.ts +++ /dev/null @@ -1,221 +0,0 @@ -import * as semver from 'semver'; -import { rorApiInternalKbnClient } from '../support/helpers/RorApiInternalKbnClient'; -import { Login } from '../support/page-objects/Login'; -import { kbnApiAdvancedClient } from '../support/helpers/KbnApiAdvancedClient'; -import { RorMenu } from '../support/page-objects/RorMenu'; -import { getKibanaVersion, requiredBaseUrl } from '../support/helpers'; -import { Discover } from '../support/page-objects/Discover'; -import { Dashboard } from '../support/page-objects/Dashboard'; -import { Reporting } from '../support/page-objects/Reporting'; -import { SampleData } from '../support/helpers/SampleData'; -import { esApiClient } from '../support/helpers/EsApiClient'; -import { esApiAdvancedClient } from '../support/helpers/EsApiAdvancedClient'; -import { Tenancy } from '../support/page-objects/Tenancy'; - -const customKibanaIndexName = '.kibana_custom'; - -// rorApiInternalKbnClient.changeKibanaConfig rewrites kibana.yml on disk and restarts Kibana, which -// this suite relies on for every nested describe. Two environments can't support that: -// - docker env (elk-ror) runs 2 kbn-ror replicas behind kbn-proxy's round robin (see -// base.docker-compose.yml). The config reload only updates the node that handles the request; the -// ROR Kibana plugin does not propagate config changes across instances, so the other replica keeps -// serving the stale config. A single client's requests can then land on nodes disagreeing about the -// active config, which is the same class of issue Activation-keys.cy.ts hit and skipped for the -// same reason. -// - eck envs run Kibana under Kubernetes, where kibana.yml is mounted read-only from a -// ConfigMap/Secret. The rewrite always 500s with EROFS, so the custom config never applies and -// every test here fails predictably. -describe.skip('Kibana-config', () => { - after(() => { - rorApiInternalKbnClient.changeKibanaConfig('defaultKibanaConfig.yml'); - kbnApiAdvancedClient.waitForKibanaHealth(requiredBaseUrl()); - esApiAdvancedClient.deleteIndicesByPattern(customKibanaIndexName); - esApiAdvancedClient.deleteDataStreamsByPattern(customKibanaIndexName); - }); - - describe('Custom kibana config', () => { - const adminCredentials = 'admin:dev'; - const customSessionIndex = `test_index`; - - before(() => { - rorApiInternalKbnClient.changeKibanaConfig('customKibanaConfig.yml'); - kbnApiAdvancedClient.waitForKibanaHealth(requiredBaseUrl()); - }); - - afterEach(() => { - kbnApiAdvancedClient.deleteSavedObjects(adminCredentials, 'template_group'); - - // deleteSavedObjects will return 404 error because, thanks to resetKibanaIndexToTemplate: true, ROR KBN plugin will reset all data to template_group deleted above, first - kbnApiAdvancedClient.getSavedObjects(adminCredentials); - - esApiClient.deleteIndex(customSessionIndex); - }); - - it('should verify kibanaIndexTemplate functionality', () => { - cy.kbnImport({ - endpoint: 'api/saved_objects/_import?overwrite=true', - credentials: adminCredentials, - fixtureFilename: 'audit_dashboard.ndjson', - currentGroupHeader: 'template_group' - }); - - Login.initialization(); - Discover.openDataViewPage(); - Discover.verifyIndexPatternSwitchLink('readonlyrest_audit-*'); - Dashboard.openDashboard(); - Dashboard.verifyDashboardExists('ReadonlyREST Audit Dashboard'); - - // Verify that the index is reset to the template - cy.kbnImport({ - endpoint: 'api/saved_objects/_import?overwrite=true', - credentials: adminCredentials, - fixtureFilename: 'file.ndjson', - currentGroupHeader: 'admins_group' - }); - - cy.reload(); - Dashboard.verifyDashboardExists('Look at my dashboard'); - RorMenu.openRorMenu(); - - RorMenu.pressLogoutButton(); - // Logging out keeps the current location as nextUrl, so this login lands back on the - // dashboards list rather than on the home page Loader.finish expects by default. - Login.initialization({ finishUrl: '/app/dashboards' }); - Dashboard.openDashboard(); - Dashboard.verifyDashboardNotExist('Look at my dashboard'); - }); - - it('should verify index based session', () => { - Login.initialization(); - esApiAdvancedClient.waitForDocsCount(customSessionIndex, 1).then(() => { - // Backdate the session instead of waiting out the 1-minute timeout: the cleanup task - // deletes documents whose expiresAt has passed, and runs every second in this stack. - // Repeated, because live Kibana traffic rolls expiresAt forward and can rescue the doc. - esApiAdvancedClient.expireAllSessionsUntilSwept(customSessionIndex); - esApiAdvancedClient.waitForDocsCount(customSessionIndex, 0, 15000); - }); - }); - - it('should verify custom Kibana CSS', () => { - Login.initialization(); - cy.get('h1').shouldHaveStyle('color', 'rgb(0,128,0)'); - }); - - it('should verify custom Kibana JS', () => { - Login.initialization(); - cy.get('[data-testid="metadata-alert-message"]') - .should('exist') - .then($el => { - cy.log(`Alert message: ${$el.text()}`); - - cy.wrap($el).should('contain', 'Dear admin'); - }); - }); - - it('should verify custom middleware', () => { - Login.initialization(); - cy.get('[data-testid="metadata-enriched-data"]') - .should('exist') - .then($el => { - cy.log(`Entiched data: ${$el.text()}`); - - cy.wrap($el).should('contain', 'custom enriched data'); - }); - }); - - it('should verify whitelisted Urls', () => { - cy.request(`${Cypress.config().baseUrl}/api/index_management/indices`).then(response => { - expect(response.status).to.equal(200); - }); - - cy.request({ url: `${Cypress.config().baseUrl}/api/spaces/space`, failOnStatusCode: false }).then(response => { - expect(response.status).to.equal(403); - expect(response.body.error).to.equal('Unauthorized'); - }); - }); - }); - - describe('Default tenant middleware', () => { - before(() => { - rorApiInternalKbnClient.changeKibanaConfig('customMiddlewareDefaultTenantKibanaConfig.yml'); - kbnApiAdvancedClient.waitForKibanaHealth(requiredBaseUrl()); - }); - - // FIXME: flaky, about 2 runs in 16 on 8.19.19. When it fails the badge reads 'administrators', - // the normal first group, so the middleware's reorder of availableGroups on /pkp/api/info did - // not take โ€” and it then fails all three retries, so it is settled state and not a slow page. - // It behaves the same with clearSessionOnEvents set and unset, so it is not that. The other - // eight tests here are steady, so this is skipped rather than left to erode the signal. - it.skip('should open correct tenancy after login when custom middleware sets defaultGroup', () => { - Login.initialization(); - - Tenancy.checkTenancyNameInBadge('infosec', 'a'); - }); - }); - - describe('Custom kibana config multitenancy disabled', () => { - before(() => { - rorApiInternalKbnClient.changeKibanaConfig('customKibanaConfigMultitenancyDisabled.yml'); - kbnApiAdvancedClient.waitForKibanaHealth(requiredBaseUrl()); - }); - - it('should verify disabled multiTenancy', () => { - // With multitenancy off there is no tenancy query string, so the default finish URL of - // Loader.finish ('/app/home?tenancy=*') never matches. - Login.initialization({ finishUrl: '/app/home' }); - RorMenu.openRorMenu(); - RorMenu.verifyNoTenantAvailable(); - }); - - it('should verify custom Kibana index', () => { - const customIndex = `${customKibanaIndexName}_${getKibanaVersion()}_001`; - esApiClient.findIndicesByPattern(customIndex).then(result => { - const foundIndex = result.find(({ index }) => index === customIndex); - if (!foundIndex) throw new Error(`Expected to find an index matching ${customIndex}`); - expect(foundIndex.index).to.equal(customIndex); - expect(foundIndex.health).to.equal('green'); - expect(Number.parseInt(foundIndex['docs.count'], 10)).to.be.greaterThan(0); - }); - }); - }); - // xpack.reporting.index was removed in Kibana 8.0, so this only applies to the 7.x leg. - if (semver.lt(getKibanaVersion(), '8.0.0')) { - describe('Custom kibana config custom xpack.reporting.index', () => { - before(() => { - rorApiInternalKbnClient.changeKibanaConfig('customKibanaConfigXpackReportingIndex.yml'); - kbnApiAdvancedClient.waitForKibanaHealth(requiredBaseUrl()); - }); - - it('should verify custom reporting index', () => { - const docsIndex = 'sample_index'; - - SampleData.createSampleData(docsIndex, 1); - Login.initialization(); - - Discover.openDataViewPage(); - Discover.createIndexPattern('sample_index'); - Discover.saveReport('admin_search'); - Discover.exportToCsv(); - Reporting.openReportingPage('kibanaNavigation'); - Reporting.verifySavedReport(['admin_search']); - esApiAdvancedClient.getAllReportingIndices().then(results => { - expect(results).to.be.length(1); - const xpackReportingCustomIndex = results.find(index => index.index.startsWith('.reporting-test-index')); - /* eslint-disable no-unused-expressions */ - expect(xpackReportingCustomIndex).to.exist; - if (!xpackReportingCustomIndex) throw new Error('Expected to find a custom reporting index'); - expect(xpackReportingCustomIndex.health).to.equal('green'); - expect(Number.parseInt(xpackReportingCustomIndex['docs.count'], 10)).to.equal(1); - }); - - esApiClient.deleteIndex(docsIndex); - esApiAdvancedClient.pruneAllReportingIndices(); - kbnApiAdvancedClient.deleteSavedObjects('admin:dev'); - }); - }); - } else { - describe.skip('Custom kibana config custom xpack.reporting.index', () => { - // Tests are skipped - }); - } -}); diff --git a/e2e-tests/cypress/e2e/ror-config.cy.ts b/e2e-tests/cypress/e2e/ror-config.cy.ts deleted file mode 100644 index dc35b8c9..00000000 --- a/e2e-tests/cypress/e2e/ror-config.cy.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Login } from '../support/page-objects/Login'; -import { RorMenu } from '../support/page-objects/RorMenu'; -import { Settings } from '../support/page-objects/Settings'; -import { Editor } from '../support/page-objects/Editor'; -import { rorApiInternalKbnClient } from '../support/helpers/RorApiInternalKbnClient'; - -// TODO: Uncomment when functionality enabled -describe.skip('Ror config', () => { - beforeEach(() => { - Login.initialization(); - }); - - afterEach(() => { - // const RORSettingsIndex = '.readonlyrest'; - - // FIXME: For some reason delete index freeze cypress, let's investigate later - // esApiClient.deleteIndex(RORSettingsIndex); - rorApiInternalKbnClient.changeKibanaConfig('defaultKibanaConfig.yml'); - }); - - it('should save ReadonlyREST Kibana config to the index', () => { - RorMenu.openRorMenu(); - RorMenu.openEditSecuritySettings(); - // // Fixme: This is workaround for the Es plugin validation when no index is present - Settings.clickSaveButton(); - cy.fixture('settingsWithReadonlyRestKbn.yml').then(data => { - Editor.pasteConfig(data); - }); - cy.intercept({ pathname: '/pkp/api/settings', method: 'POST' }).as('saveSettings'); - Settings.clickSaveButton(); - cy.wait('@saveSettings'); - RorMenu.openRorMenu(); - RorMenu.pressLogoutButton(); - Login.verifyLoginPageTitle('Loaded from index!'); - }); -}); diff --git a/e2e-tests/cypress/fixtures/customKibanaConfig.yml b/e2e-tests/cypress/fixtures/customKibanaConfig.yml deleted file mode 100644 index 641bd112..00000000 --- a/e2e-tests/cypress/fixtures/customKibanaConfig.yml +++ /dev/null @@ -1,69 +0,0 @@ -server.host: 0.0.0.0 -server.port: 5601 - -# csp needs to be disabled to let cypress e2e tests works -csp.strict: false -csp.warnLegacyBrowsers: false - -elasticsearch.hosts: [ "${ES_API_URL}" ] -elasticsearch.username: kibana -elasticsearch.password: kibana -elasticsearch.ssl.verificationMode: none -elasticsearch.pingTimeout: 3000 # default: 30000 -elasticsearch.requestTimeout: 30000 - -# generated with: -# $ openssl req -x509 -batch -nodes -days 3650 -newkey rsa:2048 -keyout kibana.key -out kibana.crt -server.ssl.enabled: true -server.ssl.certificate: /usr/share/kibana/config/kibana.crt -server.ssl.key: /usr/share/kibana/config/kibana.key -server.ssl.redirectHttpFromPort: 80 - -xpack.encryptedSavedObjects.encryptionKey: "19+230i1902i310293213i109312i31209302193219039120i3j23h31h3h213h123!" -xpack.reporting.encryptionKey: "321421321211231241232132132132132" - -telemetry.enabled: false - -readonlyrest_kbn: - # Without an explicit value this stack runs with no session clearing, and a previous - # spec's tenancy survives login. Clearing on login and tenancy hop keeps the specs - # independent of each other. - clearSessionOnEvents: [login, tenancyHop] - cookiePass: '12312313123213123213123adadasdasdasd' - kibanaIndexTemplate: ".kibana_template_group" - resetKibanaIndexToTemplate: true - store_sessions_in_index: true - sessions_index_name: 'test_index' - session_timeout_minutes: 0.05 - sessions_cleanup_interval: '1s' - # Every probe refreshes the session; keep probes clear of the timeout so a session can expire. - sessions_probe_interval_seconds: 180 - whitelistedPaths: [".*/api/status$", ".*/api/index_management/indices$"] - tenantIndex: - number_of_shards: 2 - number_of_replicas: 2 - kibana_custom_css_inject: 'h1 { color: rgb(0,128,0) !important;}' - kibana_custom_js_inject: "if (window.ROR_METADATA.customMetadata && window.ROR_METADATA.customMetadata.alert_message) { - const div = document.createElement('div'); - div.setAttribute('data-testid', 'metadata-alert-message'); - div.textContent = window.ROR_METADATA.customMetadata.alert_message; - document.body.appendChild(div); - }; - if (window.ROR_METADATA.enrichedData) { - const div = document.createElement('div'); - div.setAttribute('data-testid', 'metadata-enriched-data'); - div.textContent = window.ROR_METADATA.enrichedData; - document.body.appendChild(div); - };" - custom_middleware_inject: "async function customMiddleware(req, res, next) { - const metadata = - req.rorRequest && req.rorRequest.getIdentitySession() && req.rorRequest.getIdentitySession().metadata; - - if (metadata && metadata.username === 'admin') { - req.rorRequest.enrichIdentitySessionMetadata({ - enrichedData: 'custom enriched data', - }); - } - - return next(); - }" diff --git a/e2e-tests/cypress/fixtures/customKibanaConfigMultitenancyDisabled.yml b/e2e-tests/cypress/fixtures/customKibanaConfigMultitenancyDisabled.yml deleted file mode 100644 index c9f7d31b..00000000 --- a/e2e-tests/cypress/fixtures/customKibanaConfigMultitenancyDisabled.yml +++ /dev/null @@ -1,31 +0,0 @@ -server.name: "elk-ror-kbn-node-${HOSTNAME}" -server.host: 0.0.0.0 -server.port: 5601 - -# csp needs to be disabled to let cypress e2e tests works -csp.strict: false -csp.warnLegacyBrowsers: false - -elasticsearch.hosts: [ "${ES_API_URL}" ] -elasticsearch.username: kibana -elasticsearch.password: kibana -elasticsearch.ssl.verificationMode: none -elasticsearch.pingTimeout: 3000 # default: 30000 -elasticsearch.requestTimeout: 30000 - -# generated with: -# $ openssl req -x509 -batch -nodes -days 3650 -newkey rsa:2048 -keyout kibana.key -out kibana.crt -server.ssl.enabled: true -server.ssl.certificate: /usr/share/kibana/config/kibana.crt -server.ssl.key: /usr/share/kibana/config/kibana.key -server.ssl.redirectHttpFromPort: 80 - -xpack.encryptedSavedObjects.encryptionKey: "19+230i1902i310293213i109312i31209302193219039120i3j23h31h3h213h123!" -xpack.reporting.encryptionKey: "321421321211231241232132132132132" - -telemetry.enabled: false - -kibana.index: .kibana_custom -readonlyrest_kbn: - cookiePass: '12312313123213123213123adadasdasdasd' - multiTenancyEnabled: false diff --git a/e2e-tests/cypress/fixtures/customKibanaConfigXpackReportingIndex.yml b/e2e-tests/cypress/fixtures/customKibanaConfigXpackReportingIndex.yml deleted file mode 100644 index ea5072b3..00000000 --- a/e2e-tests/cypress/fixtures/customKibanaConfigXpackReportingIndex.yml +++ /dev/null @@ -1,30 +0,0 @@ -server.name: "elk-ror-kbn-node-${HOSTNAME}" -server.host: 0.0.0.0 -server.port: 5601 - -# csp needs to be disabled to let cypress e2e tests works -csp.strict: false -csp.warnLegacyBrowsers: false - -elasticsearch.hosts: [ "${ES_API_URL}" ] -elasticsearch.username: kibana -elasticsearch.password: kibana -elasticsearch.ssl.verificationMode: none -elasticsearch.pingTimeout: 3000 # default: 30000 -elasticsearch.requestTimeout: 30000 - -# generated with: -# $ openssl req -x509 -batch -nodes -days 3650 -newkey rsa:2048 -keyout kibana.key -out kibana.crt -server.ssl.enabled: true -server.ssl.certificate: /usr/share/kibana/config/kibana.crt -server.ssl.key: /usr/share/kibana/config/kibana.key -server.ssl.redirectHttpFromPort: 80 - -xpack.encryptedSavedObjects.encryptionKey: "19+230i1902i310293213i109312i31209302193219039120i3j23h31h3h213h123!" -xpack.reporting.encryptionKey: "321421321211231241232132132132132" -xpack.reporting.index: '.reporting-test-index' - -telemetry.enabled: false - -readonlyrest_kbn: - cookiePass: '12312313123213123213123adadasdasdasd' diff --git a/e2e-tests/cypress/fixtures/customMiddlewareDefaultTenantKibanaConfig.yml b/e2e-tests/cypress/fixtures/customMiddlewareDefaultTenantKibanaConfig.yml deleted file mode 100644 index b9869d54..00000000 --- a/e2e-tests/cypress/fixtures/customMiddlewareDefaultTenantKibanaConfig.yml +++ /dev/null @@ -1,61 +0,0 @@ -server.name: "elk-ror-kbn-node-${HOSTNAME}" -server.host: 0.0.0.0 -server.port: 5601 - -# csp needs to be disabled to let cypress e2e tests works -csp.strict: false -csp.warnLegacyBrowsers: false - -elasticsearch.hosts: [ "${ES_API_URL}" ] -elasticsearch.username: kibana -elasticsearch.password: kibana -elasticsearch.ssl.verificationMode: none -elasticsearch.pingTimeout: 3000 # default: 30000 -elasticsearch.requestTimeout: 30000 - -# generated with: -# $ openssl req -x509 -batch -nodes -days 3650 -newkey rsa:2048 -keyout kibana.key -out kibana.crt -server.ssl.enabled: true -server.ssl.certificate: /usr/share/kibana/config/kibana.crt -server.ssl.key: /usr/share/kibana/config/kibana.key -server.ssl.redirectHttpFromPort: 80 - -xpack.encryptedSavedObjects.encryptionKey: "19+230i1902i310293213i109312i31209302193219039120i3j23h31h3h213h123!" -xpack.reporting.encryptionKey: "321421321211231241232132132132132" - -telemetry.enabled: false - -readonlyrest_kbn: - # Without an explicit value this stack runs with no session clearing, and a previous - # spec's tenancy survives login. Clearing on login and tenancy hop keeps the specs - # independent of each other. - clearSessionOnEvents: [login, tenancyHop] - cookiePass: '12312313123213123213123adadasdasdasd' - store_sessions_in_index: true - tenantIndex: - number_of_shards: 2 - number_of_replicas: 2 - custom_middleware_inject: "async function customMiddleware(req, res, next) { - const rorRequest = req.rorRequest; - const userRequest = rorRequest && (await req.rorRequest.getUserRequestIdentity()); - const metadata = userRequest && userRequest.metadata; - const defaultGroup = 'infosec_group'; - - if (rorRequest.getPath() === '/login' && rorRequest.getMethod() === 'post') { - if (rorRequest.getBody().username === 'admin') { - rorRequest.setQuery('defaultGroup', defaultGroup); - } - } - - if (metadata && rorRequest.getPath() === '/pkp/api/info') { - const availableGroups = metadata.availableGroups; - if (availableGroups.some(availableGroup => availableGroup.id === defaultGroup)) { - const reorderedGroups = [...availableGroups].sort((a, b) => - a.id === defaultGroup ? -1 : b.id === defaultGroup ? 1 : 0 - ); - rorRequest.enrichIdentitySessionMetadata({ availableGroups: reorderedGroups }); - } - } - - return next(); - }" diff --git a/e2e-tests/cypress/fixtures/defaultKibanaConfig.yml b/e2e-tests/cypress/fixtures/defaultKibanaConfig.yml deleted file mode 100644 index 170c4be9..00000000 --- a/e2e-tests/cypress/fixtures/defaultKibanaConfig.yml +++ /dev/null @@ -1,31 +0,0 @@ -server.name: "elk-ror-kbn-node-${HOSTNAME}" -server.host: 0.0.0.0 -server.port: 5601 - -# csp needs to be disabled to let cypress e2e tests works -csp.strict: false -csp.warnLegacyBrowsers: false - -elasticsearch.hosts: [ "${ES_API_URL}" ] -elasticsearch.username: kibana -elasticsearch.password: kibana -elasticsearch.ssl.verificationMode: none -elasticsearch.pingTimeout: 3000 # default: 30000 -elasticsearch.requestTimeout: 30000 - -# generated with: -# $ openssl req -x509 -batch -nodes -days 3650 -newkey rsa:2048 -keyout kibana.key -out kibana.crt -server.ssl.enabled: true -server.ssl.certificate: /usr/share/kibana/config/kibana.crt -server.ssl.key: /usr/share/kibana/config/kibana.key -server.ssl.redirectHttpFromPort: 80 - -xpack.encryptedSavedObjects.encryptionKey: "19+230i1902i310293213i109312i31209302193219039120i3j23h31h3h213h123!" -xpack.reporting.encryptionKey: "321421321211231241232132132132132" - -telemetry.enabled: false - -readonlyrest_kbn: - cookiePass: '12312313123213123213123adadasdasdasd' - logLevel: info - store_sessions_in_index: true diff --git a/e2e-tests/cypress/fixtures/settingsWithReadonlyRestKbn.yml b/e2e-tests/cypress/fixtures/settingsWithReadonlyRestKbn.yml deleted file mode 100644 index 46a94527..00000000 --- a/e2e-tests/cypress/fixtures/settingsWithReadonlyRestKbn.yml +++ /dev/null @@ -1,76 +0,0 @@ -helpers: - ckr: &common-kibana-rules - access: rw - hide_apps: ["Enterprise Search|Overview", "Observability"] - index: ".kibana_@{acl:current_group}" - - ag: &all-groups - groups: - - id: admins_group - name: administrators - - id: infosec_group - name: infosec - - id: template_group - name: template - -readonlyrest: - response_if_req_forbidden: Forbidden by ReadonlyREST ES plugin - audit: - enabled: true - outputs: - - type: index - index_template: "'readonlyrest_audit_'yyyy-MM-dd" - - access_control_rules: - - - name: "Kibana service account - user/pass" - verbosity: error - auth_key: kibana:kibana - - - name: PERSONAL_GRP - groups: [Personal] - kibana: - <<: *common-kibana-rules - index: ".kibana_@{user}" - - - name: ADMIN_GRP - groups: [admins_group] - kibana: - <<: *common-kibana-rules - access: admin - metadata: - alert_message: "Dear @{acl:user}" - - - name: infosec - groups: [infosec_group] - kibana: - <<: *common-kibana-rules - hide_apps: ["Enterprise Search|Overview", "Observability", "Management"] - - - name: Template Tenancy - groups: [template_group] - kibana: - <<: *common-kibana-rules - - users: - - username: admin - auth_key: admin:dev - <<: *all-groups - - - username: user1 - auth_key: user1:dev - <<: *all-groups - -readonlyrest_kbn: - cookiePass: '12312313123213123213123adadasdasdasd' - logLevel: 'trace' - logPrettyPrintEnabled: true - whitelistedPaths: [.*/api/status$] - clearSessionOnEvents: [login, tenancyHop] - sessions_probe_interval_seconds: 60 - store_sessions_in_index: true - login_title: Loaded from index! - login_subtitle: 'PRO/Enterprise: You should see a red border, a tiny unicorn logo, a two column page, and this text. You should see none of these customisation when testing ROR Free.' - login_custom_logo: 'https://i.imgur.com/MdRBUfV.gif' - login_html_head_inject: '' - login_custom_js_inject_file: '/usr/share/kibana/custom_login.js' From b888c9fb3317f3b4aab47e4f9b69236cd8f6b745 Mon Sep 17 00:00:00 2001 From: Simone Scarduzio Date: Sat, 12 Sep 2026 14:34:01 +0000 Subject: [PATCH 09/26] Drop an assertion that could only fail if ROR got it right 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 --- e2e-tests/cypress/e2e/Settings.cy.ts | 10 ++++--- e2e-tests/cypress/e2e/Test-settings.cy.ts | 32 +++-------------------- 2 files changed, 9 insertions(+), 33 deletions(-) diff --git a/e2e-tests/cypress/e2e/Settings.cy.ts b/e2e-tests/cypress/e2e/Settings.cy.ts index 9728507e..1c16c87b 100644 --- a/e2e-tests/cypress/e2e/Settings.cy.ts +++ b/e2e-tests/cypress/e2e/Settings.cy.ts @@ -31,10 +31,12 @@ describe('settings', () => { cy.log('should check save changes functionality when no changes provided'); // Settings.currentSettingsAlreadyLoadedToast().should('be.visible'); - cy.log('should check save changes functionality when malformed settings provided'); - Editor.changeConfig('readonlyrest:'); - Settings.clickSaveButton(); - // Settings.malformedSavedConfigurationToast().should('be.visible'); + // The malformed-settings step used to sit here. Its only live assertion was clickSaveButton's + // `expect(statusCode).to.eq(200)`, so it asserted that saving `readonlyrest:` SUCCEEDS: it + // would have stayed green if ROR silently swallowed a broken config, and would have gone red + // the day ROR started rejecting one. The toast assertion that carried the real meaning is + // commented out above its sibling steps. Rejecting malformed config is covered by + // JsonSchemaValidator.test.ts and rorApi.test.ts in the ROR KBN repo. cy.log('should check save changes functionality when success'); Editor.replaceValues('PERSONAL_GRP', `PERSONAL_GRP${Cypress._.random(0, 1e6)}`); diff --git a/e2e-tests/cypress/e2e/Test-settings.cy.ts b/e2e-tests/cypress/e2e/Test-settings.cy.ts index 3b764105..236f1a87 100644 --- a/e2e-tests/cypress/e2e/Test-settings.cy.ts +++ b/e2e-tests/cypress/e2e/Test-settings.cy.ts @@ -1,7 +1,6 @@ import { Login } from '../support/page-objects/Login'; import { TestSettings } from '../support/page-objects/TestSettings'; import { Settings } from '../support/page-objects/Settings'; -import { Editor } from '../support/page-objects/Editor'; describe('Test ACL', () => { beforeEach(() => { @@ -17,36 +16,11 @@ describe('Test ACL', () => { TestSettings.pressInvalidateFileTestSettings(); cy.log('should check promote as permanent settings functionality when success'); - // Editor.replaceValues('PERSONAL_GRP', `PERSONAL_GRP1`); - TestSettings.pressSaveTestSettingsButton(); - - // TODO: Uncomment it when es plugin fix issue with settings - // TestSettings.promoteAsPermanent(); - - // cy.log( - // 'should save Test ACL promote as permanent settings functionality when not saving changes' - // ); - // Editor.replaceValues('PERSONAL_GRP', `PERSONAL_GRP2`); - // TestSettings.pressPromoteAsPermanentButton(); - // TestSettings.saveTestSettingsBeforePermanentPromote(); + TestSettings.pressSaveTestSettingsButton(); - // cy.log( - // 'should reject save Test ACL promote as permanent settings functionality when not saving changes' - // ); - // - // Editor.replaceValues('PERSONAL_GRP', `PERSONAL_GRP3`); - // TestSettings.pressPromoteAsPermanentButton(); - // TestSettings.rejectSaveTestSettingsBeforePermanentPromote(); - // - // cy.log('should failed promote as permanent when settings already exists'); - // TestSettings.pressPromoteAsPermanentButton(); - // TestSettings.saveTestSettingsBeforePermanentPromoteFailed(); - // - // cy.log('should check save Test ACL functionality'); - // Editor.replaceValues('PERSONAL_GRP', `PERSONAL_GRP4`); - // TestSettings.pressSaveTestSettingsButton(); - // TestSettings.successfulSaveTestSettingsToast().should('be.visible'); + // The promote-as-permanent steps that used to sit here were commented out waiting on an ES + // plugin fix. They are tracked in the ROR KBN repo's testController tests, not here. /** * TODO: Uncomment all toast based assertions and try to make this check non-deterministic From e44bbb5711cfdf30e4b3ecbb8eac7b66d2f45397 Mon Sep 17 00:00:00 2001 From: Simone Scarduzio Date: Sat, 12 Sep 2026 14:34:52 +0000 Subject: [PATCH 10/26] Run Tenancy's two callback-free tests once, not three times 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 --- e2e-tests/cypress/e2e/Tenancy.cy.ts | 44 +++++++++++++++-------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/e2e-tests/cypress/e2e/Tenancy.cy.ts b/e2e-tests/cypress/e2e/Tenancy.cy.ts index 5d6c2afb..eaa0ccec 100644 --- a/e2e-tests/cypress/e2e/Tenancy.cy.ts +++ b/e2e-tests/cypress/e2e/Tenancy.cy.ts @@ -56,6 +56,29 @@ describe('Tenancy', () => { runTests({ callbackBeforeLogin: openAnotherTabs }); }); + // Outside runTests: neither of these reads callbackBeforeLogin or callbackAfterLogin, so the + // three runTests passes ran byte-identical copies of them. Once is once. + it('should redirect to page not found when tenancy is not available', () => { + const urlWithTenancyId = `/s/default/app/dashboards?${TENANCY_QUERY_STRING_KEY}=${Tenancy.encryptedTenancyWithNotAvailableTenancy}`; + Login.initialization({ + visitedUrl: urlWithTenancyId, + finishUrl: `/app/page-not-found?${TENANCY_QUERY_STRING_KEY}=*`, + spacePrefix: '' + }); + }); + + it('should hide correct Kibana navigation items on tenancy switch', () => { + const urlWithTenancyId = `/s/default/app/home?${TENANCY_QUERY_STRING_KEY}=${Tenancy.encryptedInfosecGroup}`; + Login.initialization({ + visitedUrl: urlWithTenancyId, + finishUrl: `/s/default/app/home?${TENANCY_QUERY_STRING_KEY}=*`, + spacePrefix: '' + }); + + KibanaNavigation.openKibanaNavigation(); + KibanaNavigation.checkIfNotVisible('Stack Management'); + }); + it('should not apply stale remembered tenancy to a new user session after logout', () => { const homeUrlWithInfosecTenancy = `/s/default/app/home?${TENANCY_QUERY_STRING_KEY}=${Tenancy.encryptedInfosecGroup}`; @@ -205,15 +228,6 @@ function runTests({ } }); - it('should redirect to page not found when tenancy is not available', () => { - const urlWithTenancyId = `/s/default/app/dashboards?${TENANCY_QUERY_STRING_KEY}=${Tenancy.encryptedTenancyWithNotAvailableTenancy}`; - Login.initialization({ - visitedUrl: urlWithTenancyId, - finishUrl: `/app/page-not-found?${TENANCY_QUERY_STRING_KEY}=*`, - spacePrefix: '' - }); - }); - it('should correctly switch Kibana space', () => { const newSpace = 'test-space'; @@ -231,16 +245,4 @@ function runTests({ Spaces.openSpace(newSpace); Spaces.verifyCurrentSpace(newSpace); }); - - it('should hide correct Kibana navigation items on tenancy switch', () => { - const urlWithTenancyId = `/s/default/app/home?${TENANCY_QUERY_STRING_KEY}=${Tenancy.encryptedInfosecGroup}`; - Login.initialization({ - visitedUrl: urlWithTenancyId, - finishUrl: `/s/default/app/home?${TENANCY_QUERY_STRING_KEY}=*`, - spacePrefix: '' - }); - - KibanaNavigation.openKibanaNavigation(); - KibanaNavigation.checkIfNotVisible('Stack Management'); - }); } From e355ed00d04fd4d89db0aeb23351f1e0b9ae4f25 Mon Sep 17 00:00:00 2001 From: Simone Scarduzio Date: Sat, 12 Sep 2026 14:35:23 +0000 Subject: [PATCH 11/26] Remove the dead gap between the two halves of one key press 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 --- e2e-tests/cypress/support/page-objects/KibanaNavigation.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/e2e-tests/cypress/support/page-objects/KibanaNavigation.ts b/e2e-tests/cypress/support/page-objects/KibanaNavigation.ts index 1b9df609..8b141375 100644 --- a/e2e-tests/cypress/support/page-objects/KibanaNavigation.ts +++ b/e2e-tests/cypress/support/page-objects/KibanaNavigation.ts @@ -17,9 +17,10 @@ export class KibanaNavigation { static openKibanaNavigation() { cy.log('openKibanaNavigation'); - // Clear any overlays by pressing ESC prior to opening nav + // Clear any overlays by pressing ESC prior to opening nav. The two events are one key press, + // so nothing has to happen between them; the 200ms that used to sit here was dead time on + // every call, and openPage calls this for every navigation in the suite. cy.get('body').trigger('keydown', { keyCode: 27 }); - cy.wait(200); cy.get('body').trigger('keyup', { keyCode: 27 }); cy.get('[data-test-subj=toggleNavButton]').click({ force: true }); From 07893b4f8ef7bc6eff1912a56ba7ae674587b2b3 Mon Sep 17 00:00:00 2001 From: Simone Scarduzio Date: Sat, 12 Sep 2026 14:37:19 +0000 Subject: [PATCH 12/26] Name the retried tests on the run summary 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 --- e2e-tests/cypress/plugins/index.ts | 34 ++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/e2e-tests/cypress/plugins/index.ts b/e2e-tests/cypress/plugins/index.ts index 7a60a0ca..ab5fd968 100644 --- a/e2e-tests/cypress/plugins/index.ts +++ b/e2e-tests/cypress/plugins/index.ts @@ -236,6 +236,40 @@ module.exports = (on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions) } }); + // A test that needed a second or third attempt still passed, so nothing in the run output says + // it was retried โ€” and a green leg can be paying a full extra spec for it. Report every retried + // test on the run summary, so the flaky ones can be named instead of guessed at. + // + // GITHUB_STEP_SUMMARY is unset outside Actions and this then does nothing. Best effort: a broken + // report must never fail a suite that passed. + on('after:spec', async (spec, results) => { + const summaryFile = process.env.GITHUB_STEP_SUMMARY; + if (!summaryFile || !results || !results.tests) return; + + const retried = results.tests + .map(test => ({ + title: (test.title || []).join(' > '), + attempts: (test.attempts || []).length, + state: test.state + })) + .filter(test => test.attempts > 1); + + if (retried.length === 0) return; + + const rows = retried + .map(test => `| \`${path.basename(spec.relative)}\` | ${test.title} | ${test.attempts} | ${test.state} |`) + .join('\n'); + + try { + await fs.promises.appendFile( + summaryFile, + `\n\n| spec | test | attempts | final |\n| --- | --- | --- | --- |\n${rows}\n` + ); + } catch { + // best-effort reporting; never fail a green run over it + } + }); + // Discard the video for specs that finished with all tests passing. // Combined with `videoCompression: false` in cypress.config.ts, this keeps // failure-debug videos available while avoiding writing GBs of green-run From ee7e8f284f9da7807b3a938d05a2373fb0a9ce10 Mon Sep 17 00:00:00 2001 From: Simone Scarduzio Date: Sat, 12 Sep 2026 14:38:11 +0000 Subject: [PATCH 13/26] Install the ecommerce sample data once, not three times MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- e2e-tests/cypress/e2e/Tenancy.cy.ts | 141 +++++++++++++++------------- 1 file changed, 76 insertions(+), 65 deletions(-) diff --git a/e2e-tests/cypress/e2e/Tenancy.cy.ts b/e2e-tests/cypress/e2e/Tenancy.cy.ts index eaa0ccec..b96d7dc8 100644 --- a/e2e-tests/cypress/e2e/Tenancy.cy.ts +++ b/e2e-tests/cypress/e2e/Tenancy.cy.ts @@ -56,6 +56,82 @@ describe('Tenancy', () => { runTests({ callbackBeforeLogin: openAnotherTabs }); }); + // Out of runTests as well, but for a different reason. This one 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 exactly that under all three + // passes, through checkTenancyNameInBadge and verifyKibanaNavigationLinkItemHref. What is left + // here is the share panel itself, which the variants do not touch. It is also the most expensive + // test in the suite: it installs the ecommerce sample data, and it was doing so three times. + describe('share link', () => { + afterEach(() => { + kbnApiClient.deleteSampleData('ecommerce', userCredentials); + }); + + it('should copy link to specific visualization with tenancy information', () => { + const urlWithTenancyId = `/s/default/app/management/data/index_management/indices?${TENANCY_QUERY_STRING_KEY}=${Tenancy.encryptedTenancyWithTemplateGroup}`; + Login.initialization({ + visitedUrl: urlWithTenancyId, + finishUrl: urlWithTenancyId, + spacePrefix: '' + }); + + kbnApiClient.loadSampleData('ecommerce', userCredentials, 'template_group'); + cy.waitForNetworkIdle('*', 500, { timeout: 10000 }); + KibanaNavigation.openPage('Discover'); + if (semver.gte(getKibanaVersion(), '8.0.0')) { + cy.get('[data-test-subj="discover-dataView-switch-link"]', { timeout: 30000 }).should('exist'); + } else { + cy.get('[data-test-subj="indexPattern-switch-link"]', { timeout: 30000 }).should('exist'); + } + Discover.openShareDiscover(); + Discover.clickCopyLinkButton('admin'); + if (semver.gte(getKibanaVersion(), '8.0.0')) { + cy.getValueFromClipboard() + .should('contain', 'https://localhost:5601/s/default/app/r/s') + .should('contain', `?${TENANCY_QUERY_STRING_KEY}=${Tenancy.encryptedTenancyWithTemplateGroup}`); + } else { + cy.getValueFromClipboard().should( + 'contain', + `https://localhost:5601/s/default/app/discover?${TENANCY_QUERY_STRING_KEY}=${Tenancy.encryptedTenancyWithTemplateGroup}#` + ); + } + + Dashboard.openDashboard(); + Dashboard.openItem(0); + Dashboard.openShareDashboard(); + Dashboard.clickCopyLinkButton(); + + if (semver.gte(getKibanaVersion(), '8.0.0')) { + cy.getValueFromClipboard() + .should('contain', 'https://localhost:5601/s/default/app/r/s') + .should('contain', `?${TENANCY_QUERY_STRING_KEY}=${Tenancy.encryptedTenancyWithTemplateGroup}`); + } else { + cy.getValueFromClipboard().should( + 'contain', + `https://localhost:5601/s/default/app/dashboards?${TENANCY_QUERY_STRING_KEY}=${Tenancy.encryptedTenancyWithTemplateGroup}#/` + ); + } + if (semver.lt(getKibanaVersion(), '8.0.0')) { + Dashboard.backToShareDashboard(); + } + Dashboard.clickEmbedTab(); + Dashboard.clickCopyEmbedCodeButton(); + + if (semver.gte(getKibanaVersion(), '8.0.0')) { + cy.getValueFromClipboard().should( + 'contain', + `