From 249010a582d9c429c37f9c4516cfeb5647c9ffbf Mon Sep 17 00:00:00 2001 From: Viktor Vaczi Date: Wed, 10 Jun 2026 17:17:57 +0200 Subject: [PATCH] =?UTF-8?q?feat(wasm-dom):=20dual-mode=20build=20scaffold?= =?UTF-8?q?=20=E2=80=94=20native=20DOM=20port=20builds,=20links=20and=20bo?= =?UTF-8?q?ots=20alongside=20canvas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Build/test plumbing for the second wxWidgets mode: --dom on build-wxuniversal-wasm.sh (build-wasm/wxwidgets-dom, no universal, --disable-tooltips, libwx_wasmu_* stubs) and build-wasm-test.sh (mirrors sources into tests/apps-dom, PORT=dom). Build script now reconfigures when Makefile.in or autoconf_inc.m4 regenerate (bakefile chain). Harness: WX_PORT=dom serves apps-dom, namespaces screenshots into test-results/dom and logs into logs/wxwidgets/dom; compare/update screenshot scripts take --port dom with baseline-screenshots-dom. New port-agnostic boot.spec.ts (green on both ports). Co-Authored-By: Claude Fable 5 --- .gitignore | 8 ++++ scripts/build-wasm-test.sh | 29 ++++++++++++-- scripts/build-wxuniversal-wasm.sh | 54 +++++++++++++++++++++----- scripts/compare-screenshots.sh | 22 ++++++++++- scripts/update-baseline-screenshots.sh | 27 ++++++++++--- tests/apps/Makefile.wasm | 28 +++++++++++-- tests/e2e/boot.spec.ts | 31 +++++++++++++++ tests/e2e/utils/fixtures.ts | 27 ++++++++++++- tests/playwright.config.ts | 6 ++- wxwidgets | 2 +- 10 files changed, 205 insertions(+), 29 deletions(-) create mode 100644 tests/e2e/boot.spec.ts diff --git a/.gitignore b/.gitignore index ef1681b..68cb3ec 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,14 @@ wxwidgets-clean/ /tests/apps/standalone/*/*.wasm /tests/apps/kicad/*.js /tests/apps/kicad/*.wasm + +# DOM-port test bundles (mirrored + built by scripts/build-wasm-test.sh --dom) +/tests/apps-dom/ + +# Worktree-local canvas reference snapshot for regression gating (see +# features/wx-dom-port docs); derived from a local run, not a project asset +/tests/phase0-canvas-reference/ +/tests/phase0-timestamp-noisy.txt /tests/apps/kicad/*.tar.gz !tests/apps/kicad/pcbnew.html /tests/apps/gal-webgl/*.js diff --git a/scripts/build-wasm-test.sh b/scripts/build-wasm-test.sh index aa9edbf..717df28 100755 --- a/scripts/build-wasm-test.sh +++ b/scripts/build-wasm-test.sh @@ -10,6 +10,7 @@ source "$(dirname "$0")/common/env.sh" # ./build-wasm-test.sh # Incremental build (default) # ./build-wasm-test.sh --clean # Clean build from scratch # ./build-wasm-test.sh --debug # Build with debug symbols +# ./build-wasm-test.sh --dom # Build against the DOM port into tests/apps-dom # ./build-wasm-test.sh menu # Build only the menu test # ./build-wasm-test.sh --debug menu # Build menu test with debug symbols @@ -17,6 +18,7 @@ set -e DEBUG_BUILD=0 CLEAN_BUILD=0 +DOM_BUILD=0 TARGET="" # Parse arguments @@ -25,6 +27,8 @@ for arg in "$@"; do DEBUG_BUILD=1 elif [ "$arg" = "--clean" ]; then CLEAN_BUILD=1 + elif [ "$arg" = "--dom" ]; then + DOM_BUILD=1 elif [ "$arg" != "" ]; then TARGET="$arg" fi @@ -32,9 +36,26 @@ done SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" -BUILD_DIR="$PROJECT_ROOT/build-wasm/wxwidgets-universal" TESTS_DIR="$PROJECT_ROOT/tests" -WASM_APP_DIR="$TESTS_DIR/apps" + +if [ "$DOM_BUILD" = "1" ]; then + BUILD_DIR="$PROJECT_ROOT/build-wasm/wxwidgets-dom" + # DOM apps build in a mirrored source tree so canvas and DOM bundles + # coexist; relative ../../ paths in Makefile.wasm resolve identically. + WASM_APP_DIR="$TESTS_DIR/apps-dom" + echo "Mirroring test app sources into apps-dom..." + rsync -a --delete \ + --exclude '*.o' --exclude '*.d' \ + --exclude '*_test.html' --exclude '*_test.js' --exclude '*_test.wasm' \ + --exclude '*_repro.html' --exclude '*_repro.js' --exclude '*_repro.wasm' \ + --exclude '*.wasm.map' \ + "$TESTS_DIR/apps/" "$WASM_APP_DIR/" + MAKE_PORT_ARGS="PORT=dom" +else + BUILD_DIR="$PROJECT_ROOT/build-wasm/wxwidgets-universal" + WASM_APP_DIR="$TESTS_DIR/apps" + MAKE_PORT_ARGS="" +fi STANDALONE_DIR="$WASM_APP_DIR/standalone" echo "=== Building wxWidgets WASM Test Applications ===" @@ -93,9 +114,9 @@ fi # Build (pass DEBUG flag if requested) if [ "$DEBUG_BUILD" = "1" ]; then - make -f Makefile.wasm DEBUG=1 "$MAKE_TARGET" + make -f Makefile.wasm DEBUG=1 $MAKE_PORT_ARGS "$MAKE_TARGET" else - make -f Makefile.wasm "$MAKE_TARGET" + make -f Makefile.wasm $MAKE_PORT_ARGS "$MAKE_TARGET" fi echo "" diff --git a/scripts/build-wxuniversal-wasm.sh b/scripts/build-wxuniversal-wasm.sh index 29b7f20..96b21d8 100755 --- a/scripts/build-wxuniversal-wasm.sh +++ b/scripts/build-wxuniversal-wasm.sh @@ -22,14 +22,41 @@ source "$(dirname "$0")/common/stages.sh" # Usage: # ./build-wxuniversal-wasm.sh # Incremental build (default) # ./build-wxuniversal-wasm.sh --clean # Clean build from scratch +# ./build-wxuniversal-wasm.sh --dom # Build the DOM port (native widgets, +# # no wxUniversal) into build-wasm/wxwidgets-dom set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" -BUILD_DIR="$PROJECT_ROOT/build-wasm/wxwidgets-universal" WX_SOURCE="$PROJECT_ROOT/wxwidgets" +# Parse arguments: --clean and/or --dom, in any order +CLEAN_BUILD=0 +DOM_BUILD=0 +for arg in "$@"; do + case "$arg" in + --clean) CLEAN_BUILD=1 ;; + --dom) DOM_BUILD=1 ;; + *) echo "Unknown argument: $arg"; exit 1 ;; + esac +done + +if [ "$DOM_BUILD" = "1" ]; then + BUILD_DIR="$PROJECT_ROOT/build-wasm/wxwidgets-dom" + # Native (non-universal) widget set: controls are real DOM elements. + UNIVERSAL_FLAG="" + # Tooltips will be DOM title attributes (dom-phase-5); no wxToolTip yet. + # (Universal mode auto-disables them; native mode must do it explicitly.) + PORT_EXTRA_FLAGS="--disable-tooltips" + WXLIB_PREFIX="libwx_wasmu" +else + BUILD_DIR="$PROJECT_ROOT/build-wasm/wxwidgets-universal" + UNIVERSAL_FLAG="--enable-universal" + PORT_EXTRA_FLAGS="" + WXLIB_PREFIX="libwx_wasmunivu" +fi + # Use our config.sub wrapper for autoconf projects # CONFIG_SHELL is critical: nested configures (pcre, etc.) do SHELL=${CONFIG_SHELL-/bin/sh} # Without CONFIG_SHELL, nested configures would reset SHELL to /bin/sh and bypass our wrapper @@ -54,14 +81,17 @@ if [ ! -f "$WX_SOURCE/configure.in" ]; then exit 1 fi -# Regenerate configure if configure.in is newer -if [ "$WX_SOURCE/configure.in" -nt "$WX_SOURCE/configure" ]; then - echo "configure.in is newer than configure, regenerating..." +# Regenerate configure if configure.in or autoconf_inc.m4 is newer. +# (configure.in sincludes autoconf_inc.m4, which bakefile regenerates from +# build/bakefiles/files.bkl — new build conditions live there.) +if [ "$WX_SOURCE/configure.in" -nt "$WX_SOURCE/configure" ] || \ + [ "$WX_SOURCE/autoconf_inc.m4" -nt "$WX_SOURCE/configure" ]; then + echo "configure inputs changed, regenerating configure..." (cd "$WX_SOURCE" && autoconf) fi # Incremental build by default, use --clean for full rebuild -if [ "$1" = "--clean" ]; then +if [ "$CLEAN_BUILD" = "1" ]; then echo "Cleaning build directory..." rm -rf "$BUILD_DIR" fi @@ -84,6 +114,11 @@ elif [ "$WX_SOURCE/configure.in" -nt "$BUILD_DIR/Makefile" ]; then elif [ "$WX_SOURCE/configure" -nt "$BUILD_DIR/Makefile" ]; then echo "configure script changed, will reconfigure..." NEEDS_CONFIGURE=1 +elif [ "$WX_SOURCE/Makefile.in" -nt "$BUILD_DIR/Makefile" ]; then + # Makefile.in is regenerated from build/bakefiles/files.bkl (see header); + # the build Makefile must be re-derived or new source files are ignored. + echo "Makefile.in changed since last configure, will reconfigure..." + NEEDS_CONFIGURE=1 else echo "Already configured, skipping configure (use clean build to reconfigure)" fi @@ -145,7 +180,8 @@ if [ $NEEDS_CONFIGURE -eq 1 ]; then emconfigure "$WX_SOURCE/configure" \ --host=emscripten \ --without-subdirs \ - --enable-universal \ + ${UNIVERSAL_FLAG} \ + ${PORT_EXTRA_FLAGS} \ --disable-shared \ --with-opengl \ --enable-exceptions \ @@ -188,9 +224,9 @@ done echo "Creating stub libraries..." for stub in richtext webview; do # Remove any existing symlinks first to avoid "same file" errors - rm -f "libwx_wasmunivu_${stub}-3.2.a" "libwx_wasmunivu_${stub}-3.2-emscripten.a" - emar rcs "libwx_wasmunivu_${stub}-3.2.a" - ln -sf "libwx_wasmunivu_${stub}-3.2.a" "libwx_wasmunivu_${stub}-3.2-emscripten.a" + rm -f "${WXLIB_PREFIX}_${stub}-3.2.a" "${WXLIB_PREFIX}_${stub}-3.2-emscripten.a" + emar rcs "${WXLIB_PREFIX}_${stub}-3.2.a" + ln -sf "${WXLIB_PREFIX}_${stub}-3.2.a" "${WXLIB_PREFIX}_${stub}-3.2-emscripten.a" done cd "$BUILD_DIR" diff --git a/scripts/compare-screenshots.sh b/scripts/compare-screenshots.sh index 179e9f5..aefd073 100755 --- a/scripts/compare-screenshots.sh +++ b/scripts/compare-screenshots.sh @@ -7,8 +7,26 @@ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" -BASELINE_DIR="$PROJECT_ROOT/tests/baseline-screenshots" -TEST_RESULTS_DIR="$PROJECT_ROOT/tests/test-results" + +# --port dom compares the DOM-port artifacts (WX_PORT=dom test runs) against +# their own baseline set; canvas paths are the default and stay unchanged. +PORT="" +ARGS=() +while [ $# -gt 0 ]; do + case "$1" in + --port) PORT="$2"; shift 2 ;; + *) ARGS+=("$1"); shift ;; + esac +done +set -- "${ARGS[@]+"${ARGS[@]}"}" + +if [ "$PORT" = "dom" ]; then + BASELINE_DIR="$PROJECT_ROOT/tests/baseline-screenshots-dom" + TEST_RESULTS_DIR="$PROJECT_ROOT/tests/test-results/dom" +else + BASELINE_DIR="$PROJECT_ROOT/tests/baseline-screenshots" + TEST_RESULTS_DIR="$PROJECT_ROOT/tests/test-results" +fi # Check if directories exist if [ ! -d "$BASELINE_DIR" ]; then diff --git a/scripts/update-baseline-screenshots.sh b/scripts/update-baseline-screenshots.sh index ddc290e..7b178b6 100755 --- a/scripts/update-baseline-screenshots.sh +++ b/scripts/update-baseline-screenshots.sh @@ -5,19 +5,34 @@ # - Screenshots with SIGNIFICANT differences (>5% size change) when --all flag used # # Usage: -# ./update-baseline-screenshots.sh # Only copy NEW screenshots -# ./update-baseline-screenshots.sh --all # Copy new + significantly different +# ./update-baseline-screenshots.sh # Only copy NEW screenshots +# ./update-baseline-screenshots.sh --all # Copy new + significantly different +# ./update-baseline-screenshots.sh --port dom # Same, for the DOM-port baseline set SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" TESTS_DIR="$SCRIPT_DIR/../tests" -SOURCE_DIR="$TESTS_DIR/test-results" -DEST_DIR="$TESTS_DIR/baseline-screenshots" COPY_ALL=0 +PORT="" THRESHOLD=5 # Percent threshold for "significant" difference -if [ "$1" = "--all" ]; then - COPY_ALL=1 +while [ $# -gt 0 ]; do + case "$1" in + --all) COPY_ALL=1; shift ;; + --port) PORT="$2"; shift 2 ;; + *) shift ;; + esac +done + +if [ "$PORT" = "dom" ]; then + SOURCE_DIR="$TESTS_DIR/test-results/dom" + DEST_DIR="$TESTS_DIR/baseline-screenshots-dom" +else + SOURCE_DIR="$TESTS_DIR/test-results" + DEST_DIR="$TESTS_DIR/baseline-screenshots" +fi + +if [ $COPY_ALL -eq 1 ]; then echo "Mode: Copy NEW + SIGNIFICANTLY DIFFERENT screenshots" else echo "Mode: Copy NEW screenshots only (use --all to include significant changes)" diff --git a/tests/apps/Makefile.wasm b/tests/apps/Makefile.wasm index 864a2d4..ab83c7f 100644 --- a/tests/apps/Makefile.wasm +++ b/tests/apps/Makefile.wasm @@ -5,10 +5,20 @@ # make -f Makefile.wasm # Optimized release build # make -f Makefile.wasm DEBUG=1 # Debug build with DWARF symbols and source maps -WXCONFIG = ../../build-wasm/wxwidgets-universal/wx-config TOOLS_ROOT = ../../wxwidgets/build/wasm KICAD_ROOT = ../../kicad +# PORT=dom links against the DOM (non-universal) wxWidgets build and adds the +# wx-dom.js shim. Invoked by scripts/build-wasm-test.sh --dom from a mirrored +# source tree in tests/apps-dom (so canvas and DOM bundles coexist). +ifeq ($(PORT),dom) + WXCONFIG = ../../build-wasm/wxwidgets-dom/wx-config + WXLIB_PREFIX = libwx_wasmu +else + WXCONFIG = ../../build-wasm/wxwidgets-universal/wx-config + WXLIB_PREFIX = libwx_wasmunivu +endif + CXX = em++ WX_CXXFLAGS := $(shell $(WXCONFIG) --cxxflags) @@ -103,13 +113,23 @@ LDFLAGS_COROUTINE = $(DEBUG_LDFLAGS) $(COROUTINE_BASE_LDFLAGS) $(WX_LDFLAGS_NOGL # forbids the very multi-parked-sleep states the harness exists to exercise. LDFLAGS_RACES = $(DEBUG_LDFLAGS) $(COROUTINE_BASE_LDFLAGS) -sASSERTIONS=0 $(WX_LDFLAGS_NOGL) -JS = $(TOOLS_ROOT)/wx.js +# In dom mode a second pre-js carries the DOM-control shim ("--pre-js A --pre-js B" +# after expansion — emcc accepts repeated --pre-js). +ifeq ($(PORT),dom) + JS = $(TOOLS_ROOT)/wx.js --pre-js $(TOOLS_ROOT)/wx-dom.js +else + JS = $(TOOLS_ROOT)/wx.js +endif HTML = $(TOOLS_ROOT)/template.html # wxWidgets library directory - used as dependency to rebuild when libs change -WX_LIB_DIR = ../../build-wasm/wxwidgets-universal/lib +ifeq ($(PORT),dom) + WX_LIB_DIR = ../../build-wasm/wxwidgets-dom/lib +else + WX_LIB_DIR = ../../build-wasm/wxwidgets-universal/lib +endif # Key library that changes when wxWidgets is rebuilt -WX_CORE_LIB = $(WX_LIB_DIR)/libwx_wasmunivu_core-3.2-emscripten.a +WX_CORE_LIB = $(WX_LIB_DIR)/$(WXLIB_PREFIX)_core-3.2-emscripten.a # Standalone directories S = standalone diff --git a/tests/e2e/boot.spec.ts b/tests/e2e/boot.spec.ts new file mode 100644 index 0000000..7408a10 --- /dev/null +++ b/tests/e2e/boot.spec.ts @@ -0,0 +1,31 @@ +import { test, expect, waitForApp } from './utils/fixtures'; + +// Port-agnostic boot check: the app starts, the wx element registry fills, +// and (in DOM mode) the wx-dom shim is present. Runs under both WX_PORT +// values; intentionally takes no screenshots. +test.describe('Application boot', () => { + test('minimal app boots and registers elements', async ({ page, testLogger }) => { + const fatal: string[] = []; + page.on('pageerror', (err) => fatal.push(String(err))); + + await page.goto('/minimal_test.html'); + await waitForApp(page); + + // The registry fills as wx windows are created. + await page.waitForFunction( + () => (window as any).wxElementRegistry?.elements?.size > 0, + undefined, + { timeout: 30000 } + ); + + const elementCount = await page.evaluate( + () => (window as any).wxElementRegistry.elements.size + ); + expect(elementCount).toBeGreaterThan(0); + + const isDomPort = await page.evaluate(() => (window as any).wxDomPort === true); + expect(isDomPort).toBe(process.env.WX_PORT === 'dom'); + + expect(fatal, `page errors: ${fatal.join('\n')}`).toHaveLength(0); + }); +}); diff --git a/tests/e2e/utils/fixtures.ts b/tests/e2e/utils/fixtures.ts index 2459efc..0e097fd 100644 --- a/tests/e2e/utils/fixtures.ts +++ b/tests/e2e/utils/fixtures.ts @@ -2,10 +2,31 @@ import { test as base } from '@playwright/test'; import * as path from 'path'; import { setupTestLogger, writeTestLogs, TestLogger, MAIN_CANVAS, waitForApp, tryLoadApp, getCanvasBox, WXWIDGETS_LOGS_DIR, getTestFileName } from './test-utils'; +// WX_PORT=dom runs the same specs against the DOM-port bundles; screenshots +// and logs are namespaced into a dom/ subdirectory so the canvas artifacts +// stay untouched (baselines: tests/baseline-screenshots-dom/). +const IS_DOM_PORT = process.env.WX_PORT === 'dom'; + // Extend base test with automatic logging export const test = base.extend<{ testLogger: TestLogger; }>({ + page: async ({ page }, use) => { + if (IS_DOM_PORT) { + const origScreenshot = page.screenshot.bind(page); + page.screenshot = ((options?: Parameters[0]) => { + if (options?.path) { + const dir = path.dirname(options.path); + // Specs write to test-results/.png; redirect to test-results/dom/. + if (path.basename(dir) === 'test-results') { + options = { ...options, path: path.join(dir, 'dom', path.basename(options.path)) }; + } + } + return origScreenshot(options); + }) as typeof page.screenshot; + } + await use(page); + }, testLogger: async ({ page }, use, testInfo) => { // Build test name from describe block + test title const testName = testInfo.titlePath.join(' - '); @@ -14,9 +35,11 @@ export const test = base.extend<{ await use(logger); - // Write logs to wxwidgets// directory + // Write logs to wxwidgets// directory (wxwidgets/dom// for the DOM port) const testFileName = getTestFileName(testInfo.file); - const logsDir = path.join(WXWIDGETS_LOGS_DIR, testFileName); + const logsDir = IS_DOM_PORT + ? path.join(WXWIDGETS_LOGS_DIR, 'dom', testFileName) + : path.join(WXWIDGETS_LOGS_DIR, testFileName); writeTestLogs(testName, logger, logsDir); logger.cleanup(); }, diff --git a/tests/playwright.config.ts b/tests/playwright.config.ts index ee600bc..cdc44af 100644 --- a/tests/playwright.config.ts +++ b/tests/playwright.config.ts @@ -62,6 +62,10 @@ function findFreePort(): number { const port = resolvePort(); +// WX_PORT=dom runs the suite against the DOM-port bundles (built by +// scripts/build-wasm-test.sh --dom into tests/apps-dom with identical layout). +const appsDir = process.env.WX_PORT === 'dom' ? 'apps-dom' : 'apps'; + export default defineConfig({ globalSetup: './global-setup.ts', testDir: './e2e', @@ -98,7 +102,7 @@ export default defineConfig({ ], webServer: { - command: `npx serve apps -p ${port} -c ../serve.json`, + command: `npx serve ${appsDir} -p ${port} -c ../serve.json`, port: port, reuseExistingServer: !process.env.CI, }, diff --git a/wxwidgets b/wxwidgets index 014f67e..808cb90 160000 --- a/wxwidgets +++ b/wxwidgets @@ -1 +1 @@ -Subproject commit 014f67e6c1fa6e854474e12e67d70de0e12b84ee +Subproject commit 808cb90457dab7421bb1539471b5044bf910fd5f