Improve Server Pro tests to use semantic selectors (#29790)

* Replace placeholders with labels

* Add 'Close dialog' label to modal close button to distinguish from footer Close button

* Add and translate heading on the not found page

* Update textarea to have id matching label's for attribute
Simplify test for template description textarea

* Label PDF zoom level dropdown button

* Improve test selectors to use semantic roles and accessible names

GitOrigin-RevId: d215ddca30ddf844cfffbcf0e528a601b134d772
This commit is contained in:
Rebeka Dekany
2025-11-24 14:44:03 +01:00
committed by Copybot
parent 7dce5f0e25
commit b4bfff1b67
24 changed files with 294 additions and 223 deletions

View File

@@ -25,13 +25,9 @@ describe('Accounts', function () {
cy.get('@url').then(url => {
cy.visit(`${url}`)
cy.url().should('contain', '/user/activate')
cy.findByText('Please set a password')
cy.get('input[autocomplete="username"]').should(
'have.attr',
'value',
email
)
cy.get('input[name="password"]')
cy.findByRole('heading', { name: 'Please set a password' })
cy.findByLabelText('Email').should('be.visible')
cy.findByLabelText('Password').should('be.visible')
cy.findByRole('button', { name: 'Activate' })
})
})

View File

@@ -16,12 +16,10 @@ describe('admin panel', function () {
const user = `${uuid()}@example.com`
cy.findByLabelText('Emails to register new users').type(user + '{enter}')
cy.get('td')
.contains(/\/user\/activate/)
.then($td => {
const url = $td.text().trim()
activateUser(url)
})
cy.findByRole('cell', { name: /\/user\/activate/ }).then($td => {
const url = $td.text().trim()
activateUser(url)
})
})
it('via GUI and email', function () {
@@ -29,17 +27,15 @@ describe('admin panel', function () {
cy.findByLabelText('Emails to register new users').type(user + '{enter}')
let url: string
cy.get('td')
.contains(/\/user\/activate/)
.then($td => {
url = $td.text().trim()
})
cy.findByRole('cell', { name: /\/user\/activate/ }).then($td => {
url = $td.text().trim()
})
cy.then(() => {
openEmail(
'Activate your E2E test Account',
(frame, { url }) => {
frame.contains('Set password').then(el => {
frame.contains('a', 'Set password').then(el => {
expect(el.attr('href')!).to.equal(url)
})
},
@@ -69,7 +65,7 @@ describe('admin panel', function () {
openEmail(
'Activate your E2E test Account',
(frame, { url }) => {
frame.contains('Set password').then(el => {
frame.contains('a', 'Set password').then(el => {
expect(el.attr('href')!).to.equal(url)
})
},
@@ -145,10 +141,10 @@ describe('admin panel', function () {
const menuitems = ['Manage Site', 'Manage Users', 'Project URL Lookup']
menuitems.forEach(name => {
cy.findByRole('menuitem', { name: 'Admin' }).click()
cy.get('ul[role="menu"]')
cy.findByRole('menu')
.findAllByRole('menuitem')
.should('have.length', menuitems.length)
cy.get('ul[role="menu"]').findByRole('menuitem', { name }).click()
cy.findByRole('menu').findByRole('menuitem', { name }).click()
})
})
})
@@ -206,7 +202,7 @@ describe('admin panel', function () {
})
it('license usage tab', function () {
cy.get('a').contains('License Usage').click()
cy.findByRole('tab', { name: 'License Usage' }).click()
cy.findByText(
'An active user is one who has opened a project in this Server Pro instance in the last 12 months.'
)
@@ -214,7 +210,7 @@ describe('admin panel', function () {
describe('create users', function () {
beforeEach(function () {
cy.get('a').contains('New User').click()
cy.findByRole('link', { name: 'New User' }).click()
})
registrationTests()
})
@@ -263,8 +259,8 @@ describe('admin panel', function () {
it('displays required sections', function () {
// not exhaustive list, checks the tab content is rendered
cy.findByText('Profile')
cy.findByText('Editor Settings')
cy.findByRole('heading', { name: 'Profile' })
cy.findByRole('heading', { name: 'Editor Settings' })
})
it('should not display SaaS-only sections', function () {
@@ -290,9 +286,12 @@ describe('admin panel', function () {
cy.findByRole('tablist').within(() => {
cy.findByRole('tab', { name: 'Projects' }).click()
})
cy.get(`a[href="/admin/project/${testProjectId}"]`)
.should('contain.text', 'Project information')
.click()
cy.findByRole('link', { name: testProjectName })
.parent()
.parent()
.within(() => {
cy.findByRole('link', { name: 'Project information' }).click()
})
cy.findByRole('button', { name: 'Transfer Ownership' }).click()
cy.findByRole('dialog').within(() => {
@@ -319,10 +318,12 @@ describe('admin panel', function () {
cy.findByRole('tablist').within(() => {
cy.findByRole('tab', { name: 'Projects' }).click()
})
cy.get(`a[href="/admin/project/${testProjectId}"]`).should(
'contain.text',
'Project information'
)
cy.findByRole('link', { name: testProjectName })
.parent()
.parent()
.within(() => {
cy.findByRole('link', { name: 'Project information' }).click()
})
})
})

View File

@@ -123,7 +123,9 @@ describe('Project creation and compilation', function () {
cy.findByRole('button', { name: 'Share' }).click()
})
cy.findByRole('dialog').within(() => {
cy.findByTestId('collaborator-email-input').type(COLLABORATOR + ',')
cy.findByRole('combobox', { name: 'Add email address' }).type(
COLLABORATOR + ','
)
cy.findByRole('button', { name: 'Invite' }).click()
cy.findByText('Invite not yet accepted.')
})

View File

@@ -8,7 +8,9 @@ describe('Customization', function () {
it('should display the default right footer', function () {
cy.visit('/')
cy.get('footer').findByRole('link', { name: 'Fork on GitHub!' })
cy.findByRole('contentinfo').findByRole('link', {
name: 'Fork on GitHub!',
})
})
})
@@ -25,16 +27,18 @@ describe('Customization', function () {
it('should display custom name', function () {
cy.visit('/')
cy.get('nav').findByText('CUSTOM APP NAME')
cy.findByRole('navigation', { name: 'Primary' }).findByText(
'CUSTOM APP NAME'
)
})
it('should display custom left footer', function () {
cy.visit('/')
cy.get('footer').findByText('CUSTOM LEFT FOOTER')
cy.findByRole('contentinfo').findByText('CUSTOM LEFT FOOTER')
})
it('should display custom right footer', function () {
cy.visit('/')
cy.get('footer').findByText('CUSTOM RIGHT FOOTER')
cy.findByRole('contentinfo').findByText('CUSTOM RIGHT FOOTER')
})
})
})

View File

@@ -24,7 +24,7 @@ describe('SAML', function () {
it('login', function () {
cy.visit('/')
cy.findByText('Log in with SAML Test Server').click()
cy.findByRole('link', { name: 'Log in with SAML Test Server' }).click()
cy.origin(samlURL, () => {
cy.get('input[name="username"]').type('sally')
@@ -59,11 +59,11 @@ describe('LDAP', function () {
it('login', function () {
cy.visit('/')
cy.findByText('Log in LDAP')
cy.findByRole('heading', { name: 'Log in LDAP' })
cy.get('input[name="login"]').type('fry')
cy.get('input[name="password"]').type('fry')
cy.get('button[type="submit"]').click()
cy.findByLabelText('Username').type('fry')
cy.findByLabelText('Password').type('fry')
cy.findByRole('button', { name: 'Login' }).click()
cy.log('wait for login to finish')
cy.url().should('contain', '/project')

View File

@@ -72,11 +72,13 @@ describe('git-bridge', function () {
cy.findByRole('button', {
name: 'Git integration Generate token',
}).click()
cy.findByLabelText('Git authentication token')
.contains(/olp_[a-zA-Z0-9]{16}/)
.then(el => el.text())
.as('newToken')
cy.findAllByText('Close').last().click()
cy.findByRole('dialog').within(() => {
cy.findByLabelText('Git authentication token')
.contains(/olp_[a-zA-Z0-9]{16}/)
.then(el => el.text())
.as('newToken')
cy.findByRole('button', { name: 'Close dialog' }).click()
})
cy.get('@newToken').then(token => {
// There can be more than one token with the same prefix when retrying
cy.findAllByText(
@@ -258,7 +260,7 @@ describe('git-bridge', function () {
const token = tokenEl.text()
// close Git modal
cy.get('body').type('{esc}')
cy.findByRole('button', { name: 'Close dialog' }).click()
cy.findByTestId('git-bridge-modal').should('not.exist')
// close the modal
cy.get('body').type('{esc}')
@@ -345,8 +347,16 @@ Hello world
})
.findByRole('button', { name: 'History' })
.click()
cy.findByText('(via Git)').should('not.exist')
cy.findAllByText('Back to editor').last().click()
cy.findByRole('complementary', {
name: 'Project history and labels',
}).within(() => {
cy.findByText('(via Git)').should('not.exist')
})
cy.findByRole('navigation', {
name: 'Project actions',
})
.findByRole('button', { name: 'Back to editor' })
.click()
cy.then(async () => {
await git.push({
...commonOptions,
@@ -379,10 +389,10 @@ Hello world
// Wait for history sync - trigger flush by toggling the UI
cy.findByRole('navigation', {
name: 'Project actions',
}).within(() => {
cy.findByRole('button', { name: 'History' }).click()
cy.findByRole('button', { name: 'Back to editor' }).click()
})
.findByRole('button', { name: 'History' })
.click()
cy.findAllByText('Back to editor').last().click()
// check push in history
cy.findByRole('navigation', {
@@ -391,10 +401,18 @@ Hello world
.findByRole('button', { name: 'History' })
.click()
cy.findByText(/Hello world/)
cy.findByText('(via Git)').should('exist')
cy.findByRole('complementary', {
name: 'Project history and labels',
}).within(() => {
cy.findByText('(via Git)').should('exist')
})
// Back to the editor
cy.findAllByText('Back to editor').last().click()
cy.findByRole('navigation', {
name: 'Project actions',
})
.findByRole('button', { name: 'Back to editor' })
.click()
cy.findByText(/\\documentclass/)
.parent()
.parent()

View File

@@ -39,14 +39,19 @@ describe('GracefulShutdown', function () {
})
cy.log('add additional content')
cy.findByText('\\maketitle').parent().click()
cy.findByText('\\maketitle').parent().type(`\n\\section{{}New Section}`)
cy.findByRole('region', { name: 'Editor' }).within(() => {
cy.findByText('\\maketitle').parent().click()
cy.findByText('\\maketitle').parent().type(`\n\\section{{}New Section}`)
})
recompile()
cy.log(
'check flush from frontend to backend: should include new section in PDF'
)
cy.get('.pdf-viewer').should('contain.text', 'New Section')
cy.findByRole('region', { name: 'PDF preview and logs' }).should(
'contain.text',
'New Section'
)
cy.log('should have unflushed content in redis before shutdown')
cy.then(async () => {
@@ -62,8 +67,9 @@ describe('GracefulShutdown', function () {
})
cy.log('wait for banner')
cy.findByText(/performing maintenance/)
cy.findByRole('dialog').findByText(/performing maintenance/)
cy.log('wait for page reload')
cy.findByRole('heading', { name: 'Maintenance' })
cy.findByText(/is currently down for maintenance/)
cy.log('wait for shutdown to complete')
@@ -85,13 +91,19 @@ describe('GracefulShutdown', function () {
})
cy.log('check loading doc from mongo')
cy.findByText('New Section')
cy.findByRole('region', { name: 'Editor' }).findByText('New Section')
cy.log('check PDF')
cy.get('.pdf-viewer').should('contain.text', 'New Section')
cy.findByRole('region', { name: 'PDF preview and logs' }).should(
'contain.text',
'New Section'
)
cy.log('check history')
cy.findByText('History').click()
cy.findByRole('navigation', {
name: 'Project actions',
})
.findByRole('button', { name: 'History' })
.click()
cy.findByText(/\\section\{New Section}/)
})
})

View File

@@ -26,22 +26,19 @@ describe('LearnWiki', function () {
it('should add a documentation entry to the nav bar', function () {
login(REGULAR_USER)
cy.visit('/project')
cy.findByRole('menuitem', { name: 'Documentation' }).should(
'have.attr',
'href',
'/learn'
)
cy.findByRole('navigation', { name: 'Primary' }).findByRole('menuitem', {
name: 'Documentation',
})
})
it('should display a tutorial link in the welcome page', function () {
login(WITHOUT_PROJECTS_USER)
cy.visit('/project')
cy.findByRole('link', { name: LABEL_LEARN_LATEX })
.should('have.attr', 'href', '/learn/latex/Learn_LaTeX_in_30_minutes')
.and('have.attr', 'target', '_blank')
.within(() => {
cy.get('img').should('have.attr', 'src').and('not.be.empty')
})
cy.findByRole('link', { name: LABEL_LEARN_LATEX }).should(
'have.attr',
'href',
'/learn/latex/Learn_LaTeX_in_30_minutes'
)
})
it('should render wiki page', function () {
@@ -102,7 +99,11 @@ describe('LearnWiki', function () {
it('should not add a documentation entry to the nav bar', function () {
login(REGULAR_USER)
cy.visit('/project')
cy.findByText('Documentation').should('not.exist')
cy.findByRole('navigation', { name: 'Primary' })
.findByRole('menuitem', {
name: 'Documentation',
})
.should('not.exist')
})
it('should not render wiki page', function () {
@@ -110,7 +111,7 @@ describe('LearnWiki', function () {
cy.visit(COPYING_A_PROJECT_URL, {
failOnStatusCode: false,
})
cy.findByText('Not found')
cy.findByRole('heading', { name: 'Not found' })
})
it('should not display a tutorial link in the welcome page', function () {

View File

@@ -50,10 +50,12 @@ describe('Project Sharing', function () {
// Add chat message
cy.findByRole('button', { name: 'Chat' }).click()
// wait for lazy loading of the chat pane
cy.findByText('Send your first message to your collaborators')
cy.get(
'textarea[placeholder="Send a message to your collaborators…"]'
).type('New Chat Message{enter}')
cy.findByRole('complementary', { name: 'Chat' }).findByText(
'Send your first message to your collaborators'
)
cy.findByLabelText('Send a message to your collaborators…').type(
'New Chat Message{enter}'
)
// Get link sharing links
enableLinkSharing().then(
@@ -130,7 +132,9 @@ describe('Project Sharing', function () {
function expectChatAccess() {
cy.findByRole('button', { name: 'Chat' }).click()
cy.findByText('New Chat Message')
cy.findByRole('complementary', { name: 'Chat' }).findByText(
'New Chat Message'
)
}
function expectHistoryAccess() {
@@ -451,8 +455,14 @@ describe('Project Sharing', function () {
it('should not display link sharing in the sharing modal', function () {
login('user@example.com')
openProjectByName(projectName)
cy.findByText('Share').click()
cy.findByText('Turn on link sharing').should('not.exist')
cy.findByRole('navigation', {
name: 'Project actions',
})
.findByRole('button', { name: 'Share' })
.click()
cy.findByRole('button', { name: 'Turn on link sharing' }).should(
'not.exist'
)
})
it('should block new access to read-only link shared projects', function () {

View File

@@ -38,7 +38,9 @@ describe('SandboxedCompiles', function () {
cy.log('Check which compiler version was used, expect 2023')
cy.findByRole('button', { name: 'View logs' }).click()
cy.findByText(/This is pdfTeX, Version .+ \(TeX Live 2023\) /)
cy.findByLabelText('Raw logs from the LaTeX compiler').findByText(
/This is pdfTeX, Version .+ \(TeX Live 2023\) /
)
cy.log('Switch TeXLive version from 2023 to 2022')
cy.findByRole('navigation', {
@@ -59,7 +61,9 @@ describe('SandboxedCompiles', function () {
cy.log('Check which compiler version was used, expect 2022')
cy.findByRole('button', { name: 'View logs' }).click()
cy.findByText(/This is pdfTeX, Version .+ \(TeX Live 2022\) /)
cy.findByLabelText('Raw logs from the LaTeX compiler').findByText(
/This is pdfTeX, Version .+ \(TeX Live 2022\) /
)
})
checkSyncTeX()
@@ -85,10 +89,10 @@ describe('SandboxedCompiles', function () {
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()
cy.findByRole('button', { name: 'Recompile' }).click()
// Now stop the compile and kill the latex process
stopCompile({ delay: 1000 })
cy.get('.logs-pane')
cy.findByRole('region', { name: 'PDF preview and logs' })
.invoke('text')
.should('match', /PDF Rendering Error|Compilation cancelled/)
// Check that the previous compile is not running in the background by
@@ -96,11 +100,9 @@ describe('SandboxedCompiles', function () {
cy.findByText('\\def').parent().click()
cy.findByText('\\def').parent().type('{home}disabled loop% ')
recompile()
cy.get('.pdf-viewer').should('contain.text', 'disabled loop')
cy.get('.logs-pane').should(
'not.contain.text',
'A previous compile is still running'
)
cy.findByRole('region', { name: 'PDF preview and logs' })
.should('contain.text', 'disabled loop')
.should('not.contain.text', 'A previous compile is still running')
})
}
@@ -151,7 +153,10 @@ describe('SandboxedCompiles', function () {
.findByText('Section B')
.scrollIntoView()
cy.get('@start').then((start: any) => {
waitUntilScrollingFinished('.pdfjs-viewer-inner', start)
waitUntilScrollingFinished(
'[data-testid="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.
@@ -166,11 +171,13 @@ describe('SandboxedCompiles', function () {
it('should sync to pdf', function () {
cy.log('zoom in')
cy.findByRole('button', { name: /^\d+%$/ }).click() // TODO: ARIA label
cy.findByRole('button', { name: 'PDF zoom level' }).click()
cy.findByRole('menuitem', { name: '400%' }).click()
cy.log('scroll to top')
cy.findByTestId('pdfjs-viewer-inner').scrollTo('top')
waitUntilScrollingFinished('.pdfjs-viewer-inner', -1).as('start')
waitUntilScrollingFinished('[data-testid="pdfjs-viewer-inner"]', -1).as(
'start'
)
cy.log('navigate to title')
cy.findByRole('textbox', { name: 'Source Editor editing' }).within(
@@ -180,7 +187,10 @@ describe('SandboxedCompiles', function () {
)
cy.findByRole('button', { name: 'Go to code location in PDF' }).click()
cy.get('@start').then((start: any) => {
waitUntilScrollingFinished('.pdfjs-viewer-inner', start)
waitUntilScrollingFinished(
'[data-testid="pdfjs-viewer-inner"]',
start
)
.as('title')
.should('be.greaterThan', start)
})
@@ -191,7 +201,10 @@ describe('SandboxedCompiles', function () {
)
cy.findByRole('button', { name: 'Go to code location in PDF' }).click()
cy.get('@title').then((title: any) => {
waitUntilScrollingFinished('.pdfjs-viewer-inner', title)
waitUntilScrollingFinished(
'[data-testid="pdfjs-viewer-inner"]',
title
)
.as('sectionA')
.should('be.greaterThan', title)
})
@@ -202,7 +215,10 @@ describe('SandboxedCompiles', function () {
)
cy.findByRole('button', { name: 'Go to code location in PDF' }).click()
cy.get('@sectionA').then((title: any) => {
waitUntilScrollingFinished('.pdfjs-viewer-inner', title)
waitUntilScrollingFinished(
'[data-testid="pdfjs-viewer-inner"]',
title
)
.as('sectionB')
.should('be.greaterThan', title)
})
@@ -225,10 +241,9 @@ describe('SandboxedCompiles', function () {
})
recompile()
recompile()
cy.findByRole('region', { name: 'PDF preview and logs' }).within(() => {
cy.findByText('Test Section').should('contain.text', 'Test Section')
})
cy.findByTestId('logs-pane').should('not.contain.text', 'No PDF')
cy.findByRole('region', { name: 'PDF preview and logs' })
.findByText('Test Section')
.should('not.contain.text', 'No PDF')
})
}
@@ -239,13 +254,14 @@ describe('SandboxedCompiles', function () {
createProject('XeLaTeX')
})
cy.log('wait for compile')
cy.findByRole('region', { name: 'PDF preview and logs' }).should(
'contain.text',
cy.findByRole('region', { name: 'PDF preview and logs' }).findByText(
'XeLaTeX'
)
cy.log('Check which compiler was used, expect pdfLaTeX')
cy.findByRole('button', { name: 'View logs' }).click()
cy.findByText(/This is pdfTeX/)
cy.findByLabelText('Raw logs from the LaTeX compiler').findByText(
/This is pdfTeX/
)
cy.log('Switch compiler to from pdfLaTeX to XeLaTeX')
cy.findByRole('navigation', {
@@ -265,7 +281,9 @@ describe('SandboxedCompiles', function () {
cy.log('Check which compiler was used, expect XeLaTeX')
cy.findByRole('button', { name: 'View logs' }).click()
cy.findByText(/This is XeTeX/)
cy.findByLabelText('Raw logs from the LaTeX compiler').findByText(
/This is XeTeX/
)
})
}
@@ -277,11 +295,15 @@ describe('SandboxedCompiles', function () {
it('should not offer TexLive images and use default compiler', function () {
createProject('sandboxed')
cy.log('wait for compile')
cy.get('.pdf-viewer').should('contain.text', 'sandboxed')
cy.findByRole('region', { name: 'PDF preview and logs' }).findByText(
'sandboxed'
)
cy.log('Check which compiler version was used, expect 2025')
cy.get('[aria-label="View logs"]').click()
cy.findByText(/This is pdfTeX, Version .+ \(TeX Live 2025\) /)
cy.findByRole('button', { name: 'View logs' }).click()
cy.findByLabelText('Raw logs from the LaTeX compiler').findByText(
/This is pdfTeX, Version .+ \(TeX Live 2025\) /
)
cy.log('Check that there is no TeX Live version toggle')
cy.findByRole('navigation', {
@@ -289,8 +311,10 @@ describe('SandboxedCompiles', function () {
})
.findByRole('button', { name: 'Menu' })
.click()
cy.findByText('Word Count') // wait for lazy loading
cy.findByText(LABEL_TEX_LIVE_VERSION).should('not.exist')
cy.findByTestId('left-menu').within(() => {
cy.findByRole('button', { name: 'Word Count' }) // wait for lazy loading
cy.findByText(LABEL_TEX_LIVE_VERSION).should('not.exist')
})
})
}

View File

@@ -43,9 +43,7 @@ describe('Templates', function () {
it('should show templates link on welcome page', function () {
login(WITHOUT_PROJECTS_USER)
cy.visit('/')
cy.findByRole('link', { name: LABEL_BROWSE_TEMPLATES })
.should('have.attr', 'href', '/templates')
.click()
cy.findByRole('link', { name: LABEL_BROWSE_TEMPLATES }).click()
cy.url().should('match', /\/templates$/)
})
@@ -62,32 +60,31 @@ describe('Templates', function () {
})
.findByRole('button', { name: 'Menu' })
.click()
cy.findByText('Manage Template').click()
cy.findByRole('button', { name: 'Manage Template' }).click()
cy.findByText('Template Description').as('description').click()
cy.get('@description').parent().get('textarea').type(description)
cy.findByText('Publish').click()
cy.findByText('Publishing…').parent().should('be.disabled')
cy.findByText('Publish').should('not.exist')
cy.findByText('Unpublish', { timeout: 60_000 })
cy.findByText('Republish')
cy.findByLabelText('Template Description').type(description)
cy.findByRole('button', { name: 'Publish' }).click()
cy.findByRole('button', { name: 'Publishing…' }).should('be.disabled')
cy.findByRole('button', { name: 'Publish' }).should('not.exist')
cy.findByRole('button', { name: 'Unpublish', timeout: 60_000 })
cy.findByRole('button', { name: 'Republish' })
cy.findByText('View it in the template gallery').click()
cy.findByRole('link', { name: 'View it in the template gallery' }).click()
cy.url()
.should('match', /\/templates\/[a-f0-9]{24}$/)
.as('templateURL')
cy.findAllByText(name).first().should('exist')
cy.findByRole('heading', { level: 2 }).findByText(name)
cy.findByText(description)
cy.findByText('Open as Template')
cy.findByText('Unpublish')
cy.findByText('Republish')
cy.findByRole('link', { name: 'Open as Template' })
cy.findByRole('button', { name: 'Unpublish' })
cy.findByRole('button', { name: 'Republish' })
cy.get('img')
.should('have.attr', 'src')
.and('match', /\/v\/0\//)
cy.findByText('Republish').click()
cy.findByText('Publishing…').parent().should('be.disabled')
cy.findByText('Republish', { timeout: 60_000 })
cy.findByRole('button', { name: 'Republish' }).click()
cy.findByRole('button', { name: 'Publishing…' }).should('be.disabled')
cy.findByRole('button', { name: 'Republish', timeout: 60_000 })
cy.get('img', { timeout: 60_000 })
.should('have.attr', 'src')
.and('match', /\/v\/1\//)
@@ -95,43 +92,38 @@ describe('Templates', function () {
// custom tag
const tagName = `${Date.now()}`
cy.visit('/')
cy.findByText(name)
.parent()
.parent()
.within(() => cy.get('input[type="checkbox"]').first().check())
cy.get('.project-list-sidebar-scroll').within(() => {
cy.findAllByText('New tag').first().click()
})
cy.findByRole('checkbox', { name: `Select ${name}` }).check()
cy.findByRole('navigation', { name: 'Project categories and tags' })
.findByRole('button', { name: 'New tag' })
.click()
cy.focused().type(tagName)
cy.findByText('Create').click()
cy.get('.project-list-sidebar-scroll').within(() => {
cy.findByText(tagName)
.parent()
.within(() => cy.get('.name').should('have.text', `${tagName} (1)`))
})
cy.findByRole('button', { name: 'Create' }).click()
cy.findByRole('navigation', {
name: 'Project categories and tags',
}).should('contain', `${tagName} (1)`)
// Check listing
cy.visit('/templates')
cy.findByText(tagName)
cy.findByRole('link', { name: tagName })
cy.visit('/templates/all')
cy.findByText(name)
cy.findByRole('heading', { name })
cy.visit(`/templates/${tagName}`)
cy.findByText(name)
cy.findByRole('heading', { name })
// Unpublish via template page
cy.get('@templateURL').then(url => cy.visit(`${url}`))
cy.findByText('Unpublish').click()
cy.findByRole('button', { name: 'Unpublish' }).click()
cy.url().should('match', /\/templates$/)
cy.get('@templateURL').then(url =>
cy.visit(`${url}`, {
failOnStatusCode: false,
})
)
cy.findByText('Not found')
cy.findByRole('heading', { name: 'Not found' })
cy.visit('/templates/all')
cy.findByText(name).should('not.exist')
cy.findByRole('heading', { name }).should('not.exist')
cy.visit(`/templates/${tagName}`)
cy.findByText(name).should('not.exist')
cy.findByRole('heading', { name }).should('not.exist')
// Publish again
cy.get('@templateProjectId').then(projectId =>
@@ -142,12 +134,12 @@ describe('Templates', function () {
})
.findByRole('button', { name: 'Menu' })
.click()
cy.findByText('Manage Template').click()
cy.findByText('Publish').click()
cy.findByText('Unpublish', { timeout: 60_000 })
cy.findByRole('button', { name: 'Manage Template' }).click()
cy.findByRole('button', { name: 'Publish' }).click()
cy.findByRole('button', { name: 'Unpublish', timeout: 60_000 })
// Should assign a new template id
cy.findByText('View it in the template gallery').click()
cy.findByRole('link', { name: 'View it in the template gallery' }).click()
cy.url()
.should('match', /\/templates\/[a-f0-9]{24}$/)
.as('newTemplateURL')
@@ -161,31 +153,32 @@ describe('Templates', function () {
// Open project from template
login(REGULAR_USER)
cy.visit('/templates')
cy.findByText(tagName).click()
cy.findByText(name).click()
cy.findByText('Open as Template').click()
cy.url().should('match', /\/project\/[a-f0-9]{24}$/)
cy.get('.project-name').should('contain.text', 'Your Paper') // might have (1) suffix
cy.findByRole('link', { name: tagName }).click()
cy.findByRole('link', { name }).click()
cy.findByRole('link', { name: 'Open as Template' }).click()
cy.findByRole('navigation', { name: 'Project actions' }).findByText(
/Your Paper/i
) // might have (1) suffix
cy.findByRole('navigation', {
name: 'Project actions',
})
.findByRole('button', { name: 'Menu' })
.click()
cy.findByText('Word Count') // wait for lazy loading
cy.findByText('Manage Template').should('not.exist')
cy.findByRole('button', { name: 'Word Count' }).click() // wait for lazy loading
cy.findByRole('button', { name: 'Manage Template' }).should('not.exist')
// Check management as regular user
cy.get('@newTemplateURL').then(url => cy.visit(`${url}`))
cy.findByText('Open as Template')
cy.findByText('Unpublish').should('not.exist')
cy.findByText('Republish').should('not.exist')
cy.findByRole('link', { name: 'Open as Template' })
cy.findByRole('button', { name: 'Unpublish' }).should('not.exist')
cy.findByRole('button', { name: 'Republish' }).should('not.exist')
// Check management as admin user
login(ADMIN_USER)
cy.get('@newTemplateURL').then(url => cy.visit(`${url}`))
cy.findByText('Open as Template')
cy.findByText('Unpublish')
cy.findByText('Republish')
cy.findByRole('link', { name: 'Open as Template' })
cy.findByRole('button', { name: 'Unpublish' })
cy.findByRole('button', { name: 'Republish' })
cy.get('@templateProjectId').then(projectId =>
cy.visit(`/project/${projectId}`)
)
@@ -194,8 +187,8 @@ describe('Templates', function () {
})
.findByRole('button', { name: 'Menu' })
.click()
cy.findByText('Manage Template').click()
cy.findByText('Unpublish')
cy.findByRole('button', { name: 'Manage Template' }).click()
cy.findByRole('button', { name: 'Unpublish' })
// Back to templates user
login(TEMPLATES_USER)
@@ -209,19 +202,20 @@ describe('Templates', function () {
})
.findByRole('button', { name: 'Menu' })
.click()
cy.findByText('Manage Template').click()
cy.findByText('Unpublish').click()
cy.findByText('Publish')
cy.findByRole('button', { name: 'Manage Template' }).click()
cy.findByRole('button', { name: 'Unpublish' }).click()
cy.findByRole('button', { name: 'Publish' })
cy.visit('/templates/all')
cy.findByText(name).should('not.exist')
cy.findByRole('link', { name }).should('not.exist')
// check for template links, after creating the first project
cy.visit('/')
cy.findAllByRole('button', { name: NEW_PROJECT_BUTTON_MATCHER }).click()
cy.findAllByText('All Templates')
.first()
.parent()
.should('have.attr', 'href', '/templates/all')
cy.findByRole('menuitem', { name: /All Templates/ }).should(
'have.attr',
'href',
'/templates/all'
)
})
})
@@ -237,25 +231,27 @@ describe('Templates', function () {
})
.findByRole('button', { name: 'Menu' })
.click()
cy.findByText('Word Count') // wait for lazy loading
cy.findByText('Manage Template').should('not.exist')
cy.findByRole('button', { name: 'Word Count' }) // wait for lazy loading
cy.findByRole('button', { name: 'Manage Template' }).should('not.exist')
cy.visit('/templates', { failOnStatusCode: false })
cy.findByText('Not found')
cy.findByRole('heading', { name: 'Not found' })
cy.visit('/templates/all', { failOnStatusCode: false })
cy.findByText('Not found')
cy.findByRole('heading', { name: 'Not found' })
// check for template links, after creating the first project
cy.visit('/')
cy.findAllByRole('button', { name: NEW_PROJECT_BUTTON_MATCHER }).click()
cy.findAllByText('All Templates').should('not.exist')
cy.findByRole('menuitem', { name: /All Templates/ }).should('not.exist')
})
it('should not show templates link on welcome page', function () {
login(WITHOUT_PROJECTS_USER)
cy.visit('/')
cy.findByText(NEW_PROJECT_BUTTON_MATCHER) // wait for lazy loading
cy.findByText(LABEL_BROWSE_TEMPLATES).should('not.exist')
cy.findByRole('button', { name: NEW_PROJECT_BUTTON_MATCHER }) // wait for lazy loading
cy.findByRole('link', { name: LABEL_BROWSE_TEMPLATES }).should(
'not.exist'
)
})
}

View File

@@ -49,7 +49,7 @@ describe('Upgrading', function () {
})
cy.log('Wait for successful compile')
cy.get('.pdf-viewer').should('contain.text', PROJECT_NAME)
cy.findByLabelText(/Page.*1/i).findByText(PROJECT_NAME)
cy.log('Increment the doc version three times')
for (let i = 0; i < 3; i++) {
@@ -66,15 +66,15 @@ describe('Upgrading', function () {
})
.findByRole('button', { name: 'Menu' })
.click()
cy.findByText('Source').click()
cy.get('.left-menu-modal-backdrop').click({ force: true })
cy.findByRole('link', { name: 'Source' }).click()
cy.get('body').type('{esc}')
}
cy.log('Check compile and history')
for (let i = 0; i < 3; i++) {
cy.get('.pdf-viewer').should('contain.text', `Old Section ${i}`)
cy.findByLabelText(/Page.*1/i).findByText(`Old Section ${i}`)
}
cy.findByText('History').click()
cy.findByRole('button', { name: 'History' }).click()
for (let i = 0; i < 3; i++) {
cy.findByText(new RegExp(`\\\\section{Old Section ${i}}`))
}
@@ -119,7 +119,7 @@ describe('Upgrading', function () {
it('should list the old project', function () {
cy.visit('/project')
cy.findByText(PROJECT_NAME)
cy.findByRole('link', { name: PROJECT_NAME })
})
it('should open the old project', function () {
@@ -135,8 +135,8 @@ describe('Upgrading', function () {
})
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')
cy.findByLabelText(/Page.*1/i).findByText(PROJECT_NAME)
cy.findByLabelText(/Page.*1/i).findByText('Old Section 2')
cy.log('Add more content')
const newSection = `New Section ${uuid()}`
@@ -145,8 +145,8 @@ describe('Upgrading', function () {
cy.log('Check compile and history')
recompile()
cy.get('.pdf-viewer').should('contain.text', newSection)
cy.findByText('History').click()
cy.findByLabelText(/Page.*1/i).findByText(newSection)
cy.findByRole('button', { name: 'History' }).click()
cy.findByText(/\\section\{Old Section 2}/)
cy.findByText(new RegExp(`\\\\section\\{${newSection}}`))
})
@@ -208,10 +208,10 @@ describe('Upgrading', function () {
cy.log('Trigger flush')
recompile()
cy.get('.pdf-viewer').should('contain.text', 'FiveOOne Section')
cy.findByLabelText(/Page.*1/i).findByText('FiveOOne Section')
cy.log('Check for broken history, i.e. not synced with latest edit')
cy.findByText('History').click()
cy.findByRole('button', { name: 'History' }).click()
cy.findByText(/\\section\{Old Section 2}/) // wait for lazy loading
cy.findByText(/\\section\{FiveOOne Section}/).should('not.exist')
})
@@ -246,7 +246,7 @@ describe('Upgrading', function () {
cy.log(
'The edit that was made while the history was broken should be there now.'
)
cy.findByText('History').click()
cy.findByRole('button', { name: 'History' }).click()
cy.findByText(/\\section\{FiveOOne Section}/)
// TODO(das7pad): restore after https://github.com/overleaf/internal/issues/19588 is fixed.

View File

@@ -5,7 +5,7 @@ block content
.container
.error-container
.error-details
p.error-status Not found
h1.error-status #{translate("not_found")}
p.error-description #{translate("cant_find_page")}
p.error-actions
a.error-btn(href='/') Home

View File

@@ -280,6 +280,7 @@
"clicking_delete_will_remove_sso_config_and_clear_saml_data": "",
"clone_with_git": "",
"close": "",
"close_dialog": "",
"clsi_maintenance": "",
"clsi_unavailable": "",
"code_check_failed": "",
@@ -1272,6 +1273,7 @@
"pdf_unavailable_for_download": "",
"pdf_viewer": "",
"pdf_viewer_error": "",
"pdf_zoom_level": "",
"pending_additional_licenses": "",
"pending_addon_cancellation": "",
"pending_invite": "",

View File

@@ -75,6 +75,7 @@ function PdfZoomDropdown({
id="pdf-zoom-dropdown"
variant="link"
className="pdf-toolbar-btn pdfjs-zoom-dropdown-button small"
aria-label={t('pdf_zoom_level')}
>
{rawScaleToPercentage(rawScale)}
</DropdownToggle>

View File

@@ -8,6 +8,7 @@ import {
} from 'react-bootstrap'
import { ModalBodyProps } from 'react-bootstrap/ModalBody'
import type { Options as FocusTrapOptions } from 'focus-trap'
import { useTranslation } from 'react-i18next'
type OLModalProps = ModalProps & {
size?: 'sm' | 'lg'
@@ -52,8 +53,13 @@ export function OLModalHeader({
closeButton = true,
...props
}: OLModalHeaderProps) {
const { t } = useTranslation()
return (
<Modal.Header closeButton={closeButton} {...props}>
<Modal.Header
closeButton={closeButton}
closeLabel={t('close_dialog')}
{...props}
>
{children}
</Modal.Header>
)

View File

@@ -357,6 +357,7 @@
"clicking_delete_will_remove_sso_config_and_clear_saml_data": "Clicking <0>Delete</0> will remove your SSO configuration and unlink all users. You can only do this when SSO is disabled in your group settings.",
"clone_with_git": "Clone with Git",
"close": "Close",
"close_dialog": "Close dialog",
"clsi_maintenance": "The compile servers are down for maintenance, and will be back shortly.",
"clsi_unavailable": "Sorry, the compile server for your project was temporarily unavailable. Please try again in a few moments.",
"cn": "Chinese (Simplified)",
@@ -1506,6 +1507,7 @@
"normally_x_price_per_month": "Normally __price__ per month",
"normally_x_price_per_year": "Normally __price__ per year",
"not_a_student": "Not a student?",
"not_found": "Not found",
"not_found_error_from_the_supplied_url": "The link to open this content on Overleaf pointed to a file that could not be found. If this keeps happening for links on a particular site, please report this to them.",
"not_managed": "Not managed",
"not_now": "Not now",
@@ -1659,6 +1661,7 @@
"pdf_unavailable_for_download": "PDF unavailable for download",
"pdf_viewer": "PDF Viewer",
"pdf_viewer_error": "There was a problem displaying the PDF for this project.",
"pdf_zoom_level": "PDF zoom level",
"pending": "Pending",
"pending_additional_licenses": "Your subscription is changing to include <0>__pendingAdditionalLicenses__</0> additional license(s) for a total of <1>__pendingTotalLicenses__</1> licenses.",
"pending_addon_cancellation": "Your subscription will change to remove the <strong>__addOnName__</strong> add-on at the end of the current billing period.",

View File

@@ -163,7 +163,7 @@ describe('<EditorLeftMenu />', function () {
cy.findByRole('heading', { name: 'Copy project' })
// try closing & re-opening the modal with different methods
cy.findByRole('button', { name: 'Close' }).click()
cy.findByRole('button', { name: 'Close dialog' }).click()
cy.findByRole('button', { name: 'Copy project' }).click()
cy.findByRole('button', { name: 'Cancel' }).click()
cy.findByRole('button', { name: 'Copy project' }).click()

View File

@@ -43,7 +43,7 @@ describe('<OLModal />', function () {
cy.findByRole('button', { name: 'Open modal' }).click()
cy.findByRole('dialog').should('be.visible')
cy.findByLabelText(/enter text/i).should('be.visible')
cy.get('body').type('{esc}')
cy.findByRole('button', { name: 'Close dialog' }).click()
// Modal should hide with single escape (escapeDeactivates: false means FocusTrap doesn't handle it)
cy.findByRole('dialog').should('not.exist')
cy.findByRole('button', { name: 'Open modal' }).should('be.visible')
@@ -70,13 +70,13 @@ describe('<OLModal />', function () {
cy.findByRole('button', { name: 'Open modal' }).click()
cy.findByRole('dialog').should('be.visible')
cy.findByRole('button', { name: 'Close' }).should('be.focused')
cy.findByRole('button', { name: 'Close dialog' }).should('be.focused')
cy.focused().tab()
cy.findByLabelText(/enter text/i).should('be.focused')
cy.focused().tab()
cy.findByRole('button', { name: 'Close the modal' }).should('be.focused')
cy.focused().tab()
cy.findByRole('button', { name: 'Close' }).should('be.focused')
cy.findByRole('button', { name: 'Close dialog' }).should('be.focused')
cy.focused().tab({ shift: true })
cy.findByRole('button', { name: 'Close the modal' }).should('be.focused')
})
@@ -95,7 +95,7 @@ describe('<OLModal />', function () {
cy.mount(<Modal />)
cy.findByRole('button', { name: 'Open modal' }).click()
cy.findByRole('dialog').should('be.visible')
cy.findByRole('button', { name: 'Close' }).click()
cy.findByRole('button', { name: 'Close dialog' }).click()
cy.findByRole('dialog').should('not.exist')
})
@@ -103,7 +103,7 @@ describe('<OLModal />', function () {
cy.mount(<Modal backdrop="static" />)
cy.findByRole('button', { name: 'Open modal' }).click()
cy.findByRole('dialog').should('be.visible')
cy.get('body').type('{esc}')
cy.findByRole('button', { name: 'Close dialog' }).click()
cy.findByRole('dialog').should('not.exist')
})
})

View File

@@ -24,8 +24,8 @@ describe('<SettingsDictionary />', function () {
within(modal).getByRole('heading', { name: 'Edit Dictionary' })
within(modal).getByText('Your custom dictionary is empty.')
const [, closeButton] = within(modal).getAllByRole('button', {
name: 'Close',
const closeButton = within(modal).getByRole('button', {
name: 'Close dialog',
})
fireEvent.click(closeButton)
expect(screen.getByTestId('dictionary-modal')).to.not.be.null

View File

@@ -62,7 +62,7 @@ describe('<NewProjectButton />', function () {
it('close the new project modal when clicking at the top right "x" button', function () {
fireEvent.click(screen.getByRole('menuitem', { name: 'Blank project' }))
fireEvent.click(screen.getByRole('button', { name: 'Close' }))
fireEvent.click(screen.getByRole('button', { name: 'Close dialog' }))
expect(screen.queryByRole('dialog')).to.be.null
})

View File

@@ -26,8 +26,8 @@ describe('<DictionarySetting />', function () {
within(modal).getByRole('heading', { name: 'Edit Dictionary' })
within(modal).getByText('Your custom dictionary is empty.')
const [, closeButton] = within(modal).getAllByRole('button', {
name: 'Close',
const closeButton = within(modal).getByRole('button', {
name: 'Close dialog',
})
fireEvent.click(closeButton)
expect(screen.getByTestId('dictionary-modal')).to.not.be.null

View File

@@ -35,7 +35,7 @@ describe('<LeaveSection />', function () {
)
const cancelButton = screen.getByRole('button', {
name: 'Close',
name: 'Cancel',
})
fireEvent.click(cancelButton)

View File

@@ -141,15 +141,10 @@ describe('<ShareProjectModal/>', function () {
createContextProps()
)
const [headerCloseButton, footerCloseButton] = await screen.findAllByRole(
'button',
{ name: 'Close' }
)
const closeButton = screen.getByRole('button', { name: 'Close dialog' })
await userEvent.click(closeButton)
await userEvent.click(headerCloseButton)
await userEvent.click(footerCloseButton)
expect(handleHide.callCount).to.equal(2)
expect(handleHide.callCount).to.equal(1)
})
it('handles access level "private"', async function () {
@@ -410,7 +405,7 @@ describe('<ShareProjectModal/>', function () {
createContextProps({ publicAccessLevel: 'tokenBased', invites })
)
const [, closeButton] = screen.getAllByRole('button', {
const closeButton = screen.getByRole('button', {
name: 'Close',
})
@@ -446,7 +441,7 @@ describe('<ShareProjectModal/>', function () {
createContextProps({ publicAccessLevel: 'tokenBased', invites })
)
const [, closeButton] = screen.getAllByRole('button', {
const closeButton = screen.getByRole('button', {
name: 'Close',
})
@@ -481,7 +476,7 @@ describe('<ShareProjectModal/>', function () {
createContextProps({ publicAccessLevel: 'tokenBased', members })
)
const [, closeButton] = screen.getAllByRole('button', {
const closeButton = screen.getByRole('button', {
name: 'Close',
})