Skip to content

Journey runner: a phone device per journey, offline/online steps and a GPS fix per step (patch offered) #521

Closed

Description

@AlvinnYeo

Files: project-tests/e2e/journey-runner.js, project-tests/e2e/journey-runner.selftest.js, examples/validate-journeys.js (ACTIONS, line 32), skills/journey-examples.md · Toolkit: eec646e · Found: 20 Oct 2026 · Patch: offered (written and self-tested in a project copy of the runner)

Observed

The runner opens every journey in one 1440x900 desktop window. journey-runner.js calls H.launchBrowser() with no
arguments, and config.js sets viewport: { width: 1440, height: 900 }. Three things follow for a Mendix PWA:

  1. No phone profile. Mendix serves the Responsive profile to a desktop window, not Phone or PhoneOffline. A phone
    journey fails at step 1 for a test-setup reason, not because the feature is broken. helpers.launchBrowserAt(..., { mobile: true })
    has a real Playwright device profile, but the runner never calls it.
  2. No way to go offline. No file contains setOffline. On one project, 4 journeys put a placeholder wait where the
    browser must go offline or come back online:
    { "do": "wait", "label": "SET THE BROWSER OFFLINE HERE (context.setOffline(true))" }. A run of those journeys never
    actually leaves the network, so the "offline" steps measure the online app.
  3. No GPS position. A check-in nanoflow that reads the device position (NanoflowCommons
    GetCurrentLocationMinimumAccuracy) gets "permission denied". The step then fails because the test set no position.
    On that project, 10 steps across 9 journey files name a position in _geolocation, which the runner does not read.

20 of that project's 57 journey files declare "device": { "name": "iPhone 13", "viewport": { "width": 390, "height": 844 } },
and every runner result for them had to be ruled INVALID.

What the offered patch does

549 lines added and 7 changed across the runner and its selftest. Desktop journeys behave exactly as before: a journey
with no device or viewport still goes through H.launchBrowser() with no arguments.

Feature Journey field What the runner does
Phone device top level "device": "iPhone 13" or { "name", "viewport": { width, height } } Opens a context with the Playwright profile's user agent, isMobile, hasTouch and deviceScaleFactor. The size comes from device.viewport, then the profile's screen (390x844 for iPhone 13, not its 390x664 viewport). Refuses an unknown name and lists the nearest known ones; it never swaps in a neighbouring profile.
Phone by size top level "viewport": { width, height, "isMobile": true } Phone at that size, with the iPhone 13 user agent. Without isMobile: true it opens a desktop window of that size.
Run override JOURNEY_DEVICE=<name> or desktop Applies one device to every journey in the run.
One device per run (preflight) One run is one context and one sign-in. A run whose journeys need different devices exits 2 before the browser opens, with one SETUP line listing which journeys need which device.
Device measured (after launch, before sign-in) Reads the viewport, navigator.userAgent and maxTouchPoints on about:blank and prints them in the run header. A mismatch exits 2 before any sign-in. The device is also written to the findings file and to each walk.
Offline / online step action { "do": "offline" } / { "do": "online" } Calls context.setOffline(true / false). The legacy placeholder (a wait whose label contains context.setOffline(true|false)) is also honoured, with a WARN asking for it to be rewritten.
Back online at the end (after every journey, and before the sign-out) A journey still offline at the end is put back online. If it walked every step, it gets a setup INVALID row ("never declared online"). The session release goes online before it calls /xas/ logout, so an offline run cannot leak a licence session.
GPS fix "geolocation": { latitude, longitude, accuracy } on a step or on the journey; {{seed}} placeholders allowed Grants the browser's geolocation permission once, then calls context.setGeolocation(...) before the step's actions. The fix holds until the next one. Permission and position are cleared between journeys. An unresolved placeholder, a value that is not a number, or an out-of-range coordinate is INVALID and stops the journey. It is never sent as 0,0.
_geolocation same shape Read when geolocation is absent, for files written before the runner supported the field. It prints a WARN: validate-journeys.js treats _ keys as comments and does not check their placeholders.

New exports, for the selftest: resolveDevice, runDevice, contextOptions, deviceLabel, judgeDevice, networkVerb,
setNetwork, isOffline, ensureOnline, networkEndRow, resolveGeolocation, applyGeolocation, clearGeolocation,
act, describeAct.

Evidence

  • Selftest: node journey-runner.selftest.js passes 56 of 56 checks, with no app, no browser and no sign-in. Act runs
    against a fake page whose locator() throws, which proves a network step never touches the page. The checks that must
    fail do fail: an unknown device, a mixed run, a 1440x900 context for a phone journey, a desktop user agent or no touch on
    a phone, an unresolved {{placeholder}}, latitude 95, an empty-string latitude. Static checks over the project's 57
    journey files: every declared device opens 390x844; 4 files with offline steps all end online; 10 GPS fixes use only
    seeds their own file declares.
  • Real browser, no app: headless Chromium with the runner's phone options reported viewport 390x844, the iPhone user
    agent, maxTouchPoints 1, pointer: coarse, and innerWidth 390 on a page with a viewport meta tag. Against a
    throwaway http://localhost server, the position read was "User denied" before the fix, the test coordinates after it,
    the new value after a second fix, and "User denied" again after the clear. An offline reload failed with
    net::ERR_INTERNET_DISCONNECTED, and the reload after ensureOnline worked.
  • Not measured when the patch was written: a live run against a Mendix app.
  • Observed, for whoever picks this up: on about:blank Chromium refuses geolocation ("Only secure origins are
    allowed"). http://localhost counts as a secure origin, so a local app works. An app on a plain-http hostname other
    than localhost will not get a position.

Asks

  1. Take the runner patch, or something with the same contract. We can open it as a PR against
    project-tests/e2e/journey-runner.js and its selftest.
  2. validate-journeys.js: add offline and online to ACTIONS, which today rejects them with "unknown action … the
    runner throws". Placeholders in geolocation are already checked (the key has no _ prefix). Optionally flag
    _geolocation and the legacy labelled wait as "rename to the supported field". (Detail, plus a step-tag check, in the
    companion validator issue.)
  3. Document device, viewport, JOURNEY_DEVICE, offline/online and geolocation in skills/journey-examples.md,
    together with the one-device-per-run rule.

Activity

  1. MendixMau commented on Oct 26, 2026

    @MendixMau
    Owner

    Fixed in #578 (squash a088b4e). The patch wasn't attached, so this was written from the issue's contract (credited to you). It adds:

    • a phone device per journey (device, viewport, JOURNEY_DEVICE); a mixed-device run exits 2;
    • offline / online steps;
    • geolocation per step or per journey.

    All opt-in. Tested in headless Chromium, not yet on a live Mendix app.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions