Files
rustguac/tests/e2e
Dave Kempe 2dec42d019 recordings: download as MP4, converted on request with guacenc (#241)
A .guac recording only plays in rustguac's own player. The Recordings
page now has an mp4 button per recording: it starts a conversion,
shows progress, and downloads the video when ready. The file is a
standard MP4 that plays in VLC and browsers.

- POST /api/recordings/:name/mp4 starts it (202 running, 200 ready,
  409 while the session is live, 503 without guacenc),
  GET .../mp4/status reports it, GET .../mp4 downloads it. Same access
  and name checks as the recording download (poweruser or admin).
- guacenc writes <input>.m4v beside its input, so each job links the
  recording into a work directory inside the cache and moves the
  finished file to <cache>/<stem>.mp4; the recording directory is never
  written. guacenc exits 0 even on failure, so success is the output
  existing and not being empty. Runs with a timeout, killed on timeout.
- At most mp4_max_jobs at once (default 1). The video is the
  recording's own size (its largest layer 0 size), scaled down to fit
  1920x1080 and rounded to even.
- Cached MP4s go after mp4_cache_hours (24), when their recording goes,
  and all of them before rotation removes any recording for disk space.
  Leftover work directories are cleared at startup and hourly.
- The listing carries each recording's mp4 state only when conversion
  is available, so the button is hidden on a build without guacenc.

Config: mp4_enabled, guacenc_path, mp4_cache_path, mp4_cache_hours,
mp4_max_jobs, mp4_timeout_secs, mp4_bitrate, mp4_max_width/height under
[recording]. Docs in configuration.md (MP4 downloads, including
converting by hand) and api.md, which also had the recordings role as
operator; it is poweruser.

Tests: the converter (cache layout, failure leaves nothing, live and
missing refused, purge, size parsing and fitting) with a stand-in
guacenc, the shared access check, and a browser spec for the button.
2026-10-18 13:23:14 +11:00
..

End-to-end tests

Playwright specs that drive the real pages in static/ against a stubbed API. Nothing here needs Vault, guacd, Docker or a browser session: serve.mjs serves the static tree over HTTP and every spec installs lib/api.mjs, which answers /api/* from a small fixture address book (two named tenants and twenty generic ones, two sessions, two recordings) and records every request the page makes so a spec can assert on what was sent.

cd tests/e2e
npm ci
npx playwright install chromium   # once
npm test                          # all specs
npx playwright test specs/global-search.spec.mjs
npx playwright test --ui          # step through a spec

PW_CHROMIUM=/path/to/chrome runs against an already installed Chromium instead of the one Playwright downloads. CI runs the suite in the e2e job.

One spec per change, named after the branch that introduced it:

spec pins
tables-1710 #252: no horizontal overflow at 1710px on Connections, Recordings and Reports, with short and with long unbreakable names
connections-sidebar #254: independent sidebar scroll, selected row marked and kept in view
connections-folder-filter #254: live filter box, ARIA tree, arrow keys, f shortcut
connections-app-shell #254: clipped body, two scroll panes, folder change scrolls the pane not the page
role-nav one nav on every page, gated by data-role-level
sessions-adhoc-modal ad-hoc form opens as a modal
facet-filters shared facets narrow lists, persist, combine with search
folder-display-name display name over the key, modal field round-trips
themed-dialogs rgConfirm/rgAlert replace native dialogs
global-search / opens the overlay on every signed-in page, folder view untouched; nothing on the login page, for anonymous or viewer callers, or from a previous login's cache
search-under-modals a page modal covers the search box
zero-click-fullscreen #257: same-tab iframe sized from the measured viewport
recordings-mp4 #241: the mp4 button converts, shows progress and downloads; refused while live; hidden without server support

The fixture suppresses the first-visit onboarding tour (options.onboarding re-enables it) and defaults to the admin role; pass role to test gating.