From c3c04acfea5f5a60f4a779145c430b83c74cf5aa Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Thu, 2 Oct 2025 09:28:09 +0200 Subject: [PATCH] [web] add junit integration for Jenkins (#28788) * [web] add junit integration for Jenkins * [web] integrate junit test report for writefull into Jenkins * [web] put all the junit test results into namespaces GitOrigin-RevId: ba1ff07b5ea7bcfa97bb4d6bf7fa9e5291ab7b0f --- package-lock.json | 20 ++ services/web/.mocharc.js | 8 + services/web/Jenkinsfile | 311 ++++-------------- services/web/Makefile | 28 +- services/web/buildscript.txt | 1 - services/web/cypress.config.ts | 15 +- .../web/cypress/cypress-multi-reporters.json | 10 + services/web/data/reports/.gitignore | 0 services/web/docker-compose.ci.yml | 23 +- services/web/docker-compose.yml | 11 + services/web/package.json | 3 + services/web/test/mocha-multi-reporters.js | 10 + services/web/vitest.config.js | 16 + 13 files changed, 194 insertions(+), 262 deletions(-) create mode 100644 services/web/.mocharc.js create mode 100644 services/web/cypress/cypress-multi-reporters.json create mode 100644 services/web/data/reports/.gitignore create mode 100644 services/web/test/mocha-multi-reporters.js diff --git a/package-lock.json b/package-lock.json index 2f5350a8c8..aac92d0267 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37047,6 +37047,23 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/mocha-multi-reporters": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/mocha-multi-reporters/-/mocha-multi-reporters-1.5.1.tgz", + "integrity": "sha512-Yb4QJOaGLIcmB0VY7Wif5AjvLMUFAdV57D2TWEva1Y0kU/3LjKpeRVmlMIfuO1SVbauve459kgtIizADqxMWPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "lodash": "^4.17.15" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "mocha": ">=3.1.2" + } + }, "node_modules/mocha/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -52717,6 +52734,7 @@ "css-loader": "^6.8.1", "css-minimizer-webpack-plugin": "^5.0.1", "cypress": "13.13.2", + "cypress-multi-reporters": "^2.0.5", "cypress-plugin-tab": "^1.0.5", "d3": "^3.5.16", "daterangepicker": "2.1.27", @@ -52758,6 +52776,8 @@ "mini-css-extract-plugin": "^2.7.6", "mocha": "^11.1.0", "mocha-each": "^2.0.1", + "mocha-junit-reporter": "^2.2.1", + "mocha-multi-reporters": "^1.5.1", "mock-fs": "^5.1.2", "nock": "^13.5.6", "nvd3": "^1.8.6", diff --git a/services/web/.mocharc.js b/services/web/.mocharc.js new file mode 100644 index 0000000000..172729399d --- /dev/null +++ b/services/web/.mocharc.js @@ -0,0 +1,8 @@ +let reporterOptions = {} +if (process.env.CI && process.env.MOCHA_ROOT_SUITE_NAME) { + reporterOptions = { + reporter: '/overleaf/node_modules/mocha-multi-reporters', + 'reporter-options': ['configFile=./test/mocha-multi-reporters.js'], + } +} +module.exports = reporterOptions diff --git a/services/web/Jenkinsfile b/services/web/Jenkinsfile index c00c0e049d..99e3d51c7b 100644 --- a/services/web/Jenkinsfile +++ b/services/web/Jenkinsfile @@ -1,17 +1,3 @@ -// Initialize variables to signal that a given stage finished. -// We use them to build a graph of interconnected steps/dependencies. -// - Incoming edges use "waitUntil" and reference the given variables of dependencies. -// - Outgoing edges set the given variable to true. -def action_test_frontend_ct_build = false -def action_build_deps = false -def action_copy_external_pages = false -def action_build_dev = false -def action_test_acceptance_app_server_pro = false -def action_build_webpack = false -def action_test_acceptance_app_saas = false -def action_build_pug = false -def action_build_production = false - pipeline { agent { node { @@ -24,8 +10,6 @@ pipeline { options { // Print timestamp next to each log line. timestamps() - // Abort build after hitting first failure. - parallelsAlwaysFailFast() retry(3) timeout(time: 15, unit: 'MINUTES') } @@ -45,34 +29,8 @@ pipeline { CDN_PROD = "gs://mgcp-1117973-ol-prod-web-assets-1" } stages { - // Retries will use the same pipeline instance. Reset the vars. - stage('Reset vars') { - steps { - script { - action_test_frontend_ct_build = false - action_build_deps = false - action_copy_external_pages = false - action_build_dev = false - action_test_acceptance_app_server_pro = false - action_build_webpack = false - action_test_acceptance_app_saas = false - action_build_pug = false - action_build_production = false - } - } - } - stage ('Build, Test and Push') { + stage('Stage 1') { parallel { - stage('Copy external pages') { - steps { - dir('services/web') { - sh 'bin/copy_external_pages' - script { - action_copy_external_pages = true - } - } - } - } stage('Prefetch Tests Images') { steps { dir('services/web') { @@ -80,50 +38,29 @@ pipeline { } } } - stage('Build Deps') { + stage('Build') { steps { dir('services/web') { + sh 'bin/copy_external_pages' sh 'make build_deps' - } - script { - action_build_deps = true + sh 'make build_dev' + sh 'make build_test_frontend_ct' } } } + } + } + stage('Stage 2') { + parallel { stage('Push Deps') { steps { - script { - waitUntil { - return action_build_deps - } - } dir('services/web') { sh 'docker push ${AR_URL}/${IMAGE_NAME}:${BRANCH_NAME}-deps' } } } - stage('Build Dev') { + stage('Format') { steps { - script { - waitUntil { - return action_build_deps && action_copy_external_pages - } - } - dir('services/web') { - sh 'make build_dev' - } - script { - action_build_dev = true - } - } - } - stage ('Format') { - steps { - script { - waitUntil { - return action_build_dev - } - } dir('services/web') { sh 'make format_in_docker' } @@ -131,15 +68,16 @@ pipeline { } stage('Lint') { steps { - script { - waitUntil { - return action_build_dev - } - } dir('services/web') { sh 'make lint_in_docker' } } + post { + always { + recordIssues checksAnnotationScope: 'ALL', enabledForFailure: true, failOnError: true, id: 'web-eslint', name: 'Web eslint', qualityGates: [[integerThreshold: 1, threshold: 1.0, type: 'TOTAL']], sourceCodeRetention: 'LAST_BUILD', tools: [esLint(pattern: 'services/web/data/reports/eslint.json')] + recordIssues checksAnnotationScope: 'ALL', enabledForFailure: true, failOnError: true, id: 'web-stylelint', name: 'Web stylelint', qualityGates: [[integerThreshold: 1, threshold: 1.0, type: 'TOTAL']], sourceCodeRetention: 'LAST_BUILD', tools: [styleLint(pattern: 'services/web/data/reports/stylelint.json')] + } + } } stage('Shellcheck') { steps { @@ -150,26 +88,13 @@ pipeline { } stage('Acceptance SaaS') { steps { - script { - waitUntil { - return action_build_dev - } - } dir('services/web') { sh 'make test_acceptance_app_saas' } - script { - action_test_acceptance_app_saas = true - } } } stage('Acceptance Server CE') { steps { - script { - waitUntil { - return action_build_dev - } - } dir('services/web') { sh "make test_acceptance_app_server_ce" } @@ -177,26 +102,13 @@ pipeline { } stage('Acceptance Server Pro') { steps { - script { - waitUntil { - return action_build_dev - } - } dir('services/web') { sh "make test_acceptance_app_server_pro" } - script { - action_test_acceptance_app_server_pro = true - } } } stage('test_acceptance_modules_merged_saas_1') { steps { - script { - waitUntil { - return action_build_dev - } - } dir('services/web') { sh "make test_acceptance_modules_merged_saas_1" } @@ -204,11 +116,6 @@ pipeline { } stage('test_acceptance_modules_merged_saas_2') { steps { - script { - waitUntil { - return action_build_dev - } - } dir('services/web') { sh "make test_acceptance_modules_merged_saas_2" } @@ -216,11 +123,6 @@ pipeline { } stage('test_acceptance_modules_merged_saas_3') { steps { - script { - waitUntil { - return action_build_dev - } - } dir('services/web') { sh "make test_acceptance_modules_merged_saas_3" } @@ -228,11 +130,6 @@ pipeline { } stage('test_acceptance_modules_merged_saas_4') { steps { - script { - waitUntil { - return action_build_dev - } - } dir('services/web') { sh "make test_acceptance_modules_merged_saas_4" } @@ -240,11 +137,6 @@ pipeline { } stage('test_acceptance_modules_merged_server_ce') { steps { - script { - waitUntil { - return action_build_dev - } - } dir('services/web') { sh "make test_acceptance_modules_merged_server_ce" } @@ -252,11 +144,6 @@ pipeline { } stage('test_acceptance_modules_merged_server_pro') { steps { - script { - waitUntil { - return action_build_dev - } - } dir('services/web') { sh "make test_acceptance_modules_merged_server_pro" } @@ -264,11 +151,6 @@ pipeline { } stage('test_frontend') { steps { - script { - waitUntil { - return action_build_dev - } - } dir('services/web') { sh "make test_frontend" } @@ -276,41 +158,16 @@ pipeline { } stage('test_writefull') { steps { - script { - waitUntil { - return action_build_dev - } - } dir('services/web') { sh "make test_writefull" } } } - stage('test_frontend_ct_build') { - steps { - script { - waitUntil { - return action_build_dev - } - } - dir('services/web') { - sh "make build_test_frontend_ct" - } - script { - action_test_frontend_ct_build = true - } - } - } stage('test_frontend_ct_core_other') { environment { CYPRESS_INTERNAL_BROWSER_CONNECT_TIMEOUT = '120000' } steps { - script { - waitUntil { - return action_test_frontend_ct_build - } - } dir('services/web') { sh "make test_frontend_ct_core_other" } @@ -321,11 +178,6 @@ pipeline { CYPRESS_INTERNAL_BROWSER_CONNECT_TIMEOUT = '120000' } steps { - script { - waitUntil { - return action_test_frontend_ct_build - } - } dir('services/web') { sh "make test_frontend_ct_core_features" } @@ -336,11 +188,6 @@ pipeline { CYPRESS_INTERNAL_BROWSER_CONNECT_TIMEOUT = '120000' } steps { - script { - waitUntil { - return action_test_frontend_ct_build - } - } dir('services/web') { sh "make test_frontend_ct_modules" } @@ -348,11 +195,6 @@ pipeline { } stage('test_frontend_ct_editor_visual') { steps { - script { - waitUntil { - return action_test_frontend_ct_build - } - } dir('services/web') { sh "make test_frontend_ct_editor_visual" } @@ -360,11 +202,6 @@ pipeline { } stage('test_frontend_ct_editor_other') { steps { - script { - waitUntil { - return action_test_frontend_ct_build - } - } dir('services/web') { sh "make test_frontend_ct_editor_other" } @@ -372,11 +209,6 @@ pipeline { } stage('Test Unit ESM') { steps { - script { - waitUntil { - return action_build_dev - } - } dir('services/web') { sh "make test_unit_esm" } @@ -384,90 +216,60 @@ pipeline { } stage('Test Unit Mocha') { steps { - script { - waitUntil { - return action_build_dev - } - } dir('services/web') { sh "make test_unit_mocha" } } } - stage('Build Webpack') { - steps { - script { - waitUntil { - return action_build_dev && action_test_acceptance_app_server_pro + stage('Build webpack + production + cdn upload + sentry upload') { + stages { + stage('Wait a bit to give tests all the CPU capacity') { + steps { + sh 'sleep 120' } } - dir('services/web') { - sh 'make build_webpack' - } - script { - action_build_webpack = true - } - } - } - stage('Build Pug') { - steps { - script { - waitUntil { - return action_build_dev && action_test_acceptance_app_server_pro + stage('Build Webpack') { + steps { + dir('services/web') { + sh 'make build_webpack' + } } } - dir('services/web') { - sh 'make build_pug' - } - script { - action_build_pug = true - } - } - } - stage('CDN Upload Image') { - steps { - script { - waitUntil { - return action_build_webpack + stage('Build Pug') { + steps { + dir('services/web') { + sh 'make build_pug' + } } } - dir('services/web') { - sh 'make tar' - sh 'bin/cdn_upload' - } - } - } - stage('Build Production') { - steps { - script { - waitUntil { - return action_build_webpack && action_build_pug + stage('CDN Upload Image') { + steps { + dir('services/web') { + sh 'make tar' + sh 'bin/cdn_upload' + } } } - dir('services/web') { - sh 'make build' - } - script { - action_build_production = true - } - } - } - stage('Sentry Upload') { - steps { - script { - waitUntil { - return action_build_webpack && action_build_production + stage('Build Production') { + steps { + dir('services/web') { + sh 'make build' + } } } - dir('services/web') { - sh 'gcloud secrets versions access latest --secret=web-sentryclirc > .sentryclirc' - sh 'make sentry_upload' - } - } - post { - cleanup { - dir('services/web') { - sh 'rm -f .sentryclirc' + stage('Sentry Upload') { + steps { + dir('services/web') { + sh 'gcloud secrets versions access latest --secret=web-sentryclirc > .sentryclirc' + sh 'make sentry_upload' + } + } + post { + cleanup { + dir('services/web') { + sh 'rm -f .sentryclirc' + } + } } } } @@ -483,6 +285,9 @@ pipeline { } } post { + always { + junit checksName: 'Web test results', testResults: 'services/web/data/reports/junit-*.xml' + } // Ensure tear down of test containers, then run general Jenkins VM cleanup. cleanup { dir('services/web') { diff --git a/services/web/Makefile b/services/web/Makefile index 35faba7407..499e19d9cf 100644 --- a/services/web/Makefile +++ b/services/web/Makefile @@ -6,6 +6,7 @@ export COMMIT_SHA ?= $(shell git rev-parse HEAD) PROJECT_NAME = web BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]') PWD = $(shell pwd) +export MONOREPO ?= $(shell cd ../../ && pwd) export OVERLEAF_CONFIG ?= /overleaf/services/web/test/acceptance/config/settings.test.saas.js export BASE_CONFIG ?= ${OVERLEAF_CONFIG} @@ -60,6 +61,7 @@ SHARD_PROJECT_NAMES = \ CLEAN_SHARDS=$(addprefix clean/,$(SHARD_PROJECT_NAMES)) clean: $(CLEAN_SHARDS) + -docker run --rm --volume /dev/shm:/dev/shm --user root $(IMAGE_CI) rm -rf /dev/shm/overleaf -docker rmi --force $(IMAGE_CI) $(IMAGE_CI)-dev $(IMAGE_CI)-pug $(IMAGE_CI)-webpack $(IMAGE_REPO_FINAL) -docker compose down --remove-orphans --rmi local --timeout 0 --volumes -git clean -dfX data/ @@ -98,10 +100,12 @@ test_unit_app: export COMPOSE_PROJECT_NAME=unit_test_$(BUILD_DIR_NAME) test_unit_app: mongo_migrations_for_tests $(DOCKER_COMPOSE) run --name unit_test_$(BUILD_DIR_NAME) --rm test_unit +test_unit_mocha: export MOCHA_ROOT_SUITE_NAME = Mocha unit tests test_unit_mocha: export COMPOSE_PROJECT_NAME=unit_test_mocha_$(BUILD_DIR_NAME) test_unit_mocha: mongo_migrations_for_tests $(DOCKER_COMPOSE) run --rm test_unit npm run test:unit:mocha +test_unit_esm: export MOCHA_ROOT_SUITE_NAME = ESM unit tests test_unit_esm: export COMPOSE_PROJECT_NAME=unit_test_esm_$(BUILD_DIR_NAME) test_unit_esm: mongo_migrations_for_tests $(DOCKER_COMPOSE) run --rm test_unit npm run test:unit:esm @@ -148,6 +152,7 @@ mongo_migrations_for_tests: # Frontend tests # +test_frontend: export MOCHA_ROOT_SUITE_NAME = JSDOM frontend tests test_frontend: COMPOSE_PROJECT_NAME=frontend_test_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) run --rm test_frontend COMPOSE_PROJECT_NAME=frontend_test_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) down -v -t 0 @@ -169,13 +174,14 @@ TEST_FRONTEND_CT_VARIANTS = \ # Writefull tests # +test_writefull: export MOCHA_ROOT_SUITE_NAME = Writefull tests test_writefull: COMPOSE_PROJECT_NAME=writefull_test_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) run --rm test_writefull COMPOSE_PROJECT_NAME=writefull_test_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) down -v -t 0 # Note: The below cypress targets are for CI only build_test_frontend_ct: - docker run --rm --volume /dev/shm:/dev/shm --user root $(IMAGE_CI) bash -ec 'tar -cC / overleaf | tar -xC /dev/shm' + docker run --rm --volume /dev/shm:/dev/shm --user root $(IMAGE_CI) bash -ec 'for path in /overleaf/services/web/cypress/results /overleaf/services/web/node_modules/.cache; do mkdir -p $$path; chown -R node:node $$path; done && tar -cC / overleaf | tar -xC /dev/shm' test_frontend_ct_core_other: export CYPRESS_RESULTS=./cypress/results/core test_frontend_ct_core_other: export CYPRESS_SPEC_PATTERN=./test/frontend/**/*.spec.{js,jsx,ts,tsx} @@ -214,10 +220,13 @@ TEST_ACCEPTANCE_APP := \ test_acceptance_app_server_pro \ test_acceptance_app: $(TEST_ACCEPTANCE_APP) +test_acceptance_app_saas: export MOCHA_ROOT_SUITE_NAME = SaaS app acceptance tests test_acceptance_app_saas: export COMPOSE_PROJECT_NAME=acceptance_test_saas_$(BUILD_DIR_NAME) test_acceptance_app_saas: export OVERLEAF_CONFIG=$(CFG_SAAS) +test_acceptance_app_server_ce: export MOCHA_ROOT_SUITE_NAME = Server CE app acceptance tests test_acceptance_app_server_ce: export COMPOSE_PROJECT_NAME=acceptance_test_server_ce_$(BUILD_DIR_NAME) test_acceptance_app_server_ce: export OVERLEAF_CONFIG=$(CFG_SERVER_CE) +test_acceptance_app_server_pro: export MOCHA_ROOT_SUITE_NAME = Server Pro app acceptance tests test_acceptance_app_server_pro: export COMPOSE_PROJECT_NAME=acceptance_test_server_pro_$(BUILD_DIR_NAME) test_acceptance_app_server_pro: export OVERLEAF_CONFIG=$(CFG_SERVER_PRO) @@ -349,14 +358,17 @@ $(TEST_ACCEPTANCE_MODULES_MERGED_INNER_SPLIT): ) # See docs for test_acceptance_server_ce how this works. +test_acceptance_modules_merged_saas: export MOCHA_ROOT_SUITE_NAME = SaaS modules acceptance tests test_acceptance_modules_merged_saas: export COMPOSE_PROJECT_NAME = \ acceptance_test_modules_merged_saas_$(BUILD_DIR_NAME) test_acceptance_modules_merged_saas: export BASE_CONFIG = $(CFG_SAAS) +test_acceptance_modules_merged_server_ce: export MOCHA_ROOT_SUITE_NAME = Server CE modules acceptance tests test_acceptance_modules_merged_server_ce: export COMPOSE_PROJECT_NAME = \ acceptance_test_modules_merged_server_ce_$(BUILD_DIR_NAME) test_acceptance_modules_merged_server_ce: export BASE_CONFIG = $(CFG_SERVER_CE) +test_acceptance_modules_merged_server_pro: export MOCHA_ROOT_SUITE_NAME = Server Pro modules acceptance tests test_acceptance_modules_merged_server_pro: export COMPOSE_PROJECT_NAME = \ acceptance_test_modules_merged_server_pro_$(BUILD_DIR_NAME) test_acceptance_modules_merged_server_pro: export BASE_CONFIG = $(CFG_SERVER_PRO) @@ -388,6 +400,7 @@ test_acceptance_modules_merged_saas_3: export COMPOSE_PROJECT_NAME = \ test_acceptance_modules_merged_saas_4: export COMPOSE_PROJECT_NAME = \ acceptance_test_modules_merged_saas_4_$(BUILD_DIR_NAME) $(TEST_ACCEPTANCE_MODULES_MERGED_SPLIT_SAAS): export BASE_CONFIG = $(CFG_SAAS) +$(TEST_ACCEPTANCE_MODULES_MERGED_SPLIT_SAAS): export MOCHA_ROOT_SUITE_NAME = SaaS modules acceptance tests $(TEST_ACCEPTANCE_MODULES_MERGED_SPLIT_SAAS): test_acceptance_modules_merged_saas_%: $(DOCKER_COMPOSE) run --rm test_acceptance make test_acceptance_modules_merged_inner_$* @@ -408,7 +421,7 @@ ci: # ORG_PATH = /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin RUN_LINT_FORMAT ?= \ - docker run --rm ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) + docker run --rm --env BRANCH_NAME --env CI --env COMMIT_SHA --env MONOREPO --volume ${PWD}/data/reports:/overleaf/services/web/data/reports ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) NODE_MODULES_PATH := ${PATH}:${PWD}/node_modules/.bin:/overleaf/services/web/node_modules/.bin WITH_NODE_MODULES_PATH = \ @@ -425,11 +438,21 @@ $(WITH_NODE_MODULES_PATH): export PATH=$(NODE_MODULES_PATH) lint: lint_eslint lint_eslint: +ifeq ($(CI),true) + -npm run lint -- --format json --output-file data/reports/eslint.json + sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' data/reports/eslint.json +else npm run lint +endif lint: lint_stylelint lint_stylelint: +ifeq ($(CI),true) + -npm run lint:styles -- --formatter json --output-file data/reports/stylelint.json + sed -i 's_"source":"/overleaf_"source":"$(MONOREPO)_g' data/reports/stylelint.json +else npm run lint:styles +endif lint: lint_pug lint_pug: @@ -589,7 +612,6 @@ SENTRY_IMAGE=getsentry/sentry-cli:2.16.1 sentry_prefetch: docker pull $(SENTRY_IMAGE) -sentry_upload: MONOREPO=$(shell cd ../../ && pwd) sentry_upload: docker run --rm \ --volume $(MONOREPO):$(MONOREPO) --workdir $(PWD) \ diff --git a/services/web/buildscript.txt b/services/web/buildscript.txt index 66f3dc9b43..24a4717282 100644 --- a/services/web/buildscript.txt +++ b/services/web/buildscript.txt @@ -7,4 +7,3 @@ web --esmock-loader=False --node-version=22.18.0 --public-repo=False ---script-version=4.7.0 diff --git a/services/web/cypress.config.ts b/services/web/cypress.config.ts index d44d9dc859..16dcb192a3 100644 --- a/services/web/cypress.config.ts +++ b/services/web/cypress.config.ts @@ -1,11 +1,21 @@ import { defineConfig } from 'cypress' import { webpackConfig } from './cypress/support/webpack.cypress' +let reporterOptions = {} +if (process.env.CI) { + reporterOptions = { + reporter: '/overleaf/node_modules/cypress-multi-reporters', + reporterOptions: { + configFile: 'cypress/cypress-multi-reporters.json', + }, + } +} + export default defineConfig({ fixturesFolder: 'cypress/fixtures', video: process.env.CYPRESS_VIDEO === 'true', - screenshotsFolder: 'cypress/results', - videosFolder: 'cypress/results', + screenshotsFolder: process.env.CYPRESS_RESULTS || 'cypress/results', + videosFolder: process.env.CYPRESS_RESULTS || 'cypress/results', viewportHeight: 800, viewportWidth: 800, component: { @@ -25,4 +35,5 @@ export default defineConfig({ retries: { runMode: 3, }, + ...reporterOptions, }) diff --git a/services/web/cypress/cypress-multi-reporters.json b/services/web/cypress/cypress-multi-reporters.json new file mode 100644 index 0000000000..91115417db --- /dev/null +++ b/services/web/cypress/cypress-multi-reporters.json @@ -0,0 +1,10 @@ +{ + "reporterEnabled": "spec, mocha-junit-reporter", + "mochaJunitReporterReporterOptions": { + "mochaFile": "data/reports/junit-cypress-[hash].xml", + "includePending": true, + "jenkinsMode": true, + "jenkinsClassnamePrefix": "Web Cypress tests", + "rootSuiteTitle": "Web Cypress tests" + } +} diff --git a/services/web/data/reports/.gitignore b/services/web/data/reports/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/web/docker-compose.ci.yml b/services/web/docker-compose.ci.yml index 324db08b43..b0fff7bc3f 100644 --- a/services/web/docker-compose.ci.yml +++ b/services/web/docker-compose.ci.yml @@ -13,11 +13,14 @@ services: user: node volumes: - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it + - ./data/reports:/overleaf/services/web/data/reports entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 -- command: npm run test:unit:app working_dir: /overleaf/services/web env_file: docker-compose.common.env environment: + CI: + MOCHA_ROOT_SUITE_NAME: BASE_CONFIG: OVERLEAF_CONFIG: NODE_ENV: test @@ -38,6 +41,8 @@ services: working_dir: /overleaf/services/web env_file: docker-compose.common.env environment: + CI: + MOCHA_ROOT_SUITE_NAME: BASE_CONFIG: OVERLEAF_CONFIG: REDIS_HOST: redis_test @@ -45,6 +50,7 @@ services: - 'www.overleaf.test:127.0.0.1' volumes: - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it + - ./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 @@ -61,6 +67,8 @@ services: context: ../.. dockerfile: services/web/Dockerfile image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER + volumes: + - ./data/reports:/overleaf/services/web/data/reports logging: driver: local user: node @@ -68,18 +76,23 @@ services: command: npm run test:frontend environment: NODE_OPTIONS: "--unhandled-rejections=strict" + CI: + MOCHA_ROOT_SUITE_NAME: test_frontend_ct: image: cypress/included:13.13.2 logging: driver: local working_dir: /overleaf/services/web + user: "${DOCKER_USER:-1000:1000}" environment: - CYPRESS_SPEC_PATTERN: ${CYPRESS_SPEC_PATTERN:-} - CYPRESS_EXCLUDE_SPEC_PATTERN: ${CYPRESS_EXCLUDE_SPEC_PATTERN:-} + CI: + CYPRESS_RESULTS: + CYPRESS_SPEC_PATTERN: + CYPRESS_EXCLUDE_SPEC_PATTERN: volumes: - - ${CYPRESS_RESULTS:-./cypress/results}:/overleaf/services/web/cypress/results/ - /dev/shm/overleaf:/overleaf + - ./data/reports:/overleaf/services/web/data/reports entrypoint: npm command: - "run" @@ -95,7 +108,11 @@ services: user: node working_dir: /overleaf/services/web command: npm run test:writefull + volumes: + - ./data/reports:/overleaf/services/web/data/reports environment: + CI: + MOCHA_ROOT_SUITE_NAME: NODE_OPTIONS: "--unhandled-rejections=strict" tar: diff --git a/services/web/docker-compose.yml b/services/web/docker-compose.yml index 266559d32d..b6555e706d 100644 --- a/services/web/docker-compose.yml +++ b/services/web/docker-compose.yml @@ -15,6 +15,8 @@ services: environment: BASE_CONFIG: OVERLEAF_CONFIG: + CI: + MOCHA_ROOT_SUITE_NAME: MOCHA_GREP: ${MOCHA_GREP} LOG_LEVEL: ${LOG_LEVEL:-} NODE_ENV: test @@ -40,6 +42,8 @@ services: environment: BASE_CONFIG: OVERLEAF_CONFIG: + CI: + MOCHA_ROOT_SUITE_NAME: MOCHA_GREP: ${MOCHA_GREP} LOG_LEVEL: ${LOG_LEVEL:-} MONGO_SERVER_SELECTION_TIMEOUT: 600000 @@ -68,6 +72,8 @@ services: - ../../libraries:/overleaf/libraries working_dir: /overleaf/services/web environment: + CI: + MOCHA_ROOT_SUITE_NAME: MOCHA_GREP: ${MOCHA_GREP} NODE_OPTIONS: "--unhandled-rejections=strict" VERBOSE_LOGGING: @@ -79,8 +85,11 @@ services: volumes: - ../../:/overleaf working_dir: /overleaf/services/web + user: "${DOCKER_USER:-1000:1000}" environment: VERBOSE_LOGGING: + CI: + MOCHA_ROOT_SUITE_NAME: CYPRESS_SPEC_PATTERN: ${CYPRESS_SPEC_PATTERN:-} CYPRESS_EXCLUDE_SPEC_PATTERN: ${CYPRESS_EXCLUDE_SPEC_PATTERN:-} entrypoint: npm @@ -99,6 +108,8 @@ services: - ../../libraries:/overleaf/libraries working_dir: /overleaf/services/web environment: + CI: + MOCHA_ROOT_SUITE_NAME: NODE_OPTIONS: "--unhandled-rejections=strict" VERBOSE_LOGGING: command: npm run --silent test:writefull diff --git a/services/web/package.json b/services/web/package.json index 22586568f8..d6de1f12d7 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -304,6 +304,7 @@ "css-loader": "^6.8.1", "css-minimizer-webpack-plugin": "^5.0.1", "cypress": "13.13.2", + "cypress-multi-reporters": "^2.0.5", "cypress-plugin-tab": "^1.0.5", "d3": "^3.5.16", "daterangepicker": "2.1.27", @@ -345,6 +346,8 @@ "mini-css-extract-plugin": "^2.7.6", "mocha": "^11.1.0", "mocha-each": "^2.0.1", + "mocha-junit-reporter": "^2.2.1", + "mocha-multi-reporters": "^1.5.1", "mock-fs": "^5.1.2", "nock": "^13.5.6", "nvd3": "^1.8.6", diff --git a/services/web/test/mocha-multi-reporters.js b/services/web/test/mocha-multi-reporters.js new file mode 100644 index 0000000000..26bfbfcbda --- /dev/null +++ b/services/web/test/mocha-multi-reporters.js @@ -0,0 +1,10 @@ +module.exports = { + reporterEnabled: 'spec, mocha-junit-reporter', + mochaJunitReporterReporterOptions: { + mochaFile: 'data/reports/junit-mocha-[hash]-[suiteFilename].xml', + includePending: true, + jenkinsMode: true, + jenkinsClassnamePrefix: process.env.MOCHA_ROOT_SUITE_NAME, + rootSuiteTitle: process.env.MOCHA_ROOT_SUITE_NAME, + }, +} diff --git a/services/web/vitest.config.js b/services/web/vitest.config.js index 51f4ed811f..fce3cc59de 100644 --- a/services/web/vitest.config.js +++ b/services/web/vitest.config.js @@ -1,5 +1,20 @@ const { defineConfig } = require('vitest/config') +let reporterOptions = {} +if (process.env.CI && process.env.MOCHA_ROOT_SUITE_NAME) { + reporterOptions = { + reporters: [ + 'default', + [ + 'junit', + { + classnameTemplate: `${process.env.MOCHA_ROOT_SUITE_NAME}.{filename}`, + }, + ], + ], + outputFile: 'data/reports/junit-vitest.xml', + } +} module.exports = defineConfig({ test: { include: [ @@ -9,5 +24,6 @@ module.exports = defineConfig({ setupFiles: ['./test/unit/vitest_bootstrap.mjs'], globals: true, isolate: false, + ...reporterOptions, }, })