mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #19631 from overleaf/jpa-e2e-emails
[server-pro] add e2e test for accepting project invite via email GitOrigin-RevId: c8391b57c1ee882499cfe5dc02817b5fadcd7ff4
This commit is contained in:
parent
bd2a99aca7
commit
8748ac7475
8 changed files with 124 additions and 10 deletions
1
server-ce/test/.gitignore
vendored
1
server-ce/test/.gitignore
vendored
|
@ -1 +1,2 @@
|
||||||
data/
|
data/
|
||||||
|
docker-mailtrap/
|
||||||
|
|
|
@ -12,14 +12,14 @@ export IMAGE_TAG_PRO ?= quay.io/sharelatex/sharelatex-pro:latest
|
||||||
export CYPRESS_SHARD ?=
|
export CYPRESS_SHARD ?=
|
||||||
export COMPOSE_PROJECT_NAME ?= test
|
export COMPOSE_PROJECT_NAME ?= test
|
||||||
|
|
||||||
test-e2e-native:
|
test-e2e-native: build_mailtrap
|
||||||
docker compose -f docker-compose.yml -f docker-compose.native.yml up --build --no-log-prefix sharelatex host-admin -d
|
docker compose -f docker-compose.yml -f docker-compose.native.yml up --build --no-log-prefix sharelatex host-admin -d
|
||||||
CYPRESS_ADMIN_CLIENT_URL='http://localhost:8081' CYPRESS_GIT_BRIDGE_PUBLIC_HOST='localhost' CYPRESS_SAML_PUBLIC_HOST='localhost:8082' CYPRESS_OVERLEAF_PUBLIC_HOST='localhost:8082' npm run cypress:open
|
CYPRESS_ADMIN_CLIENT_URL='http://localhost:8081' CYPRESS_GIT_BRIDGE_PUBLIC_HOST='localhost' CYPRESS_SAML_PUBLIC_HOST='localhost:8082' CYPRESS_OVERLEAF_PUBLIC_HOST='localhost:8082' npm run cypress:open
|
||||||
|
|
||||||
test-e2e:
|
test-e2e:
|
||||||
docker compose up --build --no-log-prefix --exit-code-from=e2e e2e
|
docker compose up --build --no-log-prefix --exit-code-from=e2e e2e
|
||||||
|
|
||||||
test-e2e-open:
|
test-e2e-open: build_mailtrap
|
||||||
docker compose up --build --no-log-prefix --exit-code-from=e2e-open e2e-open
|
docker compose up --build --no-log-prefix --exit-code-from=e2e-open e2e-open
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
@ -32,7 +32,7 @@ prefetch_default_compose:
|
||||||
|
|
||||||
prefetch_default: prefetch_default_compose_build
|
prefetch_default: prefetch_default_compose_build
|
||||||
prefetch_default_compose_build:
|
prefetch_default_compose_build:
|
||||||
docker compose build
|
docker compose build host-admin
|
||||||
|
|
||||||
prefetch: prefetch_custom
|
prefetch: prefetch_custom
|
||||||
prefetch_custom: prefetch_custom_compose_pull
|
prefetch_custom: prefetch_custom_compose_pull
|
||||||
|
@ -50,4 +50,11 @@ prefetch_old:
|
||||||
docker pull $(IMAGE_TAG_PRO:latest=5.0.1-RC1)
|
docker pull $(IMAGE_TAG_PRO:latest=5.0.1-RC1)
|
||||||
docker pull $(IMAGE_TAG_PRO:latest=5.0)
|
docker pull $(IMAGE_TAG_PRO:latest=5.0)
|
||||||
|
|
||||||
|
# Google Cloud Build runs on a very ancient Docker version that does not support the subdir flag.
|
||||||
|
# Use services -> mailtrap -> build -> context = https://github.com/dbck/docker-mailtrap.git#v1.5.0:build in docker-compose.yml eventually.
|
||||||
|
prefetch_default_compose_build: build_mailtrap
|
||||||
|
build_mailtrap:
|
||||||
|
git clone https://github.com/dbck/docker-mailtrap.git || true && cd docker-mailtrap && git checkout v1.5.0
|
||||||
|
docker build -t mailtrap docker-mailtrap/build
|
||||||
|
|
||||||
.PHONY: test-e2e test-e2e-open
|
.PHONY: test-e2e test-e2e-open
|
||||||
|
|
|
@ -2,6 +2,11 @@ import '@testing-library/cypress/add-commands'
|
||||||
|
|
||||||
Cypress.on('uncaught:exception', (err, runnable) => {
|
Cypress.on('uncaught:exception', (err, runnable) => {
|
||||||
if (err.message.includes('ResizeObserver')) {
|
if (err.message.includes('ResizeObserver')) {
|
||||||
|
// spurious error from PDF preview
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (err.message.includes('rcube_webmail')) {
|
||||||
|
// spurious error from mailtrap
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -11,12 +11,18 @@ services:
|
||||||
host-admin:
|
host-admin:
|
||||||
# The host-admin service initiates the mongo replica set
|
# The host-admin service initiates the mongo replica set
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
mailtrap:
|
||||||
|
condition: service_started
|
||||||
environment:
|
environment:
|
||||||
OVERLEAF_SITE_URL: 'http://sharelatex'
|
OVERLEAF_SITE_URL: 'http://sharelatex'
|
||||||
OVERLEAF_APP_NAME: Overleaf Community Edition
|
OVERLEAF_APP_NAME: Overleaf Community Edition
|
||||||
OVERLEAF_MONGO_URL: mongodb://mongo/sharelatex?directConnection=true
|
OVERLEAF_MONGO_URL: mongodb://mongo/sharelatex?directConnection=true
|
||||||
OVERLEAF_REDIS_HOST: redis
|
OVERLEAF_REDIS_HOST: redis
|
||||||
REDIS_HOST: redis
|
REDIS_HOST: redis
|
||||||
|
OVERLEAF_EMAIL_FROM_ADDRESS: 'welcome@example.com'
|
||||||
|
OVERLEAF_EMAIL_SMTP_HOST: 'mailtrap'
|
||||||
|
OVERLEAF_EMAIL_SMTP_PORT: '25'
|
||||||
|
OVERLEAF_EMAIL_SMTP_IGNORE_TLS: 'true'
|
||||||
ENABLED_LINKED_FILE_TYPES: 'project_file,project_output_file'
|
ENABLED_LINKED_FILE_TYPES: 'project_file,project_output_file'
|
||||||
ENABLE_CONVERSIONS: 'true'
|
ENABLE_CONVERSIONS: 'true'
|
||||||
EMAIL_CONFIRMATION_DISABLED: 'true'
|
EMAIL_CONFIRMATION_DISABLED: 'true'
|
||||||
|
@ -26,6 +32,11 @@ services:
|
||||||
timeout: 3s
|
timeout: 3s
|
||||||
retries: 30
|
retries: 30
|
||||||
|
|
||||||
|
mailtrap:
|
||||||
|
image: mailtrap
|
||||||
|
environment:
|
||||||
|
MAILTRAP_PASSWORD: 'password-for-mailtrap'
|
||||||
|
|
||||||
mongo:
|
mongo:
|
||||||
image: mongo:5.0.17
|
image: mongo:5.0.17
|
||||||
command: '--replSet overleaf'
|
command: '--replSet overleaf'
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { ensureUserExists, login } from './helpers/login'
|
||||||
import {
|
import {
|
||||||
createProject,
|
createProject,
|
||||||
enableLinkSharing,
|
enableLinkSharing,
|
||||||
shareProjectByEmailAndAcceptInvite,
|
shareProjectByEmailAndAcceptInviteViaDash,
|
||||||
} from './helpers/project'
|
} from './helpers/project'
|
||||||
|
|
||||||
import git from 'isomorphic-git'
|
import git from 'isomorphic-git'
|
||||||
|
@ -134,7 +134,7 @@ describe('git-bridge', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should expose r/w interface to invited r/w collaborator', () => {
|
it('should expose r/w interface to invited r/w collaborator', () => {
|
||||||
shareProjectByEmailAndAcceptInvite(
|
shareProjectByEmailAndAcceptInviteViaDash(
|
||||||
projectName,
|
projectName,
|
||||||
'collaborator-rw@example.com',
|
'collaborator-rw@example.com',
|
||||||
'Can edit'
|
'Can edit'
|
||||||
|
@ -146,7 +146,7 @@ describe('git-bridge', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should expose r/o interface to invited r/o collaborator', () => {
|
it('should expose r/o interface to invited r/o collaborator', () => {
|
||||||
shareProjectByEmailAndAcceptInvite(
|
shareProjectByEmailAndAcceptInviteViaDash(
|
||||||
projectName,
|
projectName,
|
||||||
'collaborator-ro@example.com',
|
'collaborator-ro@example.com',
|
||||||
'Read only'
|
'Read only'
|
||||||
|
|
36
server-ce/test/helpers/email.ts
Normal file
36
server-ce/test/helpers/email.ts
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
/**
|
||||||
|
* Helper function for opening an email in Roundcube based mailtrap.
|
||||||
|
* We need to cross an origin boundary, which complicates the use of variables.
|
||||||
|
* Any variables need to be explicitly defined and the "runner" may only reference these and none from its scope.
|
||||||
|
* It is not possible to use Cypress helper functions, e.g. from the testing library inside the "runner".
|
||||||
|
* REF: https://github.com/testing-library/cypress-testing-library/issues/221
|
||||||
|
*/
|
||||||
|
export function openEmail<T>(
|
||||||
|
subject: string | RegExp,
|
||||||
|
runner: (frame: Cypress.Chainable<JQuery<any>>, args?: T) => void,
|
||||||
|
args?: T
|
||||||
|
) {
|
||||||
|
const runnerS = runner.toString()
|
||||||
|
cy.origin(
|
||||||
|
'http://mailtrap',
|
||||||
|
{ args: { args, runnerS, subject } },
|
||||||
|
({ args, runnerS, subject }) => {
|
||||||
|
cy.visit('/')
|
||||||
|
cy.get('input[name="_user"]').type('mailtrap')
|
||||||
|
cy.get('input[name="_pass"]').type('password-for-mailtrap')
|
||||||
|
cy.get('button[type="submit"]').click()
|
||||||
|
cy.log('mailtrap login is flaky in cypress, submit again')
|
||||||
|
cy.get('input[name="_pass"]').type('password-for-mailtrap')
|
||||||
|
cy.get('button[type="submit"]').click()
|
||||||
|
// Use force as the subject is partially hidden
|
||||||
|
cy.contains(subject).click({ force: true })
|
||||||
|
cy.log('wait for iframe loading')
|
||||||
|
cy.wait(1000)
|
||||||
|
cy.get('iframe[id="messagecontframe"]').then(frame => {
|
||||||
|
// runnerS='(frame, args) => { runner body }'. Extract the runnable function.
|
||||||
|
const runner = new Function('return ' + runnerS)()
|
||||||
|
runner(cy.wrap(frame.prop('contentWindow').document.body), args)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import { login } from './login'
|
import { login } from './login'
|
||||||
|
import { openEmail } from './email'
|
||||||
|
|
||||||
export function createProject(
|
export function createProject(
|
||||||
name: string,
|
name: string,
|
||||||
|
@ -23,7 +24,7 @@ export function createProject(
|
||||||
.then(url => url.split('/').pop())
|
.then(url => url.split('/').pop())
|
||||||
}
|
}
|
||||||
|
|
||||||
export function shareProjectByEmailAndAcceptInvite(
|
function shareProjectByEmail(
|
||||||
projectName: string,
|
projectName: string,
|
||||||
email: string,
|
email: string,
|
||||||
level: 'Read only' | 'Can edit'
|
level: 'Read only' | 'Can edit'
|
||||||
|
@ -38,6 +39,14 @@ export function shareProjectByEmailAndAcceptInvite(
|
||||||
.within(() => cy.findByText('Can edit').parent().select(level))
|
.within(() => cy.findByText('Can edit').parent().select(level))
|
||||||
cy.findByText('Share').click({ force: true })
|
cy.findByText('Share').click({ force: true })
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shareProjectByEmailAndAcceptInviteViaDash(
|
||||||
|
projectName: string,
|
||||||
|
email: string,
|
||||||
|
level: 'Read only' | 'Can edit'
|
||||||
|
) {
|
||||||
|
shareProjectByEmail(projectName, email, level)
|
||||||
|
|
||||||
login(email)
|
login(email)
|
||||||
cy.visit('/project')
|
cy.visit('/project')
|
||||||
|
@ -49,6 +58,29 @@ export function shareProjectByEmailAndAcceptInvite(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function shareProjectByEmailAndAcceptInviteViaEmail(
|
||||||
|
projectName: string,
|
||||||
|
email: string,
|
||||||
|
level: 'Read only' | 'Can edit'
|
||||||
|
) {
|
||||||
|
shareProjectByEmail(projectName, email, level)
|
||||||
|
|
||||||
|
login(email)
|
||||||
|
|
||||||
|
openEmail(projectName, frame => {
|
||||||
|
frame.contains('View project').then(a => {
|
||||||
|
cy.log(
|
||||||
|
'bypass target=_blank and navigate current browser tab/cypress-iframe to project invite'
|
||||||
|
)
|
||||||
|
cy.visit(a.attr('href')!)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
cy.url().should('match', /\/project\/[a-f0-9]+\/invite\/token\/[a-f0-9]+/)
|
||||||
|
cy.findByText(/user would like you to join/)
|
||||||
|
cy.contains(new RegExp(`You are accepting this invite as ${email}`))
|
||||||
|
cy.findByText('Join Project').click()
|
||||||
|
}
|
||||||
|
|
||||||
export function enableLinkSharing() {
|
export function enableLinkSharing() {
|
||||||
let linkSharingReadOnly: string
|
let linkSharingReadOnly: string
|
||||||
let linkSharingReadAndWrite: string
|
let linkSharingReadAndWrite: string
|
||||||
|
|
|
@ -4,7 +4,8 @@ import { ensureUserExists, login } from './helpers/login'
|
||||||
import {
|
import {
|
||||||
createProject,
|
createProject,
|
||||||
enableLinkSharing,
|
enableLinkSharing,
|
||||||
shareProjectByEmailAndAcceptInvite,
|
shareProjectByEmailAndAcceptInviteViaDash,
|
||||||
|
shareProjectByEmailAndAcceptInviteViaEmail,
|
||||||
} from './helpers/project'
|
} from './helpers/project'
|
||||||
import { throttledRecompile } from './helpers/compile'
|
import { throttledRecompile } from './helpers/compile'
|
||||||
import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry'
|
import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry'
|
||||||
|
@ -143,13 +144,34 @@ describe('Project Sharing', function () {
|
||||||
.should('contain.text', author) // might have other edits in the same group
|
.should('contain.text', author) // might have other edits in the same group
|
||||||
}
|
}
|
||||||
|
|
||||||
|
describe('via email', function () {
|
||||||
|
const email = 'collaborator-email@example.com'
|
||||||
|
ensureUserExists({ email })
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
login('user@example.com')
|
||||||
|
shareProjectByEmailAndAcceptInviteViaEmail(
|
||||||
|
projectName,
|
||||||
|
email,
|
||||||
|
'Read only'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should grant the collaborator read access', () => {
|
||||||
|
cy.visit('/project')
|
||||||
|
cy.findByText(projectName).click()
|
||||||
|
expectFullReadOnlyAccess()
|
||||||
|
expectProjectDashboardEntry()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('read only', () => {
|
describe('read only', () => {
|
||||||
const email = 'collaborator-ro@example.com'
|
const email = 'collaborator-ro@example.com'
|
||||||
ensureUserExists({ email })
|
ensureUserExists({ email })
|
||||||
|
|
||||||
beforeWithReRunOnTestRetry(function () {
|
beforeWithReRunOnTestRetry(function () {
|
||||||
login('user@example.com')
|
login('user@example.com')
|
||||||
shareProjectByEmailAndAcceptInvite(projectName, email, 'Read only')
|
shareProjectByEmailAndAcceptInviteViaDash(projectName, email, 'Read only')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should grant the collaborator read access', () => {
|
it('should grant the collaborator read access', () => {
|
||||||
|
@ -167,7 +189,7 @@ describe('Project Sharing', function () {
|
||||||
|
|
||||||
beforeWithReRunOnTestRetry(function () {
|
beforeWithReRunOnTestRetry(function () {
|
||||||
login('user@example.com')
|
login('user@example.com')
|
||||||
shareProjectByEmailAndAcceptInvite(projectName, email, 'Can edit')
|
shareProjectByEmailAndAcceptInviteViaDash(projectName, email, 'Can edit')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should grant the collaborator write access', () => {
|
it('should grant the collaborator write access', () => {
|
||||||
|
|
Loading…
Reference in a new issue