Merge pull request #19778 from overleaf/jpa-e2e-reuse-sessions

[server-pro] tests: implement session re-usage

GitOrigin-RevId: 81f2feb39e413c858eb287784e378cf43423d0a4
This commit is contained in:
Jakob Ackermann 2024-08-06 11:10:03 +02:00 committed by Copybot
parent 399e834e36
commit 37f486ca9a
8 changed files with 58 additions and 35 deletions

View file

@ -11,6 +11,9 @@ describe('Accounts', function () {
cy.visit('/project') cy.visit('/project')
cy.findByText('Account').click() cy.findByText('Account').click()
cy.findByText('Log Out').click() cy.findByText('Log Out').click()
cy.url().should('include', '/login')
cy.visit('/project')
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', () => {

View file

@ -134,9 +134,8 @@ describe('admin panel', function () {
}) })
describe('manage site', () => { describe('manage site', () => {
let resumeAdminSession: () => void
beforeEach(() => { beforeEach(() => {
resumeAdminSession = login(admin) login(admin)
cy.visit('/project') cy.visit('/project')
cy.get('nav').findByText('Admin').click() cy.get('nav').findByText('Admin').click()
cy.get('nav').findByText('Manage Site').click() cy.get('nav').findByText('Manage Site').click()
@ -151,12 +150,12 @@ describe('admin panel', function () {
cy.get('button').contains('Post Message').click() cy.get('button').contains('Post Message').click()
cy.findByText(message) cy.findByText(message)
const resumeUser1Session = login(user1) login(user1)
cy.visit('/project') cy.visit('/project')
cy.findByText(message) cy.findByText(message)
cy.log('clear system messages') cy.log('clear system messages')
resumeAdminSession() login(admin)
cy.visit('/project') cy.visit('/project')
cy.get('nav').findByText('Admin').click() cy.get('nav').findByText('Admin').click()
cy.get('nav').findByText('Manage Site').click() cy.get('nav').findByText('Manage Site').click()
@ -164,7 +163,7 @@ describe('admin panel', function () {
cy.get('button').contains('Clear all messages').click() cy.get('button').contains('Clear all messages').click()
cy.log('verify system messages are no longer displayed') cy.log('verify system messages are no longer displayed')
resumeUser1Session() login(user1)
cy.visit('/project') cy.visit('/project')
cy.findByText(message).should('not.exist') cy.findByText(message).should('not.exist')
}) })
@ -260,7 +259,7 @@ describe('admin panel', function () {
}) })
it('restore deleted projects', () => { it('restore deleted projects', () => {
const resumeUserSession = login(user1) login(user1)
cy.visit('/project') cy.visit('/project')
cy.log('select project to delete') cy.log('select project to delete')
@ -299,7 +298,7 @@ describe('admin panel', function () {
cy.url().should('contain', `/admin/project/${projectToDeleteId}`) cy.url().should('contain', `/admin/project/${projectToDeleteId}`)
cy.log('login as the user and verify the project is restored') cy.log('login as the user and verify the project is restored')
resumeUserSession() login(user1)
cy.visit('/project') cy.visit('/project')
cy.get('.project-list-sidebar-react').within(() => { cy.get('.project-list-sidebar-react').within(() => {
cy.findByText('Trashed Projects').click() cy.findByText('Trashed Projects').click()

View file

@ -66,11 +66,9 @@ describe('editor', () => {
describe('collaboration', () => { describe('collaboration', () => {
let projectId: string let projectId: string
let resumeUserSession: () => void
let resumeCollaboratorSession: () => void
beforeEach(() => { beforeEach(() => {
resumeUserSession = login('user@example.com') login('user@example.com')
cy.visit(`/project`) cy.visit(`/project`)
createProject('test-editor', { type: 'Example Project' }).then( createProject('test-editor', { type: 'Example Project' }).then(
(id: string) => { (id: string) => {
@ -86,7 +84,7 @@ describe('editor', () => {
.should('contain.text', 'http://') // wait for the link to appear .should('contain.text', 'http://') // wait for the link to appear
.then(el => { .then(el => {
const linkSharingReadAndWrite = el.text() const linkSharingReadAndWrite = el.text()
resumeCollaboratorSession = login('collaborator@example.com') login('collaborator@example.com')
cy.visit(linkSharingReadAndWrite) cy.visit(linkSharingReadAndWrite)
cy.get('button').contains('Join Project').click() cy.get('button').contains('Join Project').click()
cy.log( cy.log(
@ -95,7 +93,7 @@ describe('editor', () => {
cy.visit('/project') cy.visit('/project')
}) })
resumeUserSession() login('user@example.com')
cy.visit(`/project/${projectId}`) cy.visit(`/project/${projectId}`)
} }
) )
@ -112,7 +110,7 @@ describe('editor', () => {
.within(() => cy.get('.input-switch').click()) .within(() => cy.get('.input-switch').click())
cy.wait('@enableTrackChanges') cy.wait('@enableTrackChanges')
resumeCollaboratorSession() login('collaborator@example.com')
cy.visit(`/project/${projectId}`) cy.visit(`/project/${projectId}`)
cy.log('make changes in main file') cy.log('make changes in main file')
@ -127,7 +125,7 @@ describe('editor', () => {
cy.log('recompile to force flush') cy.log('recompile to force flush')
cy.findByText('Recompile').click() cy.findByText('Recompile').click()
resumeUserSession() login('user@example.com')
cy.visit(`/project/${projectId}`) cy.visit(`/project/${projectId}`)
cy.log('reject changes') cy.log('reject changes')
@ -151,7 +149,7 @@ describe('editor', () => {
.within(() => cy.get('.input-switch').click()) .within(() => cy.get('.input-switch').click())
cy.wait('@enableTrackChanges') cy.wait('@enableTrackChanges')
resumeCollaboratorSession() login('collaborator@example.com')
cy.visit(`/project/${projectId}`) cy.visit(`/project/${projectId}`)
cy.log('enable visual editor and make changes in main file') cy.log('enable visual editor and make changes in main file')
@ -168,7 +166,7 @@ describe('editor', () => {
cy.log('recompile to force flush') cy.log('recompile to force flush')
cy.findByText('Recompile').click() cy.findByText('Recompile').click()
resumeUserSession() login('user@example.com')
cy.visit(`/project/${projectId}`) cy.visit(`/project/${projectId}`)
cy.log('reject changes') cy.log('reject changes')

View file

@ -19,7 +19,7 @@ export function isExcludedBySharding(
return SHARD && shard !== SHARD return SHARD && shard !== SHARD
} }
let lastConfig: string let previousConfigFrontend: string
export function startWith({ export function startWith({
pro = false, pro = false,
@ -36,18 +36,28 @@ export function startWith({
version, version,
vars, vars,
withDataDir, withDataDir,
resetData,
}) })
if (resetData) { if (resetData) {
resetCreatedUsersCache() resetCreatedUsersCache()
resetActivateUserRateLimit() resetActivateUserRateLimit()
// no return here, always reconfigure when resetting data // no return here, always reconfigure when resetting data
} else if (lastConfig === cfg) { } else if (previousConfigFrontend === cfg) {
return return
} }
this.timeout(STARTUP_TIMEOUT) this.timeout(STARTUP_TIMEOUT)
await reconfigure({ pro, version, vars, withDataDir, resetData }) const { previousConfigServer } = await reconfigure({
lastConfig = cfg pro,
version,
vars,
withDataDir,
resetData,
})
if (previousConfigServer !== cfg) {
await Cypress.session.clearAllSavedSessions()
}
previousConfigFrontend = cfg
}) })
} }

View file

@ -15,7 +15,7 @@ export async function reconfigure({
vars = {}, vars = {},
withDataDir = false, withDataDir = false,
resetData = false, resetData = false,
}) { }): Promise<{ previousConfigServer: string }> {
return await fetchJSON(`${hostAdminUrl}/reconfigure`, { return await fetchJSON(`${hostAdminUrl}/reconfigure`, {
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
@ -28,15 +28,15 @@ export async function reconfigure({
}) })
} }
async function fetchJSON( async function fetchJSON<T = { stdout: string; stderr: string }>(
input: RequestInfo, input: RequestInfo,
init?: RequestInit init?: RequestInit
): Promise<{ stdout: string; stderr: string }> { ): Promise<T> {
if (init?.body) { if (init?.body) {
init.headers = { 'Content-Type': 'application/json' } init.headers = { 'Content-Type': 'application/json' }
} }
const res = await fetch(input, init) const res = await fetch(input, init)
const { error, stdout, stderr } = await res.json() const { error, stdout, stderr, ...rest } = await res.json()
if (error) { if (error) {
console.error(input, init, 'failed:', error) console.error(input, init, 'failed:', error)
if (stdout) console.log(stdout) if (stdout) console.log(stdout)
@ -45,7 +45,7 @@ async function fetchJSON(
Object.assign(err, error) Object.assign(err, error)
throw err throw err
} }
return { stdout, stderr } return { stdout, stderr, ...rest }
} }
export async function runScript({ export async function runScript({

View file

@ -56,18 +56,26 @@ export function ensureUserExists({
} }
export function login(username: string, password = DEFAULT_PASSWORD) { export function login(username: string, password = DEFAULT_PASSWORD) {
const id = [username, password, new Date()] cy.session(
function startOrResumeSession() { [username, password],
cy.session(id, () => { () => {
cy.visit('/login') cy.visit('/login')
cy.get('input[name="email"]').type(username) cy.get('input[name="email"]').type(username)
cy.get('input[name="password"]').type(password) cy.get('input[name="password"]').type(password)
cy.findByRole('button', { name: 'Login' }).click() cy.findByRole('button', { name: 'Login' }).click()
cy.url().should('contain', '/project') cy.url().should('contain', '/project')
}) },
{
cacheAcrossSpecs: true,
async validate() {
cy.request({ url: '/project', followRedirect: false }).then(
response => {
expect(response.status).to.equal(200)
} }
startOrResumeSession() )
return startOrResumeSession },
}
)
} }
let activateRateLimitState = { count: 0, reset: 0 } let activateRateLimitState = { count: 0, reset: 0 }

View file

@ -29,6 +29,7 @@ const IMAGES = {
PRO: process.env.IMAGE_TAG_PRO.replace(/:.+/, ''), PRO: process.env.IMAGE_TAG_PRO.replace(/:.+/, ''),
} }
let previousConfig = ''
let mongoIsInitialized = false let mongoIsInitialized = false
function readDockerComposeOverride() { function readDockerComposeOverride() {
@ -295,6 +296,7 @@ function maybeResetData(resetData, callback) {
return callback(error) return callback(error)
} }
previousConfig = ''
mongoIsInitialized = false mongoIsInitialized = false
runDockerCompose( runDockerCompose(
'down', 'down',
@ -336,7 +338,9 @@ app.post(
'up', 'up',
['--detach', '--wait', 'sharelatex'], ['--detach', '--wait', 'sharelatex'],
(error, stdout, stderr) => { (error, stdout, stderr) => {
res.json({ error, stdout, stderr }) const previousConfigServer = previousConfig
previousConfig = JSON.stringify(req.body)
res.json({ error, stdout, stderr, previousConfigServer })
} }
) )
}) })

View file

@ -48,7 +48,7 @@ describe('Templates', () => {
}) })
it('should have templates feature', () => { it('should have templates feature', () => {
const resumeTemplatesUserSession = login(TEMPLATES_USER) login(TEMPLATES_USER)
const name = `Template ${Date.now()}` const name = `Template ${Date.now()}`
const description = `Template Description ${Date.now()}` const description = `Template Description ${Date.now()}`
@ -182,7 +182,8 @@ describe('Templates', () => {
cy.findByText('Manage Template').click() cy.findByText('Manage Template').click()
cy.findByText('Unpublish') cy.findByText('Unpublish')
resumeTemplatesUserSession() // Back to templates user
login(TEMPLATES_USER)
// Unpublish via editor // Unpublish via editor
cy.get('@templateProjectId').then(projectId => cy.get('@templateProjectId').then(projectId =>