openvibers/openvibe.shared Commits

CI: Shared's suite on Windows and macOS too (#140)

OP OpenVibers committed 16 days ago · bc7d41350a · 5 files, +36 −0

* CI: Shared's suite on Windows and macOS too (owner, 2026-10-27: work well across multi-platform)

* Browser harness on macOS: pages draw and run timers as if in front (no occluded-window backgrounding)

The first macOS CI run: perf-budget-chrome saw no paint and no layout shift, because a new page counted as an occluded
window. Puppeteer's defaults, now ours too: --disable-backgrounding-occluded-windows, --disable-renderer-backgrounding,
--disable-background-timer-throttling. CHANGELOG under Unreleased (test tooling only, no release).

* macOS runner: vitals click after the first paint (LCP stops at input); the navbar listener test waits for the bar's one-time extras

The second macOS run measured the shift but no LCP (the click came before any paint), and the boost-navbar test counted
the release watch's window listeners, which load once at 4 s, as growth mid-lap.

Changes

ddiff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
iindex 03a1688..bd8a949 100644
---- a/.github/workflows/ci.yml
++++ b/.github/workflows/ci.yml
@@@ -31,6 +31,22 @@ jobs:
mkdir "$RUNNER_TEMP/consumer" && cd "$RUNNER_TEMP/consumer" && npm init -y >/dev/null
npm install --no-audit --no-fund "$RUNNER_TEMP/openvibe-shared.tgz"
node -e "require('openvibe-shared'); require('openvibe-shared/navbar'); require('openvibe-shared/legal'); require('openvibe-shared/metrics'); require('openvibe-shared/ready'); require('openvibe-shared/release'); require('openvibe-shared/release-compat'); require('openvibe-shared/analytics'); require('openvibe-shared/analytics/pg'); require('openvibe-shared/analytics/event.v1.json'); const f = require('openvibe-shared/files'); for (const n of f.BROWSER) require('fs').accessSync(f.path(n)); console.log('ok', require('openvibe-shared/package.json').version)"
++ # Sites built on these files are developed on every OS (owner, 2026-10-27: "the shared SDK and API … work well across
++ # multi-platform"): the whole suite on Windows and macOS too.
++ other-os:
++ name: test (${{ matrix.os }})
++ strategy:
++ fail-fast: false
++ matrix:
++ os: [windows-latest, macos-latest]
++ runs-on: ${{ matrix.os }}
++ steps:
++ - uses: actions/checkout@v4
++ - uses: actions/setup-node@v4
++ with:
++ node-version: 22.22.1
++ - run: npm ci --no-audit --no-fund
++ - run: npm test
# Secret scan (gitleaks) and dependency audit, the workflow every OpenVibers repo calls (Track Q).
security:
uses: ./.github/workflows/security.yml
ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 12201b2..609357f 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -4,6 +4,15 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semve
breaking change to any exported module, browser global or served file name is a new major.
A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).
++## Unreleased
++
++- **The browser harness on macOS** (`browser-harness.js`): Chrome starts with `--disable-backgrounding-occluded-windows`,
++ `--disable-renderer-backgrounding` and `--disable-background-timer-throttling`, so every page draws and runs its timers
++ as if in front. On macOS a new page counted as an occluded window and painted nothing (the Core Web Vitals test saw no
++ paint and no shift). CI now runs the suite on Windows and macOS too.
++- **`perf-budget` vitals wait for the first paint before the click** (`measure({ browser })`): Chrome stops recording
++ LCP at the first input, and a slower machine clicked before anything was painted (no LCP at all on the macOS runner).
++
## 4.17.0 — 2026-10-27
- **`wallet: true`: OpenCoins and Vibes in the bar on any site** (`navbar.js`; owner, 2026-10-27: "make that as a
ddiff --git a/browser-harness.js b/browser-harness.js
iindex a99160e..b2ae9cf 100644
---- a/browser-harness.js
++++ b/browser-harness.js
@@@ -138,6 +138,9 @@ async function launch({ bin, browserURL, tmpDir, args = [], sandbox = false, hea
...(headless ? ['--headless=new'] : []), '--remote-debugging-port=0', `--user-data-dir=${profile}`,
...(sandbox ? [] : ['--no-sandbox']), '--no-first-run', '--no-default-browser-check', '--disable-extensions',
'--disable-background-networking', '--disable-component-update', '--disable-sync', '--disable-default-apps',
++ // Every page draws and runs timers as if in front: on macOS a new page counted as an occluded window, painted
++ // nothing, and the Core Web Vitals test saw no paint and no shift (2026-10-27, the first macOS CI run).
++ '--disable-backgrounding-occluded-windows', '--disable-renderer-backgrounding', '--disable-background-timer-throttling',
// No back/forward cache: kept pages would count in the navigation check's heap, node and document samples.
'--mute-audio', '--disk-cache-size=16777216', '--window-size=1280,900', '--disable-features=BackForwardCache', ...args, 'about:blank',
], { stdio: 'ignore', detached: true }); // its own process group: close() stops Chrome and its helpers together
ddiff --git a/perf-budget.js b/perf-budget.js
iindex 2f75aa9..bcbe6ea 100644
---- a/perf-budget.js
++++ b/perf-budget.js
@@@ -97,6 +97,9 @@ async function vitals(url, browser) {
page = await harness.openPage(b, context, VIEWPORT);
await page.send('Page.addScriptToEvaluateOnNewDocument', { source: CWV_PROBE });
await page.goto(url);
++ // The click waits for the first paint: Chrome stops recording LCP at the first input, and a slower machine (the
++ // macOS CI runner, 2026-10-27) could click before anything was painted, so no LCP at all.
++ await page.evaluate(`new Promise((ok) => { const t0 = Date.now(); (function poll() { if (performance.getEntriesByType('paint').some((e) => e.name === 'first-contentful-paint') || Date.now() - t0 > 3000) return ok(true); requestAnimationFrame(() => setTimeout(poll, 16)); })(); })`);
const at = { x: VIEWPORT.width / 2, y: VIEWPORT.height / 2, button: 'left', clickCount: 1 };
await page.send('Input.dispatchMouseEvent', { type: 'mousePressed', ...at });
await page.send('Input.dispatchMouseEvent', { type: 'mouseReleased', ...at });
ddiff --git a/test/boost-navbar-chrome.test.js b/test/boost-navbar-chrome.test.js
iindex 6de1f34..0cdd167 100644
---- a/test/boost-navbar-chrome.test.js
++++ b/test/boost-navbar-chrome.test.js
@@@ -52,6 +52,11 @@ const server = http.createServer((req, res) => {
ac.abort(); return [added - before, __ovProbe().documentListeners - before]; })()`);
assert.deepStrictEqual(probe, [1, 0], 'the probe counts a signal listener, then forgets it on abort');
++ // The bar's one-time extras load on their own a few seconds in (the release watch at 4 s, the launcher's icons
++ // when idle): let them settle first, so the laps count only what each re-render adds (a slower macOS runner
++ // caught the release watch's window listeners mid-lap, 2026-10-27).
++ await p.evaluate(`new Promise((r) => { const t0 = Date.now(); const t = setInterval(() => { if ((window.OVRelease && window.OpenVibeIcons) || Date.now() - t0 > 9000) { clearInterval(t); setTimeout(r, 300); } }, 100); })`);
++
const move = (id) => p.evaluate(`new Promise((done) => { document.addEventListener('ov:boost:load', () => done(location.pathname), { once: true }); document.getElementById('${id}').click(); })`);
const sample = async () => {
await p.send('HeapProfiler.collectGarbage'); await wait(150); await p.send('HeapProfiler.collectGarbage');