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

View File

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

View File

@@ -14,7 +14,7 @@
*/
let redisConfig, siteUrl
let e
const Path = require('path')
const Path = require('node:path')
// These credentials are used for authenticating api requests
// 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,
// sometimes long running, it is a good idea to increase the default number
// of sockets that Node will hold open.
const http = require('http')
const http = require('node:http')
http.globalAgent.maxSockets = 300
const https = require('https')
const https = require('node:https')
https.globalAgent.maxSockets = 300
module.exports = settings

View File

@@ -75,6 +75,24 @@ pipeline {
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') {
steps {
sh 'copybara/bin/sync'
@@ -337,7 +355,7 @@ pipeline {
post {
// Collect junit test results for both success and failure case.
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.
cleanup {

View File

@@ -16,7 +16,7 @@ describe('Accounts', function () {
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'
cy.then(async () => {
const { url } = await createMongoUser({ email })

View File

@@ -12,7 +12,7 @@ import { openEmail } from './helpers/email'
describe('admin panel', function () {
function registrationTests() {
it('via GUI and opening URL manually', () => {
it('via GUI and opening URL manually', function () {
const user = `${uuid()}@example.com`
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`
cy.findByLabelText('Emails to register new users').type(user + '{enter}')
@@ -49,7 +49,7 @@ describe('admin panel', function () {
activateUser(url)
})
})
it('via script and opening URL manually', () => {
it('via script and opening URL manually', function () {
const user = `${uuid()}@example.com`
let url: string
cy.then(async () => {
@@ -59,7 +59,7 @@ describe('admin panel', function () {
activateUser(url)
})
})
it('via script and email', () => {
it('via script and email', function () {
const user = `${uuid()}@example.com`
let url: string
cy.then(async () => {
@@ -81,7 +81,7 @@ describe('admin panel', function () {
})
}
describe('in CE', () => {
describe('in CE', function () {
if (isExcludedBySharding('CE_DEFAULT')) return
startWith({ pro: false, version: 'latest' })
const admin = 'admin@example.com'
@@ -89,8 +89,8 @@ describe('admin panel', function () {
ensureUserExists({ email: admin, isAdmin: true })
ensureUserExists({ email: user })
describe('create users', () => {
beforeEach(() => {
describe('create users', function () {
beforeEach(function () {
login(admin)
cy.visit('/project')
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 user1 = 'user@example.com'
const user2 = 'user2@example.com'
@@ -135,13 +135,13 @@ describe('admin panel', function () {
)
})
describe('admin menu items', () => {
beforeEach(() => {
describe('admin menu items', function () {
beforeEach(function () {
login(admin)
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']
menuitems.forEach(name => {
cy.findByRole('menuitem', { name: 'Admin' }).click()
@@ -153,15 +153,15 @@ describe('admin panel', function () {
})
})
describe('manage site', () => {
beforeEach(() => {
describe('manage site', function () {
beforeEach(function () {
login(admin)
cy.visit('/project')
cy.findByRole('menuitem', { name: 'Admin' }).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()
cy.log('create system message')
@@ -189,15 +189,15 @@ describe('admin panel', function () {
})
})
describe('manage users', () => {
beforeEach(() => {
describe('manage users', function () {
beforeEach(function () {
login(admin)
cy.visit('/project')
cy.findByRole('menuitem', { name: 'Admin' }).click()
cy.findByRole('menuitem', { name: 'Manage Users' }).click()
})
it('displays expected tabs', () => {
it('displays expected tabs', function () {
const tabs = ['Users', 'License Usage']
cy.findAllByRole('tab').should('have.length', tabs.length)
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.findByText(
'An active user is one who has opened a project in this Server Pro instance in the last 12 months.'
)
})
describe('create users', () => {
beforeEach(() => {
describe('create users', function () {
beforeEach(function () {
cy.get('a').contains('New User').click()
})
registrationTests()
})
it('user list RegExp search', () => {
it('user list RegExp search', function () {
cy.findByLabelText('RegExp').click()
cy.findByPlaceholderText('Search users by email or id…').type(
'user[0-9]{enter}'
@@ -229,8 +229,8 @@ describe('admin panel', function () {
})
})
describe('user page', () => {
beforeEach(() => {
describe('user page', function () {
beforeEach(function () {
login(admin)
cy.visit('/project')
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}/)
})
it('displays expected tabs', () => {
it('displays expected tabs', function () {
const tabs = [
'User Info',
'Projects',
@@ -256,18 +256,18 @@ describe('admin panel', function () {
})
})
describe('user info tab', () => {
beforeEach(() => {
describe('user info tab', function () {
beforeEach(function () {
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
cy.findByText('Profile')
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.findByRole('heading', { name: /Split Test Assignments/ }).should(
'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.findByRole('tablist').within(() => {
cy.findByRole('tab', { name: 'Projects' }).click()
@@ -326,13 +326,13 @@ describe('admin panel', function () {
})
})
describe('project page', () => {
beforeEach(() => {
describe('project page', function () {
beforeEach(function () {
login(admin)
cy.visit(`/admin/project/${testProjectId}`)
})
it('displays expected tabs', () => {
it('displays expected tabs', function () {
const tabs = ['Project Info', 'Deleted Docs', 'Audit Log']
cy.findAllByRole('tab').should('have.length', tabs.length)
tabs.forEach(tabName => {
@@ -341,7 +341,7 @@ describe('admin panel', function () {
})
})
it('restore deleted projects', () => {
it('restore deleted projects', function () {
login(user1)
cy.visit('/project')

View File

@@ -42,7 +42,8 @@ describe('Project creation and compilation', function () {
.findByRole('button', { name: 'New file' })
.click()
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: fileName }).click()

View File

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

View File

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

View File

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

View File

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

View File

@@ -295,7 +295,7 @@ describe('filestore migration', function () {
// -------------------
// filestore-migration
beforeEach(() => {
beforeEach(function () {
login(email)
waitForCompile(() => {
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.get(`[alt="${defaultImage}"]`)
.should('be.visible')

View File

@@ -65,7 +65,7 @@ describe('git-bridge', function () {
login(USER)
})
it('should render the git-bridge UI in the settings', () => {
it('should render the git-bridge UI in the settings', function () {
maybeClearAllTokens()
cy.visit('/user/settings')
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-ro@example.com' })
ensureUserExists({ email: 'collaborator-link-rw@example.com' })
@@ -156,13 +156,13 @@ describe('git-bridge', function () {
let projectName: string
let recompile: () => void
let waitForCompile: (triggerCompile: () => void) => void
beforeEach(() => {
beforeEach(function () {
projectName = uuid()
createProject(projectName, { open: false }).as('projectId')
;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot())
})
it('should expose r/w interface to owner', () => {
it('should expose r/w interface to owner', function () {
maybeClearAllTokens()
waitForCompile(() => {
openProjectByName(projectName)
@@ -170,7 +170,7 @@ describe('git-bridge', function () {
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(
projectName,
'collaborator-rw@example.com',
@@ -183,7 +183,7 @@ describe('git-bridge', function () {
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(
projectName,
'collaborator-ro@example.com',
@@ -196,7 +196,7 @@ describe('git-bridge', function () {
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)
enableLinkSharing().then(({ linkSharingReadAndWrite }) => {
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(() => {
openProjectByName(projectName)
})
@@ -267,7 +267,7 @@ describe('git-bridge', function () {
const dir = `/${projectId}`
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) => {
if (err) return reject(err)
resolve(blob as string)
@@ -276,7 +276,7 @@ describe('git-bridge', function () {
}
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 => {
if (err) return reject(err)
resolve()
@@ -398,8 +398,9 @@ Hello world
cy.findByText(/\\documentclass/)
.parent()
.parent()
.as('documentclass')
.click()
.type('% via editor{enter}')
cy.get('@documentclass').type('% via editor{enter}')
// Trigger flush via compile
recompile()
@@ -433,7 +434,7 @@ Hello world
function checkDisabled() {
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)
cy.visit('/user/settings')
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
startWith({
pro: true,
@@ -462,7 +463,7 @@ Hello world
checkDisabled()
})
describe('unavailable in CE', () => {
describe('unavailable in CE', function () {
if (isExcludedBySharding('CE_CUSTOM_1')) return
startWith({
pro: false,

View File

@@ -28,7 +28,7 @@ describe('GracefulShutdown', function () {
ensureUserExists({ email: USER })
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()
login(USER)
const { recompile, waitForCompile } = prepareWaitForNextCompileSlot()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -61,7 +61,7 @@ describe('History', function () {
const CLASS_ADDITION = 'ol-cm-addition-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()
waitForCompile(() => {
createProject('labels')

View File

@@ -1,6 +1,5 @@
import { isExcludedBySharding, startWith } from './helpers/config'
import { ensureUserExists, login } from './helpers/login'
import { v4 as uuid } from 'uuid'
describe('LearnWiki', function () {
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: REGULAR_USER })
describe('enabled in Pro', () => {
describe('enabled in Pro', function () {
if (isExcludedBySharding('PRO_CUSTOM_2')) return
startWith({
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)
cy.visit('/project')
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)
cy.visit('/project')
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)
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
startWith({ pro: true })
checkDisabled()
})
describe('unavailable in CE', () => {
describe('unavailable in CE', function () {
if (isExcludedBySharding('CE_CUSTOM_1')) return
startWith({
pro: false,
@@ -100,13 +99,13 @@ describe('LearnWiki', function () {
})
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)
cy.visit('/project')
cy.findByText('Documentation').should('not.exist')
})
it('should not render wiki page', () => {
it('should not render wiki page', function () {
login(REGULAR_USER)
cy.visit(COPYING_A_PROJECT_URL, {
failOnStatusCode: false,
@@ -114,7 +113,7 @@ describe('LearnWiki', function () {
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)
cy.visit('/project')
cy.findByText(LABEL_LEARN_LATEX).should('not.exist')

View File

@@ -7,14 +7,15 @@
"cypress:open": "cypress open --e2e --browser chrome",
"cypress:run": "cypress run --e2e --browser chrome",
"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": {
"@isomorphic-git/lightning-fs": "^4.6.0",
"@overleaf/validation-tools": "*",
"@testing-library/cypress": "^10.0.3",
"@types/adm-zip": "^0.5.7",
"@types/pdf-parse": "^1.1.5",
"@types/uuid": "^9.0.8",
"adm-zip": "^0.5.12",
"body-parser": "^1.20.3",
@@ -24,7 +25,7 @@
"isomorphic-git": "^1.33.1",
"js-yaml": "^4.1.1",
"mocha-junit-reporter": "^2.2.1",
"pdf-parse": "^1.1.1",
"pdf-parse": "^2.3.0",
"uuid": "^9.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 REGULAR_USER = 'user@example.com'
describe('Project List', () => {
describe('Project List', function () {
if (isExcludedBySharding('PRO_DEFAULT_2')) return
startWith({ pro: true })
@@ -15,10 +15,10 @@ describe('Project List', () => {
return cy.findByText(projectName).parent().parent()
}
describe('user with no projects', () => {
describe('user with no projects', function () {
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)
cy.visit('/project')
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()}`
ensureUserExists({ email: REGULAR_USER })
before(() => {
before(function () {
login(REGULAR_USER)
createProject(projectName, { type: 'Example project', open: false })
})
@@ -41,7 +41,7 @@ describe('Project List', () => {
cy.visit('/project')
})
it('Can download project sources', () => {
it('Can download project sources', function () {
findProjectRow(projectName).within(() =>
cy.findByRole('button', { name: 'Download .zip file' }).click()
)
@@ -53,7 +53,7 @@ describe('Project List', () => {
}).should('contain', 'Your introduction goes here')
})
it('Can download project PDF', () => {
it('Can download project PDF', function () {
findProjectRow(projectName).within(() =>
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
cy.log('select project')
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')
const nonTaggedProjectName = `project-${uuid()}`
createProject(nonTaggedProjectName, { open: false })

View File

@@ -27,13 +27,13 @@ describe('Project Sharing', function () {
let projectName: string
let recompile: () => void
let waitForCompile: (triggerCompile: () => void) => void
beforeWithReRunOnTestRetry(function () {
beforeWithReRunOnTestRetry(() => {
projectName = getSpamSafeProjectName()
;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot())
setupTestProject()
})
beforeEach(() => {
beforeEach(function () {
// Always start with a fresh session
cy.session([uuid()], () => {})
})
@@ -245,22 +245,22 @@ describe('Project Sharing', function () {
shareProjectByEmailAndAcceptInviteViaEmail(projectName, email, 'Viewer')
})
it('should grant the collaborator read access', () => {
it('should grant the collaborator read access', function () {
expectFullReadOnlyAccess()
expectProjectDashboardEntry()
})
})
describe('read only', () => {
describe('read only', function () {
const email = 'collaborator-ro@example.com'
ensureUserExists({ email })
beforeWithReRunOnTestRetry(function () {
beforeWithReRunOnTestRetry(() => {
login('user@example.com')
shareProjectByEmailAndAcceptInviteViaDash(projectName, email, 'Viewer')
})
it('should grant the collaborator read access', () => {
it('should grant the collaborator read access', function () {
login(email)
openProjectByName(projectName)
expectFullReadOnlyAccess()
@@ -268,16 +268,16 @@ describe('Project Sharing', function () {
})
})
describe('read and write', () => {
describe('read and write', function () {
const email = 'collaborator-rw@example.com'
ensureUserExists({ email })
beforeWithReRunOnTestRetry(function () {
beforeWithReRunOnTestRetry(() => {
login('user@example.com')
shareProjectByEmailAndAcceptInviteViaDash(projectName, email, 'Editor')
})
it('should grant the collaborator write access', () => {
it('should grant the collaborator write access', function () {
login(email)
openProjectByName(projectName)
expectFullReadAndWriteAccess()
@@ -286,13 +286,13 @@ describe('Project Sharing', function () {
})
})
describe('token access', () => {
describe('logged in', () => {
describe('read only', () => {
describe('token access', function () {
describe('logged in', function () {
describe('read only', function () {
const email = 'collaborator-link-ro@example.com'
ensureUserExists({ email })
it('should grant restricted read access', () => {
it('should grant restricted read access', function () {
login(email)
openProjectViaLinkSharingAsUser(
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'
ensureUserExists({ email })
it('should grant full write access', () => {
it('should grant full write access', function () {
login(email)
openProjectViaLinkSharingAsUser(
linkSharingReadAndWrite,
@@ -322,8 +322,8 @@ describe('Project Sharing', function () {
})
})
describe('with OVERLEAF_ALLOW_PUBLIC_ACCESS=false', () => {
describe('wrap startup', () => {
describe('with OVERLEAF_ALLOW_PUBLIC_ACCESS=false', function () {
describe('wrap startup', function () {
startWith({
pro: true,
vars: {
@@ -331,12 +331,12 @@ describe('Project Sharing', function () {
},
withDataDir: true,
})
it('should block access', () => {
it('should block access', function () {
expectNoAccess()
})
})
describe('with OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING=true', () => {
describe('with OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING=true', function () {
startWith({
pro: true,
vars: {
@@ -345,14 +345,14 @@ describe('Project Sharing', function () {
},
withDataDir: true,
})
it('should block access', () => {
it('should block access', function () {
expectNoAccess()
})
})
})
describe('with OVERLEAF_ALLOW_PUBLIC_ACCESS=true', () => {
describe('wrap startup', () => {
describe('with OVERLEAF_ALLOW_PUBLIC_ACCESS=true', function () {
describe('wrap startup', function () {
startWith({
pro: true,
vars: {
@@ -360,18 +360,18 @@ describe('Project Sharing', function () {
},
withDataDir: true,
})
it('should grant read access with read link', () => {
it('should grant read access with read link', function () {
openProjectViaLinkSharingAsAnon(linkSharingReadOnly)
expectRestrictedReadOnlyAccess()
})
it('should prompt for login with write link', () => {
it('should prompt for login with write link', function () {
cy.visit(linkSharingReadAndWrite)
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({
pro: true,
vars: {
@@ -381,12 +381,12 @@ describe('Project Sharing', function () {
withDataDir: true,
})
it('should grant read access with read link', () => {
it('should grant read access with read link', function () {
openProjectViaLinkSharingAsAnon(linkSharingReadOnly)
expectRestrictedReadOnlyAccess()
})
it('should grant write access with write link', () => {
it('should grant write access with write link', function () {
openProjectViaLinkSharingAsAnon(linkSharingReadAndWrite)
expectAnonymousReadAndWriteAccess()
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'
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')
openProjectByName(projectName)
cy.findByText('Share').click()
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)
// 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)
// 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')
shareProjectByEmailAndAcceptInviteViaEmail(
projectName,
@@ -490,14 +490,14 @@ describe('Project Sharing', function () {
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)
openProjectByName(projectName)
expectRestrictedReadOnlyAccess()
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)
openProjectByName(projectName)
expectFullReadAndWriteAccess()

View File

@@ -156,6 +156,7 @@ describe('SandboxedCompiles', function () {
// The sync button is swapped as the position in the PDF changes.
// Cypress appears to click on a button that references a stale position.
// Adding a cy.wait() statement is the most reliable "fix" so far :/
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(1000)
cy.findByRole('button', {
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
// eslint-disable-next-line mocha/no-skipped-tests
describe.skip('unavailable in CE', function () {
if (isExcludedBySharding('CE_CUSTOM_1')) return
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
const LABEL_BROWSE_TEMPLATES = 'Browse templates'
describe('Templates', () => {
describe('Templates', function () {
ensureUserExists({ email: TEMPLATES_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
startWith({
pro: true,
@@ -40,7 +40,7 @@ describe('Templates', () => {
ensureUserExists({ email: REGULAR_USER })
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)
cy.visit('/')
cy.findByRole('link', { name: LABEL_BROWSE_TEMPLATES })
@@ -49,7 +49,7 @@ describe('Templates', () => {
cy.url().should('match', /\/templates$/)
})
it('should have templates feature', () => {
it('should have templates feature', function () {
login(TEMPLATES_USER)
const name = `Template ${Date.now()}`
const description = `Template Description ${Date.now()}`
@@ -64,11 +64,8 @@ describe('Templates', () => {
.click()
cy.findByText('Manage Template').click()
cy.findByText('Template Description')
.click()
.parent()
.get('textarea')
.type(description)
cy.findByText('Template Description').as('description').click()
cy.get('@description').parent().get('textarea').type(description)
cy.findByText('Publish').click()
cy.findByText('Publishing…').parent().should('be.disabled')
cy.findByText('Publish').should('not.exist')
@@ -229,7 +226,7 @@ describe('Templates', () => {
})
function checkDisabled() {
it('should not have templates feature', () => {
it('should not have templates feature', function () {
login(TEMPLATES_USER)
cy.visit('/')
@@ -254,7 +251,7 @@ describe('Templates', () => {
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)
cy.visit('/')
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
startWith({ pro: true })
checkDisabled()
})
describe('unavailable in CE', () => {
describe('unavailable in CE', function () {
if (isExcludedBySharding('CE_CUSTOM_1')) return
startWith({
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'. */,
"skipLibCheck": true /* Skip type checking of declaration files. */,
"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()!
before(async () => {
before(async function () {
cy.log('Create old instance')
})
startWith({
@@ -38,7 +38,7 @@ describe('Upgrading', function () {
cy.log('Create initial user after deleting it')
})
ensureUserExists({ email: USER })
before(() => {
before(function () {
cy.log('Populate old instance')
login(USER)
;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot())
@@ -76,12 +76,12 @@ describe('Upgrading', function () {
}
cy.findByText('History').click()
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) {
before(() => {
before(function () {
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)
@@ -113,16 +113,16 @@ describe('Upgrading', function () {
step.hook?.()
}
beforeEach(() => {
beforeEach(function () {
login(USER)
})
it('should list the old project', () => {
it('should list the old project', function () {
cy.visit('/project')
cy.findByText(PROJECT_NAME)
})
it('should open the old project', () => {
it('should open the old project', function () {
waitForCompile(() => {
openProjectByName(PROJECT_NAME)
})
@@ -174,21 +174,21 @@ describe('Upgrading', function () {
})
},
}
describe('from 4.2 to latest', () => {
describe('from 4.2 to latest', function () {
testUpgrade([
optionsFourDotTwo,
optionsBinaryFilesMigration,
{ version: 'latest' },
])
})
describe('from 5.0 to latest', () => {
describe('from 5.0 to latest', function () {
testUpgrade([
{ version: '5.0' },
optionsBinaryFilesMigration,
{ version: 'latest' },
])
})
describe('doc version recovery', () => {
describe('doc version recovery', function () {
testUpgrade([
optionsFourDotTwo,
{