diff --git a/server-ce/test/admin.spec.ts b/server-ce/test/admin.spec.ts index c48d2cd455..7ee079010f 100644 --- a/server-ce/test/admin.spec.ts +++ b/server-ce/test/admin.spec.ts @@ -2,6 +2,7 @@ import { startWith } from './helpers/config' import { activateUser, ensureUserExists, login } from './helpers/login' import { v4 as uuid } from 'uuid' import { createProject } from './helpers/project' +import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry' describe('admin panel', function () { describe('in server pro', () => { @@ -26,7 +27,7 @@ describe('admin panel', function () { ensureUserExists({ email: user1 }) ensureUserExists({ email: user2 }) - before(() => { + beforeWithReRunOnTestRetry(() => { login(user1) cy.visit('/project') createProject(testProjectName).then(id => (testProjectId = id)) diff --git a/server-ce/test/editor.spec.ts b/server-ce/test/editor.spec.ts index 3b583c3bbf..6d9c65c47d 100644 --- a/server-ce/test/editor.spec.ts +++ b/server-ce/test/editor.spec.ts @@ -2,6 +2,7 @@ import { createProject } from './helpers/project' import { startWith } from './helpers/config' import { ensureUserExists, login } from './helpers/login' import { v4 as uuid } from 'uuid' +import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry' describe('editor', () => { startWith({ pro: true }) @@ -168,7 +169,7 @@ describe('editor', () => { describe('editor', () => { let projectId: string - before(() => { + beforeWithReRunOnTestRetry(() => { login('user@example.com') cy.visit(`/project`) createProject(`project-${uuid()}`, { type: 'Example Project' }).then( diff --git a/server-ce/test/helpers/beforeWithReRunOnTestRetry.ts b/server-ce/test/helpers/beforeWithReRunOnTestRetry.ts new file mode 100644 index 0000000000..ce552c3759 --- /dev/null +++ b/server-ce/test/helpers/beforeWithReRunOnTestRetry.ts @@ -0,0 +1,8 @@ +export function beforeWithReRunOnTestRetry(fn: () => void | Promise) { + let ranOnce = false + beforeEach(() => { + if (ranOnce && Cypress.currentRetry === 0) return + ranOnce = true + return fn() + }) +} diff --git a/server-ce/test/helpers/compile.ts b/server-ce/test/helpers/compile.ts index f88415a75e..e65b36f332 100644 --- a/server-ce/test/helpers/compile.ts +++ b/server-ce/test/helpers/compile.ts @@ -19,5 +19,7 @@ export function throttledRecompile() { cy.wait(Math.max(0, 1_000 - msSinceLastCompile)) cy.findByText('Recompile').click() queueReset() + cy.log('Wait for recompile to finish') + cy.findByText('Recompile') }) } diff --git a/server-ce/test/helpers/waitUntilScrollingFinished.ts b/server-ce/test/helpers/waitUntilScrollingFinished.ts index fd8d6ccd00..af4c5657d1 100644 --- a/server-ce/test/helpers/waitUntilScrollingFinished.ts +++ b/server-ce/test/helpers/waitUntilScrollingFinished.ts @@ -15,7 +15,7 @@ export function waitUntilScrollingFinished(selector: string, start = -1) { const current = el.scrollTop()! if (current !== prev) { setTimeout(() => waitForStable(current, 0), pollFast) - } else if (stableFor < 3) { + } else if (stableFor < 5) { setTimeout(() => waitForStable(current, stableFor + 1), pollFast) } else { resolve(current) diff --git a/server-ce/test/project-sharing.spec.ts b/server-ce/test/project-sharing.spec.ts index e19ddb1380..c545e09706 100644 --- a/server-ce/test/project-sharing.spec.ts +++ b/server-ce/test/project-sharing.spec.ts @@ -3,13 +3,14 @@ import { startWith } from './helpers/config' import { ensureUserExists, login } from './helpers/login' import { createProject } from './helpers/project' import { throttledRecompile } from './helpers/compile' +import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry' describe('Project Sharing', function () { ensureUserExists({ email: 'user@example.com' }) startWith({ withDataDir: true }) let projectName: string - before(function () { + beforeWithReRunOnTestRetry(function () { projectName = `Project ${uuid()}` setupTestProject() }) @@ -175,7 +176,7 @@ describe('Project Sharing', function () { const email = 'collaborator-ro@example.com' ensureUserExists({ email }) - before(function () { + beforeWithReRunOnTestRetry(function () { shareProjectByEmailAndAcceptInvite(email, 'Read Only') }) @@ -192,7 +193,7 @@ describe('Project Sharing', function () { const email = 'collaborator-rw@example.com' ensureUserExists({ email }) - before(function () { + beforeWithReRunOnTestRetry(function () { shareProjectByEmailAndAcceptInvite(email, 'Can Edit') }) diff --git a/server-ce/test/sandboxed-compiles.spec.ts b/server-ce/test/sandboxed-compiles.spec.ts index 097184868d..69e6292b27 100644 --- a/server-ce/test/sandboxed-compiles.spec.ts +++ b/server-ce/test/sandboxed-compiles.spec.ts @@ -4,6 +4,7 @@ import { startWith } from './helpers/config' import { throttledRecompile } 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' @@ -60,8 +61,9 @@ describe('SandboxedCompiles', function () { function checkSyncTeX() { describe('SyncTeX', () => { - const projectName = `Project ${uuid()}` - before(function () { + let projectName: string + beforeWithReRunOnTestRetry(function () { + projectName = `Project ${uuid()}` login('user@example.com') cy.visit('/project') createProject(projectName) @@ -101,6 +103,10 @@ describe('SandboxedCompiles', function () { cy.get('@start').then((start: any) => { waitUntilScrollingFinished('.pdfjs-viewer-inner', start) }) + // The sync button is swapped as the position in the PDF changes. + // Cypress appears to click on a button that references a stale position. + // Adding a cy.wait() statement is the most reliable "fix" so far :/ + cy.wait(1000) cy.get('[aria-label^="Go to PDF location in code"]').click() cy.get('.cm-activeLine').should('have.text', '\\section{Section B}') }) diff --git a/server-ce/test/upgrading.spec.ts b/server-ce/test/upgrading.spec.ts index ff25ee0d59..ae242fc085 100644 --- a/server-ce/test/upgrading.spec.ts +++ b/server-ce/test/upgrading.spec.ts @@ -7,6 +7,7 @@ import { startWith } from './helpers/config' import { dockerCompose, resetData, runScript } from './helpers/hostAdminClient' import { createProject } from './helpers/project' import { throttledRecompile } from './helpers/compile' +import { v4 as uuid } from 'uuid' const USER = 'user@example.com' const PROJECT_NAME = 'Old Project' @@ -22,23 +23,25 @@ describe('Upgrading', function () { ) { const startOptions = steps.shift()! - // Reset mongo/redis/on-disk data before(async () => { + cy.log('Reset mongo/redis/on-disk data') resetCreatedUsersCache() await resetData() - }) - // Create old instance + cy.log('Create old instance') + }) startWith({ pro: true, version: startOptions.version, withDataDir: true, vars: startOptions.vars, }) + before(function () { + cy.log('Create initial user after deleting it') + }) ensureUserExists({ email: USER }) - - // Populate old instance before(() => { + cy.log('Populate old instance') login(USER) cy.visit('/project') @@ -46,26 +49,25 @@ describe('Upgrading', function () { newProjectButtonMatcher: startOptions.newProjectButtonMatcher, }) const recompile = throttledRecompile() - // // wait for successful compile + cy.log('Wait for successful compile') cy.get('.pdf-viewer').should('contain.text', PROJECT_NAME) - // Increment the doc version three times + cy.log('Increment the doc version three times') for (let i = 0; i < 3; i++) { - // Add content + cy.log('Add content') cy.findByText('\\maketitle').parent().click() cy.findByText('\\maketitle') .parent() .type(`\n\\section{{}Old Section ${i}}`) - // Trigger full flush + cy.log('Trigger full flush') recompile() cy.get('header').findByText('Menu').click() cy.findByText('Source').click() - // close editor menu cy.get('#left-menu-modal').click() } - // Check compile and history + cy.log('Check compile and history') for (let i = 0; i < 3; i++) { cy.get('.pdf-viewer').should('contain.text', `Old Section ${i}`) } @@ -75,14 +77,15 @@ describe('Upgrading', function () { } }) - // Upgrades for (const step of steps) { before(() => { + cy.log(`Upgrade to version ${step.version}`) + // Navigate way from editor to avoid redirect to /login when the next instance comes up (which slows down tests) cy.visit('/project', {}) }) - // Graceful shutdown before(async function () { + cy.log('Graceful shutdown: flush all the things') this.timeout(20 * 1000) // Ideally we could use the container shutdown procedure, but it's too slow and unreliable for tests. // TODO(das7pad): adopt the below after speeding up the graceful shutdown procedure on all supported releases @@ -126,20 +129,21 @@ describe('Upgrading', function () { }) const recompile = throttledRecompile() - // wait for successful compile + cy.log('wait for successful compile') cy.get('.pdf-viewer').should('contain.text', PROJECT_NAME) cy.get('.pdf-viewer').should('contain.text', 'Old Section 2') - // // Add more content + cy.log('Add more content') + const newSection = `New Section ${uuid()}` cy.findByText('\\maketitle').parent().click() - cy.findByText('\\maketitle').parent().type('\n\\section{{}New Section}') + cy.findByText('\\maketitle').parent().type(`\n\\section{{}${newSection}}`) - // Check compile and history + cy.log('Check compile and history') recompile() - cy.get('.pdf-viewer').should('contain.text', 'New Section') + cy.get('.pdf-viewer').should('contain.text', newSection) cy.findByText('History').click() cy.findByText(/\\section\{Old Section 2}/) - cy.findByText(/\\section\{New Section}/) + cy.findByText(new RegExp(`\\\\section\\{${newSection}}`)) }) } @@ -170,17 +174,17 @@ describe('Upgrading', function () { cy.findByText(PROJECT_NAME).click() const recompile = throttledRecompile() - // Make a change + cy.log('Make a change') cy.findByText('\\maketitle').parent().click() cy.findByText('\\maketitle') .parent() .type('\n\\section{{}FiveOOne Section}') - // Trigger flush + cy.log('Trigger flush') recompile() cy.get('.pdf-viewer').should('contain.text', 'FiveOOne Section') - // Check for broken history, i.e. not synced with latest edit + cy.log('Check for broken history, i.e. not synced with latest edit') cy.findByText('History').click() cy.findByText(/\\section\{Old Section 2}/) // wait for lazy loading cy.findByText(/\\section\{FiveOOne Section}/).should('not.exist') @@ -212,11 +216,13 @@ describe('Upgrading', function () { cy.visit('/') cy.findByText(PROJECT_NAME).click() - // The edit that was made while the history was broken should be there now. + cy.log( + 'The edit that was made while the history was broken should be there now.' + ) cy.findByText('History').click() cy.findByText(/\\section\{FiveOOne Section}/) - // Check indicator of force resync + cy.log('Check indicator of force resync') cy.findByText('Overleaf History System') }) },