diff --git a/server-ce/test/create-and-compile-project.spec.ts b/server-ce/test/create-and-compile-project.spec.ts index ac73f8ab7a..62852d0611 100644 --- a/server-ce/test/create-and-compile-project.spec.ts +++ b/server-ce/test/create-and-compile-project.spec.ts @@ -4,7 +4,7 @@ import { openProjectViaInviteNotification, } from './helpers/project' import { isExcludedBySharding, startWith } from './helpers/config' -import { throttledRecompile } from './helpers/compile' +import { prepareWaitForNextCompileSlot } from './helpers/compile' const USER = 'user@example.com' const COLLABORATOR = 'collaborator@example.com' @@ -17,8 +17,10 @@ describe('Project creation and compilation', function () { it('users can create project and compile it', function () { login(USER) - createProject('test-project') - const recompile = throttledRecompile() + const { recompile, waitForCompile } = prepareWaitForNextCompileSlot() + waitForCompile(() => { + createProject('test-project') + }) cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => { cy.findByText('\\maketitle').parent().click() cy.findByText('\\maketitle').parent().type('\n\\section{{}Test Section}') @@ -109,7 +111,6 @@ describe('Project creation and compilation', function () { cy.findByRole('navigation', { name: 'Project files and outline' }) .findByRole('button', { name: 'New file' }) .click() - cy.findByRole('dialog').within(() => { cy.findByRole('button', { name: 'From another project' }).click() cy.findByLabelText('Select a Project').select(sourceProjectName) diff --git a/server-ce/test/editor.spec.ts b/server-ce/test/editor.spec.ts index 8e3fe4827e..8132deb383 100644 --- a/server-ce/test/editor.spec.ts +++ b/server-ce/test/editor.spec.ts @@ -22,20 +22,19 @@ describe('editor', () => { let projectName: string let projectId: string let recompile: () => void - let waitForCompileRateLimitCoolOff: (fn: () => void) => void + let waitForCompile: (fn: () => void) => void beforeWithReRunOnTestRetry(function () { projectName = `project-${uuid()}` login(USER) createProject(projectName, { type: 'Example project', open: false }).then( id => (projectId = id) ) - ;({ recompile, waitForCompileRateLimitCoolOff } = - prepareWaitForNextCompileSlot()) + ;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot()) }) beforeEach(() => { login(USER) - waitForCompileRateLimitCoolOff(() => { + waitForCompile(() => { openProjectById(projectId) }) }) diff --git a/server-ce/test/filestore-migration.spec.ts b/server-ce/test/filestore-migration.spec.ts index 43b8528d2e..28faee7b8f 100644 --- a/server-ce/test/filestore-migration.spec.ts +++ b/server-ce/test/filestore-migration.spec.ts @@ -39,7 +39,7 @@ describe('filestore migration', function () { const projectName = `project-${uuid()}` let defaultImage: string let projectId: string - let waitForCompileRateLimitCoolOff: (fn: () => void) => void + let waitForCompile: (fn: () => void) => void const previousBinaryFiles: (() => void)[] = [] function avoid502() { @@ -55,7 +55,7 @@ describe('filestore migration', function () { ) { before(function () { login(email) - waitForCompileRateLimitCoolOff(() => { + waitForCompile(() => { cy.visit(`/project/${projectId}`) }) previousBinaryFiles.push(prepareFileUploadTest(true)) @@ -112,8 +112,7 @@ describe('filestore migration', function () { .should('match', /\/project\/[a-fA-F0-9]{24}/) .then(url => (projectId = url.split('/').pop()!)) let queueReset - ;({ waitForCompileRateLimitCoolOff, queueReset } = - prepareWaitForNextCompileSlot()) + ;({ waitForCompile, queueReset } = prepareWaitForNextCompileSlot()) queueReset() // Create a new binary file @@ -265,7 +264,7 @@ describe('filestore migration', function () { createProject(projectName, { type: 'Example project', open: false }).then( id => (projectId = id) ) - ;({ waitForCompileRateLimitCoolOff } = prepareWaitForNextCompileSlot()) + ;({ waitForCompile } = prepareWaitForNextCompileSlot()) }) } addNewBinaryFileAndCheckPrevious() @@ -298,7 +297,7 @@ describe('filestore migration', function () { // filestore-migration beforeEach(() => { login(email) - waitForCompileRateLimitCoolOff(() => { + waitForCompile(() => { openProjectById(projectId) }) ensureStopOnFirstErrorIsActive() @@ -327,7 +326,7 @@ describe('filestore migration', function () { .parent() .type(`\n\\section{{}Test Section ${id}}`) - waitForCompileRateLimitCoolOff(() => { + waitForCompile(() => { cy.findByRole('button', { name: 'Toggle compile options menu' }).click() cy.findByRole('menuitem', { diff --git a/server-ce/test/git-bridge.spec.ts b/server-ce/test/git-bridge.spec.ts index 4d6a069416..9b97c5538d 100644 --- a/server-ce/test/git-bridge.spec.ts +++ b/server-ce/test/git-bridge.spec.ts @@ -12,7 +12,7 @@ import { import git from 'isomorphic-git' import http from 'isomorphic-git/http/web' import LightningFS from '@isomorphic-git/lightning-fs' -import { throttledRecompile } from './helpers/compile' +import { prepareWaitForNextCompileSlot } from './helpers/compile' const USER = 'user@example.com' @@ -52,13 +52,13 @@ describe('git-bridge', function () { function maybeClearAllTokens() { cy.visit('/user/settings') cy.findByRole('heading', { name: 'Git integration' }) - cy.get('button') - .contains(/Generate token|Add another token/) - .then(btn => { - if (btn.text() === 'Add another token') { - clearAllTokens() - } - }) + cy.findByRole('button', { + name: /Generate token|Add another token/i, + }).then(btn => { + if (btn.text() === 'Add another token') { + clearAllTokens() + } + }) } beforeEach(function () { @@ -130,9 +130,12 @@ describe('git-bridge', function () { cy.findByTestId('left-menu').within(() => { cy.findByRole('button', { name: 'Git' }).click() }) + cy.findByTestId('git-bridge-modal').within(() => { cy.get('@projectId').then(id => { - cy.get('code').contains(`git clone ${gitURL(id.toString())}`) + cy.findByLabelText('Git clone project command').contains( + `git clone ${gitURL(id.toString())}` + ) }) cy.findByRole('button', { name: 'Generate token', @@ -151,14 +154,19 @@ describe('git-bridge', function () { ensureUserExists({ email: 'collaborator-link-ro@example.com' }) let projectName: string + let recompile: () => void + let waitForCompile: (triggerCompile: () => void) => void beforeEach(() => { projectName = uuid() createProject(projectName, { open: false }).as('projectId') + ;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot()) }) it('should expose r/w interface to owner', () => { maybeClearAllTokens() - openProjectByName(projectName) + waitForCompile(() => { + openProjectByName(projectName) + }) checkGitAccess('readAndWrite') }) @@ -169,7 +177,9 @@ describe('git-bridge', function () { 'Editor' ) maybeClearAllTokens() - openProjectByName(projectName) + waitForCompile(() => { + openProjectByName(projectName) + }) checkGitAccess('readAndWrite') }) @@ -180,7 +190,9 @@ describe('git-bridge', function () { 'Viewer' ) maybeClearAllTokens() - openProjectByName(projectName) + waitForCompile(() => { + openProjectByName(projectName) + }) checkGitAccess('readOnly') }) @@ -190,212 +202,232 @@ describe('git-bridge', function () { const email = 'collaborator-link-rw@example.com' login(email) maybeClearAllTokens() - openProjectViaLinkSharingAsUser( - linkSharingReadAndWrite, - projectName, - email - ) + waitForCompile(() => { + openProjectViaLinkSharingAsUser( + linkSharingReadAndWrite, + projectName, + email + ) + }) checkGitAccess('readAndWrite') }) }) it('should expose r/o interface to link-sharing r/o collaborator', () => { - openProjectByName(projectName) + waitForCompile(() => { + openProjectByName(projectName) + }) enableLinkSharing().then(({ linkSharingReadOnly }) => { const email = 'collaborator-link-ro@example.com' login(email) maybeClearAllTokens() - openProjectViaLinkSharingAsUser( - linkSharingReadOnly, - projectName, - email - ) + waitForCompile(() => { + openProjectViaLinkSharingAsUser( + linkSharingReadOnly, + projectName, + email + ) + }) checkGitAccess('readOnly') }) }) - }) - function checkGitAccess(access: 'readOnly' | 'readAndWrite') { - const recompile = throttledRecompile() - - cy.findByRole('navigation', { - name: 'Project actions', - }) - .findByRole('button', { name: 'Menu' }) - .click() - cy.findByTestId('left-menu').within(() => { - cy.findByRole('heading', { name: 'Sync' }) - cy.findByRole('button', { name: 'Git' }).click() - }) - cy.get('@projectId').then(projectId => { - cy.findByTestId('git-bridge-modal').within(() => { - cy.findByLabelText('Git clone project command').contains( - `git clone ${gitURL(projectId.toString())}` - ) + function checkGitAccess(access: 'readOnly' | 'readAndWrite') { + cy.findByRole('navigation', { + name: 'Project actions', }) - cy.findByRole('heading', { name: 'Clone with Git' }) - cy.findByRole('button', { - name: 'Generate token', - }).click() - cy.get('code') - .contains(/olp_[a-zA-Z0-9]{16}/) - .then(async tokenEl => { - const token = tokenEl.text() + .findByRole('button', { name: 'Menu' }) + .click() + cy.findByTestId('left-menu').within(() => { + cy.findByRole('heading', { name: 'Sync' }) + cy.findByRole('button', { name: 'Git' }).click() + }) + cy.get('@projectId').then(projectId => { + cy.findByTestId('git-bridge-modal').within(() => { + cy.findByLabelText('Git clone project command').contains( + `git clone ${gitURL(projectId.toString())}` + ) + cy.findByRole('heading', { name: 'Clone with Git' }) + cy.findByRole('button', { + name: 'Generate token', + }).click() + }) + cy.findByLabelText('Git authentication token') + .contains(/olp_[a-zA-Z0-9]{16}/) + .then(async tokenEl => { + const token = tokenEl.text() - // close Git modal - cy.findAllByText('Close').last().click() - // close editor menu - cy.get('.left-menu-modal-backdrop').click() + // close Git modal + cy.get('body').type('{esc}') + cy.findByTestId('git-bridge-modal').should('not.exist') + // close the modal + cy.get('body').type('{esc}') + cy.findByTestId('left-menu').should('not.exist') + const fs = new LightningFS('fs') + const dir = `/${projectId}` - const fs = new LightningFS('fs') - const dir = `/${projectId}` + async function readFile(path: string): Promise { + return new Promise((resolve, reject) => { + fs.readFile(path, { encoding: 'utf8' }, (err, blob) => { + if (err) return reject(err) + resolve(blob as string) + }) + }) + } - async function readFile(path: string): Promise { - return new Promise((resolve, reject) => { - fs.readFile(path, { encoding: 'utf8' }, (err, blob) => { - if (err) return reject(err) - resolve(blob as string) + async function writeFile(path: string, data: string) { + return new Promise((resolve, reject) => { + fs.writeFile(path, data, undefined, err => { + if (err) return reject(err) + resolve() + }) + }) + } + + const commonOptions = { + dir, + fs, + } + const url = gitURL(projectId.toString()) + url.username = '' // basic auth is specified separately. + const httpOptions = { + http, + url: url.toString(), + headers: { + Authorization: `Basic ${Buffer.from(`git:${token}`).toString('base64')}`, + }, + } + const authorOptions = { + author: { name: 'user', email: USER }, + committer: { name: 'user', email: USER }, + } + const mainTex = `${dir}/main.tex` + + // Clone + cy.then({ timeout: 10_000 }, async () => { + await git.clone({ + ...commonOptions, + ...httpOptions, }) }) - } - - async function writeFile(path: string, data: string) { - return new Promise((resolve, reject) => { - fs.writeFile(path, data, undefined, err => { - if (err) return reject(err) - resolve() + cy.findByText(/\\documentclass/) + .parent() + .parent() + .then(async editor => { + const onDisk = await readFile(mainTex) + expect(onDisk.replaceAll('\n', '')).to.equal(editor.text()) }) - }) - } - - const commonOptions = { - dir, - fs, - } - const url = gitURL(projectId.toString()) - url.username = '' // basic auth is specified separately. - const httpOptions = { - http, - url: url.toString(), - headers: { - Authorization: `Basic ${Buffer.from(`git:${token}`).toString('base64')}`, - }, - } - const authorOptions = { - author: { name: 'user', email: USER }, - committer: { name: 'user', email: USER }, - } - const mainTex = `${dir}/main.tex` - - // Clone - cy.then({ timeout: 10_000 }, async () => { - await git.clone({ - ...commonOptions, - ...httpOptions, - }) - }) - - cy.findByText(/\\documentclass/) - .parent() - .parent() - .then(async editor => { - const onDisk = await readFile(mainTex) - expect(onDisk.replaceAll('\n', '')).to.equal(editor.text()) - }) - - const text = ` + const text = ` \\documentclass{article} \\begin{document} Hello world \\end{document} ` - // Make a change - cy.then(async () => { - await writeFile(mainTex, text) - await git.add({ - ...commonOptions, - filepath: 'main.tex', - }) - await git.commit({ - ...commonOptions, - ...authorOptions, - message: 'Swap main.tex', - }) - }) - - if (access === 'readAndWrite') { - // check history before push - cy.findAllByText('History').last().click() - cy.findByText('(via Git)').should('not.exist') - cy.findAllByText('Back to editor').last().click() - + // Make a change cy.then(async () => { - await git.push({ + await writeFile(mainTex, text) + await git.add({ ...commonOptions, - ...httpOptions, + filepath: 'main.tex', + }) + await git.commit({ + ...commonOptions, + ...authorOptions, + message: 'Swap main.tex', }) }) - } else { - cy.then(async () => { - try { + + if (access === 'readAndWrite') { + // check history before push + cy.findByRole('navigation', { + name: 'Project actions', + }) + .findByRole('button', { name: 'History' }) + .click() + cy.findByText('(via Git)').should('not.exist') + cy.findAllByText('Back to editor').last().click() + cy.then(async () => { await git.push({ ...commonOptions, ...httpOptions, }) - expect.fail('push should have failed') - } catch (err) { - expect(err).to.match(/branches were not updated/) - expect(err).to.match(/forbidden/) - } + }) + } else { + cy.then(async () => { + try { + await git.push({ + ...commonOptions, + ...httpOptions, + }) + expect.fail('push should have failed') + } catch (err) { + expect(err).to.match(/branches were not updated/) + expect(err).to.match(/forbidden/) + } + }) + + return // return early, below are write access bits + } + + // check push in editor + cy.findByText(/\\documentclass/) + .parent() + .parent() + .should('have.text', text.replaceAll('\n', '')) + + // Wait for history sync - trigger flush by toggling the UI + cy.findByRole('navigation', { + name: 'Project actions', }) + .findByRole('button', { name: 'History' }) + .click() + cy.findAllByText('Back to editor').last().click() - return // return early, below are write access bits - } - - // check push in editor - cy.findByText(/\\documentclass/) - .parent() - .parent() - .should('have.text', text.replaceAll('\n', '')) - - // Wait for history sync - trigger flush by toggling the UI - cy.findAllByText('History').last().click() - cy.findAllByText('Back to editor').last().click() - - // check push in history - cy.findAllByText('History').last().click() - cy.findByText(/Hello world/) - cy.findByText('(via Git)').should('exist') - - // Back to the editor - cy.findAllByText('Back to editor').last().click() - cy.findByText(/\\documentclass/) - .parent() - .parent() - .click() - .type('% via editor{enter}') - - // Trigger flush via compile - recompile() - - // Back into the history, check what we just added - cy.findAllByText('History').last().click() - cy.findByText(/% via editor/) - - // Pull the change - cy.then(async () => { - await git.pull({ - ...commonOptions, - ...httpOptions, - ...authorOptions, + // check push in history + cy.findByRole('navigation', { + name: 'Project actions', }) + .findByRole('button', { name: 'History' }) + .click() + cy.findByText(/Hello world/) + cy.findByText('(via Git)').should('exist') - expect(await readFile(mainTex)).to.equal(text + '% via editor\n') + // Back to the editor + cy.findAllByText('Back to editor').last().click() + cy.findByText(/\\documentclass/) + .parent() + .parent() + .click() + .type('% via editor{enter}') + + // Trigger flush via compile + recompile() + + // Back into the history, check what we just added + cy.findByRole('navigation', { + name: 'Project actions', + }) + .findByRole('button', { name: 'History' }) + .click() + cy.findByText(/% via editor/) + + // Pull the change + cy.then(async () => { + await git.pull({ + ...commonOptions, + ...httpOptions, + ...authorOptions, + }) + + expect(await readFile(mainTex)).to.equal( + text + '% via editor\n' + ) + }) }) - }) - }) - } + }) + } + }) }) function checkDisabled() { diff --git a/server-ce/test/graceful-shutdown.spec.ts b/server-ce/test/graceful-shutdown.spec.ts index 40dc144be9..014b3c978d 100644 --- a/server-ce/test/graceful-shutdown.spec.ts +++ b/server-ce/test/graceful-shutdown.spec.ts @@ -6,7 +6,7 @@ import { } from './helpers/config' import { dockerCompose, getRedisKeys } from './helpers/hostAdminClient' import { createProject } from './helpers/project' -import { throttledRecompile } from './helpers/compile' +import { prepareWaitForNextCompileSlot } from './helpers/compile' const USER = 'user@example.com' const PROJECT_NAME = 'Old Project' @@ -31,10 +31,12 @@ describe('GracefulShutdown', function () { it('should display banner and flush changes out of redis', () => { bringServerProBackUp() login(USER) - createProject(PROJECT_NAME).then(id => { - projectId = id + const { recompile, waitForCompile } = prepareWaitForNextCompileSlot() + waitForCompile(() => { + createProject(PROJECT_NAME).then(id => { + projectId = id + }) }) - const recompile = throttledRecompile() cy.log('add additional content') cy.findByText('\\maketitle').parent().click() diff --git a/server-ce/test/helpers/compile.ts b/server-ce/test/helpers/compile.ts index 742cc83b92..ada71f03ec 100644 --- a/server-ce/test/helpers/compile.ts +++ b/server-ce/test/helpers/compile.ts @@ -3,11 +3,6 @@ * The naive approach is waiting a fixed a mount of time (3s) just before clicking the button. * This helper takes into account that other UI interactions take time. We can deduce that latency from the fixed delay (3s minus other latency). This can bring down the effective waiting time to 0s. */ -export function throttledRecompile() { - const { queueReset, recompile } = prepareWaitForNextCompileSlot() - queueReset() - return recompile -} export function stopCompile(options: { delay?: number } = {}) { const { delay = 0 } = options @@ -24,25 +19,51 @@ export function prepareWaitForNextCompileSlot() { lastCompile = Date.now() }) } - function waitForCompileRateLimitCoolOff(triggerCompile: () => void) { + function waitForCompileRateLimitCoolOff() { cy.then(() => { cy.log('Wait for recompile rate-limit to cool off') const msSinceLastCompile = Date.now() - lastCompile cy.wait(Math.max(0, 1_000 - msSinceLastCompile)) queueReset() + }) + } + function waitForCompile(triggerCompile: () => void) { + waitForCompileRateLimitCoolOff() + cy.then(() => { + let compilingVisible: () => void + const waitForCompilingVisible = new Promise(resolve => { + compilingVisible = resolve + }) + cy.intercept( + { + method: 'POST', + pathname: /\/project\/[a-fA-F0-9]{24}\/compile$/, + times: 1, + }, + async req => { + await waitForCompilingVisible + req.continue() + } + ).as('recompile') triggerCompile() cy.log('Wait for compile to finish') + cy.findByRole('button', { name: 'Compiling…' }).then(() => + compilingVisible() + ) + cy.wait('@recompile') + cy.findByRole('button', { name: 'Compiling…' }).should('not.exist') cy.findByRole('button', { name: 'Recompile' }).should('be.visible') }) } function recompile() { - waitForCompileRateLimitCoolOff(() => { - cy.findByText('Recompile').click() + waitForCompile(() => { + cy.findByRole('button', { name: 'Recompile' }).click() }) } return { queueReset, waitForCompileRateLimitCoolOff, + waitForCompile, recompile, } } diff --git a/server-ce/test/history.spec.ts b/server-ce/test/history.spec.ts index 2f83c9890b..3b4b253b8a 100644 --- a/server-ce/test/history.spec.ts +++ b/server-ce/test/history.spec.ts @@ -1,5 +1,5 @@ import { createProject } from './helpers/project' -import { throttledRecompile } from './helpers/compile' +import { prepareWaitForNextCompileSlot } from './helpers/compile' import { ensureUserExists, login } from './helpers/login' import { isExcludedBySharding, startWith } from './helpers/config' @@ -62,8 +62,10 @@ describe('History', function () { const CLASS_DELETION = 'ol-cm-deletion-marker' it('should support labels, comparison and download', () => { - createProject('labels') - const recompile = throttledRecompile() + const { recompile, waitForCompile } = prepareWaitForNextCompileSlot() + waitForCompile(() => { + createProject('labels') + }) cy.log('add content, including a line that will get removed soon') cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => { diff --git a/server-ce/test/project-sharing.spec.ts b/server-ce/test/project-sharing.spec.ts index 003ee4f334..e9cb8e5e23 100644 --- a/server-ce/test/project-sharing.spec.ts +++ b/server-ce/test/project-sharing.spec.ts @@ -16,7 +16,7 @@ import { shareProjectByEmailAndAcceptInviteViaDash, shareProjectByEmailAndAcceptInviteViaEmail, } from './helpers/project' -import { throttledRecompile } from './helpers/compile' +import { prepareWaitForNextCompileSlot } from './helpers/compile' import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry' describe('Project Sharing', function () { @@ -25,8 +25,11 @@ describe('Project Sharing', function () { startWith({ withDataDir: true, pro: true }) let projectName: string + let recompile: () => void + let waitForCompile: (triggerCompile: () => void) => void beforeWithReRunOnTestRetry(function () { projectName = getSpamSafeProjectName() + ;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot()) setupTestProject() }) @@ -40,7 +43,9 @@ describe('Project Sharing', function () { function setupTestProject() { login('user@example.com') - createProject(projectName) + waitForCompile(() => { + createProject(projectName) + }) // Add chat message cy.findByRole('button', { name: 'Chat' }).click() @@ -75,7 +80,6 @@ describe('Project Sharing', function () { function expectContentWriteAccess() { const section = `Test Section ${uuid()}` cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/) - const recompile = throttledRecompile() // wait for the editor to finish loading cy.findByRole('textbox', { name: 'Source Editor editing' }).should( 'contain.text', @@ -87,8 +91,10 @@ describe('Project Sharing', function () { 'contenteditable', 'true' ) - cy.findByText('\\maketitle').parent().click() - cy.findByText('\\maketitle').parent().type(`\n\\section{{}${section}}`) + cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => { + cy.findByText('\\maketitle').parent().click() + cy.findByText('\\maketitle').parent().type(`\n\\section{{}${section}}`) + }) // should have written cy.findByRole('textbox', { name: 'Source Editor editing' }).should( 'contain.text', @@ -128,13 +134,28 @@ describe('Project Sharing', function () { } function expectHistoryAccess() { - cy.findByText('History').click() - cy.findByText('Labels') + cy.findByRole('button', { name: 'History' }).click() + // The input is not clickable due to being visually hidden, click its label instead + cy.findByRole('complementary', { + name: 'Project history and labels', + }).within(() => { + cy.findByRole('group', { + name: 'Show all of the project history or only labelled versions.', + }).within(() => { + cy.findByText('All history').click() + }) + cy.findByRole('radio', { name: 'Labels' }).should('not.be.checked') + cy.findByRole('radio', { name: 'All history' }).should('be.checked') + }) cy.findByText(/\\begin\{document}/) - cy.findAllByTestId('history-version-metadata-users') - .last() - .should('have.text', 'user') - cy.findByText('Back to editor').click() + cy.findByRole('complementary', { + name: 'Project history and labels', + }).within(() => { + cy.findAllByTestId('history-version-metadata-users') + .last() + .should('have.text', 'user') + }) + cy.findByRole('button', { name: 'Back to editor' }).click() } function expectNoChatAccess() { diff --git a/server-ce/test/sandboxed-compiles.spec.ts b/server-ce/test/sandboxed-compiles.spec.ts index 8599809c7c..7e7ebac7fb 100644 --- a/server-ce/test/sandboxed-compiles.spec.ts +++ b/server-ce/test/sandboxed-compiles.spec.ts @@ -1,10 +1,9 @@ import { ensureUserExists, login } from './helpers/login' import { createProject } from './helpers/project' import { isExcludedBySharding, startWith } from './helpers/config' -import { throttledRecompile, stopCompile } from './helpers/compile' +import { prepareWaitForNextCompileSlot, stopCompile } from './helpers/compile' import { v4 as uuid } from 'uuid' import { waitUntilScrollingFinished } from './helpers/waitUntilScrollingFinished' -import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry' const LABEL_TEX_LIVE_VERSION = 'TeX Live version' @@ -27,8 +26,10 @@ describe('SandboxedCompiles', function () { }) it('should offer TexLive images and switch the compiler', function () { - createProject('sandboxed') - const recompile = throttledRecompile() + const { recompile, waitForCompile } = prepareWaitForNextCompileSlot() + waitForCompile(() => { + createProject('sandboxed') + }) cy.log('wait for compile') cy.findByRole('region', { name: 'PDF preview and logs' }).should( 'contain.text', @@ -53,7 +54,6 @@ describe('SandboxedCompiles', function () { }) cy.get('body').type('{esc}') cy.findByRole('dialog').should('not.exist') - cy.log('Trigger compile with other TeX Live version') recompile() @@ -71,13 +71,18 @@ describe('SandboxedCompiles', function () { function checkStopCompile() { it('users can stop a running compile', function () { login('user@example.com') - createProject('test-project') + const { recompile, waitForCompile, waitForCompileRateLimitCoolOff } = + prepareWaitForNextCompileSlot() + waitForCompile(() => { + createProject('test-project') + }) // create an infinite loop in the main document // this will cause the compile to run indefinitely cy.findByText('\\maketitle').parent().click() cy.findByText('\\maketitle') .parent() .type('\n\\def\\x{{}Hello!\\par\\x}\\x') + waitForCompileRateLimitCoolOff() cy.log('Start compile') // We need to start the compile manually because we do not want to wait for it to finish cy.findByText('Recompile').click() @@ -90,7 +95,7 @@ describe('SandboxedCompiles', function () { // disabling the infinite loop and recompiling cy.findByText('\\def').parent().click() cy.findByText('\\def').parent().type('{home}disabled loop% ') - cy.findByText('Recompile').click() + recompile() cy.get('.pdf-viewer').should('contain.text', 'disabled loop') cy.get('.logs-pane').should( 'not.contain.text', @@ -104,8 +109,10 @@ describe('SandboxedCompiles', function () { let projectName: string beforeEach(function () { projectName = `Project ${uuid()}` - createProject(projectName) - const recompile = throttledRecompile() + const { recompile, waitForCompile } = prepareWaitForNextCompileSlot() + waitForCompile(() => { + createProject(projectName) + }) cy.findByRole('textbox', { name: 'Source Editor editing' }).within( () => { cy.findByText('\\maketitle').parent().click() @@ -205,8 +212,10 @@ describe('SandboxedCompiles', function () { function checkRecompilesAfterErrors() { it('recompiles even if there are Latex errors', function () { login('user@example.com') - createProject('test-project') - const recompile = throttledRecompile() + const { recompile, waitForCompile } = prepareWaitForNextCompileSlot() + waitForCompile(() => { + createProject('test-project') + }) cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => { cy.findByText('\\maketitle').parent().click() cy.findByText('\\maketitle') @@ -224,8 +233,10 @@ describe('SandboxedCompiles', function () { function checkXeTeX() { it('should be able to use XeLaTeX', function () { - createProject('XeLaTeX') - const recompile = throttledRecompile() + const { recompile, waitForCompile } = prepareWaitForNextCompileSlot() + waitForCompile(() => { + createProject('XeLaTeX') + }) cy.log('wait for compile') cy.findByRole('region', { name: 'PDF preview and logs' }).should( 'contain.text', diff --git a/server-ce/test/upgrading.spec.ts b/server-ce/test/upgrading.spec.ts index ace62c1386..fe9f4c3a46 100644 --- a/server-ce/test/upgrading.spec.ts +++ b/server-ce/test/upgrading.spec.ts @@ -2,7 +2,7 @@ import { ensureUserExists, login } from './helpers/login' import { isExcludedBySharding, startWith } from './helpers/config' import { dockerCompose, runScript } from './helpers/hostAdminClient' import { createProject, openProjectByName } from './helpers/project' -import { throttledRecompile } from './helpers/compile' +import { prepareWaitForNextCompileSlot } from './helpers/compile' import { v4 as uuid } from 'uuid' const USER = 'user@example.com' @@ -11,6 +11,9 @@ const PROJECT_NAME = 'Old Project' describe('Upgrading', function () { if (isExcludedBySharding('PRO_CUSTOM_3')) return + let recompile: () => void + let waitForCompile: (triggerCompile: () => void) => void + function testUpgrade( steps: { version: string @@ -38,10 +41,13 @@ describe('Upgrading', function () { before(() => { cy.log('Populate old instance') login(USER) - createProject(PROJECT_NAME, { - newProjectButtonMatcher: startOptions.newProjectButtonMatcher, + ;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot()) + waitForCompile(() => { + createProject(PROJECT_NAME, { + newProjectButtonMatcher: startOptions.newProjectButtonMatcher, + }) }) - const recompile = throttledRecompile() + cy.log('Wait for successful compile') cy.get('.pdf-viewer').should('contain.text', PROJECT_NAME) @@ -117,7 +123,9 @@ describe('Upgrading', function () { }) it('should open the old project', () => { - openProjectByName(PROJECT_NAME) + waitForCompile(() => { + openProjectByName(PROJECT_NAME) + }) cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/) cy.findByRole('navigation', { @@ -125,7 +133,6 @@ describe('Upgrading', function () { }).within(() => { cy.findByText(PROJECT_NAME) }) - const recompile = throttledRecompile() cy.log('wait for successful compile') cy.get('.pdf-viewer').should('contain.text', PROJECT_NAME) @@ -189,9 +196,9 @@ describe('Upgrading', function () { hook() { before(function () { login(USER) - cy.visit('/') - cy.findByText(PROJECT_NAME).click() - const recompile = throttledRecompile() + waitForCompile(() => { + openProjectByName(PROJECT_NAME) + }) cy.log('Make a change') cy.findByText('\\maketitle').parent().click()