Skip to content

JSON mapping: codecs, creators, generics, arrays and null semantics #10477

JSON mapping: codecs, creators, generics, arrays and null semantics

JSON mapping: codecs, creators, generics, arrays and null semantics #10477

name: Test JavaScript screenshot scripts
on:
# Manual dispatch: this PR's changed-file count exceeds GitHub's paths-filter
# diff limit, so pull_request triggers stopped firing -- dispatch with
# gh workflow run <file> --ref <branch>
workflow_dispatch:
inputs:
port_status_browser_evidence:
description: Run only the Port Status browser evidence matrix
required: false
default: false
type: boolean
schedule:
- cron: '35 1 * * *'
pull_request:
paths:
# The application these jobs build compiles a Unity project, from an
# archive that unity-gallery-prebuilt.yml makes out of these.
- 'scripts/unity-compat-samples/gallery/**'
- 'maven/cil-translator/**'
- 'maven/unity-compat/**'
- 'maven/build-engine/src/main/java/com/codename1/maven/Unity*'
- 'maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/*Unity*'
- 'scripts/unity-gallery-prebuilt.sh'
- '.github/workflows/unity-gallery-prebuilt.yml'
- '.github/actions/unity-gallery-prebuilt/**'
- 'scripts/ci/retry.sh'
- '.github/workflows/scripts-javascript.yml'
- 'scripts/run-javascript-browser-tests.sh'
- 'scripts/run-javascript-screenshot-tests.sh'
- 'scripts/run-javascript-headless-browser.mjs'
- 'scripts/run-javascript-lifecycle-tests.mjs'
- 'scripts/test-javascript-surface-replay.mjs'
- 'scripts/test-javascript-weak-refs.mjs'
- 'scripts/test-javascript-native-stub-bindings.mjs'
- 'scripts/ci/tests/cn1ss-async-sink-test.sh'
- 'scripts/ci/tests/Cn1ssAsyncSinkTest.java'
- 'scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/Cn1ssAsyncSink.java'
- 'scripts/test-javascript-vault-bridge.mjs'
- 'scripts/test-javascript-composited-rendering.mjs'
- 'scripts/javascript/composited-app/**'
- 'vm/ByteCodeTranslator/src/javascript/browser_bridge.js'
- 'scripts/run-javascript-lifecycle-tests.sh'
- 'scripts/verify-javascript-lens-parity.mjs'
- 'scripts/build-javascript-port-hellocodenameone.sh'
- 'scripts/javascript_browser_harness.py'
- 'scripts/javascript/screenshots/**'
- 'scripts/lib/cn1ss.sh'
- 'scripts/common/java/**'
- 'scripts/hellocodenameone/**'
- 'CodenameOne/src/**'
- '!CodenameOne/src/**/*.md'
- 'Ports/JavaScriptPort/**'
- 'vm/**'
- 'maven/**'
- '!maven/core-unittests/**'
- '!docs/**'
# The screenshot transport is now a Codename One backend application, so the
# two files that are ONLY exercised here have to trigger this leg: the
# server itself and the script that launches it.
#
# Deliberately NOT the rest of vm/backend. The websocket protocol code is
# covered by maven/backend's unit tests, by vm/tests against the translated
# binary, and by the Autobahn conformance job -- all of which already
# trigger on vm/**. Fanning every backend change out to the macOS and
# emulator legs as well would buy nothing and cost a great deal: a change to
# the ORM, the database layer or the HTTP parser cannot reach this
# transport.
- 'scripts/hellocodenameone/backend/**'
push:
branches: [ master ]
paths:
# The application these jobs build compiles a Unity project, from an
# archive that unity-gallery-prebuilt.yml makes out of these.
- 'scripts/unity-compat-samples/gallery/**'
- 'maven/cil-translator/**'
- 'maven/unity-compat/**'
- 'maven/build-engine/src/main/java/com/codename1/maven/Unity*'
- 'maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/*Unity*'
- 'scripts/unity-gallery-prebuilt.sh'
- '.github/workflows/unity-gallery-prebuilt.yml'
- '.github/actions/unity-gallery-prebuilt/**'
- 'scripts/ci/retry.sh'
- '.github/workflows/scripts-javascript.yml'
- 'scripts/run-javascript-browser-tests.sh'
- 'scripts/test-javascript-composited-rendering.mjs'
- 'scripts/javascript/composited-app/**'
- 'scripts/run-javascript-screenshot-tests.sh'
- 'scripts/run-javascript-headless-browser.mjs'
- 'scripts/run-javascript-lifecycle-tests.mjs'
- 'scripts/test-javascript-surface-replay.mjs'
- 'scripts/test-javascript-weak-refs.mjs'
- 'scripts/test-javascript-native-stub-bindings.mjs'
- 'scripts/ci/tests/cn1ss-async-sink-test.sh'
- 'scripts/ci/tests/Cn1ssAsyncSinkTest.java'
- 'scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/Cn1ssAsyncSink.java'
- 'scripts/run-javascript-lifecycle-tests.sh'
- 'scripts/verify-javascript-lens-parity.mjs'
- 'scripts/build-javascript-port-hellocodenameone.sh'
- 'scripts/javascript_browser_harness.py'
- 'scripts/javascript/screenshots/**'
- 'scripts/lib/cn1ss.sh'
- 'scripts/common/java/**'
- 'scripts/hellocodenameone/**'
- 'CodenameOne/src/**'
- '!CodenameOne/src/**/*.md'
- 'Ports/JavaScriptPort/**'
- 'vm/**'
- 'maven/**'
- '!maven/core-unittests/**'
- '!docs/**'
# The screenshot transport is now a Codename One backend application, so the
# two files that are ONLY exercised here have to trigger this leg: the
# server itself and the script that launches it.
#
# Deliberately NOT the rest of vm/backend. The websocket protocol code is
# covered by maven/backend's unit tests, by vm/tests against the translated
# binary, and by the Autobahn conformance job -- all of which already
# trigger on vm/**. Fanning every backend change out to the macOS and
# emulator legs as well would buy nothing and cost a great deal: a change to
# the ORM, the database layer or the HTTP parser cannot reach this
# transport.
- 'scripts/hellocodenameone/backend/**'
concurrency:
# Only the latest revision needs validation. PR numbers keep forks isolated;
# event names keep a manual/scheduled run from cancelling a push or PR run.
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
# The application has a Unity project in it. One Linux job compiles it and
# hands the result to the jobs below as an artifact; none of them installs
# the .NET SDK or builds the Unity modules.
unity-gallery:
if: ${{ !inputs.port_status_browser_evidence }}
uses: ./.github/workflows/unity-gallery-prebuilt.yml
javascript-screenshots:
needs: unity-gallery
if: ${{ !inputs.port_status_browser_evidence }}
permissions:
contents: read
pull-requests: write
issues: write
runs-on: ubuntu-24.04
env:
GITHUB_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}
# The device runner reports logical test failures through CN1SS log
# markers, not through the build or the screenshot comparison. Make the
# normalized report authoritative so a failing or never-run compliance
# test cannot leave this workflow green and then be published from master.
CN1SS_FAIL_ON_TEST_PROBLEMS: '1'
GH_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}
ARTIFACTS_DIR: ${{ github.workspace }}/artifacts/javascript-ui-tests
# CN1_JS_TIMEOUT_SECONDS guards the per-suite SUITE:FINISHED wait.
# Bumped from 720s to 1200s after merging master's #4875 chunk-emit
# fix and removing the ``jsChunkDrop`` skip block in port.js: with
# the previously-silent graphics / chart / kotlin / mainscreen /
# transition tests now actually rendering and emitting full PNG
# streams instead of being dropped, the 73-test suite walks at
# ~10 s/test on shared GHA runners and lands 12-17 min wall-clock
# depending on canvas-accumulation pressure later in the run. The
# 720s budget was too tight: comparison-step runs succeed at 12-14
# min but SUITE:FINISHED-wait runs that race the bottom of the
# suite were timing out before any tests could be diff'd. 1200s
# absorbs the variance without re-introducing the
# silently-dropped-test workaround.
# 1800 -> 2400: un-skipping the StickyHeader (x3) + StatusBar grid
# composites (now that WebSocket transport handles their large payloads)
# adds wall-clock; 2400 absorbs the extra tests plus the cross-recovery
# retry overhead.
CN1_JS_TIMEOUT_SECONDS: "2400"
CN1_JS_BROWSER_LIFETIME_SECONDS: "2340"
CN1SS_SKIP_COVERAGE: "1"
CN1SS_FAIL_ON_MISMATCH: "1"
# Strict, matching the native ports. The "variable tail of dropped
# screenshots" that previously forced a tolerance of 20 was the
# canvas-accumulation root cause: browser_bridge.js retained every host
# canvas forever, so by ~test 66 the main thread thrashed, capture
# stalled and the suite re-dispatched. That leak is fixed (dead canvas
# host-refs are now released on the worker GC signal), so the suite
# delivers every screenshot deterministically -- a missing capture is a
# real regression again. Keep at 0 like Android/JavaSE. Assertion failures
# are separate from screenshot coverage and must never be tolerated here.
CN1SS_ALLOWED_MISSING: "0"
BROWSER_CMD: "node \"$GITHUB_WORKSPACE/scripts/run-javascript-headless-browser.mjs\""
steps:
- uses: actions/checkout@v6
- name: Set TMPDIR
run: echo "TMPDIR=${{ runner.temp }}" >> $GITHUB_ENV
# Milliseconds, no browser and no bundle, so it runs before the build
# rather than after a 20-minute suite. Guards the surface command replay:
# one undrawable image source used to throw and take every op behind it
# in that batch with it, which is what truncated graphics-draw-image-rect
# intermittently.
#
# Deliberately ahead of "Set up Node 20": it uses only node: builtins
# (fs/path/url/vm), so the runner image's preinstalled Node runs it, and
# placing it here means a broken guard fails in seconds instead of after
# the ParparVM build.
- name: Surface command replay guards
run: node scripts/test-javascript-surface-replay.mjs
# Same placement rationale: node: builtins only, so it runs before the
# ParparVM build and fails in seconds. Guards createSoftWeakRef /
# extractHardRef in port.js -- the token has to be a real WeakRef, because
# the WeakMap it used to be held its referent as the map VALUE, which is a
# strong edge and made every "weak" cache in the framework unreclaimable.
- name: Weak reference guards
run: node scripts/test-javascript-weak-refs.mjs
# A static interop factory (JSString.valueOf, Int32Array.create, ...) runs
# its Java body -- `return null;` -- unless port.js binds it; an unbound
# JSString.valueOf made every directory marker a delete.
- name: Interop stub bindings
run: node scripts/test-javascript-native-stub-bindings.mjs
# Plain JVM, no build, so it also runs ahead of the ParparVM build. The
# sink the Java-side captures ship through must redial after the test
# server sheds its idle socket (close 1006): latching "failed" on that
# close lost VideoIODecodedFrames with websocket-unavailable.
- name: CN1SS async sink reconnect
run: bash scripts/ci/tests/cn1ss-async-sink-test.sh
- name: Cache codenameone-tools
uses: actions/cache@v5
with:
path: ${{ runner.temp }}/codenameone-tools
key: ${{ runner.os }}-cn1-tools-${{ hashFiles('scripts/setup-workspace.sh') }}
restore-keys: |
${{ runner.os }}-cn1-tools-
- name: Set up Java 8 for ParparVM
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '8'
cache: 'maven'
# build.sh requires a JDK 8 BY NAME, and the Java 17 setup below replaces
# JAVA_HOME -- so the value has to be captured while it is still correct.
- name: Record JDK 8 for the native screenshot server
run: echo "JDK_8_HOME=$JAVA_HOME" >> "$GITHUB_ENV"
- name: Prepare Codename One binaries for Maven plugin
run: |
mkdir -p ~/.codenameone
cp maven/UpdateCodenameOne.jar ~/.codenameone/
- name: Build ParparVM compiler bundle
run: bash $GITHUB_WORKSPACE/scripts/ci/retry.sh mvn -B -f maven/pom.xml -pl parparvm -am -DskipTests -Dmaven.javadoc.skip=true package
- name: Set up Java 17
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '17'
cache: 'maven'
- name: Set up Node 20
uses: actions/setup-node@v6
with:
node-version: '20'
- name: Verify JavaScript glass-tab lens parity
run: node scripts/verify-javascript-lens-parity.mjs
- name: Cache npm modules for scripts/
uses: actions/cache@v5
with:
path: scripts/node_modules
key: ${{ runner.os }}-scripts-npm-${{ hashFiles('scripts/package-lock.json') }}
restore-keys: |
${{ runner.os }}-scripts-npm-
- name: Cache Playwright browsers
id: playwright-cache
uses: actions/cache@v5
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-chromium-firefox-${{ hashFiles('scripts/package-lock.json') }}
restore-keys: |
${{ runner.os }}-playwright-
- name: Install Playwright Chromium
run: |
cd scripts
npm ci
# OS-level deps (apt packages) aren't cached so always install them.
# Playwright shells out to apt for the OS-level deps, so the runner
# image's third-party sources have to be pruned FIRST -- a broken
# vendor mirror makes its apt-get fail as a whole and it reports
# only "Failed to install browser dependencies".
bash "$GITHUB_WORKSPACE/scripts/ci/apt-get-update.sh"
npx playwright install-deps chromium firefox
# Install the browser revisions selected by the committed lockfile;
# a restored fallback cache may contain a different revision.
npx playwright install chromium
# Firefox for the composited rendering check below: issue #5910 was reported
# from Firefox, and the two engines composite the canvas and the text layer
# differently enough that one passing says little about the other.
npx playwright install firefox
# Placed here, before the workspace build, because it needs nothing from
# it: the vault bridge is sliced straight out of browser_bridge.js and run
# in a page. That also means a failure here is unambiguous -- it is the
# bridge, not the bundle.
#
# This is the only coverage the WebAuthn PRF path has anywhere. It needs an
# authenticator and a user, which no unit test can supply; Chrome's virtual
# authenticator over the DevTools protocol supplies both. The Node test in
# vm/tests covers the same bridge against a stub IndexedDB and cannot reach
# any of the passkey code.
- name: Vault bridge in a real browser (IndexedDB, Web Crypto, WebAuthn PRF)
run: node scripts/test-javascript-vault-bridge.mjs
# No virtual display for the Maven steps below: they only build, and the
# css goal compiles theme.css headless. The browsers the tests drive are
# launched headless by their own scripts.
- name: Setup workspace
run: ./scripts/setup-workspace.sh -q -DskipTests
# After everything that fills the local Maven repository and before the
# application is built; this job has no .NET SDK (see the action).
- name: Unpack the Unity gallery prebuilt
uses: ./.github/actions/unity-gallery-prebuilt
- name: Build HelloCodenameOne JavaScript port bundle
run: |
# Use JAVA_HOME set by actions/setup-java for Java 17
# (JAVA_HOME_17_X64 is always available after setup-java@v4)
export JAVA_HOME="${JAVA_HOME_17_X64}"
export PATH="$JAVA_HOME/bin:$PATH"
echo "Using JAVA_HOME=$JAVA_HOME"
java -version
SKIP_PARPARVM_BUILD=1 ./scripts/build-javascript-port-hellocodenameone.sh "${GITHUB_WORKSPACE}/artifacts/javascript-ui-tests/hellocodenameone-javascript-port.zip"
# What the screenshot suite cannot see. Its capture reads the app's canvas back, which
# suspends the DOM text layer, so it never looks at the page a browser shows -- and
# issues #5910 (toolbar scrolled away, dialog over a blank page) and #5912 (a phone could
# not scroll or tap) came back more than once while it stayed green. This builds the app
# those issues were reported against through the Maven plugin, as a user's project is
# built, and drives it with real wheel, mouse and touch input in Chromium and Firefox.
- name: Composited rendering and input (Chromium, Firefox, phone emulation)
run: |
set -euo pipefail
export JAVA_HOME="${JAVA_HOME_17_X64}"
export PATH="$JAVA_HOME/bin:$PATH"
(cd scripts/javascript/composited-app && mvn -B package -DskipTests \
-Dcodename1.platform=javascript -Dcodename1.buildTarget=local-javascript)
bundle_dir="${RUNNER_TEMP}/composited-app"
rm -rf "$bundle_dir" && mkdir -p "$bundle_dir"
unzip -q scripts/javascript/composited-app/javascript/target/compositedapp-javascript-1.0-SNAPSHOT.zip -d "$bundle_dir"
node scripts/test-javascript-composited-rendering.mjs "$bundle_dir" "${GITHUB_WORKSPACE}/artifacts/javascript-ui-tests/composited"
- name: Locate JavaScript bundle
id: locate_bundle
run: |
set -euo pipefail
bundle="$(ls -1 \
artifacts/javascript-ui-tests/hellocodenameone-javascript-port.zip \
scripts/hellocodenameone/parparvm/target/hellocodenameone-javascript-port.zip \
2>/dev/null | head -n1 || true)"
if [ -z "$bundle" ]; then
echo "Unable to locate HelloCodenameOne JavaScript-port bundle" >&2
exit 1
fi
echo "bundle=$bundle" >> "$GITHUB_OUTPUT"
# The standalone JavaScript lifecycle boot test was removed: it was
# advisory (continue-on-error) but flaky on shared GHA runners -- when a
# slow runner's cooperative scheduler stalled, the harness's page.evaluate
# poll could hang indefinitely (past the 480s budget), wedging the whole
# job. The screenshot suite below exercises the same boot path AND the
# rendering, with its own per-suite timeout, so it is the single source of
# truth. This keeps the workflow focused on the screenshot tests and fast.
# THIS LEG RUNS THE TRANSLATED SCREENSHOT SERVER, and it is the only one
# that does. Every other leg runs the same Java on a JVM, which covers the
# protocol but not the arm that ships. This job is the right host for it: it
# is Linux, where the native backend links; it already sets up the JDKs and
# builds the ParparVM compiler above; it is a single job rather than a
# matrix, so the binary is built once; and its client is the browser's own
# WebSocket rather than one of ours, which makes it the only pairing of our
# shipping server with a third-party RFC 6455 implementation.
#
# NOT with CN1_BACKEND_HTTPS=0, tempting as that is for avoiding these
# packages: the build deletes cn1_backend_crypto.c in that mode, and a native
# with no C symbol takes its Java method with it -- so Crypto.sha1 would
# vanish and every handshake would compute the wrong accept value.
- name: Install the native backend toolchain
run: |
bash "$GITHUB_WORKSPACE/scripts/ci/apt-get-update.sh"
sudo apt-get install -y --no-install-recommends \
clang zlib1g-dev libcurl4-openssl-dev libssl-dev libnghttp2-dev
- name: Run JavaScript screenshot browser tests
env:
# The translated binary, built by scripts/hellocodenameone/backend/server.sh
# through the Maven plugin and self-checked by scripts/lib/cn1ss.sh before
# the suite starts, so a broken one costs minutes rather than the whole
# 40-minute timeout.
CN1SS_WS_SERVER: native
run: |
mkdir -p "${ARTIFACTS_DIR}"
./scripts/run-javascript-browser-tests.sh "${{ steps.locate_bundle.outputs.bundle }}" "${GITHUB_WORKSPACE}/scripts/javascript/screenshots"
- name: Upload JavaScript port status
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v7
with:
name: port-status-javascript
path: artifacts/javascript-ui-tests/port-status-javascript.json
if-no-files-found: warn
retention-days: 14
- name: Upload JavaScript screenshot artifacts
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v7
with:
name: javascript-ui-tests
path: artifacts/javascript-ui-tests
if-no-files-found: warn
retention-days: 14
port-status-browser-evidence:
if: ${{ github.event_name == 'workflow_dispatch' && inputs.port_status_browser_evidence }}
permissions:
actions: write
contents: write
uses: ./.github/workflows/port-status-nightly.yml