diff --git a/.github/workflows/cli-node-tests.yml b/.github/workflows/cli-node-tests.yml index b7ed3cdc..56f47f86 100644 --- a/.github/workflows/cli-node-tests.yml +++ b/.github/workflows/cli-node-tests.yml @@ -219,10 +219,27 @@ jobs: run: npm ci - name: Build CLI + if: ${{ matrix.os != 'ubuntu-latest' }} env: LIVESYNC_CLI_TEST_SUPPORT: '1' run: npm run build --workspace self-hosted-livesync-cli - name: Run local smoke tests + if: ${{ matrix.os != 'ubuntu-latest' }} working-directory: src/apps/cli run: node ../../../test/e2e-cli/run-task.ts test:local + + - name: Run local smoke tests with coverage + if: ${{ matrix.os == 'ubuntu-latest' }} + run: npm run test:e2e:coverage --workspace self-hosted-livesync-cli + + - name: Upload local smoke coverage reports + if: ${{ always() && matrix.os == 'ubuntu-latest' }} + uses: actions/upload-artifact@v6 + with: + name: cli-e2e-local-coverage + path: | + coverage/cli-e2e/** + !coverage/cli-e2e/raw/** + if-no-files-found: warn + retention-days: 7 diff --git a/test/e2e-cli/test_dev_node.md b/test/e2e-cli/test_dev_node.md index ad7c1de9..2f006878 100644 --- a/test/e2e-cli/test_dev_node.md +++ b/test/e2e-cli/test_dev_node.md @@ -71,6 +71,8 @@ npm run test:e2e:coverage --workspace self-hosted-livesync-cli -- test:ci The runner defaults to `test:local`, which does not require services. It writes HTML, JSON, and text reports under `coverage/cli-e2e/`. The runner enables CLI bundle source maps only for the coverage build with `LIVESYNC_CLI_COVERAGE=1`; ordinary builds do not enable them. +The CLI Node.js workflow runs this local smoke coverage on Linux and publishes the HTML and JSON reports as the `cli-e2e-local-coverage` artifact, retained for seven days. This artifact covers `test:local` only and is separate from unit-test and service-backed/P2P coverage. + ## Environment variables ### CouchDB diff --git a/utils/cli-ci.unit.spec.ts b/utils/cli-ci.unit.spec.ts index 8b1896dc..dd60faf0 100644 --- a/utils/cli-ci.unit.spec.ts +++ b/utils/cli-ci.unit.spec.ts @@ -144,6 +144,33 @@ describe("CLI CI selection", () => { expect(test.steps?.find((step) => step.run?.includes("node run-task.ts"))?.run).toContain('node run-task.ts "$TASK"'); }); + it("collects local smoke coverage once on Linux and retains its reports for seven days", () => { + const steps = workflow("cli-node-tests.yml").jobs["local-smoke"].steps ?? []; + const coverage = steps.filter((step) => step.run?.includes("test:e2e:coverage")); + expect(coverage).toHaveLength(1); + expect(coverage[0].run).toBe("npm run test:e2e:coverage --workspace self-hosted-livesync-cli"); + expect(coverage[0].if).toBe("${{ matrix.os == 'ubuntu-latest' }}"); + for (const step of steps.filter( + (step) => step.run?.includes("npm run build --workspace") || step.run?.includes("run-task.ts test:local") + )) { + expect(step.if).toBe("${{ matrix.os != 'ubuntu-latest' }}"); + } + const uploads = steps.filter((step) => step.uses?.startsWith("actions/upload-artifact@")); + expect(uploads).toHaveLength(1); + const upload = uploads[0]; + expect(upload.if).toBe("${{ always() && matrix.os == 'ubuntu-latest' }}"); + expect(upload.with?.name).toBe("cli-e2e-local-coverage"); + expect(upload.with?.["retention-days"]).toBe(7); + expect(upload.with?.["if-no-files-found"]).toBe("warn"); + const paths = String(upload.with?.path).trim().split("\n"); + expect(matches(paths, "coverage/cli-e2e/index.html")).toBe(true); + expect(matches(paths, "coverage/cli-e2e/src/apps/cli/main.ts.html")).toBe(true); + expect(matches(paths, "coverage/cli-e2e/base.css")).toBe(true); + expect(matches(paths, "coverage/cli-e2e/coverage-final.json")).toBe(true); + expect(matches(paths, "coverage/cli-e2e/raw/coverage-123.json")).toBe(false); + expect(matches(paths, "coverage/unit/index.html")).toBe(false); + }); + it("passes a single CLI build to every standard test job", () => { const { prepare, test } = workflow("cli-node-tests.yml").jobs; expect(test.needs).toBe("prepare");