Skip to content

Commit 3060c1b

Browse files
committed
ci: run e2e test against k8s deployment
1 parent 2f1946e commit 3060c1b

2 files changed

Lines changed: 209 additions & 11 deletions

File tree

‎.github/workflows/k8s.yml‎

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,3 +443,172 @@ jobs:
443443
WITH_REMOTE_PHP: "false"
444444
K8S: ${{ env.K8S }}
445445
run: make test-acceptance-api
446+
447+
e2e-tests:
448+
name: e2e-${{ matrix.suite }}-k8s
449+
runs-on: ubuntu-latest
450+
timeout-minutes: 60
451+
452+
if: >-
453+
github.event_name == 'schedule' ||
454+
github.event_name == 'workflow_dispatch' ||
455+
(github.event_name == 'pull_request' &&
456+
contains(github.event.pull_request.title || '', 'k8s'))
457+
458+
strategy:
459+
fail-fast: false
460+
matrix:
461+
include:
462+
- suite: smoke
463+
464+
steps:
465+
- name: Checkout
466+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
467+
468+
- name: Setup Node.js
469+
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
470+
with:
471+
# 24.16.0 breaks playwright install (yauzl zip extraction hangs); pin to last known good.
472+
# https://github.com/microsoft/playwright/issues/40724
473+
node-version: "24.15.0"
474+
475+
- name: Enable pnpm
476+
run: |
477+
corepack enable && corepack prepare pnpm@10.33.3 --activate
478+
pnpm config set store-dir ./.pnpm-store
479+
480+
- name: Cache Playwright Chromium
481+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
482+
with:
483+
path: ~/.cache/ms-playwright
484+
key: playwright-chromium-${{ hashFiles('web/pnpm-lock.yaml') }}
485+
486+
- name: Download k3d
487+
run: |
488+
curl -L \
489+
-o k3d \
490+
https://github.com/k3d-io/k3d/releases/download/${{ env.K3D_VERSION }}/k3d-linux-amd64
491+
echo "dbaa79a76ace7f4ca230a1ff41dc7d8a5036a8ad0309e9c54f9bf3836dbe853e k3d" | sha256sum --check
492+
chmod +x k3d
493+
sudo mv k3d /usr/local/bin/
494+
k3d version
495+
496+
- name: Install Helm
497+
run: |
498+
curl -fsSL -o helm-${{ env.HELM_VERSION }}-linux-amd64.tar.gz https://get.helm.sh/helm-${{ env.HELM_VERSION }}-linux-amd64.tar.gz
499+
tar -zxvf helm-${{ env.HELM_VERSION }}-linux-amd64.tar.gz
500+
sudo mv linux-amd64/helm /usr/local/bin/helm
501+
helm version --short
502+
503+
- name: Prepare hosts
504+
run: |
505+
echo "127.0.0.1 $TEST_SERVER_DOMAIN clamav email collabora onlyoffice fakeoffice tika $FED_SERVER_DOMAIN keycloak" \
506+
| sudo tee -a /etc/hosts
507+
508+
- name: Spin up K3d Cluster
509+
run: make -C tests/config/k8s create-cluster
510+
511+
- name: Prepare Helm Charts
512+
run: make -C tests/config/k8s prepare-charts
513+
514+
- name: Deploy oCIS
515+
run: |
516+
cd tests/config/k8s
517+
kubectl get pods -n "$TEST_SERVER_DOMAIN" -Aw &
518+
make deploy-ocis
519+
520+
- name: Wait for oCIS to be ready
521+
env:
522+
FILE_URL: ${{ env.TEST_SERVER_URL }}/remote.php/dav/files/admin/hello1.txt
523+
run: |
524+
kubectl wait \
525+
--namespace "$TEST_SERVER_DOMAIN" \
526+
--for=condition=Available \
527+
deployment \
528+
--all \
529+
--timeout=10m
530+
531+
kubectl wait \
532+
--namespace "$TEST_SERVER_DOMAIN" \
533+
--for=condition=Ready \
534+
pod \
535+
--all \
536+
--timeout=10m
537+
538+
kubectl get pods -n "$TEST_SERVER_DOMAIN"
539+
540+
retry() {
541+
local label=$1
542+
shift
543+
for i in {1..30}; do
544+
if "$@"; then
545+
echo "$label succeeded"
546+
return 0
547+
fi
548+
echo "$label attempt $i failed, retrying in 10s..."
549+
sleep 10
550+
done
551+
echo "$label failed after 30 attempts"
552+
return 1
553+
}
554+
555+
echo "Creating test file in oCIS..."
556+
557+
request() {
558+
local method=$1
559+
shift
560+
curl -ks -o /dev/null -w "%{http_code}" \
561+
-X "$method" \
562+
-u admin:admin \
563+
"$@" \
564+
"$FILE_URL"
565+
}
566+
put_file() {
567+
request PUT \
568+
-H "Content-Type: text/plain" \
569+
--data "Hello from GitHub Actions!"
570+
}
571+
delete_file() {
572+
request DELETE
573+
}
574+
check_status() {
575+
local expected=$1
576+
shift
577+
status=$("$@")
578+
[ "$status" = "$expected" ]
579+
}
580+
echo "Creating test file..."
581+
retry "create file (HTTP 201)" check_status 201 put_file || exit 1
582+
echo "Deleting test file..."
583+
retry "delete file (HTTP 204)" check_status 204 delete_file || exit 1
584+
585+
- name: Run e2e ${{ matrix.suite }} tests
586+
env:
587+
E2E_ARGS: "--suites ${{ matrix.suite }}"
588+
K8S: ${{ env.K8S }}
589+
TEST_SERVER_URL: ${{ env.TEST_SERVER_URL }}
590+
run: python3 tests/acceptance/run-e2e.py
591+
592+
- name: Upload Playwright traces
593+
if: failure()
594+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
595+
with:
596+
name: playwright-traces-${{ matrix.suite }}-k8s-${{ github.run_attempt }}
597+
path: web/reports/e2e
598+
retention-days: 7
599+
600+
- name: Upload a11y result
601+
if: ${{ !cancelled() }}
602+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
603+
with:
604+
name: playwright-a11y-result-${{ matrix.suite }}-k8s-${{ github.run_attempt }}
605+
path: web/reports/e2e/a11y-report.json
606+
retention-days: 7
607+
608+
- name: Upload oCIS logs
609+
if: always()
610+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
611+
with:
612+
name: ocis-logs-${{ matrix.suite }}-k8s-${{ github.run_attempt }}
613+
path: tests/config/k8s/logs
614+
retention-days: 7

