Skip to content

Commit ac30245

Browse files
Merge branch 'main' into off/873-close-db-connection-before-navigating-between-pages
2 parents d377d5d + 9b973b4 commit ac30245

181 files changed

Lines changed: 1364 additions & 826 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/actions/archive-test-results/action.yaml

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,46 @@ runs:
3030
name: ${{ inputs.platform }}-screenshots-${{ inputs.test-type }}
3131
path: ${{ inputs.workspace-path }}/maestro/images/actual/${{ inputs.platform }}/**/*.png
3232
if-no-files-found: ignore
33-
33+
34+
- name: Collect screenshot diffs
35+
# On a failed assertScreenshot, Maestro writes the visual diff (the most useful artifact
36+
# for triaging a mismatch) next to the baseline — but under a *doubled* path, e.g.
37+
# maestro/images/expected/ios/maestro/images/expected/ios/<name>_diff.png, because it
38+
# resolves the diff name against the baseline's own relative path. That nested location
39+
# was never matched by the screenshots glob above, so diffs were silently lost. Gather
40+
# any *_diff.png from wherever it landed into one flat dir for upload.
41+
if: always()
42+
shell: bash
43+
run: |
44+
dest="${{ inputs.workspace-path }}/maestro/images/diff/${{ inputs.platform }}"
45+
mkdir -p "$dest"
46+
find "${{ inputs.workspace-path }}/maestro/images" -name '*_diff.png' \
47+
-not -path "$dest/*" -exec cp -f {} "$dest/" \; 2>/dev/null || true
48+
ls -1 "$dest" 2>/dev/null || true
49+
50+
- name: Archive screenshot diffs
51+
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f #v7
52+
if: always()
53+
with:
54+
name: ${{ inputs.platform }}-diffs-${{ inputs.test-type }}
55+
path: ${{ inputs.workspace-path }}/maestro/images/diff/${{ inputs.platform }}/*.png
56+
if-no-files-found: ignore
57+
3458
- name: Archive artifacts
3559
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f #v7
3660
if: always()
3761
with:
3862
name: ${{ inputs.platform }}-artifacts-${{ inputs.test-type }}
3963
path: packages/pluggableWidgets/**/artifacts/
64+
if-no-files-found: ignore
65+
66+
# Per-flow videos are recorded for every flow but kept only for FAILED flows (passing
67+
# clips are deleted in helpers.sh to save storage), so on a fully-green shard this dir is
68+
# empty and the upload is a no-op (if-no-files-found: ignore).
69+
- name: Archive failure videos
70+
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f #v7
71+
if: always()
72+
with:
73+
name: ${{ inputs.platform }}-videos-${{ inputs.test-type }}
74+
path: maestro/videos/*.mp4
4075
if-no-files-found: ignore

.github/actions/create-native-bundle/action.yml

Lines changed: 0 additions & 47 deletions
This file was deleted.
Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,30 @@
11
name: "Setup Maestro"
2-
description: "Install and cache Maestro"
2+
description: "Install and cache a pinned version of Maestro"
3+
inputs:
4+
version:
5+
description: "Maestro CLI version to install (release tag is cli-<version>)"
6+
required: false
7+
default: "2.6.1"
38
runs:
49
using: "composite"
510
steps:
611
- name: "Cache Maestro"
712
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
813
with:
9-
path: $HOME/.local/bin/maestro
10-
key: maestro-${{ runner.os }}-v1
11-
14+
# Use ~ rather than $HOME: actions/cache does not expand environment variables.
15+
path: ~/.local/bin/maestro
16+
key: maestro-${{ runner.os }}-${{ inputs.version }}
17+
1218
- name: "Install Maestro"
1319
shell: bash
20+
env:
21+
MAESTRO_VERSION: ${{ inputs.version }}
1422
run: |
23+
set -euo pipefail
1524
if [ ! -f "$HOME/.local/bin/maestro/bin/maestro" ]; then
16-
mkdir -p $HOME/.local/bin
17-
curl -L "https://github.com/mobile-dev-inc/maestro/releases/latest/download/maestro.zip" -o maestro.zip
18-
unzip maestro.zip -d $HOME/.local/bin
19-
chmod +x $HOME/.local/bin/maestro/bin/maestro
25+
mkdir -p "$HOME/.local/bin"
26+
curl -fL "https://github.com/mobile-dev-inc/maestro/releases/download/cli-${MAESTRO_VERSION}/maestro.zip" -o maestro.zip
27+
unzip -q maestro.zip -d "$HOME/.local/bin"
28+
chmod +x "$HOME/.local/bin/maestro/bin/maestro"
2029
fi
21-
echo "$HOME/.local/bin" >> $GITHUB_PATH
30+
echo "$HOME/.local/bin" >> "$GITHUB_PATH"

.github/actions/setup-node-with-cache/action.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,8 @@ runs:
2525
${{ runner.os }}-pnpm-store-
2626
2727
- name: "Install dependencies"
28-
run: pnpm install --frozen-lockfile
28+
run: pnpm install --frozen-lockfile --ignore-scripts
29+
shell: bash
30+
- name: "Building internal utilities"
31+
run: pnpm run build:internal-utils
2932
shell: bash

.github/actions/setup-python/action.yaml

Lines changed: 0 additions & 10 deletions
This file was deleted.

.github/actions/setup-tools/action.yaml

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,25 @@
11
name: "Setup Tools"
22
description: "Common setup for widget and js test jobs"
3-
inputs:
4-
mendix_version:
5-
description: "Mendix version"
6-
required: true
73
outputs:
84
java-path:
95
description: "Path to the installed Java"
106
value: ${{ steps.setup-java.outputs.path }}
117
runs:
128
using: "composite"
139
steps:
14-
- name: "Setup Python and dependencies"
15-
uses: ./.github/actions/setup-python
10+
# Test jobs need only Java + Maestro:
11+
# - the portable app package bundles the runtime, so there's no CDN runtime download and no
12+
# m2ee tooling to set up (and nothing here needs Python).
13+
# - Maestro is a standalone binary, so there's no Node/pnpm install — which would also run the
14+
# root `prepare` lifecycle the test jobs don't need (a known ENOTEMPTY flake source).
1615

17-
- name: "Setup Node and dependencies"
18-
uses: ./.github/actions/setup-node-with-cache
19-
2016
- name: "Setup Java 21"
2117
id: setup-java
2218
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 #v5.2.0
2319
with:
2420
distribution: "temurin"
2521
java-version: "21"
2622

27-
- name: "Cache Mendix runtime"
28-
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 #v5
29-
with:
30-
path: tmp/runtime.tar.gz
31-
key: mendix-runtime-${{ inputs.mendix_version }}
32-
33-
- name: "Cache m2ee-tools"
34-
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 #v5
35-
with:
36-
path: tmp/m2ee
37-
key: m2ee-tools-v1
38-
3923
- name: "Install Maestro"
4024
uses: ./.github/actions/setup-maestro
4125

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
name: "Start Mendix runtime (portable app)"
2+
description: "Extract the portable app package, start it in the background, and wait until it actually serves requests"
3+
inputs:
4+
app-package:
5+
description: "Path to the portable app package zip produced by 'mxbuild --target=portable-app-package'"
6+
required: false
7+
default: "app.zip"
8+
java-home:
9+
description: "JAVA_HOME to run the runtime with (defaults to the JAVA_HOME already in the environment)"
10+
required: false
11+
default: ""
12+
admin-pass:
13+
description: "Value for M2EE_ADMIN_PASS (secures the runtime admin port)"
14+
required: false
15+
default: "Password1!"
16+
runtime-url:
17+
description: "URL polled until the runtime responds, confirming readiness"
18+
required: false
19+
default: "http://localhost:8080/"
20+
timeout-seconds:
21+
description: "How long to wait for the runtime to become ready before failing"
22+
required: false
23+
default: "300"
24+
session-timeout-ms:
25+
description: >
26+
Server-side session idle timeout (ms). The runtime uses a trial license capped at
27+
6 concurrent named users, and every Maestro flow does `launchApp clearState: true`,
28+
which starts a FRESH server session while the previous one lingers for the full
29+
timeout. With Mendix's 10-min default, a widget with >5 flows piles past 6 sessions
30+
and the next login fails with "Maximum number of sessions exceeded". A short idle
31+
timeout lets each flow's session expire before the next ones accumulate. Active
32+
sessions aren't affected — client requests keep resetting the idle timer mid-flow.
33+
required: false
34+
default: "60000"
35+
runs:
36+
using: "composite"
37+
steps:
38+
- name: "Start runtime and wait until ready"
39+
shell: bash
40+
env:
41+
APP_PACKAGE: ${{ inputs.app-package }}
42+
INPUT_JAVA_HOME: ${{ inputs.java-home }}
43+
M2EE_ADMIN_PASS: ${{ inputs.admin-pass }}
44+
RUNTIME_URL: ${{ inputs.runtime-url }}
45+
TIMEOUT_SECONDS: ${{ inputs.timeout-seconds }}
46+
# Read by the portable app's HOCON config (etc/variables.conf: ${?RUNTIME_PARAMS_*}),
47+
# inherited by the JVM started below. Caps session pile-up under the 6-user trial
48+
# license — see the session-timeout-ms input for the full rationale.
49+
RUNTIME_PARAMS_SESSIONTIMEOUT: ${{ inputs.session-timeout-ms }}
50+
# Reap expired sessions promptly (default cluster cleanup runs only every 5 min, which
51+
# would hold license slots long after the sessions go idle).
52+
RUNTIME_PARAMS_CLUSTERMANAGERACTIONINTERVAL: "30000"
53+
run: |
54+
set -euo pipefail
55+
56+
# Allow an explicit JAVA_HOME override; otherwise rely on the one set up earlier.
57+
if [ -n "$INPUT_JAVA_HOME" ]; then
58+
export JAVA_HOME="$INPUT_JAVA_HOME"
59+
fi
60+
echo "Using JAVA_HOME=${JAVA_HOME:-<from PATH>}"
61+
62+
rm -rf runtime-app
63+
mkdir -p runtime-app log
64+
unzip -qq "$APP_PACKAGE" -d runtime-app
65+
chmod +x runtime-app/bin/start
66+
67+
# Start the bundled runtime in the background; it must outlive this step.
68+
(cd runtime-app && nohup ./bin/start etc/Default > "$GITHUB_WORKSPACE/log/runtime.log" 2>&1 &)
69+
70+
echo "Waiting up to ${TIMEOUT_SECONDS}s for the runtime at ${RUNTIME_URL} ..."
71+
deadline=$(( SECONDS + TIMEOUT_SECONDS ))
72+
until curl -fsS -o /dev/null "$RUNTIME_URL"; do
73+
if [ "$SECONDS" -ge "$deadline" ]; then
74+
echo "::error::Mendix runtime did not become ready within ${TIMEOUT_SECONDS}s"
75+
echo "----- last 100 lines of runtime.log -----"
76+
tail -n 100 "$GITHUB_WORKSPACE/log/runtime.log" 2>/dev/null || echo "(no log)"
77+
exit 1
78+
fi
79+
sleep 3
80+
done
81+
echo "Mendix runtime is ready and serving at ${RUNTIME_URL}"

.github/scripts/README.md

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,40 +4,74 @@ This directory contains helper scripts used by the CI/CD pipeline.
44

55
## determine-widget-scope.sh
66

7-
Determines which widgets and JS actions should be built and tested based on changed files or manual input.
7+
Determines which widgets and JS actions should be built and tested, based on the
8+
files changed in a PR or on manual `workflow_dispatch` input.
89

910
### Usage
1011

1112
```bash
12-
./determine-widget-scope.sh <event_name> <input_workspace> <before_commit> <current_commit>
13+
./determine-widget-scope.sh <event_name> <input_workspace> <base_commit> <current_commit>
1314
```
1415

16+
For a `pull_request`, `<base_commit>` is the PR base SHA
17+
(`github.event.pull_request.base.sha`). The script diffs against the **merge-base** of
18+
that and `<current_commit>`, so every commit in the PR is considered (not just the
19+
latest). This needs full git history — the `scope` job checks out with `fetch-depth: 0`.
20+
On `workflow_dispatch`, `<input_workspace>` drives the scope and the commit args are
21+
ignored.
22+
1523
### Outputs
1624

17-
| Output | Description |
18-
| -------------------- | ---------------------------------------------------- |
19-
| `scope` | pnpm filter scope for building |
20-
| `widgets` | JSON array of widgets to BUILD |
21-
| `widgets_to_test` | JSON array of widgets to TEST (used for test matrix) |
22-
| `js_actions_changed` | Boolean flag indicating if JS actions changed |
25+
| Output | Description |
26+
| -------------------- | -------------------------------------------------------------------------------- |
27+
| `scope` | pnpm filter scope (used by the unit-test step) |
28+
| `widgets` | JSON array of widgets to BUILD |
29+
| `widgets_to_test` | JSON array of widgets to TEST (the test matrix) |
30+
| `js_actions_changed` | Boolean — whether JS actions changed |
31+
| `full_build` | Boolean — true when every widget is (re)built; gates the dist cache restore/save |
32+
33+
### Build vs. test scope
34+
35+
`widgets_to_test` and `widgets` are deliberately **separate**:
36+
37+
- A change anywhere under a widget folder marks it for **testing**.
38+
- Only a **build-affecting** change (anything outside the widget's `e2e/` folder, e.g.
39+
`src/**` or `package.json`) marks it for **building**. A change confined to `e2e/`
40+
(Maestro flows / screenshots) changes only the test, so the widget is tested against
41+
the test project's baseline `.mpk` instead of being rebuilt.
42+
43+
So `widgets` can be a strict subset of `widgets_to_test` (or empty while
44+
`widgets_to_test` is non-empty, for an e2e-only PR).
2345

2446
### Pipeline Behavior
2547

26-
| Scenario | Widgets Built | Widgets Tested | JS Actions Built | JS Actions Tested |
27-
| ---------------------- | --------------- | --------------- | ---------------- | ----------------- |
28-
| Only JS actions change | All | None | Yes | Yes |
29-
| Only widgets change | Changed only | Changed only | No | No |
30-
| Both change | Changed widgets | Changed widgets | Yes | Yes |
31-
| Full run (`*-native`) | All | All | Yes | Yes |
32-
| Manual: `js-actions` | All | None | Yes | Yes |
48+
| Scenario | Widgets Built | Widgets Tested | JS Actions Built | JS Actions Tested |
49+
| ------------------------------- | ---------------------- | --------------- | ---------------- | ----------------- |
50+
| Only JS actions change | All | None | Yes | Yes |
51+
| Only widget `src`/deps change | Changed only | Changed only | No | No |
52+
| Only widget `e2e/` changes | None (use baseline) | Changed only | No | No |
53+
| Both widgets and JS actions | Build-affected widgets | Changed widgets | Yes | Yes |
54+
| Full run (`*-native` / default) | All | All | Yes | Yes |
55+
| Manual: `js-actions` | All | None | Yes | Yes |
3356

3457
### Why build all widgets when only JS actions change?
3558

36-
The JS action tests run against a full test project that requires all widgets to be present. Without building all widgets, the test project would be incomplete and tests would fail.
59+
The JS action tests run against a full test project that requires all widgets to be
60+
present. Without building all widgets, the test project would be incomplete and tests
61+
would fail.
3762

3863
## Other Scripts
3964

40-
- **determine-nt-version.py** - Determines the Native Template version based on Mendix version
41-
- **mxbuild.Dockerfile** - Docker image for mxbuild
42-
- **setup-runtime.sh** - Sets up the Mendix runtime
43-
- **start-runtime-with-verification.sh** - Starts the runtime with health verification
65+
- **determine-nt-version.mjs** — resolves the Native Template release/branch from the
66+
Mendix version. Node ESM, no external deps (uses global `fetch` + a small version
67+
comparator); run with the runner's preinstalled `node`. Fails loudly rather than
68+
silently pinning `master`.
69+
- **mxbuild.Dockerfile** — Docker image for mxbuild.
70+
71+
> The Mendix runtime for the test jobs is produced as a **portable app package**
72+
> (`mxbuild --target=portable-app-package`) in the `project` job and started by the
73+
> `start-mendix-runtime` composite action, which waits for a real readiness probe.
74+
> That runtime runs under a 6-user trial license, so the action shortens the server
75+
> session idle timeout (`RUNTIME_PARAMS_SESSIONTIMEOUT`) — each Maestro flow opens a
76+
> fresh session via `launchApp clearState`, and a short timeout lets them expire
77+
> between flows instead of piling up past the cap.

0 commit comments

Comments
 (0)