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:
Mathias Jakobsen 2023-11-23 10:40:13 +00:00 committed by Copybot
parent 21c61be543
commit 732cbf0c26
14 changed files with 3023 additions and 0 deletions

8
server-ce/test/Makefile Normal file
View 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

View 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()
})
})

View 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}`)
})
})
})
})

View 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
View file

@ -0,0 +1 @@
results/

View file

@ -0,0 +1 @@
import '@testing-library/cypress/add-commands'

View 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

View 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')
})
}

View 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

File diff suppressed because it is too large Load diff

View 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"
}
}

View 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"]
}

View 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)
}

View 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"