vaadin / vaadin/browserless-test

Browserless simulators for browser features (Screen Orientation, Screen Lock etc)

Open
#166 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Java
Stars
1
Forks
3
Avg merge
1d 20h
Merged PRs (30d)
51

Description

Extend browserless trigger/action simulation to Flow's other browser features

Overall idea

The feat/trigger-actions-tester-api branch added browserless-test support for Flow's client-side trigger/action framework (com.vaadin.flow.component.trigger.internal), with Clipboard as the first vertical slice. That framework runs client-only JS (via Element.addJsInitializer) and is therefore invisible to browserless's server-side click()/fireDomEvent; the approach bridges it without a JS engine via flow-server seams (PromiseAction.deliverSuccess/deliverError, CallbackAction.deliver, Action.Input.evaluate, "for browserless simulation" input getters) consumed by a browserless SPI (TriggerSimulation + ActionSimulator + per-UI *Simulator state holders).

Flow ships four more browser features on this same framework — Fullscreen, Web Share, Download, Open-in-new-tab — plus framework-level generics (double-click, CallbackAction/SetSignalAction, the UI.triggerAfter timer, resize) that no simulator covers yet. This issue tracks extending the existing SPI to all of them, reusing the established *Simulator + *ActionSimulators patterns, landed incrementally in phases (Fullscreen+WebShare → Download+OpenInNewTab → framework generics → resize).

Per-feature analysis
Feature Simulatable? flow-server seam needed What a test can assert
Fullscreen ✅ outcome + state signal none (setStateFromClient/exit/stateSignal already public) onSuccess/onError ran; stateSignal().peek()
Web Share ✅ outcome + recorded content ShareAction input getters onShared/onError ran; recorded share content; support signal
Download ✅ record URL/filename + capture handler bytes DownloadAction getters + HandlerBinding.evaluate download initiated, resolved URL/filename, served bytes
Open in new tab ✅ record args OpenInNewTabAction getters + reusable isBlockedUrl tab requested, URL, features, javascript: blocked
Double-click ✅ trivially none (dblclick already matches fire) same as click, on dblclick
CallbackAction / SetSignalAction ✅ callback runs / signal set CallbackAction.deliverFromEvent callback side-effect; signal.get()
UI.triggerAfter (timer) ✅ task runs when fired none beyond deliverFromEvent deferred task ran after advanceTimers()
Resize (SizeTrigger) ⚠️ only if wired to a server-effect action SizeTrigger.EventData.size.evaluate callback/signal receives synthetic {width,height}
SetPropertyAction ❌ pure client-side, no server effect skip (unregistered = correct no-op)
Limitations
  • No real browser state. Actual fullscreen DOM, the fullscreen component-move-into-wrapper reparent, the native share sheet, and whether a file was truly saved / a tab truly opened cannot be observed server-side — only the server-initiated effect and the promise outcome.
  • Outcome is chosen, not inferred. The user-gesture requirement can't be reproduced (a simulated click always "has" a gesture), and success-vs-AbortError for Web Share must be selected by the test rather than derived.
  • Download byte capture works only for the DownloadHandler flavour (resolves to an ElementStreamResource); a plain-URL / blob: / external download records the URL string only. Input-derived URLs are observable only when backed by a server-synced PropertyInput.
  • Signals start UNKNOWN. Bootstrap params (v-fs, v-ws) never run in browserless, so stateSignal()/supportSignal() must be seeded via the simulator.
  • Timer has no elapsed-time semantics — the harness fires it explicitly; Duration is ignored.
  • Resize requires the harness to invent width/height and is only meaningful when wired to a server-effect action (SetPropertyAction has no server effect).
  • SignalInput-sourced callbacks remain unsimulatable (evaluate not overridden).
Out of scope (different mechanism, future work)

Flow browser APIs that do not use the trigger/action framework — WebStorage.getItem, WakeLock.request, ScreenOrientation.lock, Page.fetchCurrentURL, ExtendedClientDetails.refresh — would need a different seam (resolving PendingJavaScriptInvocation round-trips or seeding ExtendedClientDetails), and are not planned here.

Browserless simulation for Flow's non-trigger browser features

Overall idea

These are the Flow browser features that do not ride the client-side trigger/action framework (the follow-up set to Clipboard/Fullscreen/WebShare/Download etc.). They reach the browser through three other mechanisms:

  • Client JS round-tripsexecuteJs(...).then(...) / .toCompletableFuture(...), where the client must post a JSON value back to resolve the callback/future.
  • Bootstrap-parameter seedingExtendedClientDetails and several capability/availability signals are populated from bootstrap request params (v-sw, v-tzid, v-pv, v-ww, v-ga, …), which browserless never runs.
  • Event-push — the server registers a listener/handler that the browser later invokes (focus/blur, window resize, page visibility, history state change, drag & drop).

