Merge pull request #29038 from overleaf/td-eslint-e2e-tests

Enable ESLint for all end-to-end tests

GitOrigin-RevId: 5d085f52fabcc794b0457edbbb551500477d4110
This commit is contained in:
Tim Down
2025-11-18 10:57:17 +00:00
committed by Copybot
parent 3227502aeb
commit 41d120d8f1
29 changed files with 288 additions and 265 deletions

145
package-lock.json generated
View File

@@ -13174,32 +13174,35 @@
] ]
}, },
"node_modules/@napi-rs/canvas": { "node_modules/@napi-rs/canvas": {
"version": "0.1.68", "version": "0.1.80",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.68.tgz", "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.80.tgz",
"integrity": "sha512-LQESrePLEBLvhuFkXx9jjBXRC2ClYsO5mqQ1m/puth5z9SOuM3N/B3vDuqnC3RJFktDktyK9khGvo7dTkqO9uQ==", "integrity": "sha512-DxuT1ClnIPts1kQx8FBmkk4BQDTfI5kIzywAaMjQSXfNnra5UFU9PwurXrl+Je3bJ6BGsp/zmshVVFbCmyI+ww==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"workspaces": [
"e2e/*"
],
"engines": { "engines": {
"node": ">= 10" "node": ">= 10"
}, },
"optionalDependencies": { "optionalDependencies": {
"@napi-rs/canvas-android-arm64": "0.1.68", "@napi-rs/canvas-android-arm64": "0.1.80",
"@napi-rs/canvas-darwin-arm64": "0.1.68", "@napi-rs/canvas-darwin-arm64": "0.1.80",
"@napi-rs/canvas-darwin-x64": "0.1.68", "@napi-rs/canvas-darwin-x64": "0.1.80",
"@napi-rs/canvas-linux-arm-gnueabihf": "0.1.68", "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.80",
"@napi-rs/canvas-linux-arm64-gnu": "0.1.68", "@napi-rs/canvas-linux-arm64-gnu": "0.1.80",
"@napi-rs/canvas-linux-arm64-musl": "0.1.68", "@napi-rs/canvas-linux-arm64-musl": "0.1.80",
"@napi-rs/canvas-linux-riscv64-gnu": "0.1.68", "@napi-rs/canvas-linux-riscv64-gnu": "0.1.80",
"@napi-rs/canvas-linux-x64-gnu": "0.1.68", "@napi-rs/canvas-linux-x64-gnu": "0.1.80",
"@napi-rs/canvas-linux-x64-musl": "0.1.68", "@napi-rs/canvas-linux-x64-musl": "0.1.80",
"@napi-rs/canvas-win32-x64-msvc": "0.1.68" "@napi-rs/canvas-win32-x64-msvc": "0.1.80"
} }
}, },
"node_modules/@napi-rs/canvas-android-arm64": { "node_modules/@napi-rs/canvas-android-arm64": {
"version": "0.1.68", "version": "0.1.80",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.68.tgz", "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.80.tgz",
"integrity": "sha512-h1KcSR4LKLfRfzeBH65xMxbWOGa1OtMFQbCMVlxPCkN1Zr+2gK+70pXO5ktojIYcUrP6KDcOwoc8clho5ccM/w==", "integrity": "sha512-sk7xhN/MoXeuExlggf91pNziBxLPVUqF2CAVnB57KLG/pz7+U5TKG8eXdc3pm0d7Od0WreB6ZKLj37sX9muGOQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -13214,9 +13217,9 @@
} }
}, },
"node_modules/@napi-rs/canvas-darwin-arm64": { "node_modules/@napi-rs/canvas-darwin-arm64": {
"version": "0.1.68", "version": "0.1.80",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.68.tgz", "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.80.tgz",
"integrity": "sha512-/VURlrAD4gDoxW1GT/b0nP3fRz/fhxmHI/xznTq2FTwkQLPOlLkDLCvTmQ7v6LtGKdc2Ed6rvYpRan+JXThInQ==", "integrity": "sha512-O64APRTXRUiAz0P8gErkfEr3lipLJgM6pjATwavZ22ebhjYl/SUbpgM0xcWPQBNMP1n29afAC/Us5PX1vg+JNQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -13231,9 +13234,9 @@
} }
}, },
"node_modules/@napi-rs/canvas-darwin-x64": { "node_modules/@napi-rs/canvas-darwin-x64": {
"version": "0.1.68", "version": "0.1.80",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.68.tgz", "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.80.tgz",
"integrity": "sha512-tEpvGR6vCLTo1Tx9wmDnoOKROpw57wiCWwCpDOuVlj/7rqEJOUYr9ixW4aRJgmeGBrZHgevI0EURys2ER6whmg==", "integrity": "sha512-FqqSU7qFce0Cp3pwnTjVkKjjOtxMqRe6lmINxpIZYaZNnVI0H5FtsaraZJ36SiTHNjZlUB69/HhxNDT1Aaa9vA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -13248,9 +13251,9 @@
} }
}, },
"node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": {
"version": "0.1.68", "version": "0.1.80",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.68.tgz", "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.80.tgz",
"integrity": "sha512-U9xbJsumPOiAYeAFZMlHf62b9dGs2HJ6Q5xt7xTB0uEyPeurwhgYBWGgabdsEidyj38YuzI/c3LGBbSQB3vagw==", "integrity": "sha512-eyWz0ddBDQc7/JbAtY4OtZ5SpK8tR4JsCYEZjCE3dI8pqoWUC8oMwYSBGCYfsx2w47cQgQCgMVRVTFiiO38hHQ==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -13265,9 +13268,9 @@
} }
}, },
"node_modules/@napi-rs/canvas-linux-arm64-gnu": { "node_modules/@napi-rs/canvas-linux-arm64-gnu": {
"version": "0.1.68", "version": "0.1.80",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.68.tgz", "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.80.tgz",
"integrity": "sha512-KFkn8wEm3mPnWD4l8+OUUkxylSJuN5q9PnJRZJgv15RtCA1bgxIwTkBhI/+xuyVMcHqON9sXq7cDkEJtHm35dg==", "integrity": "sha512-qwA63t8A86bnxhuA/GwOkK3jvb+XTQaTiVML0vAWoHyoZYTjNs7BzoOONDgTnNtr8/yHrq64XXzUoLqDzU+Uuw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -13282,9 +13285,9 @@
} }
}, },
"node_modules/@napi-rs/canvas-linux-arm64-musl": { "node_modules/@napi-rs/canvas-linux-arm64-musl": {
"version": "0.1.68", "version": "0.1.80",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.68.tgz", "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.80.tgz",
"integrity": "sha512-IQzts91rCdOALXBWQxLZRCEDrfFTGDtNRJMNu+2SKZ1uT8cmPQkPwVk5rycvFpvgAcmiFiOSCp1aRrlfU8KPpQ==", "integrity": "sha512-1XbCOz/ymhj24lFaIXtWnwv/6eFHXDrjP0jYkc6iHQ9q8oXKzUX1Lc6bu+wuGiLhGh2GS/2JlfORC5ZcXimRcg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -13299,9 +13302,9 @@
} }
}, },
"node_modules/@napi-rs/canvas-linux-riscv64-gnu": { "node_modules/@napi-rs/canvas-linux-riscv64-gnu": {
"version": "0.1.68", "version": "0.1.80",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.68.tgz", "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.80.tgz",
"integrity": "sha512-e9AS5UttoIKqXSmBzKZdd3NErSVyOEYzJfNOCGtafGk1//gibTwQXGlSXmAKuErqMp09pyk9aqQRSYzm1AQfBw==", "integrity": "sha512-XTzR125w5ZMs0lJcxRlS1K3P5RaZ9RmUsPtd1uGt+EfDyYMu4c6SEROYsxyatbbu/2+lPe7MPHOO/0a0x7L/gw==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@@ -13316,9 +13319,9 @@
} }
}, },
"node_modules/@napi-rs/canvas-linux-x64-gnu": { "node_modules/@napi-rs/canvas-linux-x64-gnu": {
"version": "0.1.68", "version": "0.1.80",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.68.tgz", "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.80.tgz",
"integrity": "sha512-Pa/I36VE3j57I3Obhrr+J48KGFfkZk2cJN/2NmW/vCgmoF7kCP6aTVq5n+cGdGWLd/cN9CJ9JvNwEoMRDghu0g==", "integrity": "sha512-BeXAmhKg1kX3UCrJsYbdQd3hIMDH/K6HnP/pG2LuITaXhXBiNdh//TVVVVCBbJzVQaV5gK/4ZOCMrQW9mvuTqA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -13333,9 +13336,9 @@
} }
}, },
"node_modules/@napi-rs/canvas-linux-x64-musl": { "node_modules/@napi-rs/canvas-linux-x64-musl": {
"version": "0.1.68", "version": "0.1.80",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.68.tgz", "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.80.tgz",
"integrity": "sha512-9c6rkc5195wNxuUHJdf4/mmnq433OQey9TNvQ9LspJazvHbfSkTij8wtKjASVQsJyPDva4fkWOeV/OQ7cLw0GQ==", "integrity": "sha512-x0XvZWdHbkgdgucJsRxprX/4o4sEed7qo9rCQA9ugiS9qE2QvP0RIiEugtZhfLH3cyI+jIRFJHV4Fuz+1BHHMg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -13350,9 +13353,9 @@
} }
}, },
"node_modules/@napi-rs/canvas-win32-x64-msvc": { "node_modules/@napi-rs/canvas-win32-x64-msvc": {
"version": "0.1.68", "version": "0.1.80",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.68.tgz", "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.80.tgz",
"integrity": "sha512-Fc5Dez23u0FoSATurT6/w1oMytiRnKWEinHivdMvXpge6nG4YvhrASrtqMk8dGJMVQpHr8QJYF45rOrx2YU2Aw==", "integrity": "sha512-Z8jPsM6df5V8B1HrCHB05+bDiCxjE9QA//3YrkKIdVDEwn5RKaqOxCJDRJkl48cJbylcrJbW4HxZbTte8juuPg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -20010,16 +20013,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/pdf-parse": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@types/pdf-parse/-/pdf-parse-1.1.5.tgz",
"integrity": "sha512-kBfrSXsloMnUJOKi25s3+hRmkycHfLK6A09eRGqF/N8BkQoPUmaCr+q8Cli5FnfohEz/rsv82zAiPz/LXtOGhA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/pg": { "node_modules/@types/pg": {
"version": "8.6.1", "version": "8.6.1",
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.1.tgz", "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.1.tgz",
@@ -39620,13 +39613,6 @@
"node": ">=10.5.0" "node": ">=10.5.0"
} }
}, },
"node_modules/node-ensure": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/node-ensure/-/node-ensure-0.0.0.tgz",
"integrity": "sha512-DRI60hzo2oKN1ma0ckc6nQWlHU69RH6xN0sjQTjMpChPfTYvKZdcQFfdYK2RWbJcKyUizSIy/l8OTGxMAM1QDw==",
"dev": true,
"license": "MIT"
},
"node_modules/node-fetch": { "node_modules/node-fetch": {
"version": "2.7.0", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
@@ -41147,27 +41133,32 @@
"integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10="
}, },
"node_modules/pdf-parse": { "node_modules/pdf-parse": {
"version": "1.1.1", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-1.1.1.tgz", "resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-2.3.0.tgz",
"integrity": "sha512-v6ZJ/efsBpGrGGknjtq9J/oC8tZWq0KWL5vQrk2GlzLEQPUDB1ex+13Rmidl1neNN358Jn9EHZw5y07FFtaC7A==", "integrity": "sha512-VRKvhqdZ694CjdR1vusZ7VIA7ZuMN/GQ7eKz+e3z9ujCQdCQMOEG9x6cHfq8ddS7XspXVrruWuKmXm8g0hFlSQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"debug": "^3.1.0", "pdfjs-dist": "^5.4.296"
"node-ensure": "^0.0.0"
}, },
"engines": { "engines": {
"node": ">=6.8.1" "node": ">=20.16.0 <21 || >=22.3.0"
},
"optionalDependencies": {
"@napi-rs/canvas": "^0.1.80"
} }
}, },
"node_modules/pdf-parse/node_modules/debug": { "node_modules/pdf-parse/node_modules/pdfjs-dist": {
"version": "3.2.7", "version": "5.4.296",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.4.296.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "integrity": "sha512-DlOzet0HO7OEnmUmB6wWGJrrdvbyJKftI1bhMitK7O2N8W2gc757yyYBbINy9IDafXAV9wmKr9t7xsTaNKRG5Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "Apache-2.0",
"dependencies": { "engines": {
"ms": "^2.1.1" "node": ">=20.16.0 || >=22.3.0"
},
"optionalDependencies": {
"@napi-rs/canvas": "^0.1.80"
} }
}, },
"node_modules/pdfjs-dist": { "node_modules/pdfjs-dist": {
@@ -53269,7 +53260,6 @@
"@overleaf/validation-tools": "*", "@overleaf/validation-tools": "*",
"@testing-library/cypress": "^10.0.3", "@testing-library/cypress": "^10.0.3",
"@types/adm-zip": "^0.5.7", "@types/adm-zip": "^0.5.7",
"@types/pdf-parse": "^1.1.5",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
"adm-zip": "^0.5.12", "adm-zip": "^0.5.12",
"body-parser": "^1.20.3", "body-parser": "^1.20.3",
@@ -53279,7 +53269,7 @@
"isomorphic-git": "^1.33.1", "isomorphic-git": "^1.33.1",
"js-yaml": "^4.1.1", "js-yaml": "^4.1.1",
"mocha-junit-reporter": "^2.2.1", "mocha-junit-reporter": "^2.2.1",
"pdf-parse": "^1.1.1", "pdf-parse": "^2.3.0",
"uuid": "^9.0.1", "uuid": "^9.0.1",
"zod-validation-error": "^4.0.1" "zod-validation-error": "^4.0.1"
} }
@@ -56646,7 +56636,6 @@
"@isomorphic-git/lightning-fs": "^4.6.0", "@isomorphic-git/lightning-fs": "^4.6.0",
"@testing-library/cypress": "^10.0.3", "@testing-library/cypress": "^10.0.3",
"@types/adm-zip": "^0.5.7", "@types/adm-zip": "^0.5.7",
"@types/pdf-parse": "^1.1.5",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
"adm-zip": "^0.5.12", "adm-zip": "^0.5.12",
"cypress": "13.13.2", "cypress": "13.13.2",
@@ -56654,7 +56643,7 @@
"isomorphic-git": "^1.33.1", "isomorphic-git": "^1.33.1",
"mailtrap": "^4.3.0", "mailtrap": "^4.3.0",
"mocha-junit-reporter": "^2.2.1", "mocha-junit-reporter": "^2.2.1",
"pdf-parse": "^1.1.1", "pdf-parse": "^2.3.0",
"typescript": "^5.0.4", "typescript": "^5.0.4",
"uuid": "^9.0.1" "uuid": "^9.0.1"
} }