‎tests/acceptance/run-e2e.py‎

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
55
Usage: E2E_ARGS='--run-part 1' python3 tests/acceptance/run-e2e.py
66
Optional: TIKA_NEEDED=true, KEYCLOAK_NEEDED=true
7+
Optional: K8S=true, TEST_SERVER_URL=<url> - run against an already-deployed
8+
k8s OCIS instance (see .github/workflows/k8s.yml) instead of starting one
9+
locally. All other *_NEEDED flags are ignored in this mode.
710
"""
811

912
import json
@@ -120,6 +123,43 @@ def main() -> int:
120123
file=sys.stderr)
121124
return 1
122125

126+
repo_root = Path(__file__).resolve().parents[2]
127+
web_dir = repo_root / "web"
128+
129+
if not (web_dir / "node_modules").exists():
130+
pkg_manager = json.loads((web_dir / "package.json").read_text()).get("packageManager", "pnpm")
131+
run(["npm", "install", "--silent", "--global", "--force", pkg_manager])
132+
subprocess.run(["pnpm", "config", "set", "store-dir", "./.pnpm-store"],
133+
cwd=web_dir, check=True)
134+
subprocess.run(["pnpm", "install"], cwd=web_dir, check=True)
135+
subprocess.run(["pnpm", "exec", "playwright", "install", "--with-deps", "chromium"],
136+
cwd=web_dir, check=True)
137+
138+
if os.environ.get("K8S", "").lower() == "true":
139+
# oCIS is already running as a Helm-deployed k8s cluster (see
140+
# .github/workflows/k8s.yml) -- unlike the flow below, there's no local
141+
# binary to build, init, or start. Just confirm it's up and point
142+
# Playwright straight at it.
143+
ocis_url = os.environ["TEST_SERVER_URL"]
144+
wait_for(lambda: ocis_healthy(ocis_url), 120, "ocis")
145+
print("ocis ready.")
146+
147+
playwright_env = {
148+
**os.environ,
149+
"BASE_URL_OCIS": ocis_url,
150+
"HEADLESS": "true",
151+
"RETRY": "3",
152+
"REPORT_TRACING": "false",
153+
"BROWSER": "chromium",
154+
}
155+
print(f"Running e2e: {e2e_args}")
156+
result = subprocess.run(
157+
["bash", "run-e2e.sh"] + shlex.split(e2e_args),
158+
cwd=web_dir / "tests/e2e",
159+
env=playwright_env,
160+
)
161+
return result.returncode
162+
123163
tika_needed = os.environ.get("TIKA_NEEDED", "").lower() == "true"
124164
keycloak_needed = os.environ.get("KEYCLOAK_NEEDED", "").lower() == "true"
125165
mfa_needed = os.environ.get("MFA_NEEDED", "").lower() == "true"
@@ -131,7 +171,6 @@ def main() -> int:
131171
# MFA mode runs ocis behind keycloak with TOTP enforced
132172
keycloak_needed = keycloak_needed or mfa_needed or vault_storage_needed
133173

134-
repo_root = Path(__file__).resolve().parents[2]
135174
ocis_bin = repo_root / "ocis/bin/ocis"
136175
wrapper_bin = repo_root / "tests/ociswrapper/bin/ociswrapper"
137176
# The federated suite must address the primary as localhost instead of
@@ -141,7 +180,6 @@ def main() -> int:
141180
# suite), which only lists localhost:9200.
142181
ocis_url = "https://localhost:9200" if federated_needed else "https://127.0.0.1:9200"
143182
ocis_config_dir = Path.home() / ".ocis/config"
144-
web_dir = repo_root / "web"
145183

146184
# build ocis + ociswrapper only if not already provided (e.g. via artifact)
147185
if not ocis_bin.exists():
@@ -150,15 +188,6 @@ def main() -> int:
150188
run(["make", "-C", str(repo_root / "tests/ociswrapper"), "build"],
151189
env={"GOWORK": "off"})
152190

153-
if not (web_dir / "node_modules").exists():
154-
pkg_manager = json.loads((web_dir / "package.json").read_text()).get("packageManager", "pnpm")
155-
run(["npm", "install", "--silent", "--global", "--force", pkg_manager])
156-
subprocess.run(["pnpm", "config", "set", "store-dir", "./.pnpm-store"],
157-
cwd=web_dir, check=True)
158-
subprocess.run(["pnpm", "install"], cwd=web_dir, check=True)
159-
subprocess.run(["pnpm", "exec", "playwright", "install", "--with-deps", "chromium"],
160-
cwd=web_dir, check=True)
161-
162191
# init ocis
163192
run([str(ocis_bin), "init", "--insecure", "true"])
164193
# tests/config/ci/app-registry.yaml maps its one mimetype to "FakeOffice", the

0 commit comments

Comments
 (0)