mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
Merge pull request #15547 from overleaf/mj-community-edition-tests
[server-ce] Add e2e test for CE GitOrigin-RevId: f76ee4d19680c57a3a0854bc89175b3fb352ca41
This commit is contained in:
parent
21c61be543
commit
732cbf0c26
14 changed files with 3023 additions and 0 deletions
8
server-ce/test/Makefile
Normal file
8
server-ce/test/Makefile
Normal file
|
@ -0,0 +1,8 @@
|
|||
all: test-e2e
|
||||
|
||||
test-e2e:
|
||||
docker-compose down -v -t 0
|
||||
docker-compose -f docker-compose.yml run --rm e2e
|
||||
docker-compose down -v -t 0
|
||||
|
||||
.PHONY: test-e2e
|
10
server-ce/test/accounts.spec.ts
Normal file
10
server-ce/test/accounts.spec.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { login } from './helpers/login'
|
||||
|
||||
describe('Accounts', function () {
|
||||
it('can log in and out', function () {
|
||||
login('user@example.com')
|
||||
cy.visit('/project')
|
||||
cy.findByText('Account').click()
|
||||
cy.findByText('Log Out').click()
|
||||
})
|
||||
})
|
136
server-ce/test/create-and-compile-project.spec.ts
Normal file
136
server-ce/test/create-and-compile-project.spec.ts
Normal file
|
@ -0,0 +1,136 @@
|
|||
import { login } from './helpers/login'
|
||||
import { createProject } from './helpers/project'
|
||||
|
||||
describe('Project creation and compilation', function () {
|
||||
it('users can create project and compile it', function () {
|
||||
login('user@example.com')
|
||||
cy.visit('/project')
|
||||
createProject('test-project')
|
||||
cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
|
||||
cy.findByText('\\maketitle')
|
||||
.parent()
|
||||
.click()
|
||||
.type('\n\\section{{}Test Section}')
|
||||
// Wait for the PDF compilation throttling
|
||||
cy.wait(3000)
|
||||
cy.findByText('Recompile').click()
|
||||
cy.get('.pdf-viewer').should('contain.text', 'Test Section')
|
||||
})
|
||||
|
||||
it('create and edit markdown file', function () {
|
||||
const fileName = `test-${Date.now()}.md`
|
||||
const markdownContent = '# Markdown title'
|
||||
login('user@example.com')
|
||||
cy.visit('/project')
|
||||
createProject('test-project')
|
||||
// FIXME: Add aria-label maybe? or at least data-test-id
|
||||
cy.findByText('New File').click({ force: true })
|
||||
cy.findByRole('dialog').within(() => {
|
||||
cy.get('input').clear()
|
||||
cy.get('input').type(fileName)
|
||||
cy.findByText('Create').click()
|
||||
})
|
||||
cy.findByText(fileName).click()
|
||||
// wait until we've switched to the newly created empty file
|
||||
cy.get('.cm-line').should('have.length', 1)
|
||||
cy.get('.cm-line').type(markdownContent)
|
||||
cy.findByText('main.tex').click()
|
||||
cy.get('.cm-content').should('contain.text', '\\maketitle')
|
||||
cy.findByText(fileName).click()
|
||||
cy.get('.cm-content').should('contain.text', markdownContent)
|
||||
})
|
||||
|
||||
it('can link and display linked image from other project', function () {
|
||||
const sourceProjectName = `test-project-${Date.now()}`
|
||||
const targetProjectName = `${sourceProjectName}-target`
|
||||
login('user@example.com')
|
||||
|
||||
cy.visit('/project')
|
||||
createProject(sourceProjectName, { type: 'Example Project' }).as(
|
||||
'sourceProjectId'
|
||||
)
|
||||
|
||||
cy.visit('/project')
|
||||
createProject(targetProjectName)
|
||||
|
||||
// link the image from `projectName` into this project
|
||||
cy.findByText('New File').click({ force: true })
|
||||
cy.findByRole('dialog').within(() => {
|
||||
cy.findByText('From Another Project').click()
|
||||
cy.findByLabelText('Select a Project').select(sourceProjectName)
|
||||
cy.findByLabelText('Select a File').select('frog.jpg')
|
||||
cy.findByText('Create').click()
|
||||
})
|
||||
// FIXME: should be aria-labeled or data-test-id
|
||||
cy.get('.file-tree').within(() => {
|
||||
cy.findByText('frog.jpg').click()
|
||||
})
|
||||
cy.findByText('Another project')
|
||||
.should('have.attr', 'href')
|
||||
.then(href => {
|
||||
cy.get('@sourceProjectId').then(sourceProjectId => {
|
||||
expect(href).to.equal(`/project/${sourceProjectId}`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('can refresh linked files as collaborator', function () {
|
||||
const sourceProjectName = `test-project-${Date.now()}`
|
||||
const targetProjectName = `${sourceProjectName}-target`
|
||||
login('user@example.com')
|
||||
|
||||
cy.visit('/project')
|
||||
createProject(sourceProjectName, { type: 'Example Project' }).as(
|
||||
'sourceProjectId'
|
||||
)
|
||||
|
||||
cy.visit('/project')
|
||||
createProject(targetProjectName).as('targetProjectId')
|
||||
|
||||
// link the image from `projectName` into this project
|
||||
cy.findByText('New File').click({ force: true })
|
||||
cy.findByRole('dialog').within(() => {
|
||||
cy.findByText('From Another Project').click()
|
||||
cy.findByLabelText('Select a Project').select(sourceProjectName)
|
||||
cy.findByLabelText('Select a File').select('frog.jpg')
|
||||
cy.findByText('Create').click()
|
||||
})
|
||||
|
||||
cy.findByText('Share').click()
|
||||
cy.findByRole('dialog').within(() => {
|
||||
cy.get('input').type('collaborator@example.com,')
|
||||
// FIXME: Open an issue for this.
|
||||
cy.get('button[type="submit"]').click({ force: true })
|
||||
cy.get('button[type="submit"]').click({ force: true })
|
||||
})
|
||||
|
||||
cy.visit('/project')
|
||||
cy.findByText('Account').click()
|
||||
cy.findByText('Log Out').click()
|
||||
|
||||
login('collaborator@example.com')
|
||||
cy.visit('/project')
|
||||
// FIXME: Should have data-test-id
|
||||
cy.findByText(targetProjectName)
|
||||
.parent()
|
||||
.parent()
|
||||
.find('button.btn-info')
|
||||
.click()
|
||||
cy.findByText('Open Project').click()
|
||||
cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
|
||||
cy.get('@targetProjectId').then(targetProjectId => {
|
||||
cy.url().should('include', targetProjectId)
|
||||
})
|
||||
|
||||
cy.get('.file-tree').within(() => {
|
||||
cy.findByText('frog.jpg').click()
|
||||
})
|
||||
cy.findByText('Another project')
|
||||
.should('have.attr', 'href')
|
||||
.then(href => {
|
||||
cy.get('@sourceProjectId').then(sourceProjectId => {
|
||||
expect(href).to.equal(`/project/${sourceProjectId}`)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
23
server-ce/test/cypress.config.js
Normal file
23
server-ce/test/cypress.config.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
const { defineConfig } = require('cypress')
|
||||
|
||||
const specPattern = process.env.SPEC_PATTERN || './**/*.spec.{js,ts,tsx}'
|
||||
|
||||
module.exports = defineConfig({
|
||||
fixturesFolder: 'cypress/fixtures',
|
||||
video: !!process.env.CI,
|
||||
screenshotsFolder: 'cypress/results',
|
||||
videosFolder: 'cypress/results',
|
||||
videoUploadOnPasses: false,
|
||||
viewportHeight: 768,
|
||||
viewportWidth: 1024,
|
||||
e2e: {
|
||||
baseUrl: 'http://localhost',
|
||||
setupNodeEvents(on, config) {
|
||||
// implement node event listeners here
|
||||
},
|
||||
specPattern,
|
||||
},
|
||||
retries: {
|
||||
runMode: 1,
|
||||
},
|
||||
})
|
1
server-ce/test/cypress/.gitignore
vendored
Normal file
1
server-ce/test/cypress/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
results/
|
1
server-ce/test/cypress/support/e2e.js
Normal file
1
server-ce/test/cypress/support/e2e.js
Normal file
|
@ -0,0 +1 @@
|
|||
import '@testing-library/cypress/add-commands'
|
64
server-ce/test/docker-compose.yml
Normal file
64
server-ce/test/docker-compose.yml
Normal file
|
@ -0,0 +1,64 @@
|
|||
version: '2.2'
|
||||
services:
|
||||
sharelatex:
|
||||
image: ${IMAGE_TAG:-sharelatex/sharelatex:latest}
|
||||
container_name: sharelatex
|
||||
depends_on:
|
||||
mongo:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_started
|
||||
ports:
|
||||
- 80:80
|
||||
links:
|
||||
- mongo
|
||||
- redis
|
||||
environment:
|
||||
SHARELATEX_APP_NAME: Overleaf Community Edition
|
||||
SHARELATEX_MONGO_URL: mongodb://mongo/sharelatex?directConnection=true
|
||||
SHARELATEX_REDIS_HOST: redis
|
||||
REDIS_HOST: redis
|
||||
ENABLED_LINKED_FILE_TYPES: 'project_file,project_output_file'
|
||||
ENABLE_CONVERSIONS: 'true'
|
||||
EMAIL_CONFIRMATION_DISABLED: 'true'
|
||||
healthcheck:
|
||||
test: curl --fail http://localhost:3000/status || exit 1
|
||||
interval: 10s
|
||||
timeout: 10s
|
||||
retries: 10
|
||||
volumes:
|
||||
- ./util/seed-mongo.sh:/etc/my_init.d/99_seed-mongo.sh
|
||||
- ./util/seed-mongo.js:/overleaf/services/web/modules/server-ce-scripts/scripts/seed-mongo.js
|
||||
|
||||
mongo:
|
||||
image: mongo:5.0.17
|
||||
container_name: mongo
|
||||
command: '--replSet overleaf'
|
||||
expose:
|
||||
- 27017
|
||||
healthcheck:
|
||||
# FIXME: silly hack to make sure replicaset is initialized
|
||||
test: 'echo ''rs.initiate({ _id: "overleaf", members: [{ _id: 0, host: "mongo:27017" }] })'' | mongo localhost:27017/test --quiet'
|
||||
interval: 10s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
|
||||
redis:
|
||||
image: redis:7.2.1
|
||||
container_name: redis
|
||||
expose:
|
||||
- 6379
|
||||
|
||||
e2e:
|
||||
image: cypress/included:13.3.0
|
||||
links:
|
||||
- sharelatex
|
||||
working_dir: /e2e
|
||||
volumes:
|
||||
- ./:/e2e
|
||||
environment:
|
||||
CYPRESS_BASE_URL: http://sharelatex
|
||||
SPEC_PATTERN: '**/*.spec.{js,jsx,ts,tsx}'
|
||||
depends_on:
|
||||
sharelatex:
|
||||
condition: service_healthy
|
9
server-ce/test/helpers/login.ts
Normal file
9
server-ce/test/helpers/login.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
export function login(username: string, password = 'Passw0rd!') {
|
||||
cy.session([username, password, new Date()], () => {
|
||||
cy.visit('/login')
|
||||
cy.get('input[name="email"]').type(username)
|
||||
cy.get('input[name="password"]').type(password)
|
||||
cy.findByRole('button', { name: 'Login' }).click()
|
||||
cy.url().should('contain', '/project')
|
||||
})
|
||||
}
|
19
server-ce/test/helpers/project.ts
Normal file
19
server-ce/test/helpers/project.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
export function createProject(
|
||||
name: string,
|
||||
{
|
||||
type = 'Blank Project',
|
||||
}: { type?: 'Blank Project' | 'Example Project' } = {}
|
||||
): Cypress.Chainable<string> {
|
||||
// FIXME: This should be be a data-test-id shared between the welcome page and project list
|
||||
cy.get('.new-project-button').first().click()
|
||||
// FIXME: This should only look in the left menu
|
||||
cy.findAllByText(type).first().click()
|
||||
cy.findByRole('dialog').within(() => {
|
||||
cy.get('input').type(name)
|
||||
cy.findByText('Create').click()
|
||||
})
|
||||
return cy
|
||||
.url()
|
||||
.should('match', /\/project\/[a-fA-F0-9]{24}/)
|
||||
.then(url => url.split('/').pop())
|
||||
}
|
2590
server-ce/test/package-lock.json
generated
Normal file
2590
server-ce/test/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
16
server-ce/test/package.json
Normal file
16
server-ce/test/package.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "@overleaf/server-ce/test",
|
||||
"description": "e2e tests for Overleaf Community Edition",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"cypress:open": "cypress open",
|
||||
"cypress:run": "cypress run",
|
||||
"format": "prettier --list-different $PWD/'**/*.{js,mjs,ts,tsx,json}'",
|
||||
"format:fix": "prettier --write $PWD/'**/*.{js,mjs,ts,tsx,json}'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@testing-library/cypress": "^10.0.1",
|
||||
"cypress": "13.3.0",
|
||||
"typescript": "^5.0.4"
|
||||
}
|
||||
}
|
17
server-ce/test/tsconfig.json
Normal file
17
server-ce/test/tsconfig.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext" /* Specify ECMAScript target version */,
|
||||
"module": "es2020" /* Specify module code generation */,
|
||||
"allowJs": true /* Allow JavaScript files to be compiled. */,
|
||||
// "checkJs": true /* Report errors in .js files. */,
|
||||
"jsx": "preserve" /* Specify JSX code generation */,
|
||||
"noEmit": true /* Do not emit outputs. */,
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
"moduleResolution": "node" /* Specify module resolution strategy */,
|
||||
"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"]
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.tsx"]
|
||||
}
|
121
server-ce/test/util/seed-mongo.js
Normal file
121
server-ce/test/util/seed-mongo.js
Normal file
|
@ -0,0 +1,121 @@
|
|||
const { ObjectId } = require('mongodb')
|
||||
const { waitForDb, db } = require('../../../app/src/infrastructure/mongodb')
|
||||
|
||||
waitForDb()
|
||||
.then(async () => {
|
||||
await seedUsers()
|
||||
process.exit(0)
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
const DEFAULT_USER_PROPERTIES = {
|
||||
staffAccess: {
|
||||
publisherMetrics: false,
|
||||
publisherManagement: false,
|
||||
institutionMetrics: false,
|
||||
institutionManagement: false,
|
||||
groupMetrics: false,
|
||||
groupManagement: false,
|
||||
adminMetrics: false,
|
||||
splitTestMetrics: false,
|
||||
splitTestManagement: false,
|
||||
},
|
||||
ace: {
|
||||
mode: 'none',
|
||||
theme: 'textmate',
|
||||
overallTheme: '',
|
||||
fontSize: 12,
|
||||
autoComplete: true,
|
||||
autoPairDelimiters: true,
|
||||
spellCheckLanguage: 'en',
|
||||
pdfViewer: 'pdfjs',
|
||||
syntaxValidation: true,
|
||||
},
|
||||
features: {
|
||||
collaborators: -1,
|
||||
versioning: true,
|
||||
dropbox: true,
|
||||
github: true,
|
||||
gitBridge: true,
|
||||
compileTimeout: 180,
|
||||
compileGroup: 'standard',
|
||||
templates: true,
|
||||
references: true,
|
||||
trackChanges: true,
|
||||
},
|
||||
first_name: 'user',
|
||||
role: '',
|
||||
institution: '',
|
||||
isAdmin: false,
|
||||
lastLoginIp: '',
|
||||
loginCount: 0,
|
||||
holdingAccount: false,
|
||||
must_reconfirm: false,
|
||||
refered_users: [],
|
||||
refered_user_count: 0,
|
||||
alphaProgram: false,
|
||||
betaProgram: false,
|
||||
labsProgram: false,
|
||||
awareOfV2: false,
|
||||
samlIdentifiers: [],
|
||||
thirdPartyIdentifiers: [],
|
||||
|
||||
signUpDate: new Date('2023-11-02T11:36:40.151Z'),
|
||||
featuresOverrides: [],
|
||||
referal_id: 'scTS4kjjJENbfbjG',
|
||||
__v: 0,
|
||||
hashedPassword:
|
||||
'$2a$12$nRvTj6U896uUnE.RFhnGKOyi/CvqBpfxezlqwyIPpezRa2xXLW7MO',
|
||||
}
|
||||
|
||||
async function seedUsers() {
|
||||
const adminUser = {
|
||||
...DEFAULT_USER_PROPERTIES,
|
||||
email: 'admin@example.com',
|
||||
first_name: 'admin',
|
||||
isAdmin: true,
|
||||
emails: [
|
||||
{
|
||||
email: 'admin@example.com',
|
||||
reversedHostname: '',
|
||||
_id: ObjectId('646ca54806d54400b74e77c6'),
|
||||
createdAt: new Date('2023-05-23T11:36:40.494Z'),
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const user = {
|
||||
...DEFAULT_USER_PROPERTIES,
|
||||
_id: ObjectId('6543cf90bbe1368944db04d7'),
|
||||
emails: [
|
||||
{
|
||||
email: 'user@example.com',
|
||||
reversedHostname: '',
|
||||
_id: ObjectId('6543c614d58b090b461f3549'),
|
||||
createdAt: new Date('2023-11-21T11:36:40.494Z'),
|
||||
},
|
||||
],
|
||||
email: 'user@example.com',
|
||||
}
|
||||
|
||||
const collaborator = {
|
||||
...DEFAULT_USER_PROPERTIES,
|
||||
_id: ObjectId('6544e78c9b6e937424976b64'),
|
||||
emails: [
|
||||
{
|
||||
email: 'collaborator@example.com',
|
||||
reversedHostname: '',
|
||||
_id: ObjectId('6543c614d58b090b461f354a'),
|
||||
createdAt: new Date('2023-11-21T11:36:40.494Z'),
|
||||
},
|
||||
],
|
||||
email: 'collaborator@example.com',
|
||||
}
|
||||
|
||||
await db.users.insertOne(adminUser)
|
||||
await db.users.insertOne(user)
|
||||
await db.users.insertOne(collaborator)
|
||||
}
|
8
server-ce/test/util/seed-mongo.sh
Executable file
8
server-ce/test/util/seed-mongo.sh
Executable file
|
@ -0,0 +1,8 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
echo "Seeding mongo for e2e tests"
|
||||
cd /overleaf/services/web
|
||||
node modules/server-ce-scripts/scripts/seed-mongo
|
||||
node modules/server-ce-scripts/scripts/check-redis
|
||||
echo "mongo seeding complete"
|
Loading…
Reference in a new issue