diff --git a/.github/workflows/browser-suite.yml b/.github/workflows/browser-suite.yml new file mode 100644 index 000000000..1b515cf1e --- /dev/null +++ b/.github/workflows/browser-suite.yml @@ -0,0 +1,57 @@ +# Runs the ClojureScript test suite in headless Chromium (lein fig:test, then +# test/e2e/cljs-harness.js; docs/kb/cljs-headless-harness.md). +# +# The JVM suite runs on integration through locale.yml, but the builder's wiring +# (re-frame events, interceptors, subscriptions) is tested only in the browser, +# so until this ran a change that broke it passed CI. + +name: Browser suite + +on: + push: + branches: [integration, develop, main] + pull_request: + branches: [integration, develop, main] + workflow_dispatch: + +permissions: + contents: read + +jobs: + cljs-suite: + name: ClojureScript suite in Chromium + runs-on: ubuntu-latest + timeout-minutes: 25 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '21' + + # Pinned to the version locale.yml and continuous-integration.yml use. + - name: Install Leiningen + run: | + curl -fsSL -o "$HOME/lein" \ + https://raw.githubusercontent.com/technomancy/leiningen/2.11.2/bin/lein + chmod +x "$HOME/lein" + echo "$HOME" >> "$GITHUB_PATH" + "$HOME/lein" version + + - uses: actions/setup-node@v4 + with: + node-version: '22' + + # package.json declares Playwright; its own Chromium, which find-chrome falls back to. + - name: Install Playwright and Chromium + run: | + npm install --no-audit --no-fund + npx playwright install --with-deps chromium + + - name: Compile the test build + run: lein fig:test + + # Exits 0 only when every test passes and no FAIL or ERROR is reported. + - name: Run the suite headless + run: node test/e2e/cljs-harness.js diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 83aa9268d..7e8ea72da 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -6,9 +6,9 @@ name: CI on: push: - branches: [develop, main] + branches: [integration, develop, main] pull_request: - branches: [develop, main] + branches: [integration, develop, main] workflow_dispatch: permissions: @@ -134,7 +134,8 @@ jobs: name: Submit Clojure dependencies runs-on: ubuntu-latest needs: detect-stack - if: github.event_name != 'pull_request' + # Not integration: alerts read the default branch, and this job ran only for develop and main. + if: github.event_name != 'pull_request' && github.ref_name != 'integration' permissions: contents: write steps: diff --git a/test/e2e/cljs-harness.js b/test/e2e/cljs-harness.js index b8a81a738..d0e9f677a 100644 --- a/test/e2e/cljs-harness.js +++ b/test/e2e/cljs-harness.js @@ -2,7 +2,10 @@ const http=require('http'),fs=require('fs'),path=require('path');const {chromium}=require('playwright'); const {findChrome}=require('../browser/lib/find-chrome'); const ROOT=path.resolve('target/test'); +// fig:test writes only the scripts; the page that runs them is served from here unless one exists. +const RUNNER='
'; const srv=http.createServer((q,r)=>{const u=decodeURIComponent(q.url.split('?')[0]);const f=path.join(ROOT,u==='/'?'runner-all.html':u); + if(f===path.join(ROOT,'runner-all.html')&&!fs.existsSync(f)){r.writeHead(200,{'Content-Type':'text/html; charset=utf-8'});return r.end(RUNNER);} if(!f.startsWith(ROOT)||!fs.existsSync(f)||fs.statSync(f).isDirectory()){r.writeHead(404);return r.end();} r.writeHead(200,{'Content-Type':f.endsWith('.js')?'application/javascript; charset=utf-8':f.endsWith('.html')?'text/html; charset=utf-8':'application/octet-stream'});fs.createReadStream(f).pipe(r);}); (async()=>{await new Promise(r=>srv.listen(0,r));const port=srv.address().port; @@ -18,7 +21,8 @@ const srv=http.createServer((q,r)=>{const u=decodeURIComponent(q.url.split('?')[ const totals=ti>=0?lines.slice(ti+1,lines.indexOf('Hide/Show Passing',ti)).join(', '):''; const verdict=ti>0?lines[ti-1]:''; console.log('SUMMARY:',ran.slice(-1)[0]||(totals?`${verdict}: ${totals}`:'(none)'),tot.slice(-1)[0]||''); - const passed=ran.length?/ 0 failures, 0 errors/.test(' '+(tot.slice(-1)[0]||'')):verdict==='All Tests Passed'; + // Figwheel says "All Tests Passed" for zero tests too (none found or loaded), so a pass needs a count. + const passed=ran.length?/ 0 failures, 0 errors/.test(' '+(tot.slice(-1)[0]||'')):verdict==='All Tests Passed'&&/\b[1-9]\d*\s+Tests\b/.test(totals); process.exitCode=passed?0:1; // The auto-testing page marks each failed assertion with a .test-fail node inside its test's node. const shown=await pg.evaluate(()=>[...document.querySelectorAll('.test-fail')].map(n=> @@ -28,6 +32,5 @@ const srv=http.createServer((q,r)=>{const u=decodeURIComponent(q.url.split('?')[ fs.writeFileSync('target/test/cljs-run.log',all); // Green only on a complete summary reading zero failures and zero errors; a // timeout or crash leaves no summary and must not pass. - const last=tot.slice(-1)[0]||''; const clean=ran.length>0&&/^0 failures, 0 errors\./.test(last); - process.exitCode=clean&&fails.length===0?0:1; + process.exitCode=passed&&fails.length===0?0:1; await br.close();srv.close();})();