Преглед на файлове

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
Rebeka Dekany преди 8 месеца
родител
ревизия
b4bfff1b67
променени са 24 файла, в които са добавени 294 реда и са изтрити 223 реда
  1. 3 7
      server-ce/test/accounts.spec.ts
  2. 27 26
      server-ce/test/admin.spec.ts
  3. 3 1
      server-ce/test/create-and-compile-project.spec.ts
  4. 8 4
      server-ce/test/customization.spec.ts
  5. 5 5
      server-ce/test/external-auth.spec.ts
  6. 31 13
      server-ce/test/git-bridge.spec.ts
  7. 20 8
      server-ce/test/graceful-shutdown.spec.ts
  8. 14 13
      server-ce/test/learn-wiki.spec.ts
  9. 17 7
      server-ce/test/project-sharing.spec.ts
  10. 52 28
      server-ce/test/sandboxed-compiles.spec.ts
  11. 69 73
      server-ce/test/templates.spec.ts
  12. 13 13
      server-ce/test/upgrading.spec.ts
  13. 1 1
      services/web/app/views/general/404.pug
  14. 2 0
      services/web/frontend/extracted-translations.json
  15. 1 0
      services/web/frontend/js/features/pdf-preview/components/pdf-zoom-dropdown.tsx
  16. 7 1
      services/web/frontend/js/shared/components/ol/ol-modal.tsx
  17. 3 0
      services/web/locales/en.json
  18. 1 1
      services/web/test/frontend/components/editor-left-menu/editor-left-menu.spec.tsx
  19. 5 5
      services/web/test/frontend/components/shared/modal.spec.tsx
  20. 2 2
      services/web/test/frontend/features/editor-left-menu/components/settings/settings-dictionary.test.tsx
  21. 1 1
      services/web/test/frontend/features/project-list/components/new-project-button.test.tsx
  22. 2 2
      services/web/test/frontend/features/settings-modal/settings/dictionary-setting.test.tsx
  23. 1 1
      services/web/test/frontend/features/settings/components/leave-section.test.tsx
  24. 6 11
      services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.tsx

+ 3 - 7
server-ce/test/accounts.spec.ts

@@ -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' })
     })
   })

+ 27 - 26
server-ce/test/admin.spec.ts

@@ -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()
+          })
       })
     })
 

+ 3 - 1
server-ce/test/create-and-compile-project.spec.ts

@@ -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.')
     })

+ 8 - 4
server-ce/test/customization.spec.ts

@@ -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')
     })
   })
 })

+ 5 - 5
server-ce/test/external-auth.spec.ts

@@ -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')

+ 31 - 13
server-ce/test/git-bridge.spec.ts

@@ -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()

+ 20 - 8
server-ce/test/graceful-shutdown.spec.ts

@@ -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}/)
   })
 })

+ 14 - 13
server-ce/test/learn-wiki.spec.ts

@@ -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 () {

+ 17 - 7
server-ce/test/project-sharing.spec.ts

@@ -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 () {

+ 52 - 28
server-ce/test/sandboxed-compiles.spec.ts

@@ -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')
+      })
     })
   }
 

+ 69 - 73
server-ce/test/templates.spec.ts

@@ -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'
+      )
     })
   }
 

+ 13 - 13
server-ce/test/upgrading.spec.ts

@@ -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.

+ 1 - 1
services/web/app/views/general/404.pug

@@ -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

+ 2 - 0
services/web/frontend/extracted-translations.json

@@ -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": "",

+ 1 - 0
services/web/frontend/js/features/pdf-preview/components/pdf-zoom-dropdown.tsx

@@ -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>

+ 7 - 1
services/web/frontend/js/shared/components/ol/ol-modal.tsx

@@ -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>
   )

+ 3 - 0
services/web/locales/en.json

@@ -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.",

+ 1 - 1
services/web/test/frontend/components/editor-left-menu/editor-left-menu.spec.tsx

@@ -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()

+ 5 - 5
services/web/test/frontend/components/shared/modal.spec.tsx

@@ -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')
   })
 })

+ 2 - 2
services/web/test/frontend/features/editor-left-menu/components/settings/settings-dictionary.test.tsx

@@ -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

+ 1 - 1
services/web/test/frontend/features/project-list/components/new-project-button.test.tsx

@@ -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
     })

+ 2 - 2
services/web/test/frontend/features/settings-modal/settings/dictionary-setting.test.tsx

@@ -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

+ 1 - 1
services/web/test/frontend/features/settings/components/leave-section.test.tsx

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

+ 6 - 11
services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.tsx

@@ -141,15 +141,10 @@ describe('<ShareProjectModal/>', function () {
       createContextProps()
     )
 
-    const [headerCloseButton, footerCloseButton] = await screen.findAllByRole(
-      'button',
-      { name: 'Close' }
-    )
-
-    await userEvent.click(headerCloseButton)
-    await userEvent.click(footerCloseButton)
+    const closeButton = screen.getByRole('button', { name: 'Close dialog' })
+    await userEvent.click(closeButton)
 
-    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',
     })