View File

@@ -21,5 +21,9 @@
"overrides": [ "overrides": [
// Extra rules for Cypress tests // Extra rules for Cypress tests
{ "files": ["**/*.spec.ts"], "extends": ["plugin:cypress/recommended"] } { "files": ["**/*.spec.ts"], "extends": ["plugin:cypress/recommended"] }
],
"ignorePatterns": [
"hotfix/",
"develop/"
] ]
} }

View File

@@ -14,7 +14,7 @@
*/ */
let redisConfig, siteUrl let redisConfig, siteUrl
let e let e
const Path = require('path') const Path = require('node:path')
// These credentials are used for authenticating api requests // These credentials are used for authenticating api requests
// between services that may need to go over public channels // between services that may need to go over public channels
@@ -491,9 +491,9 @@ if (
// With lots of incoming and outgoing HTTP connections to different services, // With lots of incoming and outgoing HTTP connections to different services,
// sometimes long running, it is a good idea to increase the default number // sometimes long running, it is a good idea to increase the default number
// of sockets that Node will hold open. // of sockets that Node will hold open.
const http = require('http') const http = require('node:http')
http.globalAgent.maxSockets = 300 http.globalAgent.maxSockets = 300
const https = require('https') const https = require('node:https')
https.globalAgent.maxSockets = 300 https.globalAgent.maxSockets = 300
module.exports = settings module.exports = settings

View File

@@ -75,6 +75,24 @@ pipeline {
sh 'bin/run -w /overleaf/server-ce/test monorepo npm run format' sh 'bin/run -w /overleaf/server-ce/test monorepo npm run format'
} }
} }
stage('Lint') {
steps {
script {
waitUntil {
return job_npm_install_done
}
}
sh 'bin/run -w /overleaf/server-ce/test monorepo npm run lint -- --format json --output-file reports/eslint.json'
}
post {
always {
sh """
sed -i 's_"filePath":"/overleaf_"filePath":"/workspace_g' server-ce/test/reports/eslint.json
"""
recordIssues checksAnnotationScope: 'ALL', enabledForFailure: true, failOnError: true, id: 'server-pro-e2e-tests-eslint', name: 'Server-Pro-E2E-Tests eslint', qualityGates: [[integerThreshold: 1, threshold: 1.0, type: 'TOTAL']], sourceCodeRetention: 'LAST_BUILD', tools: [esLint(pattern: 'server-ce/test/reports/eslint.json')]
}
}
}
stage('Copybara') { stage('Copybara') {
steps { steps {
sh 'copybara/bin/sync' sh 'copybara/bin/sync'
@@ -337,7 +355,7 @@ pipeline {
post { post {
// Collect junit test results for both success and failure case. // Collect junit test results for both success and failure case.
always { always {
junit checksName: 'Server Pro E2E test results', testResults: 'server-ce/test/reports/junit-*.xml' junit checksName: 'Server-Pro-E2E-Tests results', testResults: 'server-ce/test/reports/junit-*.xml'
} }
// Ensure tear down of test containers, remove CE docker images, then run general Jenkins VM cleanup. // Ensure tear down of test containers, remove CE docker images, then run general Jenkins VM cleanup.
cleanup { cleanup {

View File

@@ -16,7 +16,7 @@ describe('Accounts', function () {
cy.url().should('include', '/login') cy.url().should('include', '/login')
}) })
it('should render the email on the user activate screen', () => { it('should render the email on the user activate screen', function () {
const email = 'not-activated-user@example.com' const email = 'not-activated-user@example.com'
cy.then(async () => { cy.then(async () => {
const { url } = await createMongoUser({ email }) const { url } = await createMongoUser({ email })

View File

@@ -12,7 +12,7 @@ import { openEmail } from './helpers/email'
describe('admin panel', function () { describe('admin panel', function () {
function registrationTests() { function registrationTests() {
it('via GUI and opening URL manually', () => { it('via GUI and opening URL manually', function () {
const user = `${uuid()}@example.com` const user = `${uuid()}@example.com`
cy.findByLabelText('Emails to register new users').type(user + '{enter}') cy.findByLabelText('Emails to register new users').type(user + '{enter}')
@@ -24,7 +24,7 @@ describe('admin panel', function () {
}) })
}) })
it('via GUI and email', () => { it('via GUI and email', function () {
const user = `${uuid()}@example.com` const user = `${uuid()}@example.com`
cy.findByLabelText('Emails to register new users').type(user + '{enter}') cy.findByLabelText('Emails to register new users').type(user + '{enter}')
@@ -49,7 +49,7 @@ describe('admin panel', function () {
activateUser(url) activateUser(url)
}) })
}) })
it('via script and opening URL manually', () => { it('via script and opening URL manually', function () {
const user = `${uuid()}@example.com` const user = `${uuid()}@example.com`
let url: string let url: string
cy.then(async () => { cy.then(async () => {
@@ -59,7 +59,7 @@ describe('admin panel', function () {
activateUser(url) activateUser(url)
}) })
}) })
it('via script and email', () => { it('via script and email', function () {
const user = `${uuid()}@example.com` const user = `${uuid()}@example.com`
let url: string let url: string
cy.then(async () => { cy.then(async () => {
@@ -81,7 +81,7 @@ describe('admin panel', function () {
}) })
} }
describe('in CE', () => { describe('in CE', function () {
if (isExcludedBySharding('CE_DEFAULT')) return if (isExcludedBySharding('CE_DEFAULT')) return
startWith({ pro: false, version: 'latest' }) startWith({ pro: false, version: 'latest' })
const admin = 'admin@example.com' const admin = 'admin@example.com'
@@ -89,8 +89,8 @@ describe('admin panel', function () {
ensureUserExists({ email: admin, isAdmin: true }) ensureUserExists({ email: admin, isAdmin: true })
ensureUserExists({ email: user }) ensureUserExists({ email: user })
describe('create users', () => { describe('create users', function () {
beforeEach(() => { beforeEach(function () {
login(admin) login(admin)
cy.visit('/project') cy.visit('/project')
cy.findByRole('menuitem', { name: 'Admin' }).click() cy.findByRole('menuitem', { name: 'Admin' }).click()
@@ -100,7 +100,7 @@ describe('admin panel', function () {
}) })
}) })
describe('in server pro', () => { describe('in server pro', function () {
const admin = 'admin@example.com' const admin = 'admin@example.com'
const user1 = 'user@example.com' const user1 = 'user@example.com'
const user2 = 'user2@example.com' const user2 = 'user2@example.com'
@@ -135,13 +135,13 @@ describe('admin panel', function () {
) )
}) })
describe('admin menu items', () => { describe('admin menu items', function () {
beforeEach(() => { beforeEach(function () {
login(admin) login(admin)
cy.visit('/project') cy.visit('/project')
}) })
it('displays expected admin menu items', () => { it('displays expected admin menu items', function () {
const menuitems = ['Manage Site', 'Manage Users', 'Project URL Lookup'] const menuitems = ['Manage Site', 'Manage Users', 'Project URL Lookup']
menuitems.forEach(name => { menuitems.forEach(name => {
cy.findByRole('menuitem', { name: 'Admin' }).click() cy.findByRole('menuitem', { name: 'Admin' }).click()
@@ -153,15 +153,15 @@ describe('admin panel', function () {
}) })
}) })
describe('manage site', () => { describe('manage site', function () {
beforeEach(() => { beforeEach(function () {
login(admin) login(admin)
cy.visit('/project') cy.visit('/project')
cy.findByRole('menuitem', { name: 'Admin' }).click() cy.findByRole('menuitem', { name: 'Admin' }).click()
cy.findByRole('menuitem', { name: 'Manage Site' }).click() cy.findByRole('menuitem', { name: 'Manage Site' }).click()
}) })
it('publish and clear admin messages', () => { it('publish and clear admin messages', function () {
const message = 'Admin Message ' + uuid() const message = 'Admin Message ' + uuid()
cy.log('create system message') cy.log('create system message')
@@ -189,15 +189,15 @@ describe('admin panel', function () {
}) })
}) })
describe('manage users', () => { describe('manage users', function () {
beforeEach(() => { beforeEach(function () {
login(admin) login(admin)
cy.visit('/project') cy.visit('/project')
cy.findByRole('menuitem', { name: 'Admin' }).click() cy.findByRole('menuitem', { name: 'Admin' }).click()
cy.findByRole('menuitem', { name: 'Manage Users' }).click() cy.findByRole('menuitem', { name: 'Manage Users' }).click()
}) })
it('displays expected tabs', () => { it('displays expected tabs', function () {
const tabs = ['Users', 'License Usage'] const tabs = ['Users', 'License Usage']
cy.findAllByRole('tab').should('have.length', tabs.length) cy.findAllByRole('tab').should('have.length', tabs.length)
tabs.forEach(tabName => { tabs.forEach(tabName => {
@@ -205,21 +205,21 @@ describe('admin panel', function () {
}) })
}) })
it('license usage tab', () => { it('license usage tab', function () {
cy.get('a').contains('License Usage').click() cy.get('a').contains('License Usage').click()
cy.findByText( cy.findByText(
'An active user is one who has opened a project in this Server Pro instance in the last 12 months.' 'An active user is one who has opened a project in this Server Pro instance in the last 12 months.'
) )
}) })
describe('create users', () => { describe('create users', function () {
beforeEach(() => { beforeEach(function () {
cy.get('a').contains('New User').click() cy.get('a').contains('New User').click()
}) })
registrationTests() registrationTests()
}) })
it('user list RegExp search', () => { it('user list RegExp search', function () {
cy.findByLabelText('RegExp').click() cy.findByLabelText('RegExp').click()
cy.findByPlaceholderText('Search users by email or id…').type( cy.findByPlaceholderText('Search users by email or id…').type(
'user[0-9]{enter}' 'user[0-9]{enter}'
@@ -229,8 +229,8 @@ describe('admin panel', function () {
}) })
}) })
describe('user page', () => { describe('user page', function () {
beforeEach(() => { beforeEach(function () {
login(admin) login(admin)
cy.visit('/project') cy.visit('/project')
cy.findByRole('menuitem', { name: 'Admin' }).click() cy.findByRole('menuitem', { name: 'Admin' }).click()
@@ -242,7 +242,7 @@ describe('admin panel', function () {
cy.url().should('match', /\/admin\/user\/[a-fA-F0-9]{24}/) cy.url().should('match', /\/admin\/user\/[a-fA-F0-9]{24}/)
}) })
it('displays expected tabs', () => { it('displays expected tabs', function () {
const tabs = [ const tabs = [
'User Info', 'User Info',
'Projects', 'Projects',
@@ -256,18 +256,18 @@ describe('admin panel', function () {
}) })
}) })
describe('user info tab', () => { describe('user info tab', function () {
beforeEach(() => { beforeEach(function () {
cy.findByRole('tab', { name: 'User Info' }).click() cy.findByRole('tab', { name: 'User Info' }).click()
}) })
it('displays required sections', () => { it('displays required sections', function () {
// not exhaustive list, checks the tab content is rendered // not exhaustive list, checks the tab content is rendered
cy.findByText('Profile') cy.findByText('Profile')
cy.findByText('Editor Settings') cy.findByText('Editor Settings')
}) })
it('should not display SaaS-only sections', () => { it('should not display SaaS-only sections', function () {
cy.findByLabelText('Referred User Count').should('not.exist') cy.findByLabelText('Referred User Count').should('not.exist')
cy.findByRole('heading', { name: /Split Test Assignments/ }).should( cy.findByRole('heading', { name: /Split Test Assignments/ }).should(
'not.exist' 'not.exist'
@@ -285,7 +285,7 @@ describe('admin panel', function () {
}) })
}) })
it('transfer project ownership', () => { it('transfer project ownership', function () {
cy.log("access project admin through owners' project list") cy.log("access project admin through owners' project list")
cy.findByRole('tablist').within(() => { cy.findByRole('tablist').within(() => {
cy.findByRole('tab', { name: 'Projects' }).click() cy.findByRole('tab', { name: 'Projects' }).click()
@@ -326,13 +326,13 @@ describe('admin panel', function () {
}) })
}) })
describe('project page', () => { describe('project page', function () {
beforeEach(() => { beforeEach(function () {
login(admin) login(admin)
cy.visit(`/admin/project/${testProjectId}`) cy.visit(`/admin/project/${testProjectId}`)
}) })
it('displays expected tabs', () => { it('displays expected tabs', function () {
const tabs = ['Project Info', 'Deleted Docs', 'Audit Log'] const tabs = ['Project Info', 'Deleted Docs', 'Audit Log']
cy.findAllByRole('tab').should('have.length', tabs.length) cy.findAllByRole('tab').should('have.length', tabs.length)
tabs.forEach(tabName => { tabs.forEach(tabName => {
@@ -341,7 +341,7 @@ describe('admin panel', function () {
}) })
}) })
it('restore deleted projects', () => { it('restore deleted projects', function () {
login(user1) login(user1)
cy.visit('/project') cy.visit('/project')

View File

@@ -42,7 +42,8 @@ describe('Project creation and compilation', function () {
.findByRole('button', { name: 'New file' }) .findByRole('button', { name: 'New file' })
.click() .click()
cy.findByRole('dialog').within(() => { cy.findByRole('dialog').within(() => {
cy.findByLabelText('File Name').clear().type(fileName) cy.findByLabelText('File Name').as('filename').clear()
cy.get('@filename').type(fileName)
cy.findByRole('button', { name: 'Create' }).click() cy.findByRole('button', { name: 'Create' }).click()
}) })
cy.findByRole('button', { name: fileName }).click() cy.findByRole('button', { name: fileName }).click()

View File

@@ -1,18 +1,18 @@
import { isExcludedBySharding, startWith } from './helpers/config' import { isExcludedBySharding, startWith } from './helpers/config'
describe('Customization', () => { describe('Customization', function () {
if (isExcludedBySharding('CE_CUSTOM_1')) return if (isExcludedBySharding('CE_CUSTOM_1')) return
describe('default settings', () => { describe('default settings', function () {
startWith({}) startWith({})
it('should display the default right footer', () => { it('should display the default right footer', function () {
cy.visit('/') cy.visit('/')
cy.get('footer').findByRole('link', { name: 'Fork on GitHub!' }) cy.get('footer').findByRole('link', { name: 'Fork on GitHub!' })
}) })
}) })
describe('custom settings', () => { describe('custom settings', function () {
startWith({ startWith({
vars: { vars: {
OVERLEAF_APP_NAME: 'CUSTOM APP NAME', OVERLEAF_APP_NAME: 'CUSTOM APP NAME',
@@ -23,16 +23,16 @@ describe('Customization', () => {
}, },
}) })
it('should display custom name', () => { it('should display custom name', function () {
cy.visit('/') cy.visit('/')
cy.get('nav').findByText('CUSTOM APP NAME') cy.get('nav').findByText('CUSTOM APP NAME')
}) })
it('should display custom left footer', () => { it('should display custom left footer', function () {
cy.visit('/') cy.visit('/')
cy.get('footer').findByText('CUSTOM LEFT FOOTER') cy.get('footer').findByText('CUSTOM LEFT FOOTER')
}) })
it('should display custom right footer', () => { it('should display custom right footer', function () {
cy.visit('/') cy.visit('/')
cy.get('footer').findByText('CUSTOM RIGHT FOOTER') cy.get('footer').findByText('CUSTOM RIGHT FOOTER')
}) })

View File

@@ -53,7 +53,7 @@ export default defineConfig({
viewportWidth: 1024, viewportWidth: 1024,
e2e: { e2e: {
baseUrl: 'http://localhost', baseUrl: 'http://localhost',
setupNodeEvents(on, config) { setupNodeEvents(on) {
on('task', { on('task', {
readPdf, readPdf,
readFileInZip, readFileInZip,

View File

@@ -13,7 +13,7 @@ import { prepareWaitForNextCompileSlot } from './helpers/compile'
const USER = 'user@example.com' const USER = 'user@example.com'
const COLLABORATOR = 'collaborator@example.com' const COLLABORATOR = 'collaborator@example.com'
describe('editor', () => { describe('editor', function () {
if (isExcludedBySharding('PRO_DEFAULT_1')) return if (isExcludedBySharding('PRO_DEFAULT_1')) return
startWith({ pro: true }) startWith({ pro: true })
ensureUserExists({ email: USER }) ensureUserExists({ email: USER })
@@ -23,7 +23,7 @@ describe('editor', () => {
let projectId: string let projectId: string
let recompile: () => void let recompile: () => void
let waitForCompile: (fn: () => void) => void let waitForCompile: (fn: () => void) => void
beforeWithReRunOnTestRetry(function () { beforeWithReRunOnTestRetry(() => {
projectName = `project-${uuid()}` projectName = `project-${uuid()}`
login(USER) login(USER)
createProject(projectName, { type: 'Example project', open: false }).then( createProject(projectName, { type: 'Example project', open: false }).then(
@@ -32,7 +32,7 @@ describe('editor', () => {
;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot()) ;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot())
}) })
beforeEach(() => { beforeEach(function () {
login(USER) login(USER)
waitForCompile(() => { waitForCompile(() => {
openProjectById(projectId) openProjectById(projectId)
@@ -58,7 +58,7 @@ describe('editor', () => {
changeSpellCheckLanguageTo('Off') changeSpellCheckLanguageTo('Off')
}) })
it('word dictionary and spelling', () => { it('word dictionary and spelling', function () {
changeSpellCheckLanguageTo('English (American)') changeSpellCheckLanguageTo('English (American)')
createNewFile() createNewFile()
const word = createRandomLetterString() const word = createRandomLetterString()
@@ -109,8 +109,8 @@ describe('editor', () => {
}) })
}) })
describe('editor', () => { describe('editor', function () {
it('renders jpg', () => { it('renders jpg', function () {
cy.findByRole('navigation', { cy.findByRole('navigation', {
name: 'Project files and outline', name: 'Project files and outline',
}) })
@@ -122,7 +122,7 @@ describe('editor', () => {
.should('be.greaterThan', 0) .should('be.greaterThan', 0)
}) })
it('symbol palette', () => { it('symbol palette', function () {
createNewFile() createNewFile()
cy.get('button[aria-label="Insert symbol"]').click({ cy.get('button[aria-label="Insert symbol"]').click({
@@ -139,20 +139,20 @@ describe('editor', () => {
}) })
}) })
describe('add new file to project', () => { describe('add new file to project', function () {
beforeEach(() => { beforeEach(function () {
cy.findByRole('button', { name: 'New file' }).click() cy.findByRole('button', { name: 'New file' }).click()
}) })
testNewFileUpload() testNewFileUpload()
it('should not display import from URL', () => { it('should not display import from URL', function () {
cy.findByRole('button', { name: 'From external URL' }).should('not.exist') cy.findByRole('button', { name: 'From external URL' }).should('not.exist')
}) })
}) })
describe('left menu', () => { describe('left menu', function () {
beforeEach(() => { beforeEach(function () {
cy.findByRole('navigation', { cy.findByRole('navigation', {
name: 'Project actions', name: 'Project actions',
}) })
@@ -160,7 +160,7 @@ describe('editor', () => {
.click() .click()
}) })
it('can download project sources', () => { it('can download project sources', function () {
cy.findByRole('link', { name: 'Source' }).click() cy.findByRole('link', { name: 'Source' }).click()
const zipName = projectName.replaceAll('-', '_') const zipName = projectName.replaceAll('-', '_')
cy.task('readFileInZip', { cy.task('readFileInZip', {
@@ -169,7 +169,7 @@ describe('editor', () => {
}).should('contain', 'Your introduction goes here') }).should('contain', 'Your introduction goes here')
}) })
it('can download project PDF', () => { it('can download project PDF', function () {
cy.log('ensure project is compiled') cy.log('ensure project is compiled')
cy.findByRole('region', { name: 'PDF preview and logs' }).should( cy.findByRole('region', { name: 'PDF preview and logs' }).should(
'contain.text', 'contain.text',
@@ -185,7 +185,7 @@ describe('editor', () => {
}) })
}) })
it('word count', () => { it('word count', function () {
cy.log('ensure project is compiled') cy.log('ensure project is compiled')
cy.findByRole('region', { name: 'PDF preview and logs' }).should( cy.findByRole('region', { name: 'PDF preview and logs' }).should(
'contain.text', 'contain.text',
@@ -206,8 +206,8 @@ describe('editor', () => {
}) })
}) })
describe('layout selector', () => { describe('layout selector', function () {
it('show editor only and switch between editor and pdf', () => { it('show editor only and switch between editor and pdf', function () {
cy.findByRole('region', { name: 'PDF preview and logs' }).should( cy.findByRole('region', { name: 'PDF preview and logs' }).should(
'be.visible' 'be.visible'
) )
@@ -238,7 +238,7 @@ describe('editor', () => {
cy.get('.cm-editor').should('be.visible') cy.get('.cm-editor').should('be.visible')
}) })
it('show PDF only and go back to Editor & PDF', () => { it('show PDF only and go back to Editor & PDF', function () {
cy.findByRole('region', { name: 'PDF preview and logs' }).should( cy.findByRole('region', { name: 'PDF preview and logs' }).should(
'be.visible' 'be.visible'
) )
@@ -265,7 +265,7 @@ describe('editor', () => {
cy.get('.cm-editor').should('be.visible') cy.get('.cm-editor').should('be.visible')
}) })
it('PDF in a separate tab (tests editor only)', () => { it('PDF in a separate tab (tests editor only)', function () {
cy.findByTestId('pdf-viewer').should('be.visible') cy.findByTestId('pdf-viewer').should('be.visible')
cy.get('.cm-editor').should('be.visible') cy.get('.cm-editor').should('be.visible')

View File

@@ -1,7 +1,7 @@
import { isExcludedBySharding, startWith } from './helpers/config' import { isExcludedBySharding, startWith } from './helpers/config'
import { createProject } from './helpers/project' import { createProject } from './helpers/project'
describe('SAML', () => { describe('SAML', function () {
if (isExcludedBySharding('PRO_CUSTOM_1')) return if (isExcludedBySharding('PRO_CUSTOM_1')) return
const samlURL = Cypress.env('SAML_URL') || 'http://saml' const samlURL = Cypress.env('SAML_URL') || 'http://saml'
@@ -22,7 +22,7 @@ describe('SAML', () => {
}, },
}) })
it('login', () => { it('login', function () {
cy.visit('/') cy.visit('/')
cy.findByText('Log in with SAML Test Server').click() cy.findByText('Log in with SAML Test Server').click()
@@ -39,7 +39,7 @@ describe('SAML', () => {
}) })
}) })
describe('LDAP', () => { describe('LDAP', function () {
if (isExcludedBySharding('PRO_CUSTOM_1')) return if (isExcludedBySharding('PRO_CUSTOM_1')) return
startWith({ startWith({
pro: true, pro: true,
@@ -57,7 +57,7 @@ describe('LDAP', () => {
}, },
}) })
it('login', () => { it('login', function () {
cy.visit('/') cy.visit('/')
cy.findByText('Log in LDAP') cy.findByText('Log in LDAP')

View File

@@ -295,7 +295,7 @@ describe('filestore migration', function () {
// ------------------- // -------------------
// filestore-migration // filestore-migration
beforeEach(() => { beforeEach(function () {
login(email) login(email)
waitForCompile(() => { waitForCompile(() => {
openProjectById(projectId) openProjectById(projectId)
@@ -311,7 +311,7 @@ describe('filestore migration', function () {
} }
}) })
it('renders image of example project', () => { it('renders image of example project', function () {
cy.findByTestId('file-tree').findByText(defaultImage).click() cy.findByTestId('file-tree').findByText(defaultImage).click()
cy.get(`[alt="${defaultImage}"]`) cy.get(`[alt="${defaultImage}"]`)
.should('be.visible') .should('be.visible')

View File

@@ -65,7 +65,7 @@ describe('git-bridge', function () {
login(USER) login(USER)
}) })
it('should render the git-bridge UI in the settings', () => { it('should render the git-bridge UI in the settings', function () {
maybeClearAllTokens() maybeClearAllTokens()
cy.visit('/user/settings') cy.visit('/user/settings')
cy.findByRole('heading', { name: 'Git integration' }) cy.findByRole('heading', { name: 'Git integration' })
@@ -147,7 +147,7 @@ describe('git-bridge', function () {
}) })
}) })
describe('git access', () => { describe('git access', function () {
ensureUserExists({ email: 'collaborator-rw@example.com' }) ensureUserExists({ email: 'collaborator-rw@example.com' })
ensureUserExists({ email: 'collaborator-ro@example.com' }) ensureUserExists({ email: 'collaborator-ro@example.com' })
ensureUserExists({ email: 'collaborator-link-rw@example.com' }) ensureUserExists({ email: 'collaborator-link-rw@example.com' })
@@ -156,13 +156,13 @@ describe('git-bridge', function () {
let projectName: string let projectName: string
let recompile: () => void let recompile: () => void
let waitForCompile: (triggerCompile: () => void) => void let waitForCompile: (triggerCompile: () => void) => void
beforeEach(() => { beforeEach(function () {
projectName = uuid() projectName = uuid()
createProject(projectName, { open: false }).as('projectId') createProject(projectName, { open: false }).as('projectId')
;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot()) ;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot())
}) })
it('should expose r/w interface to owner', () => { it('should expose r/w interface to owner', function () {
maybeClearAllTokens() maybeClearAllTokens()
waitForCompile(() => { waitForCompile(() => {
openProjectByName(projectName) openProjectByName(projectName)
@@ -170,7 +170,7 @@ describe('git-bridge', function () {
checkGitAccess('readAndWrite') checkGitAccess('readAndWrite')
}) })
it('should expose r/w interface to invited r/w collaborator', () => { it('should expose r/w interface to invited r/w collaborator', function () {
shareProjectByEmailAndAcceptInviteViaDash( shareProjectByEmailAndAcceptInviteViaDash(
projectName, projectName,
'collaborator-rw@example.com', 'collaborator-rw@example.com',
@@ -183,7 +183,7 @@ describe('git-bridge', function () {
checkGitAccess('readAndWrite') checkGitAccess('readAndWrite')
}) })
it('should expose r/o interface to invited r/o collaborator', () => { it('should expose r/o interface to invited r/o collaborator', function () {
shareProjectByEmailAndAcceptInviteViaDash( shareProjectByEmailAndAcceptInviteViaDash(
projectName, projectName,
'collaborator-ro@example.com', 'collaborator-ro@example.com',
@@ -196,7 +196,7 @@ describe('git-bridge', function () {
checkGitAccess('readOnly') checkGitAccess('readOnly')
}) })
it('should expose r/w interface to link-sharing r/w collaborator', () => { it('should expose r/w interface to link-sharing r/w collaborator', function () {
openProjectByName(projectName) openProjectByName(projectName)
enableLinkSharing().then(({ linkSharingReadAndWrite }) => { enableLinkSharing().then(({ linkSharingReadAndWrite }) => {
const email = 'collaborator-link-rw@example.com' const email = 'collaborator-link-rw@example.com'
@@ -213,7 +213,7 @@ describe('git-bridge', function () {
}) })
}) })
it('should expose r/o interface to link-sharing r/o collaborator', () => { it('should expose r/o interface to link-sharing r/o collaborator', function () {
waitForCompile(() => { waitForCompile(() => {
openProjectByName(projectName) openProjectByName(projectName)
}) })
@@ -267,7 +267,7 @@ describe('git-bridge', function () {
const dir = `/${projectId}` const dir = `/${projectId}`
async function readFile(path: string): Promise<string> { async function readFile(path: string): Promise<string> {
return new Promise((resolve, reject) => { return await new Promise((resolve, reject) => {
fs.readFile(path, { encoding: 'utf8' }, (err, blob) => { fs.readFile(path, { encoding: 'utf8' }, (err, blob) => {
if (err) return reject(err) if (err) return reject(err)
resolve(blob as string) resolve(blob as string)
@@ -276,7 +276,7 @@ describe('git-bridge', function () {
} }
async function writeFile(path: string, data: string) { async function writeFile(path: string, data: string) {
return new Promise<void>((resolve, reject) => { return await new Promise<void>((resolve, reject) => {
fs.writeFile(path, data, undefined, err => { fs.writeFile(path, data, undefined, err => {
if (err) return reject(err) if (err) return reject(err)
resolve() resolve()
@@ -398,8 +398,9 @@ Hello world
cy.findByText(/\\documentclass/) cy.findByText(/\\documentclass/)
.parent() .parent()
.parent() .parent()
.as('documentclass')
.click() .click()
.type('% via editor{enter}') cy.get('@documentclass').type('% via editor{enter}')
// Trigger flush via compile // Trigger flush via compile
recompile() recompile()
@@ -433,7 +434,7 @@ Hello world
function checkDisabled() { function checkDisabled() {
ensureUserExists({ email: USER }) ensureUserExists({ email: USER })
it('should not render the git-bridge UI in the settings', () => { it('should not render the git-bridge UI in the settings', function () {
login(USER) login(USER)
cy.visit('/user/settings') cy.visit('/user/settings')
cy.findByRole('heading', { name: 'Git integration' }).should('not.exist') cy.findByRole('heading', { name: 'Git integration' }).should('not.exist')
@@ -454,7 +455,7 @@ Hello world
}) })
} }
describe('disabled in Server Pro', () => { describe('disabled in Server Pro', function () {
if (isExcludedBySharding('PRO_DEFAULT_1')) return if (isExcludedBySharding('PRO_DEFAULT_1')) return
startWith({ startWith({
pro: true, pro: true,
@@ -462,7 +463,7 @@ Hello world
checkDisabled() checkDisabled()
}) })
describe('unavailable in CE', () => { describe('unavailable in CE', function () {
if (isExcludedBySharding('CE_CUSTOM_1')) return if (isExcludedBySharding('CE_CUSTOM_1')) return
startWith({ startWith({
pro: false, pro: false,

View File

@@ -28,7 +28,7 @@ describe('GracefulShutdown', function () {
ensureUserExists({ email: USER }) ensureUserExists({ email: USER })
let projectId: string let projectId: string
it('should display banner and flush changes out of redis', () => { it('should display banner and flush changes out of redis', function () {
bringServerProBackUp() bringServerProBackUp()
login(USER) login(USER)
const { recompile, waitForCompile } = prepareWaitForNextCompileSlot() const { recompile, waitForCompile } = prepareWaitForNextCompileSlot()

View File

@@ -1,6 +1,6 @@
export function beforeWithReRunOnTestRetry(fn: () => void | Promise<any>) { export function beforeWithReRunOnTestRetry(fn: () => void | Promise<any>) {
let ranOnce = false let ranOnce = false
beforeEach(() => { beforeEach(function () {
if (ranOnce && Cypress.currentRetry === 0) return if (ranOnce && Cypress.currentRetry === 0) return
ranOnce = true ranOnce = true
return fn() return fn()

View File

@@ -7,7 +7,10 @@
*/ */
export function openEmail<T>( export function openEmail<T>(
subject: string | RegExp, subject: string | RegExp,
runner: (frame: Cypress.Chainable<JQuery<any>>, args: T) => void, runner: (
frame: Cypress.Chainable<Cypress.JQueryWithSelector<any>>,
args: T
) => void,
args?: T args?: T
) { ) {
const runnerS = runner.toString() const runnerS = runner.toString()
@@ -28,9 +31,11 @@ export function openEmail<T>(
// Use force as the subject is partially hidden // Use force as the subject is partially hidden
cy.contains(subject).click({ force: true }) cy.contains(subject).click({ force: true })
cy.log('wait for iframe loading') cy.log('wait for iframe loading')
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(1000) cy.wait(1000)
cy.get('iframe[id="messagecontframe"]').then(frame => { cy.get('iframe[id="messagecontframe"]').then(frame => {
// runnerS='(frame, args) => { runner body }'. Extract the runnable function. // runnerS='(frame, args) => { runner body }'. Extract the runnable function.
// eslint-disable-next-line no-new-func
const runner = new Function('return ' + runnerS)() const runner = new Function('return ' + runnerS)()
runner(cy.wrap(frame.prop('contentWindow').document.body), args) runner(cy.wrap(frame.prop('contentWindow').document.body), args)
}) })

View File

@@ -130,7 +130,7 @@ export async function purgeFilestoreData() {
} }
async function sleep(ms: number) { async function sleep(ms: number) {
return new Promise(resolve => { return await new Promise(resolve => {
setTimeout(resolve, ms) setTimeout(resolve, ms)
}) })
} }

View File

@@ -114,9 +114,8 @@ function shareProjectByEmail(
cy.findByLabelText('Add email address', { selector: 'input' }) cy.findByLabelText('Add email address', { selector: 'input' })
.parents('form') .parents('form')
.within(() => { .within(() => {
cy.findByTestId('add-collaborator-select') cy.findByTestId('add-collaborator-select').as('select').click()
.click() cy.get('@select').then(() => {
.then(() => {
cy.findByRole('option', { name: level }).click() cy.findByRole('option', { name: level }).click()
}) })
}) })
@@ -275,12 +274,12 @@ export function prepareFileUploadTest(binary = false) {
} }
export function testNewFileUpload() { export function testNewFileUpload() {
it('can upload text file', () => { it('can upload text file', function () {
const check = prepareFileUploadTest(false) const check = prepareFileUploadTest(false)
check() check()
}) })
it('can upload binary file', () => { it('can upload binary file', function () {
const check = prepareFileUploadTest(true) const check = prepareFileUploadTest(true)
check() check()
}) })

View File

@@ -1,9 +1,8 @@
import fs from 'fs' import fs from 'node:fs'
import path from 'path' import path from 'node:path'
// @ts-ignore broken package entrypoint import { PDFParse } from 'pdf-parse'
import pdf from 'pdf-parse/lib/pdf-parse.js'
import AdmZip from 'adm-zip' import AdmZip from 'adm-zip'
import { setTimeout } from 'timers/promises' import { setTimeout } from 'node:timers/promises'
const MAX_ATTEMPTS = 15 const MAX_ATTEMPTS = 15
const POLL_INTERVAL = 500 const POLL_INTERVAL = 500
@@ -23,7 +22,7 @@ export async function readFileInZip({
const zip = new AdmZip(path.resolve(pathToZip)) const zip = new AdmZip(path.resolve(pathToZip))
const entry = zip const entry = zip
.getEntries() .getEntries()
.find(entry => entry.entryName == fileToRead) .find(entry => entry.entryName === fileToRead)
if (entry) { if (entry) {
return entry.getData().toString('utf8') return entry.getData().toString('utf8')
} else { } else {
@@ -41,8 +40,15 @@ export async function readPdf(file: string) {
while (attempt < MAX_ATTEMPTS) { while (attempt < MAX_ATTEMPTS) {
if (fs.existsSync(file)) { if (fs.existsSync(file)) {
const dataBuffer = fs.readFileSync(path.resolve(file)) const dataBuffer = fs.readFileSync(path.resolve(file))
const { text } = await pdf(dataBuffer) const parser = new PDFParse({ data: dataBuffer })
return text try {
const result = await parser.getText()
return result.text
} catch (error) {
console.error('PDF parsing failed:', error)
} finally {
await parser.destroy()
}
} }
await setTimeout(POLL_INTERVAL) await setTimeout(POLL_INTERVAL)
attempt++ attempt++

View File

@@ -61,7 +61,7 @@ describe('History', function () {
const CLASS_ADDITION = 'ol-cm-addition-marker' const CLASS_ADDITION = 'ol-cm-addition-marker'
const CLASS_DELETION = 'ol-cm-deletion-marker' const CLASS_DELETION = 'ol-cm-deletion-marker'
it('should support labels, comparison and download', () => { it('should support labels, comparison and download', function () {
const { recompile, waitForCompile } = prepareWaitForNextCompileSlot() const { recompile, waitForCompile } = prepareWaitForNextCompileSlot()
waitForCompile(() => { waitForCompile(() => {
createProject('labels') createProject('labels')

View File

@@ -1,6 +1,5 @@
import { isExcludedBySharding, startWith } from './helpers/config' import { isExcludedBySharding, startWith } from './helpers/config'
import { ensureUserExists, login } from './helpers/login' import { ensureUserExists, login } from './helpers/login'
import { v4 as uuid } from 'uuid'
describe('LearnWiki', function () { describe('LearnWiki', function () {
const COPYING_A_PROJECT_URL = '/learn/how-to/Copying_a_project' const COPYING_A_PROJECT_URL = '/learn/how-to/Copying_a_project'
@@ -15,7 +14,7 @@ describe('LearnWiki', function () {
ensureUserExists({ email: WITHOUT_PROJECTS_USER }) ensureUserExists({ email: WITHOUT_PROJECTS_USER })
ensureUserExists({ email: REGULAR_USER }) ensureUserExists({ email: REGULAR_USER })
describe('enabled in Pro', () => { describe('enabled in Pro', function () {
if (isExcludedBySharding('PRO_CUSTOM_2')) return if (isExcludedBySharding('PRO_CUSTOM_2')) return
startWith({ startWith({
pro: true, pro: true,
@@ -24,7 +23,7 @@ describe('LearnWiki', function () {
}, },
}) })
it('should add a documentation entry to the nav bar', () => { it('should add a documentation entry to the nav bar', function () {
login(REGULAR_USER) login(REGULAR_USER)
cy.visit('/project') cy.visit('/project')
cy.findByRole('menuitem', { name: 'Documentation' }).should( cy.findByRole('menuitem', { name: 'Documentation' }).should(
@@ -34,7 +33,7 @@ describe('LearnWiki', function () {
) )
}) })
it('should display a tutorial link in the welcome page', () => { it('should display a tutorial link in the welcome page', function () {
login(WITHOUT_PROJECTS_USER) login(WITHOUT_PROJECTS_USER)
cy.visit('/project') cy.visit('/project')
cy.findByRole('link', { name: LABEL_LEARN_LATEX }) cy.findByRole('link', { name: LABEL_LEARN_LATEX })
@@ -45,7 +44,7 @@ describe('LearnWiki', function () {
}) })
}) })
it('should render wiki page', () => { it('should render wiki page', function () {
login(REGULAR_USER) login(REGULAR_USER)
cy.visit(UPLOADING_A_PROJECT_URL) cy.visit(UPLOADING_A_PROJECT_URL)
@@ -81,13 +80,13 @@ describe('LearnWiki', function () {
}) })
}) })
describe('disabled in Pro', () => { describe('disabled in Pro', function () {
if (isExcludedBySharding('PRO_DEFAULT_1')) return if (isExcludedBySharding('PRO_DEFAULT_1')) return
startWith({ pro: true }) startWith({ pro: true })
checkDisabled() checkDisabled()
}) })
describe('unavailable in CE', () => { describe('unavailable in CE', function () {
if (isExcludedBySharding('CE_CUSTOM_1')) return if (isExcludedBySharding('CE_CUSTOM_1')) return
startWith({ startWith({
pro: false, pro: false,
@@ -100,13 +99,13 @@ describe('LearnWiki', function () {
}) })
function checkDisabled() { function checkDisabled() {
it('should not add a documentation entry to the nav bar', () => { it('should not add a documentation entry to the nav bar', function () {
login(REGULAR_USER) login(REGULAR_USER)
cy.visit('/project') cy.visit('/project')
cy.findByText('Documentation').should('not.exist') cy.findByText('Documentation').should('not.exist')
}) })
it('should not render wiki page', () => { it('should not render wiki page', function () {
login(REGULAR_USER) login(REGULAR_USER)
cy.visit(COPYING_A_PROJECT_URL, { cy.visit(COPYING_A_PROJECT_URL, {
failOnStatusCode: false, failOnStatusCode: false,
@@ -114,7 +113,7 @@ describe('LearnWiki', function () {
cy.findByText('Not found') cy.findByText('Not found')
}) })
it('should not display a tutorial link in the welcome page', () => { it('should not display a tutorial link in the welcome page', function () {
login(WITHOUT_PROJECTS_USER) login(WITHOUT_PROJECTS_USER)
cy.visit('/project') cy.visit('/project')
cy.findByText(LABEL_LEARN_LATEX).should('not.exist') cy.findByText(LABEL_LEARN_LATEX).should('not.exist')

View File

@@ -7,14 +7,15 @@
"cypress:open": "cypress open --e2e --browser chrome", "cypress:open": "cypress open --e2e --browser chrome",
"cypress:run": "cypress run --e2e --browser chrome", "cypress:run": "cypress run --e2e --browser chrome",
"format": "prettier --list-different $PWD/'**/*.{js,mjs,ts,tsx}'", "format": "prettier --list-different $PWD/'**/*.{js,mjs,ts,tsx}'",
"format:fix": "prettier --write $PWD/'**/*.{js,mjs,ts,tsx}'" "format:fix": "prettier --write $PWD/'**/*.{js,mjs,ts,tsx}'",
"lint": "eslint --max-warnings 0 --format unix --ext .js,.jsx,.mjs,.ts,.tsx .",
"lint:fix": "eslint --fix --ext .js,.jsx,.mjs,.ts,.tsx ."
}, },
"devDependencies": { "devDependencies": {
"@isomorphic-git/lightning-fs": "^4.6.0", "@isomorphic-git/lightning-fs": "^4.6.0",
"@overleaf/validation-tools": "*", "@overleaf/validation-tools": "*",
"@testing-library/cypress": "^10.0.3", "@testing-library/cypress": "^10.0.3",
"@types/adm-zip": "^0.5.7", "@types/adm-zip": "^0.5.7",
"@types/pdf-parse": "^1.1.5",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
"adm-zip": "^0.5.12", "adm-zip": "^0.5.12",
"body-parser": "^1.20.3", "body-parser": "^1.20.3",
@@ -24,7 +25,7 @@
"isomorphic-git": "^1.33.1", "isomorphic-git": "^1.33.1",
"js-yaml": "^4.1.1", "js-yaml": "^4.1.1",
"mocha-junit-reporter": "^2.2.1", "mocha-junit-reporter": "^2.2.1",
"pdf-parse": "^1.1.1", "pdf-parse": "^2.3.0",
"uuid": "^9.0.1", "uuid": "^9.0.1",
"zod-validation-error": "^4.0.1" "zod-validation-error": "^4.0.1"
} }

View File

@@ -6,7 +6,7 @@ import { v4 as uuid } from 'uuid'
const WITHOUT_PROJECTS_USER = 'user-without-projects@example.com' const WITHOUT_PROJECTS_USER = 'user-without-projects@example.com'
const REGULAR_USER = 'user@example.com' const REGULAR_USER = 'user@example.com'
describe('Project List', () => { describe('Project List', function () {
if (isExcludedBySharding('PRO_DEFAULT_2')) return if (isExcludedBySharding('PRO_DEFAULT_2')) return
startWith({ pro: true }) startWith({ pro: true })
@@ -15,10 +15,10 @@ describe('Project List', () => {
return cy.findByText(projectName).parent().parent() return cy.findByText(projectName).parent().parent()
} }
describe('user with no projects', () => { describe('user with no projects', function () {
ensureUserExists({ email: WITHOUT_PROJECTS_USER }) ensureUserExists({ email: WITHOUT_PROJECTS_USER })
it("'Import from GitHub' is not displayed in the welcome page", () => { it("'Import from GitHub' is not displayed in the welcome page", function () {
login(WITHOUT_PROJECTS_USER) login(WITHOUT_PROJECTS_USER)
cy.visit('/project') cy.visit('/project')
cy.findByRole('button', { name: 'Create a new project' }).click() cy.findByRole('button', { name: 'Create a new project' }).click()
@@ -28,11 +28,11 @@ describe('Project List', () => {
}) })
}) })
describe('user with projects', () => { describe('user with projects', function () {
const projectName = `test-project-${uuid()}` const projectName = `test-project-${uuid()}`
ensureUserExists({ email: REGULAR_USER }) ensureUserExists({ email: REGULAR_USER })
before(() => { before(function () {
login(REGULAR_USER) login(REGULAR_USER)
createProject(projectName, { type: 'Example project', open: false }) createProject(projectName, { type: 'Example project', open: false })
}) })
@@ -41,7 +41,7 @@ describe('Project List', () => {
cy.visit('/project') cy.visit('/project')
}) })
it('Can download project sources', () => { it('Can download project sources', function () {
findProjectRow(projectName).within(() => findProjectRow(projectName).within(() =>
cy.findByRole('button', { name: 'Download .zip file' }).click() cy.findByRole('button', { name: 'Download .zip file' }).click()
) )
@@ -53,7 +53,7 @@ describe('Project List', () => {
}).should('contain', 'Your introduction goes here') }).should('contain', 'Your introduction goes here')
}) })
it('Can download project PDF', () => { it('Can download project PDF', function () {
findProjectRow(projectName).within(() => findProjectRow(projectName).within(() =>
cy.findByRole('button', { name: 'Download PDF' }).click() cy.findByRole('button', { name: 'Download PDF' }).click()
) )
@@ -65,7 +65,7 @@ describe('Project List', () => {
) )
}) })
it('can assign and remove tags to projects', () => { it('can assign and remove tags to projects', function () {
const tagName = uuid().slice(0, 7) // long tag names are truncated in the UI, which affects selectors const tagName = uuid().slice(0, 7) // long tag names are truncated in the UI, which affects selectors
cy.log('select project') cy.log('select project')
cy.findByRole('checkbox', { name: `Select ${projectName}` }).check() cy.findByRole('checkbox', { name: `Select ${projectName}` }).check()
@@ -88,7 +88,7 @@ describe('Project List', () => {
) )
}) })
it('can filter by tag', () => { it('can filter by tag', function () {
cy.log('create a separate project to filter') cy.log('create a separate project to filter')
const nonTaggedProjectName = `project-${uuid()}` const nonTaggedProjectName = `project-${uuid()}`
createProject(nonTaggedProjectName, { open: false }) createProject(nonTaggedProjectName, { open: false })

View File

@@ -27,13 +27,13 @@ describe('Project Sharing', function () {
let projectName: string let projectName: string
let recompile: () => void let recompile: () => void
let waitForCompile: (triggerCompile: () => void) => void let waitForCompile: (triggerCompile: () => void) => void
beforeWithReRunOnTestRetry(function () { beforeWithReRunOnTestRetry(() => {
projectName = getSpamSafeProjectName() projectName = getSpamSafeProjectName()
;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot()) ;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot())
setupTestProject() setupTestProject()
}) })
beforeEach(() => { beforeEach(function () {
// Always start with a fresh session // Always start with a fresh session
cy.session([uuid()], () => {}) cy.session([uuid()], () => {})
}) })
@@ -245,22 +245,22 @@ describe('Project Sharing', function () {
shareProjectByEmailAndAcceptInviteViaEmail(projectName, email, 'Viewer') shareProjectByEmailAndAcceptInviteViaEmail(projectName, email, 'Viewer')
}) })
it('should grant the collaborator read access', () => { it('should grant the collaborator read access', function () {
expectFullReadOnlyAccess() expectFullReadOnlyAccess()
expectProjectDashboardEntry() expectProjectDashboardEntry()
}) })
}) })
describe('read only', () => { describe('read only', function () {
const email = 'collaborator-ro@example.com' const email = 'collaborator-ro@example.com'
ensureUserExists({ email }) ensureUserExists({ email })
beforeWithReRunOnTestRetry(function () { beforeWithReRunOnTestRetry(() => {
login('user@example.com') login('user@example.com')
shareProjectByEmailAndAcceptInviteViaDash(projectName, email, 'Viewer') shareProjectByEmailAndAcceptInviteViaDash(projectName, email, 'Viewer')
}) })
it('should grant the collaborator read access', () => { it('should grant the collaborator read access', function () {
login(email) login(email)
openProjectByName(projectName) openProjectByName(projectName)
expectFullReadOnlyAccess() expectFullReadOnlyAccess()
@@ -268,16 +268,16 @@ describe('Project Sharing', function () {
}) })
}) })
describe('read and write', () => { describe('read and write', function () {
const email = 'collaborator-rw@example.com' const email = 'collaborator-rw@example.com'
ensureUserExists({ email }) ensureUserExists({ email })
beforeWithReRunOnTestRetry(function () { beforeWithReRunOnTestRetry(() => {
login('user@example.com') login('user@example.com')
shareProjectByEmailAndAcceptInviteViaDash(projectName, email, 'Editor') shareProjectByEmailAndAcceptInviteViaDash(projectName, email, 'Editor')
}) })
it('should grant the collaborator write access', () => { it('should grant the collaborator write access', function () {
login(email) login(email)
openProjectByName(projectName) openProjectByName(projectName)
expectFullReadAndWriteAccess() expectFullReadAndWriteAccess()
@@ -286,13 +286,13 @@ describe('Project Sharing', function () {
}) })
}) })
describe('token access', () => { describe('token access', function () {
describe('logged in', () => { describe('logged in', function () {
describe('read only', () => { describe('read only', function () {
const email = 'collaborator-link-ro@example.com' const email = 'collaborator-link-ro@example.com'
ensureUserExists({ email }) ensureUserExists({ email })
it('should grant restricted read access', () => { it('should grant restricted read access', function () {
login(email) login(email)
openProjectViaLinkSharingAsUser( openProjectViaLinkSharingAsUser(
linkSharingReadOnly, linkSharingReadOnly,
@@ -304,11 +304,11 @@ describe('Project Sharing', function () {
}) })
}) })
describe('read and write', () => { describe('read and write', function () {
const email = 'collaborator-link-rw@example.com' const email = 'collaborator-link-rw@example.com'
ensureUserExists({ email }) ensureUserExists({ email })
it('should grant full write access', () => { it('should grant full write access', function () {
login(email) login(email)
openProjectViaLinkSharingAsUser( openProjectViaLinkSharingAsUser(
linkSharingReadAndWrite, linkSharingReadAndWrite,
@@ -322,8 +322,8 @@ describe('Project Sharing', function () {
}) })
}) })
describe('with OVERLEAF_ALLOW_PUBLIC_ACCESS=false', () => { describe('with OVERLEAF_ALLOW_PUBLIC_ACCESS=false', function () {
describe('wrap startup', () => { describe('wrap startup', function () {
startWith({ startWith({
pro: true, pro: true,
vars: { vars: {
@@ -331,12 +331,12 @@ describe('Project Sharing', function () {
}, },
withDataDir: true, withDataDir: true,
}) })
it('should block access', () => { it('should block access', function () {
expectNoAccess() expectNoAccess()
}) })
}) })
describe('with OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING=true', () => { describe('with OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING=true', function () {
startWith({ startWith({
pro: true, pro: true,
vars: { vars: {
@@ -345,14 +345,14 @@ describe('Project Sharing', function () {
}, },
withDataDir: true, withDataDir: true,
}) })
it('should block access', () => { it('should block access', function () {
expectNoAccess() expectNoAccess()
}) })
}) })
}) })
describe('with OVERLEAF_ALLOW_PUBLIC_ACCESS=true', () => { describe('with OVERLEAF_ALLOW_PUBLIC_ACCESS=true', function () {
describe('wrap startup', () => { describe('wrap startup', function () {
startWith({ startWith({
pro: true, pro: true,
vars: { vars: {
@@ -360,18 +360,18 @@ describe('Project Sharing', function () {
}, },
withDataDir: true, withDataDir: true,
}) })
it('should grant read access with read link', () => { it('should grant read access with read link', function () {
openProjectViaLinkSharingAsAnon(linkSharingReadOnly) openProjectViaLinkSharingAsAnon(linkSharingReadOnly)
expectRestrictedReadOnlyAccess() expectRestrictedReadOnlyAccess()
}) })
it('should prompt for login with write link', () => { it('should prompt for login with write link', function () {
cy.visit(linkSharingReadAndWrite) cy.visit(linkSharingReadAndWrite)
cy.url().should('match', /\/login/) cy.url().should('match', /\/login/)
}) })
}) })
describe('with OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING=true', () => { describe('with OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING=true', function () {
startWith({ startWith({
pro: true, pro: true,
vars: { vars: {
@@ -381,12 +381,12 @@ describe('Project Sharing', function () {
withDataDir: true, withDataDir: true,
}) })
it('should grant read access with read link', () => { it('should grant read access with read link', function () {
openProjectViaLinkSharingAsAnon(linkSharingReadOnly) openProjectViaLinkSharingAsAnon(linkSharingReadOnly)
expectRestrictedReadOnlyAccess() expectRestrictedReadOnlyAccess()
}) })
it('should grant write access with write link', () => { it('should grant write access with write link', function () {
openProjectViaLinkSharingAsAnon(linkSharingReadAndWrite) openProjectViaLinkSharingAsAnon(linkSharingReadAndWrite)
expectAnonymousReadAndWriteAccess() expectAnonymousReadAndWriteAccess()
expectEditAuthoredAs('Anonymous') expectEditAuthoredAs('Anonymous')
@@ -394,7 +394,7 @@ describe('Project Sharing', function () {
}) })
}) })
describe('with OVERLEAF_DISABLE_LINK_SHARING=true', () => { describe('with OVERLEAF_DISABLE_LINK_SHARING=true', function () {
const email = 'collaborator-email@example.com' const email = 'collaborator-email@example.com'
ensureUserExists({ email }) ensureUserExists({ email })
@@ -448,14 +448,14 @@ describe('Project Sharing', function () {
) )
}) })
it('should not display link sharing in the sharing modal', () => { it('should not display link sharing in the sharing modal', function () {
login('user@example.com') login('user@example.com')
openProjectByName(projectName) openProjectByName(projectName)
cy.findByText('Share').click() cy.findByText('Share').click()
cy.findByText('Turn on link sharing').should('not.exist') cy.findByText('Turn on link sharing').should('not.exist')
}) })
it('should block new access to read-only link shared projects', () => { it('should block new access to read-only link shared projects', function () {
login(email) login(email)
// Test read-only link returns 404 // Test read-only link returns 404
@@ -467,7 +467,7 @@ describe('Project Sharing', function () {
}) })
}) })
it('should block new access to read-write link shared projects', () => { it('should block new access to read-write link shared projects', function () {
login(email) login(email)
// Test read-write link returns 404 // Test read-write link returns 404
@@ -479,7 +479,7 @@ describe('Project Sharing', function () {
}) })
}) })
it('should continue to allow email sharing', () => { it('should continue to allow email sharing', function () {
login('user@example.com') login('user@example.com')
shareProjectByEmailAndAcceptInviteViaEmail( shareProjectByEmailAndAcceptInviteViaEmail(
projectName, projectName,
@@ -490,14 +490,14 @@ describe('Project Sharing', function () {
expectProjectDashboardEntry() expectProjectDashboardEntry()
}) })
it('should retain read-only access when project was joined via link before link sharing was turned off', () => { it('should retain read-only access when project was joined via link before link sharing was turned off', function () {
login(retainedViewerEmail) login(retainedViewerEmail)
openProjectByName(projectName) openProjectByName(projectName)
expectRestrictedReadOnlyAccess() expectRestrictedReadOnlyAccess()
expectProjectDashboardEntry() expectProjectDashboardEntry()
}) })
it('should retain read-write access when project was joined via link before link sharing was turned off', () => { it('should retain read-write access when project was joined via link before link sharing was turned off', function () {
login(retainedEditorEmail) login(retainedEditorEmail)
openProjectByName(projectName) openProjectByName(projectName)
expectFullReadAndWriteAccess() expectFullReadAndWriteAccess()

View File

@@ -156,6 +156,7 @@ describe('SandboxedCompiles', function () {
// The sync button is swapped as the position in the PDF changes. // The sync button is swapped as the position in the PDF changes.
// Cypress appears to click on a button that references a stale position. // Cypress appears to click on a button that references a stale position.
// Adding a cy.wait() statement is the most reliable "fix" so far :/ // Adding a cy.wait() statement is the most reliable "fix" so far :/
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(1000) cy.wait(1000)
cy.findByRole('button', { cy.findByRole('button', {
name: 'Go to PDF location in code (Tip: double click on the PDF for best results)', name: 'Go to PDF location in code (Tip: double click on the PDF for best results)',
@@ -309,6 +310,7 @@ describe('SandboxedCompiles', function () {
}) })
// https://github.com/overleaf/internal/issues/20216 // https://github.com/overleaf/internal/issues/20216
// eslint-disable-next-line mocha/no-skipped-tests
describe.skip('unavailable in CE', function () { describe.skip('unavailable in CE', function () {
if (isExcludedBySharding('CE_CUSTOM_1')) return if (isExcludedBySharding('CE_CUSTOM_1')) return
startWith({ pro: false, vars: enabledVars, resetData: true }) startWith({ pro: false, vars: enabledVars, resetData: true })

View File

@@ -10,7 +10,7 @@ const TEMPLATES_USER = 'templates@example.com'
// Re-use value for "exists" and "does not exist" tests // Re-use value for "exists" and "does not exist" tests
const LABEL_BROWSE_TEMPLATES = 'Browse templates' const LABEL_BROWSE_TEMPLATES = 'Browse templates'
describe('Templates', () => { describe('Templates', function () {
ensureUserExists({ email: TEMPLATES_USER }) ensureUserExists({ email: TEMPLATES_USER })
ensureUserExists({ email: WITHOUT_PROJECTS_USER }) ensureUserExists({ email: WITHOUT_PROJECTS_USER })
@@ -31,7 +31,7 @@ describe('Templates', () => {
} }
} }
describe('enabled in Server Pro', () => { describe('enabled in Server Pro', function () {
if (isExcludedBySharding('PRO_CUSTOM_2')) return if (isExcludedBySharding('PRO_CUSTOM_2')) return
startWith({ startWith({
pro: true, pro: true,
@@ -40,7 +40,7 @@ describe('Templates', () => {
ensureUserExists({ email: REGULAR_USER }) ensureUserExists({ email: REGULAR_USER })
ensureUserExists({ email: ADMIN_USER, isAdmin: true }) ensureUserExists({ email: ADMIN_USER, isAdmin: true })
it('should show templates link on welcome page', () => { it('should show templates link on welcome page', function () {
login(WITHOUT_PROJECTS_USER) login(WITHOUT_PROJECTS_USER)
cy.visit('/') cy.visit('/')
cy.findByRole('link', { name: LABEL_BROWSE_TEMPLATES }) cy.findByRole('link', { name: LABEL_BROWSE_TEMPLATES })
@@ -49,7 +49,7 @@ describe('Templates', () => {
cy.url().should('match', /\/templates$/) cy.url().should('match', /\/templates$/)
}) })
it('should have templates feature', () => { it('should have templates feature', function () {
login(TEMPLATES_USER) login(TEMPLATES_USER)
const name = `Template ${Date.now()}` const name = `Template ${Date.now()}`
const description = `Template Description ${Date.now()}` const description = `Template Description ${Date.now()}`
@@ -64,11 +64,8 @@ describe('Templates', () => {
.click() .click()
cy.findByText('Manage Template').click() cy.findByText('Manage Template').click()
cy.findByText('Template Description') cy.findByText('Template Description').as('description').click()
.click() cy.get('@description').parent().get('textarea').type(description)
.parent()
.get('textarea')
.type(description)
cy.findByText('Publish').click() cy.findByText('Publish').click()
cy.findByText('Publishing…').parent().should('be.disabled') cy.findByText('Publishing…').parent().should('be.disabled')
cy.findByText('Publish').should('not.exist') cy.findByText('Publish').should('not.exist')
@@ -229,7 +226,7 @@ describe('Templates', () => {
}) })
function checkDisabled() { function checkDisabled() {
it('should not have templates feature', () => { it('should not have templates feature', function () {
login(TEMPLATES_USER) login(TEMPLATES_USER)
cy.visit('/') cy.visit('/')
@@ -254,7 +251,7 @@ describe('Templates', () => {
cy.findAllByText('All Templates').should('not.exist') cy.findAllByText('All Templates').should('not.exist')
}) })
it('should not show templates link on welcome page', () => { it('should not show templates link on welcome page', function () {
login(WITHOUT_PROJECTS_USER) login(WITHOUT_PROJECTS_USER)
cy.visit('/') cy.visit('/')
cy.findByText(NEW_PROJECT_BUTTON_MATCHER) // wait for lazy loading cy.findByText(NEW_PROJECT_BUTTON_MATCHER) // wait for lazy loading
@@ -262,13 +259,13 @@ describe('Templates', () => {
}) })
} }
describe('disabled Server Pro', () => { describe('disabled Server Pro', function () {
if (isExcludedBySharding('PRO_DEFAULT_2')) return if (isExcludedBySharding('PRO_DEFAULT_2')) return
startWith({ pro: true }) startWith({ pro: true })
checkDisabled() checkDisabled()
}) })
describe('unavailable in CE', () => { describe('unavailable in CE', function () {
if (isExcludedBySharding('CE_CUSTOM_1')) return if (isExcludedBySharding('CE_CUSTOM_1')) return
startWith({ startWith({
pro: false, pro: false,

View File

@@ -11,7 +11,8 @@
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
"skipLibCheck": true /* Skip type checking of declaration files. */, "skipLibCheck": true /* Skip type checking of declaration files. */,
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
"types": ["cypress", "node", "@testing-library/cypress"] "types": ["cypress", "node", "@testing-library/cypress"],
"allowImportingTsExtensions": true
}, },
"include": ["**/*.ts", "**/*.tsx"] "include": ["**/*.ts", "**/*.js"]
} }

View File

@@ -24,7 +24,7 @@ describe('Upgrading', function () {
) { ) {
const startOptions = steps.shift()! const startOptions = steps.shift()!
before(async () => { before(async function () {
cy.log('Create old instance') cy.log('Create old instance')
}) })
startWith({ startWith({
@@ -38,7 +38,7 @@ describe('Upgrading', function () {
cy.log('Create initial user after deleting it') cy.log('Create initial user after deleting it')
}) })
ensureUserExists({ email: USER }) ensureUserExists({ email: USER })
before(() => { before(function () {
cy.log('Populate old instance') cy.log('Populate old instance')
login(USER) login(USER)
;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot()) ;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot())
@@ -76,12 +76,12 @@ describe('Upgrading', function () {
} }
cy.findByText('History').click() cy.findByText('History').click()
for (let i = 0; i < 3; i++) { for (let i = 0; i < 3; i++) {
cy.findByText(new RegExp(`\\\\section\{Old Section ${i}}`)) cy.findByText(new RegExp(`\\\\section{Old Section ${i}}`))
} }
}) })
for (const step of steps) { for (const step of steps) {
before(() => { before(function () {
cy.log(`Upgrade to version ${step.version}`) cy.log(`Upgrade to version ${step.version}`)
// Navigate way from editor to avoid redirect to /login when the next instance comes up (which slows down tests) // Navigate way from editor to avoid redirect to /login when the next instance comes up (which slows down tests)
@@ -113,16 +113,16 @@ describe('Upgrading', function () {
step.hook?.() step.hook?.()
} }
beforeEach(() => { beforeEach(function () {
login(USER) login(USER)
}) })
it('should list the old project', () => { it('should list the old project', function () {
cy.visit('/project') cy.visit('/project')
cy.findByText(PROJECT_NAME) cy.findByText(PROJECT_NAME)
}) })
it('should open the old project', () => { it('should open the old project', function () {
waitForCompile(() => { waitForCompile(() => {
openProjectByName(PROJECT_NAME) openProjectByName(PROJECT_NAME)
}) })
@@ -174,21 +174,21 @@ describe('Upgrading', function () {
}) })
}, },
} }
describe('from 4.2 to latest', () => { describe('from 4.2 to latest', function () {
testUpgrade([ testUpgrade([
optionsFourDotTwo, optionsFourDotTwo,
optionsBinaryFilesMigration, optionsBinaryFilesMigration,
{ version: 'latest' }, { version: 'latest' },
]) ])
}) })
describe('from 5.0 to latest', () => { describe('from 5.0 to latest', function () {
testUpgrade([ testUpgrade([
{ version: '5.0' }, { version: '5.0' },
optionsBinaryFilesMigration, optionsBinaryFilesMigration,
{ version: 'latest' }, { version: 'latest' },
]) ])
}) })
describe('doc version recovery', () => { describe('doc version recovery', function () {
testUpgrade([ testUpgrade([
optionsFourDotTwo, optionsFourDotTwo,
{ {