From 8b64325e8997eac11410961971ff30205dbe2e2c Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Mon, 24 Feb 2025 14:26:06 +0000 Subject: [PATCH] [server-pro] tests: wait for editor to finish loading before interacting (#23841) GitOrigin-RevId: bef74f336c3a240da43cd5f9563629b96bc1d7ca --- server-ce/test/admin.spec.ts | 2 - .../test/create-and-compile-project.spec.ts | 27 ++------- server-ce/test/editor.spec.ts | 3 +- server-ce/test/external-auth.spec.ts | 6 ++ server-ce/test/git-bridge.spec.ts | 36 +++++------ server-ce/test/graceful-shutdown.spec.ts | 2 - server-ce/test/helpers/project.ts | 60 ++++++++++++++++--- server-ce/test/history.spec.ts | 1 - server-ce/test/project-list.spec.ts | 2 - server-ce/test/project-sharing.spec.ts | 35 ++++++----- server-ce/test/sandboxed-compiles.spec.ts | 5 -- server-ce/test/upgrading.spec.ts | 7 +-- 12 files changed, 104 insertions(+), 82 deletions(-) diff --git a/server-ce/test/admin.spec.ts b/server-ce/test/admin.spec.ts index 7a982bf672..be921d9c90 100644 --- a/server-ce/test/admin.spec.ts +++ b/server-ce/test/admin.spec.ts @@ -127,9 +127,7 @@ describe('admin panel', function () { testProjectName = `project-${uuid()}` deletedProjectName = `deleted-project-${uuid()}` login(user1) - cy.visit('/project') createProject(testProjectName).then(id => (testProjectId = id)) - cy.visit('/project') createProject(deletedProjectName).then(id => (projectToDeleteId = id)) }) diff --git a/server-ce/test/create-and-compile-project.spec.ts b/server-ce/test/create-and-compile-project.spec.ts index 149d505ab5..4298dd8774 100644 --- a/server-ce/test/create-and-compile-project.spec.ts +++ b/server-ce/test/create-and-compile-project.spec.ts @@ -1,5 +1,8 @@ import { ensureUserExists, login } from './helpers/login' -import { createProject } from './helpers/project' +import { + createProject, + openProjectViaInviteNotification, +} from './helpers/project' import { isExcludedBySharding, startWith } from './helpers/config' import { throttledRecompile } from './helpers/compile' @@ -11,10 +14,7 @@ describe('Project creation and compilation', function () { it('users can create project and compile it', function () { 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') - cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/) const recompile = throttledRecompile() cy.findByText('\\maketitle').parent().click() 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 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(() => { @@ -50,12 +50,9 @@ describe('Project creation and compilation', function () { 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 @@ -80,13 +77,9 @@ describe('Project creation and compilation', 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 @@ -110,15 +103,7 @@ describe('Project creation and compilation', function () { cy.findByText('Log Out').click() login('collaborator@example.com') - cy.visit('/project') - 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}/) + openProjectViaInviteNotification(targetProjectName) cy.get('@targetProjectId').then(targetProjectId => { cy.url().should('include', targetProjectId) }) diff --git a/server-ce/test/editor.spec.ts b/server-ce/test/editor.spec.ts index cf24be92aa..005a701c5c 100644 --- a/server-ce/test/editor.spec.ts +++ b/server-ce/test/editor.spec.ts @@ -13,7 +13,6 @@ describe('editor', () => { const fileName = 'test.tex' const word = createRandomLetterString() login('user@example.com') - cy.visit('/project') createProject('test-project') cy.log('create new project file') @@ -214,7 +213,7 @@ describe('editor', () => { projectName = `project-${uuid()}` login('user@example.com') cy.visit(`/project`) - createProject(projectName, { type: 'Example Project' }) + createProject(projectName) cy.get('button').contains('New file').click({ force: true }) }) diff --git a/server-ce/test/external-auth.spec.ts b/server-ce/test/external-auth.spec.ts index 6a073459f0..a285f97034 100644 --- a/server-ce/test/external-auth.spec.ts +++ b/server-ce/test/external-auth.spec.ts @@ -32,6 +32,9 @@ describe('SAML', () => { cy.get('button[type="submit"]').click() }) + cy.log('wait for login to finish') + cy.url().should('contain', '/project') + createProject('via SAML') }) }) @@ -62,6 +65,9 @@ describe('LDAP', () => { cy.get('input[name="password"]').type('fry') cy.get('button[type="submit"]').click() + cy.log('wait for login to finish') + cy.url().should('contain', '/project') + createProject('via LDAP') }) }) diff --git a/server-ce/test/git-bridge.spec.ts b/server-ce/test/git-bridge.spec.ts index fd590134f6..87a293e771 100644 --- a/server-ce/test/git-bridge.spec.ts +++ b/server-ce/test/git-bridge.spec.ts @@ -4,6 +4,8 @@ import { ensureUserExists, login } from './helpers/login' import { createProject, enableLinkSharing, + openProjectByName, + openProjectViaLinkSharingAsUser, shareProjectByEmailAndAcceptInviteViaDash, } from './helpers/project' @@ -77,7 +79,6 @@ describe('git-bridge', function () { it('should render the git-bridge UI in the editor', function () { maybeClearAllTokens() - cy.visit('/project') createProject('git').as('projectId') cy.get('header').findByText('Menu').click() cy.findByText('Sync') @@ -120,15 +121,13 @@ describe('git-bridge', function () { let projectName: string beforeEach(() => { - cy.visit('/project') projectName = uuid() createProject(projectName).as('projectId') }) it('should expose r/w interface to owner', () => { maybeClearAllTokens() - cy.visit('/project') - cy.findByText(projectName).click() + openProjectByName(projectName) checkGitAccess('readAndWrite') }) @@ -139,8 +138,7 @@ describe('git-bridge', function () { 'Can edit' ) maybeClearAllTokens() - cy.visit('/project') - cy.findByText(projectName).click() + openProjectByName(projectName) checkGitAccess('readAndWrite') }) @@ -151,29 +149,34 @@ describe('git-bridge', function () { 'Can view' ) maybeClearAllTokens() - cy.visit('/project') - cy.findByText(projectName).click() + openProjectByName(projectName) checkGitAccess('readOnly') }) it('should expose r/w interface to link-sharing r/w collaborator', () => { enableLinkSharing().then(({ linkSharingReadAndWrite }) => { - login('collaborator-link-rw@example.com') + const email = 'collaborator-link-rw@example.com' + login(email) maybeClearAllTokens() - cy.visit(linkSharingReadAndWrite) - cy.findByText(projectName) // wait for lazy loading - cy.findByText('OK, join project').click() + openProjectViaLinkSharingAsUser( + linkSharingReadAndWrite, + projectName, + email + ) checkGitAccess('readAndWrite') }) }) it('should expose r/o interface to link-sharing r/o collaborator', () => { enableLinkSharing().then(({ linkSharingReadOnly }) => { - login('collaborator-link-ro@example.com') + const email = 'collaborator-link-ro@example.com' + login(email) maybeClearAllTokens() - cy.visit(linkSharingReadOnly) - cy.findByText(projectName) // wait for lazy loading - cy.findByText('OK, join project').click() + openProjectViaLinkSharingAsUser( + linkSharingReadOnly, + projectName, + email + ) checkGitAccess('readOnly') }) }) @@ -363,7 +366,6 @@ Hello world }) it('should not render the git-bridge UI in the editor', function () { login('user@example.com') - cy.visit('/project') createProject('maybe git') cy.get('header').findByText('Menu').click() cy.findByText('Word Count') // wait for lazy loading diff --git a/server-ce/test/graceful-shutdown.spec.ts b/server-ce/test/graceful-shutdown.spec.ts index 8201b55b76..40dc144be9 100644 --- a/server-ce/test/graceful-shutdown.spec.ts +++ b/server-ce/test/graceful-shutdown.spec.ts @@ -31,8 +31,6 @@ describe('GracefulShutdown', function () { it('should display banner and flush changes out of redis', () => { bringServerProBackUp() login(USER) - - cy.visit('/project') createProject(PROJECT_NAME).then(id => { projectId = id }) diff --git a/server-ce/test/helpers/project.ts b/server-ce/test/helpers/project.ts index ba1e7b1da2..76f996426f 100644 --- a/server-ce/test/helpers/project.ts +++ b/server-ce/test/helpers/project.ts @@ -11,6 +11,11 @@ export function createProject( newProjectButtonMatcher?: RegExp } = {} ): Cypress.Chainable { + cy.url().then(url => { + if (!url.endsWith('/project')) { + cy.visit('/project') + } + }) cy.findAllByRole('button').contains(newProjectButtonMatcher).click() // FIXME: This should only look in the left menu cy.findAllByText(type).first().click() @@ -18,19 +23,55 @@ export function createProject( cy.get('input').type(name) cy.findByText('Create').click() }) + cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/) + waitForMainDocToLoad() return cy .url() .should('match', /\/project\/[a-fA-F0-9]{24}/) .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( projectName: string, email: string, level: 'Can view' | 'Can edit' ) { - cy.visit('/project') - cy.findByText(projectName).click() + openProjectByName(projectName) cy.findByText('Share').click() cy.findByRole('dialog').within(() => { cy.get('input').type(`${email},`) @@ -50,13 +91,7 @@ export function shareProjectByEmailAndAcceptInviteViaDash( shareProjectByEmail(projectName, email, level) login(email) - cy.visit('/project') - cy.findByText(new RegExp(projectName)) - .parent() - .parent() - .within(() => { - cy.findByText('Join Project').click() - }) + openProjectViaInviteNotification(projectName) } export function shareProjectByEmailAndAcceptInviteViaEmail( @@ -86,6 +121,8 @@ export function enableLinkSharing() { let linkSharingReadOnly: string let linkSharingReadAndWrite: string + waitForMainDocToLoad() + cy.findByText('Share').click() cy.findByText('Turn on link sharing').click() cy.findByText('Anyone with this link can view this project') @@ -105,3 +142,8 @@ export function enableLinkSharing() { 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') +} diff --git a/server-ce/test/history.spec.ts b/server-ce/test/history.spec.ts index cc1950f240..f0d7e74fb3 100644 --- a/server-ce/test/history.spec.ts +++ b/server-ce/test/history.spec.ts @@ -40,7 +40,6 @@ describe('History', function () { const CLASS_DELETION = 'ol-cm-deletion-marker' it('should support labels, comparison and download', () => { - cy.visit('/project') createProject('labels') const recompile = throttledRecompile() diff --git a/server-ce/test/project-list.spec.ts b/server-ce/test/project-list.spec.ts index 9ee9ac9ca0..1c98d20e2c 100644 --- a/server-ce/test/project-list.spec.ts +++ b/server-ce/test/project-list.spec.ts @@ -32,7 +32,6 @@ describe('Project List', () => { before(() => { login(REGULAR_USER) - cy.visit('/project') createProject(projectName, { type: 'Example Project' }) }) @@ -90,7 +89,6 @@ describe('Project List', () => { cy.log('create a separate project to filter') const nonTaggedProjectName = `project-${uuid()}` login(REGULAR_USER) - cy.visit('/project') createProject(nonTaggedProjectName) cy.visit('/project') diff --git a/server-ce/test/project-sharing.spec.ts b/server-ce/test/project-sharing.spec.ts index 783ab5230c..ebb3a7fe75 100644 --- a/server-ce/test/project-sharing.spec.ts +++ b/server-ce/test/project-sharing.spec.ts @@ -4,6 +4,9 @@ import { ensureUserExists, login } from './helpers/login' import { createProject, enableLinkSharing, + openProjectByName, + openProjectViaLinkSharingAsAnon, + openProjectViaLinkSharingAsUser, shareProjectByEmailAndAcceptInviteViaDash, shareProjectByEmailAndAcceptInviteViaEmail, } from './helpers/project' @@ -31,7 +34,6 @@ describe('Project Sharing', function () { function setupTestProject() { login('user@example.com') - cy.visit('/project') createProject(projectName) // Add chat message @@ -156,8 +158,7 @@ describe('Project Sharing', function () { }) it('should grant the collaborator read access', () => { - cy.visit('/project') - cy.findByText(projectName).click() + openProjectByName(projectName) expectFullReadOnlyAccess() expectProjectDashboardEntry() }) @@ -174,8 +175,7 @@ describe('Project Sharing', function () { it('should grant the collaborator read access', () => { login(email) - cy.visit('/project') - cy.findByText(projectName).click() + openProjectByName(projectName) expectFullReadOnlyAccess() expectProjectDashboardEntry() }) @@ -192,8 +192,7 @@ describe('Project Sharing', function () { it('should grant the collaborator write access', () => { login(email) - cy.visit('/project') - cy.findByText(projectName).click() + openProjectByName(projectName) expectReadAndWriteAccess() expectEditAuthoredAs('You') expectProjectDashboardEntry() @@ -208,9 +207,11 @@ describe('Project Sharing', function () { it('should grant restricted read access', () => { login(email) - cy.visit(linkSharingReadOnly) - cy.findByText(projectName) // wait for lazy loading - cy.findByText('OK, join project').click() + openProjectViaLinkSharingAsUser( + linkSharingReadOnly, + projectName, + email + ) expectRestrictedReadOnlyAccess() expectProjectDashboardEntry() }) @@ -222,9 +223,11 @@ describe('Project Sharing', function () { it('should grant full write access', () => { login(email) - cy.visit(linkSharingReadAndWrite) - cy.findByText(projectName) // wait for lazy loading - cy.findByText('OK, join project').click() + openProjectViaLinkSharingAsUser( + linkSharingReadAndWrite, + projectName, + email + ) expectReadAndWriteAccess() expectEditAuthoredAs('You') expectProjectDashboardEntry() @@ -268,7 +271,7 @@ describe('Project Sharing', function () { withDataDir: true, }) it('should grant read access with read link', () => { - cy.visit(linkSharingReadOnly) + openProjectViaLinkSharingAsAnon(linkSharingReadOnly) expectRestrictedReadOnlyAccess() }) @@ -288,12 +291,12 @@ describe('Project Sharing', function () { }) it('should grant read access with read link', () => { - cy.visit(linkSharingReadOnly) + openProjectViaLinkSharingAsAnon(linkSharingReadOnly) expectRestrictedReadOnlyAccess() }) it('should grant write access with write link', () => { - cy.visit(linkSharingReadAndWrite) + openProjectViaLinkSharingAsAnon(linkSharingReadAndWrite) expectReadAndWriteAccess() expectEditAuthoredAs('Anonymous') }) diff --git a/server-ce/test/sandboxed-compiles.spec.ts b/server-ce/test/sandboxed-compiles.spec.ts index 2350ff0652..f95caa503c 100644 --- a/server-ce/test/sandboxed-compiles.spec.ts +++ b/server-ce/test/sandboxed-compiles.spec.ts @@ -29,7 +29,6 @@ describe('SandboxedCompiles', function () { }) it('should offer TexLive images and switch the compiler', function () { - cy.visit('/project') createProject('sandboxed') const recompile = throttledRecompile() cy.log('wait for compile') @@ -66,7 +65,6 @@ describe('SandboxedCompiles', function () { let projectName: string beforeEach(function () { projectName = `Project ${uuid()}` - cy.visit('/project') createProject(projectName) const recompile = throttledRecompile() cy.findByText('\\maketitle').parent().click() @@ -154,7 +152,6 @@ describe('SandboxedCompiles', function () { function checkRecompilesAfterErrors() { it('recompiles even if there are Latex errors', function () { login('user@example.com') - cy.visit('/project') createProject('test-project') const recompile = throttledRecompile() cy.findByText('\\maketitle').parent().click() @@ -170,7 +167,6 @@ describe('SandboxedCompiles', function () { function checkXeTeX() { it('should be able to use XeLaTeX', function () { - cy.visit('/project') createProject('XeLaTeX') const recompile = throttledRecompile() cy.log('wait for compile') @@ -204,7 +200,6 @@ describe('SandboxedCompiles', function () { }) it('should not offer TexLive images and use default compiler', function () { - cy.visit('/project') createProject('sandboxed') cy.log('wait for compile') cy.get('.pdf-viewer').should('contain.text', 'sandboxed') diff --git a/server-ce/test/upgrading.spec.ts b/server-ce/test/upgrading.spec.ts index 0dfd541494..16e0320dcc 100644 --- a/server-ce/test/upgrading.spec.ts +++ b/server-ce/test/upgrading.spec.ts @@ -1,7 +1,7 @@ import { ensureUserExists, login } from './helpers/login' import { isExcludedBySharding, startWith } from './helpers/config' import { dockerCompose, runScript } from './helpers/hostAdminClient' -import { createProject } from './helpers/project' +import { createProject, openProjectByName } from './helpers/project' import { throttledRecompile } from './helpers/compile' import { v4 as uuid } from 'uuid' @@ -38,8 +38,6 @@ describe('Upgrading', function () { before(() => { cy.log('Populate old instance') login(USER) - - cy.visit('/project') createProject(PROJECT_NAME, { newProjectButtonMatcher: startOptions.newProjectButtonMatcher, }) @@ -115,8 +113,7 @@ describe('Upgrading', function () { }) it('should open the old project', () => { - cy.visit('/project') - cy.findByText(PROJECT_NAME).click() + openProjectByName(PROJECT_NAME) cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/) cy.findByRole('navigation').within(() => {