[web] Experiment with code coverage (#28029)

* Update test script to allow coverage to be recorded

* Collect coverage in CI

* [web] integrate coverage report with Jenkins

* [web] add coverage report for acceptance tests

* Use --all flag in vitest coverage

* Use --all flag in acceptance tests

* Switch vitest to use istanbul

* Bump versions of vitest plugins and update package-lock.json

* [web] use a dedicated parameter for coverage in unit/acceptance tests

* [web] temporarily generate HTML report and archive artifacts

* [web] do not run acceptance tests as root in CI

* [web] increase hook timeout in vitest when collecting coverage

* [web] exclude packageMapping from coverage report

* [web] use relative paths in clover reports

* [web] reduce concurrency for unit tests in CI

* [web] keep source code forever on builds

* Revert "[web] temporarily generate HTML report and archive artifacts"

This reverts commit 4c9db571

* [web] archive all the raw coverage report files

* [monorepo] update build scripts for web

* [monorepo] update package-lock.json after merge

* [web] remove spurious change

* [web] merge Jenkins parameter sections

* [web] increase hookTimeout when collecting coverage report

---------

Co-authored-by: Jakob Ackermann <jakob.ackermann@overleaf.com>
GitOrigin-RevId: 9f1ec6d38a7b037267737bec3b0bc53a0ac844af
This commit is contained in:
Andrew Rumble
2026-02-10 09:05:36 +00:00
committed by Copybot
co-authored by Jakob Ackermann
parent 9e41d9afdb
commit b2d4687c52
10 changed files with 917 additions and 147 deletions
+858 -140
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -81,6 +81,9 @@ COPY tools/migrations/ /overleaf/tools/migrations/
# Build the latex parser
RUN cd /overleaf/services/web && npm run 'lezer-latex:generate'
# The module folders are owned as root. The node user cannot create them. Create them in advance.
RUN cd /overleaf/services/web && make create_module_Makefiles
USER node
+8
View File
@@ -32,6 +32,7 @@ MODULE_DIRS := $(shell find modules -mindepth 1 -maxdepth 1 -type d -not -name '
MODULE_MAKEFILES := $(MODULE_DIRS:=/Makefile)
MODULE_NAME=$(shell basename $(MODULE))
create_module_Makefiles: $(MODULE_MAKEFILES)
$(MODULE_MAKEFILES): Makefile.module
cp Makefile.module $@ || diff Makefile.module $@
@@ -424,6 +425,13 @@ test_acceptance_modules: $(TEST_ACCEPTANCE_MODULES_MERGED_VARIANTS)
# CI tests
#
rebase_coverage_reports:
sed -i 's_path="/overleaf/_path="_' data/coverage/*/clover.xml
# Create the bundle with full paths inside the monorepo to make it easier to organize them later.
bundle_coverage_reports:
cd ../../ && tar -czf services/web/data/coverage.tar.gz services/web/data/coverage/*/clover.xml
ci:
MOCHA_ARGS="--reporter tap" \
$(MAKE) test
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
declare -a mocha_prefix=()
if [[ "$COVERAGE_ACCEPTANCE_TESTS" == "true" ]]; then
name=$(echo "$BASE_CONFIG $OVERLEAF_CONFIG" "$@" | sha256sum)
dir=data/coverage/mocha-acceptance-${name}
echo "Enabling coverage. Writing into ${dir}"
mocha_prefix+=("c8" "--reporter=clover" "--all" "--include={app.mjs,app/**/*.{mjs,js},modules/*/index.mjs,modules/*/app/**/*.{mjs,js}}" "--exclude=app/src/Features/Metadata/packageMapping.mjs" "--report-dir=${dir}")
fi
"${mocha_prefix[@]}" mocha --recursive --timeout 25000 --grep="$MOCHA_GREP" --require test/acceptance/bootstrap.js "$@"
-1
View File
@@ -14,4 +14,3 @@ fi
echo "Running unit tests in directory: $*"
npm run test:unit -- "${vitest_args[@]}"
+7 -2
View File
@@ -12,6 +12,7 @@ services:
user: node
volumes:
- ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- ./data/coverage:/overleaf/services/web/data/coverage
- ./data/reports:/overleaf/services/web/data/reports
entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
command: npm run test:unit:app
@@ -27,6 +28,8 @@ services:
NODE_OPTIONS: "--unhandled-rejections=strict"
REDIS_HOST: redis_test
VITEST_NO_CACHE: true
COVERAGE_UNIT_TESTS:
MONOREPO:
depends_on:
- redis_test
- mongo
@@ -47,21 +50,23 @@ services:
BASE_CONFIG:
OVERLEAF_CONFIG:
REDIS_HOST: redis_test
COVERAGE_ACCEPTANCE_TESTS:
MONOREPO:
extra_hosts:
- "www.overleaf.test:127.0.0.1"
volumes:
- ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- ./data/coverage:/overleaf/services/web/data/coverage
- ./data/reports:/overleaf/services/web/data/reports
entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
command: npm run test:acceptance:app
user: root
depends_on:
- redis_test
- mongo
- saml
- ldap
tmpfs:
- /overleaf/services/web/data
- /overleaf/services/web/data:uid=1000,gid=1000
test_frontend:
build:
+6 -2
View File
@@ -18,11 +18,13 @@ services:
CI:
MODULE_NAME:
JUNIT_ROOT_SUITE_NAME:
MOCHA_GREP: ${MOCHA_GREP}
COVERAGE_UNIT_TESTS: ${COVERAGE_UNIT_TESTS:-}
MOCHA_GREP: ${MOCHA_GREP:-}
LOG_LEVEL: ${LOG_LEVEL:-}
NODE_ENV: test
NODE_OPTIONS: "--unhandled-rejections=strict"
REDIS_HOST: redis_test
MONOREPO:
entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
command: npm run --silent test:unit:app
user: node
@@ -47,11 +49,13 @@ services:
CI:
MODULE_NAME:
JUNIT_ROOT_SUITE_NAME:
MOCHA_GREP: ${MOCHA_GREP}
COVERAGE_ACCEPTANCE_TESTS: ${COVERAGE_ACCEPTANCE_TESTS:-}
MOCHA_GREP: ${MOCHA_GREP:-}
LOG_LEVEL: ${LOG_LEVEL:-}
MONGO_SERVER_SELECTION_TIMEOUT: 600000
MONGO_SOCKET_TIMEOUT: 300000
REDIS_HOST: redis_test
MONOREPO:
# OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING: 'true'
extra_hosts:
+3 -2
View File
@@ -7,7 +7,7 @@
"public": "./public"
},
"scripts": {
"test:acceptance:run_dir": "mocha --recursive --timeout 25000 --grep=$MOCHA_GREP --require test/acceptance/bootstrap.js",
"test:acceptance:run_dir": "bin/test_acceptance_run_dir",
"test:acceptance:app": "npm run test:acceptance:run_dir -- test/acceptance/src",
"test:unit:run_dir": "bin/test_unit_run_dir",
"test:unit:all": "npm run test:unit:run_dir -- test/unit/src modules/*/test/unit/src",
@@ -273,7 +273,8 @@
"@uppy/react": "^3.2.1",
"@uppy/utils": "^5.7.0",
"@uppy/xhr-upload": "^3.6.0",
"@vitest/eslint-plugin": "1.1.44",
"@vitest/coverage-istanbul": "^4.0.17",
"@vitest/eslint-plugin": "1.6.6",
"@writefull/core": "^1.27.27",
"@writefull/ui": "^1.27.27",
"@writefull/utils": "^1.27.27",
+20
View File
@@ -1,8 +1,11 @@
const { defineConfig } = require('vitest/config')
const COVERAGE_ENABLED = process.env.COVERAGE_UNIT_TESTS === 'true'
let reporterOptions = {}
if (process.env.CI && process.env.JUNIT_ROOT_SUITE_NAME) {
reporterOptions = {
maxWorkers: '50%',
reporters: [
'default',
[
@@ -15,6 +18,7 @@ if (process.env.CI && process.env.JUNIT_ROOT_SUITE_NAME) {
outputFile: 'data/reports/junit-vitest.xml',
}
}
module.exports = defineConfig({
test: {
setupFiles: ['./test/unit/bootstrap.mjs'],
@@ -53,5 +57,21 @@ module.exports = defineConfig({
},
],
...reporterOptions,
hookTimeout: COVERAGE_ENABLED ? 20_000 : 10_000,
coverage: {
enabled: COVERAGE_ENABLED,
// Add 'sequential' / 'parallel' to the folder
reportsDirectory: `data/coverage/esm-unit-${(process.env.JUNIT_ROOT_SUITE_NAME || 'all').split(' ').pop()}`,
include: [
'app.mjs',
'app/**/*.{js,mjs}',
'modules/*/index.mjs',
'modules/*/app/src/**/*.{js,mjs}',
],
exclude: ['app/src/Features/Metadata/packageMapping.mjs'],
provider: 'istanbul',
reporters: ['console-details', 'clover'],
all: true,
},
},
})