Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Copy to .env and adjust. Non-secret config (images, host paths, ports, users).
# Secrets (license, cloud storage creds) go in .env.secret — see .env.secret.example.

# --- Lakekeeper images ---
LK_IMAGE_OSS=quay.io/lakekeeper/catalog:latest-main
LK_IMAGE_PLUS=quay.io/vakamo/lakekeeper-plus:latest

# --- Host paths (point these at your local checkouts / CI workspace) ---
KEYCLOAK_REALM_FILE=../lakekeeper/examples/access-control-simple/keycloak/realm.json
# Self-contained Cedar policy dir shipped with this repo (the cedar access-control
# test mutates policies.cedar live). Don't point it at a real policy dir.
CEDAR_POLICY_DIR=./cedar

# --- App locations + ports (the apps that consume this framework) ---
CONSOLE_DIR=../console
CONSOLE_PLUS_DIR=../console-plus
# Run the apps on :3001 — the AWS demo bucket's CORS allows that origin, so the
# in-browser LoQE (DuckDB-WASM) Iceberg reads/writes work. A different port breaks
# the storage CORS (the CORS test proves :3002 is blocked).
APP_PORT=3001

# --- Test users (from the iceberg realm; peter is the instance admin) ---
TEST_USERNAME=peter
TEST_PASSWORD=iceberg
TEST_USER_EMAIL=peter@example.com
TEST_ADMIN_USERNAME=peter
TEST_ADMIN_PASSWORD=iceberg
TEST_ADMIN_EMAIL=peter@example.com
46 changes: 46 additions & 0 deletions .env.secret.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Copy to .env.secret (gitignored). Local SeaweedFS S3 needs nothing here.
# Each cloud backend activates only when ALL its vars are set; otherwise skipped.
# In GitHub these come from Actions secrets.

# --- cedar mode (Plus) ---
LAKEKEEPER__LICENSE__KEY=
NPM_GITHUB_TOKEN=

# --- AWS S3 (use a throwaway, least-privilege key — never a real prod key) ---
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_S3_BUCKET=vk-lakekeeper-demo
AWS_REGION=us-east-1
# optional (STS / remote-signing profile)
AWS_KEY_PREFIX=
AWS_STS_ROLE_ARN=

# --- Cloudflare R2 ---
R2_ACCESS_KEY_ID=
R2_SECRET_ACCESS_KEY=
R2_ACCOUNT_ID=
R2_BUCKET=

# --- Azure ADLS ---
ADLS_ACCOUNT_NAME=
ADLS_FILESYSTEM=
ADLS_CLIENT_ID=
ADLS_CLIENT_SECRET=
ADLS_TENANT_ID=

# --- Microsoft OneLake ---
ONELAKE_ACCOUNT_NAME=
ONELAKE_FILESYSTEM=
ONELAKE_CLIENT_ID=
ONELAKE_CLIENT_SECRET=
ONELAKE_TENANT_ID=

# --- Google GCS ---
GCS_BUCKET=
GCS_SERVICE_ACCOUNT_KEY=

# --- Local SeaweedFS S3 (defaults; override only if you change s3.json) ---
# S3_LOCAL_ACCESS_KEY=lakekeeper
# S3_LOCAL_SECRET_KEY=lakekeeper-secret
# S3_LOCAL_BUCKET=lakekeeper-test
# S3_LOCAL_ENDPOINT=http://seaweedfs:8333
37 changes: 37 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: ci

# Self-CI for the test framework itself — keeps console-e2e's own code valid.
# It does NOT run the matrix (that needs a Lakekeeper stack, the Plus image, and
# secrets — that's the separate release gate). This is fast (~1 min), no stack,
# no secrets: typecheck, syntax-check the orchestration, and confirm the specs +
# dynamic Playwright config load for both webServer branches.

on:
push:
branches: ['**']
pull_request:

jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Install
run: npm ci

- name: Typecheck (specs + playwright.config)
run: npx tsc --noEmit -p tsconfig.json

- name: Syntax-check orchestration scripts
run: node --check run.mjs && node --check dashboard.mjs && node --check catalog.mjs && node --check reporters/current.mjs

- name: Specs + config load (authn = dual webServer, cedar = premium path)
run: |
APP=console TEST_MODE=authn npx playwright test --list
APP=console-plus TEST_MODE=cedar npx playwright test --list
18 changes: 18 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
node_modules/
.env
.env.secret
test-results/
playwright-report/
blob-report/
reports/
results/
history/
TEST-CATALOG.html
DASHBOARD.html
.cache/
coverage/
monocart-report/
*.log
loqe-debug.png
TEST-CATALOG.md
.raw/
83 changes: 83 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Agent Instructions — console-e2e

E2E test engine for the Lakekeeper consoles (`console`, `console-plus`). It brings up
a real Lakekeeper stack via **podman compose**, serves the app with Playwright's
`webServer`, and runs browser journeys across a matrix of **app × auth-mode ×
browser**. Read [README.md](README.md) first for the user-facing overview.

## Architecture (where things live)