Key finding: browserless today never resolves pending JS invocationsMockVaadin.clientRoundtrip() drains the UI queue but never dumps the JS queue, so every .then(...)/CompletableFuture on an executeJs silently never fires. The one new primitive this effort needs is a per-UI JavaScriptExecutionSimulator that captures UIInternals.dumpPendingJavaScriptInvocations() and calls complete(json)/completeExceptionally(json) on them, then drains the UI queue. Everything else layers on top of that plus already-public state setters.

Headline: unlike the trigger/action features (which each needed a small flow-server seam), this entire family is buildable on public flow-server API — zero flow-server changes required. All simulators follow the shipped ClipboardSimulator/GeolocationSimulator convention (per-UI, current()/forUI(UI), Serializable), placed in the matching com.vaadin.flow.component.* package to reach package-private members.

Per-feature analysis
Feature Simulatable? Mechanism flow-server seam
executeJs round-trip (generic) new JavaScriptExecutionSimulator (dump + complete) none
Page.fetchCurrentURL / fetchPageDirection resolve via the primitive none
WebStorage (getItem + set/remove/clear) in-memory WebStorageSimulator; auto-resolves getItem none
WakeLock (request / active / availability) resolve round-trip + public UIInternals.setWakeLock* none
ScreenOrientation (lock / unlock / signal) resolve round-trip + UIInternals.setScreenOrientationFromClient none
Focus / blur ⚠️ partial request = record-only; focus/blur events = synthetic DomEvent none
Element.scrollIntoView ⚠️ record-only match pending-JS expression none
Drag & drop direct state reads + synthetic dragstart/drop/dragend none
ExtendedClientDetails (all fields) seed via public updateFromValues none
Env signals (visibility, window size, availability) seed via updateFromValues + synthetic resize/visibility events none
History (reverse: HistoryStateChangeEvent) invoke registered handler directly none
History (outbound: pushState/back/forward/go) ⚠️ low-value / fragile match pending-JS expression none
Page nav (open / reload / setLocation) ✅ (mostly already) existing MockPage recording (openUnsafe is a gap) none
Limitations
  • No JS actually runs. Round-trip return values are synthesized by the test; expressions whose later steps depend on real side effects aren't modeled.
  • Round-trip and signal state are independent channels. For WakeLock/ScreenOrientation a "grant" resolves the promise, but the active/orientation signal must be moved separately (mirrors production, where a DOM event — not the promise — flips the signal).
  • focus()/blur()/scrollIntoView/outbound History have no server outcome — a test can only assert they were requested, by matching pending-JS expression strings (brittle; the pushState template even differs between React and legacy branches). Real focus-success is unobservable. The reverse-direction APIs (fire focus/blur events; fire HistoryStateChangeEvent) are the real deliverables.
  • Drag & drop couples to Flow-internal @EventData expression strings (no public constants like Clipboard.PASTE_TEXT_EXPR) — the main fragility; browser effectAllowed/dropEffect gating that would allow/prevent a drop is not enforced (a simulated drop always fires).
  • WebStorage is per-UI, not per-origin/tab — cross-UI localStorage sharing isn't modeled; the CompletableFuture form requires resolve-then-get ordering (the deadlock detector trips otherwise).
  • ExtendedClientDetails.refresh() won't fire in browserless (no browser to answer) — seed first so retrieveExtendedClientDetails takes its synchronous branch; timezone-offset/devicePixelRatio encoding quirks must be hidden behind a typed builder.
One design decision to make

Auto vs. explicit pending-JS resolution. For zero-boilerplate WebStorage, capture+auto-resolve at the client boundary (one line in MockVaadin.clientRoundtrip() — the only Kotlin touch, and it's behavior, not new installed state). Alternatively keep it fully explicit (test calls flush()/pending()); no Kotlin change. Recommendation: do the one-line auto-capture and also expose the explicit API.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading the existing ClipboardSimulator/GeolocationSimulator convention, then trace TriggerSimulation, ActionSimulator, MockVaadin.clientRoundtrip(), and UIInternals.dumpPendingJavaScriptInvocations(). Break the work into the listed feature phases, adding per-UI simulators and coverage for the stated observable outcomes while preserving the documented limitations and deciding whether pending JavaScript resolution is automatic, explicit, or both.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, kotlin
Domain
devtools, testing-qa
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.