[server-pro] tests: wait for editor to finish loading before interacting (#23841)

GitOrigin-RevId: bef74f336c3a240da43cd5f9563629b96bc1d7ca
This commit is contained in:
Jakob Ackermann
2025-02-24 14:26:06 +00:00
committed by Copybot
parent 8138c76c1d
commit 8b64325e89
12 changed files with 104 additions and 82 deletions

View File

@@ -127,9 +127,7 @@ describe('admin panel', function () {
testProjectName = `project-${uuid()}` testProjectName = `project-${uuid()}`
deletedProjectName = `deleted-project-${uuid()}` deletedProjectName = `deleted-project-${uuid()}`
login(user1) login(user1)
cy.visit('/project')
createProject(testProjectName).then(id => (testProjectId = id)) createProject(testProjectName).then(id => (testProjectId = id))
cy.visit('/project')
createProject(deletedProjectName).then(id => (projectToDeleteId = id)) createProject(deletedProjectName).then(id => (projectToDeleteId = id))
}) })

View File

@@ -1,5 +1,8 @@
import { ensureUserExists, login } from './helpers/login' import { ensureUserExists, login } from './helpers/login'
import { createProject } from './helpers/project' import {
createProject,
openProjectViaInviteNotification,
} from './helpers/project'
import { isExcludedBySharding, startWith } from './helpers/config' import { isExcludedBySharding, startWith } from './helpers/config'
import { throttledRecompile } from './helpers/compile' import { throttledRecompile } from './helpers/compile'
@@ -11,10 +14,7 @@ describe('Project creation and compilation', function () {
it('users can create project and compile it', function () { it('users can create project and compile it', function () {
login('user@example.com') login('user@example.com')
cy.visit('/project')
// this is the first project created, the welcome screen is displayed instead of the project list
createProject('test-project') createProject('test-project')
cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
const recompile = throttledRecompile() const recompile = throttledRecompile()
cy.findByText('\\maketitle').parent().click() cy.findByText('\\maketitle').parent().click()
cy.findByText('\\maketitle').parent().type('\n\\section{{}Test Section}') cy.findByText('\\maketitle').parent().type('\n\\section{{}Test Section}')
@@ -26,8 +26,8 @@ describe('Project creation and compilation', function () {
const fileName = `test-${Date.now()}.md` const fileName = `test-${Date.now()}.md`
const markdownContent = '# Markdown title' const markdownContent = '# Markdown title'
login('user@example.com') login('user@example.com')
cy.visit('/project')
createProject('test-project') createProject('test-project')
// FIXME: Add aria-label maybe? or at least data-test-id // FIXME: Add aria-label maybe? or at least data-test-id
cy.findByText('New file').click({ force: true }) cy.findByText('New file').click({ force: true })
cy.findByRole('dialog').within(() => { cy.findByRole('dialog').within(() => {
@@ -50,12 +50,9 @@ describe('Project creation and compilation', function () {
const targetProjectName = `${sourceProjectName}-target` const targetProjectName = `${sourceProjectName}-target`
login('user@example.com') login('user@example.com')
cy.visit('/project')
createProject(sourceProjectName, { type: 'Example Project' }).as( createProject(sourceProjectName, { type: 'Example Project' }).as(
'sourceProjectId' 'sourceProjectId'
) )
cy.visit('/project')
createProject(targetProjectName) createProject(targetProjectName)
// link the image from `projectName` into this project // link the image from `projectName` into this project
@@ -80,13 +77,9 @@ describe('Project creation and compilation', function () {
const sourceProjectName = `test-project-${Date.now()}` const sourceProjectName = `test-project-${Date.now()}`
const targetProjectName = `${sourceProjectName}-target` const targetProjectName = `${sourceProjectName}-target`
login('user@example.com') login('user@example.com')
cy.visit('/project')
createProject(sourceProjectName, { type: 'Example Project' }).as( createProject(sourceProjectName, { type: 'Example Project' }).as(
'sourceProjectId' 'sourceProjectId'
) )
cy.visit('/project')
createProject(targetProjectName).as('targetProjectId') createProject(targetProjectName).as('targetProjectId')
// link the image from `projectName` into this project // link the image from `projectName` into this project
@@ -110,15 +103,7 @@ describe('Project creation and compilation', function () {
cy.findByText('Log Out').click() cy.findByText('Log Out').click()
login('collaborator@example.com') login('collaborator@example.com')
cy.visit('/project') openProjectViaInviteNotification(targetProjectName)
cy.findByText(targetProjectName)
.parent()
.parent()
.within(() => {
cy.findByText('Join Project').click()
})
cy.findByText('Open Project').click()
cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
cy.get('@targetProjectId').then(targetProjectId => { cy.get('@targetProjectId').then(targetProjectId => {
cy.url().should('include', targetProjectId) cy.url().should('include', targetProjectId)
}) })

View File

@@ -13,7 +13,6 @@ describe('editor', () => {
const fileName = 'test.tex' const fileName = 'test.tex'
const word = createRandomLetterString() const word = createRandomLetterString()
login('user@example.com') login('user@example.com')
cy.visit('/project')
createProject('test-project') createProject('test-project')
cy.log('create new project file') cy.log('create new project file')
@@ -214,7 +213,7 @@ describe('editor', () => {
projectName = `project-${uuid()}` projectName = `project-${uuid()}`
login('user@example.com') login('user@example.com')
cy.visit(`/project`) cy.visit(`/project`)
createProject(projectName, { type: 'Example Project' }) createProject(projectName)
cy.get('button').contains('New file').click({ force: true }) cy.get('button').contains('New file').click({ force: true })
}) })

View File

@@ -32,6 +32,9 @@ describe('SAML', () => {
cy.get('button[type="submit"]').click() cy.get('button[type="submit"]').click()
}) })
cy.log('wait for login to finish')
cy.url().should('contain', '/project')
createProject('via SAML') createProject('via SAML')
}) })
}) })
@@ -62,6 +65,9 @@ describe('LDAP', () => {
cy.get('input[name="password"]').type('fry') cy.get('input[name="password"]').type('fry')
cy.get('button[type="submit"]').click() cy.get('button[type="submit"]').click()
cy.log('wait for login to finish')
cy.url().should('contain', '/project')
createProject('via LDAP') createProject('via LDAP')
}) })
}) })

View File

@@ -4,6 +4,8 @@ import { ensureUserExists, login } from './helpers/login'
import { import {
createProject, createProject,
enableLinkSharing, enableLinkSharing,
openProjectByName,
openProjectViaLinkSharingAsUser,
shareProjectByEmailAndAcceptInviteViaDash, shareProjectByEmailAndAcceptInviteViaDash,
} from './helpers/project' } from './helpers/project'
@@ -77,7 +79,6 @@ describe('git-bridge', function () {
it('should render the git-bridge UI in the editor', function () { it('should render the git-bridge UI in the editor', function () {
maybeClearAllTokens() maybeClearAllTokens()
cy.visit('/project')
createProject('git').as('projectId') createProject('git').as('projectId')
cy.get('header').findByText('Menu').click() cy.get('header').findByText('Menu').click()
cy.findByText('Sync') cy.findByText('Sync')
@@ -120,15 +121,13 @@ describe('git-bridge', function () {
let projectName: string let projectName: string
beforeEach(() => { beforeEach(() => {
cy.visit('/project')
projectName = uuid() projectName = uuid()
createProject(projectName).as('projectId') createProject(projectName).as('projectId')
}) })
it('should expose r/w interface to owner', () => { it('should expose r/w interface to owner', () => {
maybeClearAllTokens() maybeClearAllTokens()
cy.visit('/project') openProjectByName(projectName)
cy.findByText(projectName).click()
checkGitAccess('readAndWrite') checkGitAccess('readAndWrite')
}) })
@@ -139,8 +138,7 @@ describe('git-bridge', function () {
'Can edit' 'Can edit'
) )
maybeClearAllTokens() maybeClearAllTokens()
cy.visit('/project') openProjectByName(projectName)
cy.findByText(projectName).click()
checkGitAccess('readAndWrite') checkGitAccess('readAndWrite')
}) })
@@ -151,29 +149,34 @@ describe('git-bridge', function () {
'Can view' 'Can view'
) )
maybeClearAllTokens() maybeClearAllTokens()
cy.visit('/project') openProjectByName(projectName)
cy.findByText(projectName).click()
checkGitAccess('readOnly') 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', () => {
enableLinkSharing().then(({ linkSharingReadAndWrite }) => { enableLinkSharing().then(({ linkSharingReadAndWrite }) => {
login('collaborator-link-rw@example.com') const email = 'collaborator-link-rw@example.com'
login(email)
maybeClearAllTokens() maybeClearAllTokens()
cy.visit(linkSharingReadAndWrite) openProjectViaLinkSharingAsUser(
cy.findByText(projectName) // wait for lazy loading linkSharingReadAndWrite,
cy.findByText('OK, join project').click() projectName,
email
)
checkGitAccess('readAndWrite') checkGitAccess('readAndWrite')
}) })
}) })
it('should expose r/o interface to link-sharing r/o collaborator', () => { it('should expose r/o interface to link-sharing r/o collaborator', () => {
enableLinkSharing().then(({ linkSharingReadOnly }) => { enableLinkSharing().then(({ linkSharingReadOnly }) => {
login('collaborator-link-ro@example.com') const email = 'collaborator-link-ro@example.com'
login(email)
maybeClearAllTokens() maybeClearAllTokens()
cy.visit(linkSharingReadOnly) openProjectViaLinkSharingAsUser(
cy.findByText(projectName) // wait for lazy loading linkSharingReadOnly,
cy.findByText('OK, join project').click() projectName,
email
)
checkGitAccess('readOnly') checkGitAccess('readOnly')
}) })
}) })
@@ -363,7 +366,6 @@ Hello world
}) })
it('should not render the git-bridge UI in the editor', function () { it('should not render the git-bridge UI in the editor', function () {
login('user@example.com') login('user@example.com')
cy.visit('/project')
createProject('maybe git') createProject('maybe git')
cy.get('header').findByText('Menu').click() cy.get('header').findByText('Menu').click()
cy.findByText('Word Count') // wait for lazy loading cy.findByText('Word Count') // wait for lazy loading

View File

@@ -31,8 +31,6 @@ describe('GracefulShutdown', function () {
it('should display banner and flush changes out of redis', () => { it('should display banner and flush changes out of redis', () => {
bringServerProBackUp() bringServerProBackUp()
login(USER) login(USER)
cy.visit('/project')
createProject(PROJECT_NAME).then(id => { createProject(PROJECT_NAME).then(id => {
projectId = id projectId = id
}) })

View File

@@ -11,6 +11,11 @@ export function createProject(
newProjectButtonMatcher?: RegExp newProjectButtonMatcher?: RegExp
} = {} } = {}
): Cypress.Chainable<string> { ): Cypress.Chainable<string> {
cy.url().then(url => {
if (!url.endsWith('/project')) {
cy.visit('/project')
}
})
cy.findAllByRole('button').contains(newProjectButtonMatcher).click() cy.findAllByRole('button').contains(newProjectButtonMatcher).click()
// FIXME: This should only look in the left menu // FIXME: This should only look in the left menu
cy.findAllByText(type).first().click() cy.findAllByText(type).first().click()
@@ -18,19 +23,55 @@ export function createProject(
cy.get('input').type(name) cy.get('input').type(name)
cy.findByText('Create').click() cy.findByText('Create').click()
}) })
cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
waitForMainDocToLoad()
return cy return cy
.url() .url()
.should('match', /\/project\/[a-fA-F0-9]{24}/) .should('match', /\/project\/[a-fA-F0-9]{24}/)
.then(url => url.split('/').pop()) .then(url => url.split('/').pop())
} }
export function openProjectByName(projectName: string) {
cy.visit('/project')
cy.findByText(projectName).click()
waitForMainDocToLoad()
}
export function openProjectViaLinkSharingAsAnon(url: string) {
cy.visit(url)
waitForMainDocToLoad()
}
export function openProjectViaLinkSharingAsUser(
url: string,
projectName: string,
email: string
) {
cy.visit(url)
cy.findByText(projectName) // wait for lazy loading
cy.findByText(email)
cy.findByText('OK, join project').click()
waitForMainDocToLoad()
}
export function openProjectViaInviteNotification(projectName: string) {
cy.visit('/project')
cy.findByText(projectName)
.parent()
.parent()
.within(() => {
cy.findByText('Join Project').click()
})
cy.findByText('Open Project').click()
cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
}
function shareProjectByEmail( function shareProjectByEmail(
projectName: string, projectName: string,
email: string, email: string,
level: 'Can view' | 'Can edit' level: 'Can view' | 'Can edit'
) { ) {
cy.visit('/project') openProjectByName(projectName)
cy.findByText(projectName).click()
cy.findByText('Share').click() cy.findByText('Share').click()
cy.findByRole('dialog').within(() => { cy.findByRole('dialog').within(() => {
cy.get('input').type(`${email},`) cy.get('input').type(`${email},`)
@@ -50,13 +91,7 @@ export function shareProjectByEmailAndAcceptInviteViaDash(
shareProjectByEmail(projectName, email, level) shareProjectByEmail(projectName, email, level)
login(email) login(email)
cy.visit('/project') openProjectViaInviteNotification(projectName)
cy.findByText(new RegExp(projectName))
.parent()
.parent()
.within(() => {
cy.findByText('Join Project').click()
})
} }
export function shareProjectByEmailAndAcceptInviteViaEmail( export function shareProjectByEmailAndAcceptInviteViaEmail(
@@ -86,6 +121,8 @@ export function enableLinkSharing() {
let linkSharingReadOnly: string let linkSharingReadOnly: string
let linkSharingReadAndWrite: string let linkSharingReadAndWrite: string
waitForMainDocToLoad()
cy.findByText('Share').click() cy.findByText('Share').click()
cy.findByText('Turn on link sharing').click() cy.findByText('Turn on link sharing').click()
cy.findByText('Anyone with this link can view this project') cy.findByText('Anyone with this link can view this project')
@@ -105,3 +142,8 @@ export function enableLinkSharing() {
return { linkSharingReadOnly, linkSharingReadAndWrite } return { linkSharingReadOnly, linkSharingReadAndWrite }
}) })
} }
export function waitForMainDocToLoad() {
cy.log('Wait for main doc to load; it will steal the focus after loading')
cy.get('.cm-content').should('contain.text', 'Introduction')
}

View File

@@ -40,7 +40,6 @@ describe('History', function () {
const CLASS_DELETION = 'ol-cm-deletion-marker' const CLASS_DELETION = 'ol-cm-deletion-marker'
it('should support labels, comparison and download', () => { it('should support labels, comparison and download', () => {
cy.visit('/project')
createProject('labels') createProject('labels')
const recompile = throttledRecompile() const recompile = throttledRecompile()

View File

@@ -32,7 +32,6 @@ describe('Project List', () => {
before(() => { before(() => {
login(REGULAR_USER) login(REGULAR_USER)
cy.visit('/project')
createProject(projectName, { type: 'Example Project' }) createProject(projectName, { type: 'Example Project' })
}) })
@@ -90,7 +89,6 @@ describe('Project List', () => {
cy.log('create a separate project to filter') cy.log('create a separate project to filter')
const nonTaggedProjectName = `project-${uuid()}` const nonTaggedProjectName = `project-${uuid()}`
login(REGULAR_USER) login(REGULAR_USER)
cy.visit('/project')
createProject(nonTaggedProjectName) createProject(nonTaggedProjectName)
cy.visit('/project') cy.visit('/project')

View File

@@ -4,6 +4,9 @@ import { ensureUserExists, login } from './helpers/login'
import { import {
createProject, createProject,
enableLinkSharing, enableLinkSharing,
openProjectByName,
openProjectViaLinkSharingAsAnon,
openProjectViaLinkSharingAsUser,
shareProjectByEmailAndAcceptInviteViaDash, shareProjectByEmailAndAcceptInviteViaDash,
shareProjectByEmailAndAcceptInviteViaEmail, shareProjectByEmailAndAcceptInviteViaEmail,
} from './helpers/project' } from './helpers/project'
@@ -31,7 +34,6 @@ describe('Project Sharing', function () {
function setupTestProject() { function setupTestProject() {
login('user@example.com') login('user@example.com')
cy.visit('/project')
createProject(projectName) createProject(projectName)
// Add chat message // Add chat message
@@ -156,8 +158,7 @@ describe('Project Sharing', function () {
}) })
it('should grant the collaborator read access', () => { it('should grant the collaborator read access', () => {
cy.visit('/project') openProjectByName(projectName)
cy.findByText(projectName).click()
expectFullReadOnlyAccess() expectFullReadOnlyAccess()
expectProjectDashboardEntry() expectProjectDashboardEntry()
}) })
@@ -174,8 +175,7 @@ describe('Project Sharing', function () {
it('should grant the collaborator read access', () => { it('should grant the collaborator read access', () => {
login(email) login(email)
cy.visit('/project') openProjectByName(projectName)
cy.findByText(projectName).click()
expectFullReadOnlyAccess() expectFullReadOnlyAccess()
expectProjectDashboardEntry() expectProjectDashboardEntry()
}) })
@@ -192,8 +192,7 @@ describe('Project Sharing', function () {
it('should grant the collaborator write access', () => { it('should grant the collaborator write access', () => {
login(email) login(email)
cy.visit('/project') openProjectByName(projectName)
cy.findByText(projectName).click()
expectReadAndWriteAccess() expectReadAndWriteAccess()
expectEditAuthoredAs('You') expectEditAuthoredAs('You')
expectProjectDashboardEntry() expectProjectDashboardEntry()
@@ -208,9 +207,11 @@ describe('Project Sharing', function () {
it('should grant restricted read access', () => { it('should grant restricted read access', () => {
login(email) login(email)
cy.visit(linkSharingReadOnly) openProjectViaLinkSharingAsUser(
cy.findByText(projectName) // wait for lazy loading linkSharingReadOnly,
cy.findByText('OK, join project').click() projectName,
email
)
expectRestrictedReadOnlyAccess() expectRestrictedReadOnlyAccess()
expectProjectDashboardEntry() expectProjectDashboardEntry()
}) })
@@ -222,9 +223,11 @@ describe('Project Sharing', function () {
it('should grant full write access', () => { it('should grant full write access', () => {
login(email) login(email)
cy.visit(linkSharingReadAndWrite) openProjectViaLinkSharingAsUser(
cy.findByText(projectName) // wait for lazy loading linkSharingReadAndWrite,
cy.findByText('OK, join project').click() projectName,
email
)
expectReadAndWriteAccess() expectReadAndWriteAccess()
expectEditAuthoredAs('You') expectEditAuthoredAs('You')
expectProjectDashboardEntry() expectProjectDashboardEntry()
@@ -268,7 +271,7 @@ describe('Project Sharing', function () {
withDataDir: true, withDataDir: true,
}) })
it('should grant read access with read link', () => { it('should grant read access with read link', () => {
cy.visit(linkSharingReadOnly) openProjectViaLinkSharingAsAnon(linkSharingReadOnly)
expectRestrictedReadOnlyAccess() expectRestrictedReadOnlyAccess()
}) })
@@ -288,12 +291,12 @@ describe('Project Sharing', function () {
}) })
it('should grant read access with read link', () => { it('should grant read access with read link', () => {
cy.visit(linkSharingReadOnly) openProjectViaLinkSharingAsAnon(linkSharingReadOnly)
expectRestrictedReadOnlyAccess() expectRestrictedReadOnlyAccess()
}) })
it('should grant write access with write link', () => { it('should grant write access with write link', () => {
cy.visit(linkSharingReadAndWrite) openProjectViaLinkSharingAsAnon(linkSharingReadAndWrite)
expectReadAndWriteAccess() expectReadAndWriteAccess()
expectEditAuthoredAs('Anonymous') expectEditAuthoredAs('Anonymous')
}) })

View File

@@ -29,7 +29,6 @@ describe('SandboxedCompiles', function () {
}) })
it('should offer TexLive images and switch the compiler', function () { it('should offer TexLive images and switch the compiler', function () {
cy.visit('/project')
createProject('sandboxed') createProject('sandboxed')
const recompile = throttledRecompile() const recompile = throttledRecompile()
cy.log('wait for compile') cy.log('wait for compile')
@@ -66,7 +65,6 @@ describe('SandboxedCompiles', function () {
let projectName: string let projectName: string
beforeEach(function () { beforeEach(function () {
projectName = `Project ${uuid()}` projectName = `Project ${uuid()}`
cy.visit('/project')
createProject(projectName) createProject(projectName)
const recompile = throttledRecompile() const recompile = throttledRecompile()
cy.findByText('\\maketitle').parent().click() cy.findByText('\\maketitle').parent().click()
@@ -154,7 +152,6 @@ describe('SandboxedCompiles', function () {
function checkRecompilesAfterErrors() { function checkRecompilesAfterErrors() {
it('recompiles even if there are Latex errors', function () { it('recompiles even if there are Latex errors', function () {
login('user@example.com') login('user@example.com')
cy.visit('/project')
createProject('test-project') createProject('test-project')
const recompile = throttledRecompile() const recompile = throttledRecompile()
cy.findByText('\\maketitle').parent().click() cy.findByText('\\maketitle').parent().click()
@@ -170,7 +167,6 @@ describe('SandboxedCompiles', function () {
function checkXeTeX() { function checkXeTeX() {
it('should be able to use XeLaTeX', function () { it('should be able to use XeLaTeX', function () {
cy.visit('/project')
createProject('XeLaTeX') createProject('XeLaTeX')
const recompile = throttledRecompile() const recompile = throttledRecompile()
cy.log('wait for compile') cy.log('wait for compile')
@@ -204,7 +200,6 @@ describe('SandboxedCompiles', function () {
}) })
it('should not offer TexLive images and use default compiler', function () { it('should not offer TexLive images and use default compiler', function () {
cy.visit('/project')
createProject('sandboxed') createProject('sandboxed')
cy.log('wait for compile') cy.log('wait for compile')
cy.get('.pdf-viewer').should('contain.text', 'sandboxed') cy.get('.pdf-viewer').should('contain.text', 'sandboxed')

View File

@@ -1,7 +1,7 @@
import { ensureUserExists, login } from './helpers/login' import { ensureUserExists, login } from './helpers/login'
import { isExcludedBySharding, startWith } from './helpers/config' import { isExcludedBySharding, startWith } from './helpers/config'
import { dockerCompose, runScript } from './helpers/hostAdminClient' import { dockerCompose, runScript } from './helpers/hostAdminClient'
import { createProject } from './helpers/project' import { createProject, openProjectByName } from './helpers/project'
import { throttledRecompile } from './helpers/compile' import { throttledRecompile } from './helpers/compile'
import { v4 as uuid } from 'uuid' import { v4 as uuid } from 'uuid'
@@ -38,8 +38,6 @@ describe('Upgrading', function () {
before(() => { before(() => {
cy.log('Populate old instance') cy.log('Populate old instance')
login(USER) login(USER)
cy.visit('/project')
createProject(PROJECT_NAME, { createProject(PROJECT_NAME, {
newProjectButtonMatcher: startOptions.newProjectButtonMatcher, newProjectButtonMatcher: startOptions.newProjectButtonMatcher,
}) })
@@ -115,8 +113,7 @@ describe('Upgrading', function () {
}) })
it('should open the old project', () => { it('should open the old project', () => {
cy.visit('/project') openProjectByName(PROJECT_NAME)
cy.findByText(PROJECT_NAME).click()
cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/) cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
cy.findByRole('navigation').within(() => { cy.findByRole('navigation').within(() => {