- **`run.mjs`** — the orchestrator. Loops `app × mode`; per combo: `compose down -v`
→ up infra → `migrate` → serve lakekeeper → poll `/health` → `runPlaywright()` →
cross-browser passes → archive + rebuild dashboard → teardown. Also runs the
component **unit tests** once up front (`runUnitTests()`).
- **`docker-compose.yml`** — Postgres, Keycloak (`:30080`), OpenFGA, SeaweedFS
(`:8333`) + bucket-init, Lakekeeper (`:8181`). Image + `modes/<mode>.env` swapped
per combo. Postgres/OpenFGA publish **no host ports** (avoid clashes).
- **`modes/<mode>.env`** — backend `LAKEKEEPER__*` (container) + `VITE_*` (build-time
app flags). `noauth`/`authn`/`authz`(OpenFGA)/`cedar`(Cedar, premium).
- **`playwright.config.ts`** — dynamic from `APP`/`TEST_MODE`/`BROWSER` env. Loads the
mode's `VITE_*` into the dev-server env, grep-filters specs by `@<mode>` tag,
launches the app on `APP_PORT`. A **second `:3002` webServer** starts only in
`authn` (for the CORS test). `reuseExistingServer:false` + `--strictPort`.
- **`specs/`** — `_data/` (storage backends), `_fixtures/` (auth + coverage),
`_utils/` (login, warehouse, loqe, permissions, cedar, app helpers), and the
journeys by area.
- **`dashboard.mjs` / `catalog.mjs`** — build `DASHBOARD.html` / `TEST-CATALOG`.
`reporters/current.mjs` writes `results/current.json` (live test name for the
dashboard banner).

## Conventions

- **Tags drive selection.** Every `test.describe` is tagged with the modes it applies
to: `@noauth @authn @authz @cedar`. `run.mjs`/config grep by `@<mode>`. Untagged →
never runs. `@smoke` is the cross-browser (firefox/webkit) subset. Access-control is
`@authz` (OpenFGA, UI grant) and a separate `@cedar` block (policy-file grant).
- **Fixtures**: use `bootstrappedPage` (logged in + server bootstrapped, peter) from
`_fixtures/auth.fixture.ts`. A second user `anna` (non-admin) is `TEST_USER_2`.
- **Helpers, not inline flows**: warehouse create/open/namespace → `_utils/warehouse.ts`;
LoQE attach/exec/create+read → `_utils/loqe.ts`; grants → `_utils/permissions.ts`
(FGA UI) / `_utils/cedar.ts` (policy file); recover from the false offline page →
`_utils/app.ts`. Seeding helpers are **idempotent** (combos share backend state).
- **Robust selectors**: prefer `getByRole`/`getByText`. The Vuetify Permissions v-tab
resets while data loads — click until `aria-selected=true`. A restricted user's
first calls 401 during token hydration — reload until the warehouse appears.
- **Per-action timeout** is capped globally (`use.actionTimeout`) so a stuck click
fails fast and retries instead of eating the test timeout.

## Hard-won gotchas (don't relearn these)

- **podman, not docker** — `docker` is a shell alias invisible to `spawn`; `run.mjs`
uses `podman compose`.
- **App must run on `:3001`** — the AWS demo bucket's CORS allows that origin; LoQE
browser→S3 reads/writes fail on any other port. `--strictPort` enforces it.
- **AWS LoQE writes need an STS-enabled warehouse** (`sts-enabled` + `sts-role-arn` +
key-prefix). Plain access-key creds write to the bucket root and 404. A green
`CREATE` is not proof — assert the read-back.
- **Split-horizon SeaweedFS** — single host-LAN-IP endpoint for browser + container.
- **CORS error wording is browser-specific** — chromium says "…CORS/404", firefox says
"Cannot read N bytes from memory buffer". `console-components` LoQEEngine maps both
to a friendly message; the CORS test asserts the friendly message (don't tighten to
a chromium-only string).
- **Multi-statement SQL** makes one result tab per statement — run one statement at a
time and settle on the "Running query…" spinner disappearing.

## Adding things

- **A test**: new `specs/<area>/<name>.spec.ts`, tag the describe with applicable
modes, reuse `_utils` helpers + `bootstrappedPage`. Add its path to `SPEC_ORDER` in
both `dashboard.mjs` and `catalog.mjs` for journey ordering.
- **A mode**: add `modes/<mode>.env` + wire it in `run.mjs` (`ALL_MODES`, `APP_MODES`,
`SERVICES`) and `dashboard.mjs` (`MODE_LABEL`).
- **A storage backend**: add an entry to `specs/_data/storage-backends.ts`
(skip-if-absent via its env creds; `deepFlows` gates browser-reachable flows).

## Rules

- **Never commit secrets.** `.env` / `.env.secret` are git-ignored; `*.example` files
hold placeholders only. Generated artifacts (`coverage/`, `results/`, `reports/`,
`DASHBOARD.html`, `TEST-CATALOG.*`) are ignored — don't commit them.
- **Validate before a matrix run**: `node --check run.mjs dashboard.mjs`, and
`npx playwright test --list` to confirm the config + reporters load.
- Changes to `console-components` (a sibling repo) go via its **PR workflow**, not a
direct push, and need the `BEGIN_COMMIT_OVERRIDE` block.
Loading
Loading