diff --git a/server-ce/test/accounts.spec.ts b/server-ce/test/accounts.spec.ts index 85d545535a..92327a24d9 100644 --- a/server-ce/test/accounts.spec.ts +++ b/server-ce/test/accounts.spec.ts @@ -10,7 +10,7 @@ describe('Accounts', function () { login('user@example.com') cy.visit('/project') cy.findByRole('menuitem', { name: 'Account' }).click() - cy.findByText('Log Out').click() + cy.findByRole('menuitem', { name: 'Log Out' }).click() cy.url().should('include', '/login') cy.visit('/project') cy.url().should('include', '/login') diff --git a/server-ce/test/admin.spec.ts b/server-ce/test/admin.spec.ts index 538a821811..b404698b51 100644 --- a/server-ce/test/admin.spec.ts +++ b/server-ce/test/admin.spec.ts @@ -14,7 +14,7 @@ describe('admin panel', function () { function registrationTests() { it('via GUI and opening URL manually', () => { const user = `${uuid()}@example.com` - cy.get('input[name="email"]').type(user + '{enter}') + cy.findByLabelText('Emails to register new users').type(user + '{enter}') cy.get('td') .contains(/\/user\/activate/) @@ -26,7 +26,7 @@ describe('admin panel', function () { it('via GUI and email', () => { const user = `${uuid()}@example.com` - cy.get('input[name="email"]').type(user + '{enter}') + cy.findByLabelText('Emails to register new users').type(user + '{enter}') let url: string cy.get('td') @@ -93,8 +93,8 @@ describe('admin panel', function () { beforeEach(() => { login(admin) cy.visit('/project') - cy.get('nav').findByText('Admin').click() - cy.get('nav').findByText('Manage Users').click() + cy.findByRole('menuitem', { name: 'Admin' }).click() + cy.findByRole('menuitem', { name: 'Manage Users' }).click() }) registrationTests() }) @@ -139,17 +139,17 @@ describe('admin panel', function () { beforeEach(() => { login(admin) cy.visit('/project') - cy.get('nav').findByText('Admin').click() - cy.get('nav').findByText('Manage Site').click() + cy.findByRole('menuitem', { name: 'Admin' }).click() + cy.findByRole('menuitem', { name: 'Manage Site' }).click() }) it('publish and clear admin messages', () => { const message = 'Admin Message ' + uuid() cy.log('create system message') - cy.get('[role="tab"]').contains('System Messages').click() - cy.get('input[name="content"]').type(message) - cy.get('button').contains('Post Message').click() + cy.findByRole('tab', { name: 'System Messages' }).click() + cy.findByLabelText('Message').type(message) + cy.findByRole('button', { name: 'Post Message' }).click() cy.findByText(message) login(user1) @@ -159,10 +159,10 @@ describe('admin panel', function () { cy.log('clear system messages') login(admin) cy.visit('/project') - cy.get('nav').findByText('Admin').click() - cy.get('nav').findByText('Manage Site').click() - cy.get('[role="tab"]').contains('System Messages').click() - cy.get('button').contains('Clear all messages').click() + cy.findByRole('menuitem', { name: 'Admin' }).click() + cy.findByRole('menuitem', { name: 'Manage Site' }).click() + cy.findByRole('tab', { name: 'System Messages' }).click() + cy.findByRole('button', { name: 'Clear all messages' }).click() cy.log('verify system messages are no longer displayed') login(user1) @@ -175,16 +175,16 @@ describe('admin panel', function () { beforeEach(() => { login(admin) cy.visit('/project') - cy.get('nav').findByText('Admin').click() - cy.get('nav').findByText('Manage Users').click() + cy.findByRole('menuitem', { name: 'Admin' }).click() + cy.findByRole('menuitem', { name: 'Manage Users' }).click() }) it('displays expected tabs', () => { const tabs = ['Users', 'License Usage'] - cy.get('[role="tab"]').each((el, index) => { - cy.wrap(el).findByText(tabs[index]).click() + cy.findAllByRole('tab').should('have.length', tabs.length) + tabs.forEach(tabName => { + cy.findByRole('tab', { name: tabName }).click() }) - cy.get('[role="tab"]').should('have.length', tabs.length) }) it('license usage tab', () => { @@ -202,10 +202,12 @@ describe('admin panel', function () { }) it('user list RegExp search', () => { - cy.get('input[name="isRegExpSearch"]').click() - cy.get('input[name="email"]').type('user[0-9]{enter}') - cy.findByText(user2) - cy.findByText(user1).should('not.exist') + cy.findByLabelText('RegExp').click() + cy.findByPlaceholderText('Search users by email or id…').type( + 'user[0-9]{enter}' + ) + cy.findByRole('link', { name: user2 }) + cy.findByRole('link', { name: user1 }).should('not.exist') }) }) @@ -213,10 +215,12 @@ describe('admin panel', function () { beforeEach(() => { login(admin) cy.visit('/project') - cy.get('nav').findByText('Admin').click() - cy.get('nav').findByText('Manage Users').click() - cy.get('input[name="email"]').type(user1 + '{enter}') - cy.findByText(user1).click() + cy.findByRole('menuitem', { name: 'Admin' }).click() + cy.findByRole('menuitem', { name: 'Manage Users' }).click() + cy.findByPlaceholderText('Search users by email or id…').type( + user1 + '{enter}' + ) + cy.findByRole('link', { name: user1 }).click() cy.url().should('match', /\/admin\/user\/[a-fA-F0-9]{24}/) }) @@ -228,15 +232,15 @@ describe('admin panel', function () { 'Audit Log', 'Sessions', ] - cy.get('[role="tab"]').each((el, index) => { - cy.wrap(el).findByText(tabs[index]).click() + cy.findAllByRole('tab').should('have.length', tabs.length) + tabs.forEach(tabName => { + cy.findByRole('tab', { name: tabName }).click() }) - cy.get('[role="tab"]').should('have.length', tabs.length) }) describe('user info tab', () => { beforeEach(() => { - cy.get('[role="tab"]').contains('User Info').click() + cy.findByRole('tab', { name: 'User Info' }).click() }) it('displays required sections', () => { @@ -246,33 +250,61 @@ describe('admin panel', function () { }) it('should not display SaaS-only sections', () => { - cy.findByText('Referred User Count').should('not.exist') - cy.findByText('Split Test Assignments').should('not.exist') - cy.findByText('Experimental Features').should('not.exist') - cy.findByText('Service Integration').should('not.exist') - cy.findByText('SSO Integrations').should('not.exist') - cy.findByText('Security').should('not.exist') + cy.findByLabelText('Referred User Count').should('not.exist') + cy.findByRole('heading', { name: /Split Test Assignments/ }).should( + 'not.exist' + ) + cy.findByRole('heading', { name: 'Experimental Features' }).should( + 'not.exist' + ) + cy.findByRole('heading', { name: 'Service Integration' }).should( + 'not.exist' + ) + cy.findByRole('heading', { name: 'SSO Integrations' }).should( + 'not.exist' + ) + cy.findByRole('heading', { name: 'Security' }).should('not.exist') }) }) it('transfer project ownership', () => { cy.log("access project admin through owners' project list") - cy.get('[role="tab"]').contains('Projects').click() - cy.get(`a[href="/admin/project/${testProjectId}"]`).click() + cy.findByRole('tablist').within(() => { + cy.findByRole('tab', { name: 'Projects' }).click() + }) + cy.get(`a[href="/admin/project/${testProjectId}"]`) + .should('contain.text', 'Project information') + .click() - cy.findByText('Transfer Ownership').click() - cy.get('button[type="submit"]').should('be.disabled') - cy.get('input[name="user_id"]').type(user2) - cy.get('button[type="submit"]').should('not.be.disabled') - cy.get('button[type="submit"]').click() - cy.findByText('Transfer project to this user?') - cy.get('button').contains('Confirm').click() + cy.findByRole('button', { name: 'Transfer Ownership' }).click() + cy.findByRole('dialog').within(() => { + cy.findByRole('heading', { name: 'Transfer Ownership of Project' }) + cy.findByRole('button', { name: 'Find' }).should('be.disabled') + cy.findByRole('button', { name: 'Confirm' }).should('be.disabled') + cy.findByPlaceholderText('User ID or Email').type(user2) + cy.findByRole('button', { name: 'Find' }).should('not.be.disabled') + cy.findByRole('button', { name: 'Find' }).click() + cy.findByText('Transfer project to this user?') + cy.findByRole('cell', { name: 'ID' }) + cy.findByRole('cell', { name: 'Name' }) + cy.findByRole('cell', { name: 'Email' }) + cy.findByRole('cell', { name: user2 }) + cy.findByRole('button', { name: 'Confirm' }).should('not.be.disabled') + cy.findByRole('button', { name: 'Confirm' }).click() + }) cy.log('check the project is displayed in the new owner projects tab') - cy.get('input[name="email"]').type(user2 + '{enter}') - cy.findByText(user2).click() - cy.get('[role="tab"]').contains('Projects').click() - cy.get(`a[href="/admin/project/${testProjectId}"]`) + cy.findByPlaceholderText('Search users by email or id…').type( + user2 + '{enter}' + ) + cy.findByRole('link', { name: user2 }).click() + cy.findByRole('tablist').within(() => { + cy.findByRole('tab', { name: 'Projects' }).click() + }) + cy.get(`a[href="/admin/project/${testProjectId}"]`).should( + 'contain.text', + 'Project information' + ) }) }) @@ -284,10 +316,10 @@ describe('admin panel', function () { it('displays expected tabs', () => { const tabs = ['Project Info', 'Deleted Docs', 'Audit Log'] - cy.get('[role="tab"]').each((el, index) => { - cy.wrap(el).findByText(tabs[index]).click() + cy.findAllByRole('tab').should('have.length', tabs.length) + tabs.forEach(tabName => { + cy.findByRole('tab', { name: tabName }).click() }) - cy.get('[role="tab"]').should('have.length', tabs.length) }) }) @@ -297,46 +329,54 @@ describe('admin panel', function () { cy.log('select project to delete') findProjectRow(deletedProjectName).within(() => - cy.get('input[type="checkbox"]').first().check() + cy + .findByRole('checkbox', { name: `Select ${deletedProjectName}` }) + .first() + .check() ) - cy.log('delete project') findProjectRow(deletedProjectName).within(() => cy.findByRole('button', { name: 'Trash' }).click() ) - cy.get('button').contains('Confirm').click() - cy.findByText(deletedProjectName).should('not.exist') + cy.findByRole('button', { name: 'Confirm' }).click() + cy.findByRole('link', { name: deletedProjectName }).should('not.exist') cy.log('navigate to thrashed projects and delete the project') - cy.get('.project-list-sidebar-scroll').within(() => { - cy.findByText('Trashed projects').click() + cy.findByRole('navigation', { + name: 'Project categories and tags', }) + .findByRole('button', { name: 'Trashed projects' }) + .click() findProjectRow(deletedProjectName).within(() => cy.findByRole('button', { name: 'Delete' }).click() ) - cy.get('button').contains('Confirm').click() - cy.findByText(deletedProjectName).should('not.exist') + cy.findByRole('button', { name: 'Confirm' }).click() + cy.findByRole('link', { name: deletedProjectName }).should('not.exist') cy.log('login as an admin and navigate to the deleted project') login(admin) cy.visit('/admin/user') - cy.get('input[name="email"]').type(user1 + '{enter}') - cy.get('a').contains(user1).click() - cy.findByText('Deleted Projects').click() - cy.get('a').contains(deletedProjectName).click() + cy.findByPlaceholderText('Search users by email or id…').type( + user1 + '{enter}' + ) + cy.findByRole('link', { name: user1 }).click() + cy.findByRole('tab', { name: 'Deleted Projects' }).click() + cy.findByRole('link', { name: deletedProjectName }).click() cy.log('undelete the project') - cy.findByText('Undelete').click() - cy.findByText('Undelete').should('not.exist') + cy.findByRole('button', { name: 'Undelete' }).click() + cy.findByRole('button', { name: 'Undelete' }).should('not.exist') cy.url().should('contain', `/admin/project/${projectToDeleteId}`) cy.log('login as the user and verify the project is restored') login(user1) cy.visit('/project') - cy.get('.project-list-sidebar-scroll').within(() => { - cy.findByText('Trashed projects').click() + cy.findByRole('navigation', { + name: 'Project categories and tags', }) - cy.findByText(`${deletedProjectName} (Restored)`) + .findByRole('button', { name: 'Trashed projects' }) + .click() + cy.findByRole('link', { name: `${deletedProjectName} (Restored)` }) }) }) }) diff --git a/server-ce/test/create-and-compile-project.spec.ts b/server-ce/test/create-and-compile-project.spec.ts index a0e03fe8d0..ac73f8ab7a 100644 --- a/server-ce/test/create-and-compile-project.spec.ts +++ b/server-ce/test/create-and-compile-project.spec.ts @@ -6,46 +6,59 @@ import { import { isExcludedBySharding, startWith } from './helpers/config' import { throttledRecompile } from './helpers/compile' +const USER = 'user@example.com' +const COLLABORATOR = 'collaborator@example.com' + describe('Project creation and compilation', function () { if (isExcludedBySharding('CE_DEFAULT')) return startWith({}) - ensureUserExists({ email: 'user@example.com' }) - ensureUserExists({ email: 'collaborator@example.com' }) + ensureUserExists({ email: USER }) + ensureUserExists({ email: COLLABORATOR }) it('users can create project and compile it', function () { - login('user@example.com') + login(USER) createProject('test-project') const recompile = throttledRecompile() - cy.findByText('\\maketitle').parent().click() - cy.findByText('\\maketitle').parent().type('\n\\section{{}Test Section}') + cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => { + cy.findByText('\\maketitle').parent().click() + cy.findByText('\\maketitle').parent().type('\n\\section{{}Test Section}') + }) recompile() - cy.get('.pdf-viewer').should('contain.text', 'Test Section') + cy.findByRole('region', { name: 'PDF preview and logs' }).within(() => { + cy.findByLabelText(/Page.*1/i).should('be.visible') + cy.findByText('Test Section').should('be.visible') + }) }) it('create and edit markdown file', function () { const fileName = `test-${Date.now()}.md` const markdownContent = '# Markdown title' - login('user@example.com') + login(USER) createProject('test-project') - // FIXME: Add aria-label maybe? or at least data-test-id - cy.findByText('New file').click({ force: true }) + cy.findByRole('navigation', { name: 'Project files and outline' }) + .findByRole('button', { name: 'New file' }) + .click() cy.findByRole('dialog').within(() => { - cy.get('input').clear() - cy.get('input').type(fileName) - cy.findByText('Create').click() + cy.findByLabelText('File Name').clear().type(fileName) + cy.findByRole('button', { name: 'Create' }).click() }) - cy.findByText(fileName).click() + cy.findByRole('button', { name: fileName }).click() // wait until we've switched to the newly created empty file - cy.get('.cm-line').should('have.length', 1) - cy.get('.cm-line').type(markdownContent) - cy.findByText('main.tex').click() - cy.findByRole('textbox', { name: /Source Editor editing/i }).should( + cy.findByRole('textbox', { name: 'Source Editor editing' }).should( + 'have.length', + 1 + ) + cy.findByRole('textbox', { name: 'Source Editor editing' }).type( + markdownContent + ) + cy.findByRole('button', { name: 'main.tex' }).click() + cy.findByRole('textbox', { name: 'Source Editor editing' }).should( 'contain.text', '\\maketitle' ) - cy.findByText(fileName).click() - cy.findByRole('textbox', { name: /Source Editor editing/i }).should( + cy.findByRole('button', { name: fileName }).click() + cy.findByRole('textbox', { name: 'Source Editor editing' }).should( 'contain.text', markdownContent ) @@ -54,7 +67,7 @@ describe('Project creation and compilation', function () { it('can link and display linked image from other project', function () { const sourceProjectName = `test-project-${Date.now()}` const targetProjectName = `${sourceProjectName}-target` - login('user@example.com') + login(USER) createProject(sourceProjectName, { type: 'Example project', @@ -63,15 +76,17 @@ describe('Project creation and compilation', function () { createProject(targetProjectName) // link the image from `projectName` into this project - cy.findByText('New file').click({ force: true }) + cy.findByRole('button', { name: 'New file' }).click() cy.findByRole('dialog').within(() => { - cy.findByText('From another project').click() + cy.findByRole('button', { name: 'From another project' }).click() cy.findByLabelText('Select a Project').select(sourceProjectName) cy.findByLabelText('Select a File').select('frog.jpg') - cy.findByText('Create').click() + cy.findByRole('button', { name: 'Create' }).click() }) - cy.findByTestId('file-tree').findByText('frog.jpg').click() - cy.findByText('Another project') + cy.findByRole('navigation', { name: 'Project files and outline' }) + .findByRole('treeitem', { name: 'frog.jpg' }) + .click() + cy.findByRole('link', { name: 'Another project' }) .should('have.attr', 'href') .then(href => { cy.get('@sourceProjectId').then(sourceProjectId => { @@ -83,7 +98,7 @@ describe('Project creation and compilation', function () { it('can refresh linked files as collaborator', function () { const sourceProjectName = `test-project-${Date.now()}` const targetProjectName = `${sourceProjectName}-target` - login('user@example.com') + login(USER) createProject(sourceProjectName, { type: 'Example project', open: false, @@ -91,31 +106,36 @@ describe('Project creation and compilation', function () { createProject(targetProjectName).as('targetProjectId') // link the image from `projectName` into this project - cy.findByText('New file').click({ force: true }) + cy.findByRole('navigation', { name: 'Project files and outline' }) + .findByRole('button', { name: 'New file' }) + .click() + cy.findByRole('dialog').within(() => { - cy.findByText('From another project').click() + cy.findByRole('button', { name: 'From another project' }).click() cy.findByLabelText('Select a Project').select(sourceProjectName) cy.findByLabelText('Select a File').select('frog.jpg') - cy.findByText('Create').click() + cy.findByRole('button', { name: 'Create' }).click() }) - cy.findByText('Share').click() + cy.findByRole('navigation', { name: 'Project actions' }).within(() => { + cy.findByRole('button', { name: 'Share' }).click() + }) cy.findByRole('dialog').within(() => { - cy.findByTestId('collaborator-email-input').type( - 'collaborator@example.com,' - ) - cy.findByText('Invite').click({ force: true }) + cy.findByTestId('collaborator-email-input').type(COLLABORATOR + ',') + cy.findByRole('button', { name: 'Invite' }).click() cy.findByText('Invite not yet accepted.') }) - login('collaborator@example.com') + login(COLLABORATOR) openProjectViaInviteNotification(targetProjectName) cy.get('@targetProjectId').then(targetProjectId => { cy.url().should('include', targetProjectId) }) - cy.findByTestId('file-tree').findByText('frog.jpg').click() - cy.findByText('Another project') + cy.findByRole('navigation', { name: 'Project files and outline' }) + .findByRole('treeitem', { name: 'frog.jpg' }) + .click() + cy.findByRole('link', { name: 'Another project' }) .should('have.attr', 'href') .then(href => { cy.get('@sourceProjectId').then(sourceProjectId => { diff --git a/server-ce/test/editor.spec.ts b/server-ce/test/editor.spec.ts index db1b9301dc..8e3fe4827e 100644 --- a/server-ce/test/editor.spec.ts +++ b/server-ce/test/editor.spec.ts @@ -10,11 +10,14 @@ import { v4 as uuid } from 'uuid' import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry' import { prepareWaitForNextCompileSlot } from './helpers/compile' +const USER = 'user@example.com' +const COLLABORATOR = 'collaborator@example.com' + describe('editor', () => { if (isExcludedBySharding('PRO_DEFAULT_1')) return startWith({ pro: true }) - ensureUserExists({ email: 'user@example.com' }) - ensureUserExists({ email: 'collaborator@example.com' }) + ensureUserExists({ email: USER }) + ensureUserExists({ email: COLLABORATOR }) let projectName: string let projectId: string @@ -22,7 +25,7 @@ describe('editor', () => { let waitForCompileRateLimitCoolOff: (fn: () => void) => void beforeWithReRunOnTestRetry(function () { projectName = `project-${uuid()}` - login('user@example.com') + login(USER) createProject(projectName, { type: 'Example project', open: false }).then( id => (projectId = id) ) @@ -31,7 +34,7 @@ describe('editor', () => { }) beforeEach(() => { - login('user@example.com') + login(USER) waitForCompileRateLimitCoolOff(() => { openProjectById(projectId) }) @@ -40,9 +43,16 @@ describe('editor', () => { describe('spelling', function () { function changeSpellCheckLanguageTo(lng: string) { cy.log(`change project language to '${lng}'`) - cy.get('button').contains('Menu').click() - cy.get('select[id=settings-menu-spellCheckLanguage]').select(lng) - cy.get('[id="left-menu"]').type('{esc}') // close left menu + cy.findByRole('navigation', { + name: 'Project actions', + }) + .findByRole('button', { name: 'Menu' }) + .click() + + cy.findByRole('dialog').within(() => { + cy.findByLabelText('Spell check').select(lng) + }) + cy.get('body').type('{esc}') } afterEach(function () { @@ -70,20 +80,28 @@ describe('editor', () => { cy.findByText(word).should('not.have.class', 'ol-cm-spelling-error') cy.log('remove word from dictionary') - cy.get('button').contains('Menu').click() - cy.get('button#dictionary-settings').contains('Edit').click() - cy.get('[id="dictionary-modal"]').within(() => { + cy.findByRole('navigation', { + name: 'Project actions', + }) + .findByRole('button', { name: 'Menu' }) + .click() + cy.findByRole('dialog').within(() => { + cy.findByLabelText('Dictionary').click() + }) + cy.findByTestId('dictionary-modal').within(() => { cy.findByText(word) .parent() - .within(() => cy.get('button').click()) + .within(() => + cy.findByRole('button', { name: 'Remove from dictionary' }).click() + ) // the modal has 2 close buttons, this ensures the one with the visible label is // clicked, otherwise it would need `force: true` - cy.get('.btn').contains('Close').click() + cy.contains('button', /close/i).click() }) cy.log('close left panel') - cy.get('[id="left-menu"]').type('{esc}') + cy.findByTestId('left-menu').type('{esc}') cy.log('rewrite word to force spelling error') cy.get('.cm-line').type('{selectAll}{del}' + word + '{enter}') @@ -94,7 +112,11 @@ describe('editor', () => { describe('editor', () => { it('renders jpg', () => { - cy.findByTestId('file-tree').findByText('frog.jpg').click() + cy.findByRole('navigation', { + name: 'Project files and outline', + }) + .findByRole('treeitem', { name: 'frog.jpg' }) + .click() cy.get('[alt="frog.jpg"]') .should('be.visible') .and('have.prop', 'naturalWidth') @@ -108,7 +130,7 @@ describe('editor', () => { force: true, }) cy.get('button').contains('𝜉').click() - cy.findByRole('textbox', { name: /Source Editor editing/i }).should( + cy.findByRole('textbox', { name: 'Source Editor editing' }).should( 'contain.text', '\\xi' ) @@ -120,23 +142,27 @@ describe('editor', () => { describe('add new file to project', () => { beforeEach(() => { - cy.get('button').contains('New file').click({ force: true }) + cy.findByRole('button', { name: 'New file' }).click() }) testNewFileUpload() it('should not display import from URL', () => { - cy.findByText('From external URL').should('not.exist') + cy.findByRole('button', { name: 'From external URL' }).should('not.exist') }) }) describe('left menu', () => { beforeEach(() => { - cy.get('button').contains('Menu').click() + cy.findByRole('navigation', { + name: 'Project actions', + }) + .findByRole('button', { name: 'Menu' }) + .click() }) it('can download project sources', () => { - cy.get('a').contains('Source').click() + cy.findByRole('link', { name: 'Source' }).click() const zipName = projectName.replaceAll('-', '_') cy.task('readFileInZip', { pathToZip: `cypress/downloads/${zipName}.zip`, @@ -146,10 +172,12 @@ describe('editor', () => { it('can download project PDF', () => { cy.log('ensure project is compiled') - cy.get('.pdf-viewer').should('contain.text', 'Your Paper') - - cy.get('.nav-downloads').within(() => { - cy.findByText('PDF').click() + cy.findByRole('region', { name: 'PDF preview and logs' }).should( + 'contain.text', + 'Your Paper' + ) + cy.findByRole('dialog').within(() => { + cy.findByRole('link', { name: 'PDF' }).click() const pdfName = projectName.replaceAll('-', '_') cy.task('readPdf', `cypress/downloads/${pdfName}.pdf`).should( 'contain', @@ -160,11 +188,13 @@ describe('editor', () => { it('word count', () => { cy.log('ensure project is compiled') - cy.get('.pdf-viewer').should('contain.text', 'Your Paper') + cy.findByRole('region', { name: 'PDF preview and logs' }).should( + 'contain.text', + 'Your Paper' + ) + cy.findByRole('button', { name: 'Word Count' }).click() - cy.findByText('Word Count').click() - - cy.get('#word-count-modal').within(() => { + cy.findByTestId('word-count-modal').within(() => { cy.findByText('Total Words:') cy.findByText('607') cy.findByText('Headers:') @@ -179,51 +209,73 @@ describe('editor', () => { describe('layout selector', () => { it('show editor only and switch between editor and pdf', () => { - cy.get('.pdf-viewer').should('be.visible') + cy.findByRole('region', { name: 'PDF preview and logs' }).should( + 'be.visible' + ) cy.get('.cm-editor').should('be.visible') - cy.findByText('Layout').click() - cy.findByText('Editor only').click() + cy.findByRole('button', { name: 'Layout' }).click() + cy.findByRole('menu').within(() => { + cy.findByRole('menuitem', { name: /Editor only/ }).click() + }) - cy.get('.pdf-viewer').should('not.be.visible') + cy.findByRole('region', { name: 'PDF preview and logs' }).should( + 'not.be.visible' + ) cy.get('.cm-editor').should('be.visible') - cy.findByText('Switch to PDF').click() + cy.findByRole('button', { name: 'Switch to PDF' }).click() - cy.get('.pdf-viewer').should('be.visible') + cy.findByRole('region', { name: 'PDF preview and logs' }).should( + 'be.visible' + ) cy.get('.cm-editor').should('not.be.visible') - cy.findByText('Switch to editor').click() + cy.findByRole('button', { name: 'Switch to editor' }).click() - cy.get('.pdf-viewer').should('not.be.visible') + cy.findByRole('region', { name: 'PDF preview and logs' }).should( + 'not.be.visible' + ) cy.get('.cm-editor').should('be.visible') }) it('show PDF only and go back to Editor & PDF', () => { - cy.get('.pdf-viewer').should('be.visible') + cy.findByRole('region', { name: 'PDF preview and logs' }).should( + 'be.visible' + ) cy.get('.cm-editor').should('be.visible') - cy.findByText('Layout').click() - cy.findByText('PDF only').click() + cy.findByRole('button', { name: 'Layout' }).click() + cy.findByRole('menu').within(() => { + cy.findByRole('menuitem', { name: /PDF only/ }).click() + }) - cy.get('.pdf-viewer').should('be.visible') + cy.findByRole('region', { name: 'PDF preview and logs' }).should( + 'be.visible' + ) cy.get('.cm-editor').should('not.be.visible') - cy.findByText('Layout').click() - cy.findByText('Editor & PDF').click() + cy.findByRole('button', { name: 'Layout' }).click() + cy.findByRole('menu').within(() => { + cy.findByRole('menuitem', { name: 'Editor & PDF' }).click() + }) - cy.get('.pdf-viewer').should('be.visible') + cy.findByRole('region', { name: 'PDF preview and logs' }).should( + 'be.visible' + ) cy.get('.cm-editor').should('be.visible') }) it('PDF in a separate tab (tests editor only)', () => { - cy.get('.pdf-viewer').should('be.visible') + cy.findByTestId('pdf-viewer').should('be.visible') cy.get('.cm-editor').should('be.visible') - cy.findByText('Layout').click() - cy.findByText('PDF in separate tab').click() + cy.findByRole('button', { name: 'Layout' }).click() + cy.findByRole('menu').within(() => { + cy.findByRole('menuitem', { name: 'PDF in separate tab' }).click() + }) - cy.get('.pdf-viewer').should('not.exist') + cy.findByTestId('pdf-viewer').should('not.exist') cy.get('.cm-editor').should('be.visible') }) }) diff --git a/server-ce/test/filestore-migration.spec.ts b/server-ce/test/filestore-migration.spec.ts index ed55c71986..43b8528d2e 100644 --- a/server-ce/test/filestore-migration.spec.ts +++ b/server-ce/test/filestore-migration.spec.ts @@ -106,7 +106,7 @@ describe('filestore migration', function () { name: /Create First Project|New Project/, }).click() cy.findByRole('link', { name: 'Example Project' }).click() - cy.findByLabelText(/Project name/i).type(projectName) + cy.findByLabelText('Project name').type(projectName) cy.findByRole('button', { name: 'Create' }).click() cy.url() .should('match', /\/project\/[a-fA-F0-9]{24}/) diff --git a/server-ce/test/git-bridge.spec.ts b/server-ce/test/git-bridge.spec.ts index 53b1bae54c..4d6a069416 100644 --- a/server-ce/test/git-bridge.spec.ts +++ b/server-ce/test/git-bridge.spec.ts @@ -14,6 +14,8 @@ import http from 'isomorphic-git/http/web' import LightningFS from '@isomorphic-git/lightning-fs' import { throttledRecompile } from './helpers/compile' +const USER = 'user@example.com' + describe('git-bridge', function () { const ENABLED_VARS = { GIT_BRIDGE_ENABLED: 'true', @@ -35,18 +37,21 @@ describe('git-bridge', function () { pro: true, vars: ENABLED_VARS, }) - ensureUserExists({ email: 'user@example.com' }) + ensureUserExists({ email: USER }) function clearAllTokens() { - cy.get('button.linking-git-bridge-revoke-button').each(el => { - cy.wrap(el).click() - cy.findByText('Delete token').click() - }) + cy.findAllByRole('button', { name: 'Remove' }) + .not('[disabled]') + .each($button => { + cy.wrap($button).click() + cy.findByRole('button', { name: 'Delete token' }).click() + }) + cy.findByRole('dialog').should('not.exist') } function maybeClearAllTokens() { cy.visit('/user/settings') - cy.findByText('Git integration') + cy.findByRole('heading', { name: 'Git integration' }) cy.get('button') .contains(/Generate token|Add another token/) .then(btn => { @@ -57,66 +62,83 @@ describe('git-bridge', function () { } beforeEach(function () { - login('user@example.com') + login(USER) }) it('should render the git-bridge UI in the settings', () => { maybeClearAllTokens() cy.visit('/user/settings') - cy.findByText('Git integration') - cy.get('button').contains('Generate token').click() - cy.get('code') + cy.findByRole('heading', { name: 'Git integration' }) + 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.get('@newToken').then(token => { // There can be more than one token with the same prefix when retrying cy.findAllByText( - `${token.text().slice(0, 'olp_1234'.length)}${'*'.repeat(12)}` + `${token.slice(0, 'olp_1234'.length)}${'*'.repeat(12)}` ).should('have.length.at.least', 1) }) - cy.get('button').contains('Generate token').should('not.exist') - cy.get('button').contains('Add another token').should('exist') + cy.findByRole('button', { + name: 'Git integration Generate token', + }).should('not.exist') + cy.findByRole('button', { name: 'Add another token' }).should('exist') clearAllTokens() - cy.get('button').contains('Generate token').should('exist') - cy.get('button').contains('Add another token').should('not.exist') + cy.findByRole('button', { + name: 'Git integration Generate token', + }).should('exist') + cy.findByRole('button', { name: 'Add another token' }).should('not.exist') }) it('should render the git-bridge UI in the editor', function () { maybeClearAllTokens() createProject('git').as('projectId') cy.findByRole('navigation', { - name: /Project actions/i, + name: 'Project actions', }) - .findByRole('button', { name: /Menu/i }) + .findByRole('button', { name: 'Menu' }) .click() - cy.findByText('Sync') - cy.findByText('Git').click() + cy.findByTestId('left-menu').within(() => { + cy.findByRole('heading', { name: 'Sync' }) + cy.findByRole('button', { name: 'Git' }).click() + }) cy.findByTestId('git-bridge-modal').within(() => { cy.get('@projectId').then(id => { - cy.get('code').contains(`git clone ${gitURL(id.toString())}`) + cy.findByLabelText('Git clone project command').contains( + `git clone ${gitURL(id.toString())}` + ) }) cy.findByRole('button', { - name: /generate token/i, + name: 'Generate token', }).click() - cy.get('code').contains(/olp_[a-zA-Z0-9]{16}/) + cy.findByLabelText('Git authentication token').contains( + /olp_[a-zA-Z0-9]{16}/ + ) }) // Re-open cy.url().then(url => cy.visit(url)) cy.findByRole('navigation', { - name: /Project actions/i, + name: 'Project actions', }) - .findByRole('button', { name: /Menu/i }) + .findByRole('button', { name: 'Menu' }) .click() - cy.findByText('Git').click() + cy.findByTestId('left-menu').within(() => { + cy.findByRole('button', { name: 'Git' }).click() + }) cy.findByTestId('git-bridge-modal').within(() => { cy.get('@projectId').then(id => { cy.get('code').contains(`git clone ${gitURL(id.toString())}`) }) - cy.findByText('Generate token').should('not.exist') + cy.findByRole('button', { + name: 'Generate token', + }).should('not.exist') cy.findByText(/generate a new one in Account settings/) - cy.findByText('Go to settings') + cy.findByRole('link', { name: 'Go to settings' }) .should('have.attr', 'target', '_blank') .and('have.attr', 'href', '/user/settings') }) @@ -197,18 +219,23 @@ describe('git-bridge', function () { const recompile = throttledRecompile() cy.findByRole('navigation', { - name: /Project actions/i, + name: 'Project actions', }) - .findByRole('button', { name: /Menu/i }) + .findByRole('button', { name: 'Menu' }) .click() - cy.findByText('Sync') - cy.findByText('Git').click() + cy.findByTestId('left-menu').within(() => { + cy.findByRole('heading', { name: 'Sync' }) + cy.findByRole('button', { name: 'Git' }).click() + }) cy.get('@projectId').then(projectId => { cy.findByTestId('git-bridge-modal').within(() => { - cy.get('code').contains(`git clone ${gitURL(projectId.toString())}`) + cy.findByLabelText('Git clone project command').contains( + `git clone ${gitURL(projectId.toString())}` + ) }) + cy.findByRole('heading', { name: 'Clone with Git' }) cy.findByRole('button', { - name: /generate token/i, + name: 'Generate token', }).click() cy.get('code') .contains(/olp_[a-zA-Z0-9]{16}/) @@ -255,8 +282,8 @@ describe('git-bridge', function () { }, } const authorOptions = { - author: { name: 'user', email: 'user@example.com' }, - committer: { name: 'user', email: 'user@example.com' }, + author: { name: 'user', email: USER }, + committer: { name: 'user', email: USER }, } const mainTex = `${dir}/main.tex` @@ -372,24 +399,26 @@ Hello world }) function checkDisabled() { - ensureUserExists({ email: 'user@example.com' }) + ensureUserExists({ email: USER }) it('should not render the git-bridge UI in the settings', () => { - login('user@example.com') + login(USER) cy.visit('/user/settings') - cy.findByText('Git integration').should('not.exist') + cy.findByRole('heading', { name: 'Git integration' }).should('not.exist') }) it('should not render the git-bridge UI in the editor', function () { - login('user@example.com') + login(USER) createProject('maybe git') cy.findByRole('navigation', { - name: /Project actions/i, + name: 'Project actions', }) - .findByRole('button', { name: /Menu/i }) + .findByRole('button', { name: 'Menu' }) .click() - cy.findByText('Word Count') // wait for lazy loading - cy.findByText('Sync').should('not.exist') - cy.findByText('Git').should('not.exist') + cy.findByTestId('left-menu').within(() => { + cy.findByRole('button', { name: 'Word Count' }) // wait for lazy loading + cy.findByRole('heading', { name: 'Sync' }).should('not.exist') + cy.findByRole('button', { name: 'Git' }).should('not.exist') + }) }) } diff --git a/server-ce/test/helpers/compile.ts b/server-ce/test/helpers/compile.ts index d41e43221f..742cc83b92 100644 --- a/server-ce/test/helpers/compile.ts +++ b/server-ce/test/helpers/compile.ts @@ -32,7 +32,7 @@ export function prepareWaitForNextCompileSlot() { queueReset() triggerCompile() cy.log('Wait for compile to finish') - cy.findByText('Recompile').should('be.visible') + cy.findByRole('button', { name: 'Recompile' }).should('be.visible') }) } function recompile() { diff --git a/server-ce/test/helpers/project.ts b/server-ce/test/helpers/project.ts index 7f15b959e8..107cca0d95 100644 --- a/server-ce/test/helpers/project.ts +++ b/server-ce/test/helpers/project.ts @@ -2,11 +2,13 @@ import { login } from './login' import { openEmail } from './email' import { v4 as uuid } from 'uuid' +export const NEW_PROJECT_BUTTON_MATCHER = /new project/i + export function createProject( name: string, { type = 'Blank project', - newProjectButtonMatcher = /new project/i, + newProjectButtonMatcher = NEW_PROJECT_BUTTON_MATCHER, open = true, }: { type?: 'Blank project' | 'Example project' @@ -41,7 +43,7 @@ export function createProject( cy.findAllByText(type, { exact: false }).first().click() cy.findByRole('dialog').within(() => { cy.get('input').type(name) - cy.findByText('Create').click() + cy.findByRole('button', { name: 'Create' }).click() }) if (open) { cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/) @@ -104,7 +106,7 @@ function shareProjectByEmail( level: 'Viewer' | 'Editor' ) { openProjectByName(projectName) - cy.findByText('Share').click() + cy.findByRole('button', { name: 'Share' }).click() cy.findByRole('dialog').within(() => { cy.findByLabelText('Add email address', { selector: 'input' }).type( `${email},` @@ -115,10 +117,10 @@ function shareProjectByEmail( cy.findByTestId('add-collaborator-select') .click() .then(() => { - cy.findByText(level).click() + cy.findByRole('option', { name: level }).click() }) }) - cy.findByText('Invite').click({ force: true }) + cy.findByRole('button', { name: 'Invite' }).click() cy.findByText('Invite not yet accepted.') }) } @@ -203,7 +205,9 @@ export function waitForMainDocToLoad() { export function openFile(fileName: string, waitFor: string) { // force: The file-tree pane is too narrow to display the full name. - cy.findByTestId('file-tree').findByText(fileName).click({ force: true }) + cy.findByRole('navigation', { name: 'Project files and outline' }) + .findByRole('treeitem', { name: fileName }) + .click({ force: true }) // wait until we've switched to the selected file cy.findByText('Loading…').should('not.exist') @@ -221,7 +225,9 @@ export function createNewFile() { cy.findByText('Create').click() }) // force: The file-tree pane is too narrow to display the full name. - cy.findByTestId('file-tree').findByText(fileName).click({ force: true }) + cy.findByTestId('file-tree') + .findByRole('treeitem', { name: fileName }) + .click({ force: true }) // wait until we've switched to the newly created empty file cy.findByText('Loading…').should('not.exist') diff --git a/server-ce/test/history.spec.ts b/server-ce/test/history.spec.ts index 485f7c8c58..2f83c9890b 100644 --- a/server-ce/test/history.spec.ts +++ b/server-ce/test/history.spec.ts @@ -13,29 +13,49 @@ describe('History', function () { function addLabel(name: string) { cy.log(`add label ${JSON.stringify(name)}`) - cy.findByText('Labels').click() - cy.findAllByTestId('history-version-details') - .first() - .within(() => { - cy.get('button').click() // TODO: add test-id or aria-label - cy.findByText('Label this version').click() + // The input is not clickable due to being visually hidden, click its label instead + cy.findByRole('complementary', { + name: 'Project history and labels', + }).within(() => { + cy.findByRole('group', { + name: 'Show all of the project history or only labelled versions.', + }).within(() => { + cy.findByText('Labels').click() }) + cy.findByRole('radio', { name: 'Labels' }).should('be.checked') + cy.findByRole('radio', { name: 'All history' }).should('not.be.checked') + cy.findAllByTestId('history-version-details') + .first() + .within(() => { + cy.findByRole('button', { name: 'More actions' }).click() + cy.findByRole('menuitem', { name: 'Label this version' }).click() + }) + }) cy.findByRole('dialog').within(() => { - cy.findByLabelText(/New label name/i) - .as('input') - .type(`${name}{enter}`) + cy.findByLabelText('New label name').type(`${name}{enter}`) }) } function downloadVersion(name: string) { cy.log(`download version ${JSON.stringify(name)}`) - cy.findByText('Labels').click() - cy.findByText(name) - .closest('[data-testid="history-version-details"]') - .within(() => { - cy.get('.history-version-dropdown-menu-btn').click() - cy.findByText('Download this version').click() + // The input is not clickable due to being visually hidden, click its label instead + cy.findByRole('complementary', { + name: 'Project history and labels', + }).within(() => { + cy.findByRole('group', { + name: 'Show all of the project history or only labelled versions.', + }).within(() => { + cy.findByText('Labels').click() }) + cy.findByRole('radio', { name: 'Labels' }).should('be.checked') + cy.findByRole('radio', { name: 'All history' }).should('not.be.checked') + cy.findByText(name) + .closest('[data-testid="history-version-details"]') + .within(() => { + cy.findByRole('button', { name: 'More actions' }).click() + cy.findByRole('menuitem', { name: 'Download this version' }).click() + }) + }) } const CLASS_ADDITION = 'ol-cm-addition-marker' @@ -46,11 +66,13 @@ describe('History', function () { const recompile = throttledRecompile() cy.log('add content, including a line that will get removed soon') - cy.findByText('\\maketitle').parent().click() - cy.findByText('\\maketitle').parent().type('\n% added') - cy.findByText('\\maketitle').parent().type('\n% to be removed') + cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => { + cy.findByText('\\maketitle').parent().click() + cy.findByText('\\maketitle').parent().type('\n% added') + cy.findByText('\\maketitle').parent().type('\n% to be removed') + }) recompile() - cy.findByText('History').click() + cy.findByRole('button', { name: 'History' }).click() cy.log('expect to see additions in history') cy.get('.document-diff-container').within(() => { @@ -61,10 +83,10 @@ describe('History', function () { addLabel('Before removal') cy.log('remove content') - cy.findByText('Back to editor').click() + cy.findByRole('button', { name: 'Back to editor' }).click() cy.findByText('% to be removed').parent().type('{end}{shift}{upArrow}{del}') recompile() - cy.findByText('History').click() + cy.findByRole('button', { name: 'History' }).click() cy.log('expect to see annotation for newly removed content in history') cy.get('.document-diff-container').within(() => { @@ -75,18 +97,32 @@ describe('History', function () { addLabel('After removal') cy.log('add more content after labeling') - cy.findByText('Back to editor').click() - cy.findByText('\\maketitle').parent().click() - cy.findByText('\\maketitle').parent().type('\n% more') + cy.findByRole('button', { name: 'Back to editor' }).click() + cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => { + cy.findByText('\\maketitle').parent().click() + cy.findByText('\\maketitle').parent().type('\n% more') + }) recompile() cy.log('compare non current versions') - cy.findByText('History').click() - cy.findByText('Labels').click() - cy.findAllByTestId('compare-icon-version').last().click() - cy.findAllByTestId('compare-icon-version').filter(':visible').click() - cy.findByText('Compare up to this version').click() - + cy.findByRole('button', { name: 'History' }).click() + // The input is not clickable due to being visually hidden, click its label instead + cy.findByRole('complementary', { + name: 'Project history and labels', + }).within(() => { + cy.findByRole('group', { + name: 'Show all of the project history or only labelled versions.', + }).within(() => { + cy.findByText('Labels').click() + }) + cy.findByRole('radio', { name: 'Labels' }).should('be.checked') + cy.findByRole('radio', { name: 'All history' }).should('not.be.checked') + cy.findAllByTestId('compare-icon-version').last().click() + cy.findAllByTestId('compare-icon-version').filter(':visible').click() + cy.findByRole('menuitem', { + name: 'Compare up to this version', + }).click() + }) cy.log( 'expect to see annotation for removed content between the two versions' ) diff --git a/server-ce/test/learn-wiki.spec.ts b/server-ce/test/learn-wiki.spec.ts index c0cc8729fe..1d8ce44812 100644 --- a/server-ce/test/learn-wiki.spec.ts +++ b/server-ce/test/learn-wiki.spec.ts @@ -27,40 +27,49 @@ describe('LearnWiki', function () { it('should add a documentation entry to the nav bar', () => { login(REGULAR_USER) cy.visit('/project') - cy.get('nav').findByText('Documentation') + cy.findByRole('menuitem', { name: 'Documentation' }).should( + 'have.attr', + 'href', + '/learn' + ) }) it('should display a tutorial link in the welcome page', () => { login(WITHOUT_PROJECTS_USER) cy.visit('/project') - cy.findByText(LABEL_LEARN_LATEX) + 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') + }) }) it('should render wiki page', () => { login(REGULAR_USER) cy.visit(UPLOADING_A_PROJECT_URL) // Wiki content - cy.get('.page').findByText('Uploading a project') - cy.get('.page').contains(/how to create an Overleaf project/) - cy.get('img[alt="Creating a new project on Overleaf"]') + cy.findByRole('heading', { name: 'Uploading a project' }) + cy.contains(/how to create an Overleaf project/) + cy.findByRole('img', { name: 'Creating a new project on Overleaf' }) .should('be.visible') .and((el: any) => { expect(el[0].naturalWidth, 'renders image').to.be.greaterThan(0) }) // Wiki navigation - cy.get('.contents').findByText('Copying a project') + cy.findByRole('link', { name: 'Copying a project' }).should('exist') }) it('should navigate back and forth', function () { login(REGULAR_USER) cy.visit(COPYING_A_PROJECT_URL) - cy.get('.page').findByText('Copying a project') - cy.get('.contents').findByText('Uploading a project').click() + cy.findByRole('heading', { name: 'Copying a project' }) + cy.findByRole('link', { name: 'Uploading a project' }).click() cy.url().should('contain', UPLOADING_A_PROJECT_URL) - cy.get('.page').findByText('Uploading a project') - cy.get('.contents').findByText('Copying a project').click() + cy.findByRole('heading', { name: 'Uploading a project' }) + cy.findByRole('link', { name: 'Copying a project' }).click() cy.url().should('contain', COPYING_A_PROJECT_URL) - cy.get('.page').findByText('Copying a project') + cy.findByRole('heading', { name: 'Copying a project' }) }) }) diff --git a/server-ce/test/project-list.spec.ts b/server-ce/test/project-list.spec.ts index 998fcf9ffb..443a3c41af 100644 --- a/server-ce/test/project-list.spec.ts +++ b/server-ce/test/project-list.spec.ts @@ -21,8 +21,10 @@ describe('Project List', () => { it("'Import from GitHub' is not displayed in the welcome page", () => { login(WITHOUT_PROJECTS_USER) cy.visit('/project') - cy.findByText('Create a new project').click() - cy.findByText(/Import from GitHub/i).should('not.exist') + cy.findByRole('button', { name: 'Create a new project' }).click() + cy.findByRole('menuitem', { name: 'Import from GitHub' }).should( + 'not.exist' + ) }) }) @@ -66,42 +68,50 @@ describe('Project List', () => { it('can assign and remove tags to projects', () => { const tagName = uuid().slice(0, 7) // long tag names are truncated in the UI, which affects selectors cy.log('select project') - cy.get(`[aria-label="Select ${projectName}"]`).click() + cy.findByRole('checkbox', { name: `Select ${projectName}` }).check() cy.log('add tag to project') - cy.get('button[aria-label="Tags"]').click() - cy.findByText('Create new tag').click() - cy.get('input[name="new-tag-form-name"]').type(`${tagName}{enter}`) - cy.get(`button[aria-label="Select tag ${tagName}"]`) // tag label in project row + cy.findByRole('button', { name: 'Tags' }).click() + cy.findByRole('menuitem', { name: 'Create new tag' }).click() + cy.findByRole('dialog').within(() => { + cy.findByRole('heading', { name: 'Create new tag' }) + cy.findByLabelText('New tag name').type(`${tagName}{enter}`) + }) + cy.findByRole('button', { name: `Select tag ${tagName}` }) // tag label in project row cy.log('remove tag') - cy.get(`button[aria-label="Remove tag ${tagName}"]`) + cy.findByRole('button', { name: `Remove tag ${tagName}` }) .first() - .click({ force: true }) - cy.get(`button[aria-label="Select tag ${tagName}"]`).should('not.exist') + .click() + cy.findByRole('button', { name: `Select tag ${tagName}` }).should( + 'not.exist' + ) }) it('can filter by tag', () => { cy.log('create a separate project to filter') const nonTaggedProjectName = `project-${uuid()}` - login(REGULAR_USER) createProject(nonTaggedProjectName, { open: false }) cy.log('select project') - cy.get(`[aria-label="Select ${projectName}"]`).click() + cy.findByRole('checkbox', { name: `Select ${projectName}` }).check() cy.log('add tag to project') const tagName = uuid().slice(0, 7) // long tag names are truncated in the UI, which affects selectors - cy.get('button[aria-label="Tags"]').click() - cy.findByText('Create new tag').click() - cy.get('input[name="new-tag-form-name"]').type(`${tagName}{enter}`) + cy.findByRole('button', { name: 'Tags' }).click() + cy.findByRole('menuitem', { name: 'Create new tag' }).click() + + cy.findByRole('dialog').within(() => { + cy.findByRole('heading', { name: 'Create new tag' }) + cy.findByLabelText('New tag name').type(`${tagName}{enter}`) + }) cy.log( 'check the non-tagged project is filtered out after clicking the tag' ) - cy.findByText(nonTaggedProjectName).should('exist') - cy.get('button').contains(tagName).click({ force: true }) - cy.findByText(nonTaggedProjectName).should('not.exist') + cy.findByRole('link', { name: nonTaggedProjectName }).should('exist') + cy.findByRole('button', { name: `Select tag ${tagName}` }).click() + cy.findByRole('link', { name: nonTaggedProjectName }).should('not.exist') }) }) }) diff --git a/server-ce/test/project-sharing.spec.ts b/server-ce/test/project-sharing.spec.ts index 2a90df7368..003ee4f334 100644 --- a/server-ce/test/project-sharing.spec.ts +++ b/server-ce/test/project-sharing.spec.ts @@ -43,7 +43,7 @@ describe('Project Sharing', function () { createProject(projectName) // Add chat message - cy.findByText('Chat').click() + 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( @@ -61,11 +61,11 @@ describe('Project Sharing', function () { function expectContentReadOnlyAccess() { cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/) - cy.findByRole('textbox', { name: /Source Editor editing/i }).should( + cy.findByRole('textbox', { name: 'Source Editor editing' }).should( 'contain.text', '\\maketitle' ) - cy.findByRole('textbox', { name: /Source Editor editing/i }).should( + cy.findByRole('textbox', { name: 'Source Editor editing' }).should( 'have.attr', 'contenteditable', 'false' @@ -77,12 +77,12 @@ describe('Project Sharing', function () { cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/) const recompile = throttledRecompile() // wait for the editor to finish loading - cy.findByRole('textbox', { name: /Source Editor editing/i }).should( + cy.findByRole('textbox', { name: 'Source Editor editing' }).should( 'contain.text', '\\maketitle' ) // the editor should be writable - cy.findByRole('textbox', { name: /Source Editor editing/i }).should( + cy.findByRole('textbox', { name: 'Source Editor editing' }).should( 'have.attr', 'contenteditable', 'true' @@ -90,14 +90,20 @@ describe('Project Sharing', function () { cy.findByText('\\maketitle').parent().click() cy.findByText('\\maketitle').parent().type(`\n\\section{{}${section}}`) // should have written - cy.findByRole('textbox', { name: /Source Editor editing/i }).should( + cy.findByRole('textbox', { name: 'Source Editor editing' }).should( 'contain.text', `\\section{${section}}` ) // check PDF recompile() - cy.get('.pdf-viewer').should('contain.text', projectName) - cy.get('.pdf-viewer').should('contain.text', section) + cy.findByRole('region', { name: 'PDF preview and logs' }).within(() => { + cy.findByLabelText(/Page.*1/i).should('be.visible') + cy.findByText(projectName).should('be.visible') + }) + cy.findByRole('region', { name: 'PDF preview and logs' }).within(() => { + cy.findByLabelText(/Page.*1/i).should('be.visible') + cy.contains(section) + }) } function expectNoAccess() { @@ -117,7 +123,7 @@ describe('Project Sharing', function () { } function expectChatAccess() { - cy.findByText('Chat').click() + cy.findByRole('button', { name: 'Chat' }).click() cy.findByText('New Chat Message') } @@ -132,17 +138,17 @@ describe('Project Sharing', function () { } function expectNoChatAccess() { - cy.findByText('Layout') // wait for lazy loading - cy.findByText('Chat').should('not.exist') + cy.findByRole('button', { name: 'Layout' }) // wait for lazy loading + cy.findByRole('button', { name: 'Chat' }).should('not.exist') } function expectNoHistoryAccess() { - cy.findByText('Layout') // wait for lazy loading - cy.findByText('History').should('not.exist') + cy.findByRole('button', { name: 'Layout' }) // wait for lazy loading + cy.findByRole('button', { name: 'History' }).should('not.exist') } function expectCommentAccess() { - cy.findByRole('textbox', { name: /Source Editor editing/i }).should( + cy.findByRole('textbox', { name: 'Source Editor editing' }).should( 'contain.text', '\\maketitle' ) @@ -151,11 +157,11 @@ describe('Project Sharing', function () { cy.findByRole('button', { name: 'Add comment' }).should('be.visible') - cy.findByRole('textbox', { name: /Source Editor editing/i }).click() + cy.findByRole('textbox', { name: 'Source Editor editing' }).click() } function expectNoCommentAccess() { - cy.findByRole('textbox', { name: /Source Editor editing/i }).should( + cy.findByRole('textbox', { name: 'Source Editor editing' }).should( 'contain.text', '\\maketitle' ) @@ -163,7 +169,7 @@ describe('Project Sharing', function () { cy.findByText('\\maketitle').parent().dblclick() cy.findByRole('button', { name: 'Add comment' }).should('not.exist') - cy.findByRole('textbox', { name: /Source Editor editing/i }).click() + cy.findByRole('textbox', { name: 'Source Editor editing' }).click() } function expectFullReadOnlyAccess() { @@ -200,12 +206,15 @@ describe('Project Sharing', function () { } function expectEditAuthoredAs(author: string) { - cy.findByText('History').click() - cy.findAllByTestId('history-version-metadata-users') - .first() - .should('contain.text', author) // might have other edits in the same group + cy.findByRole('button', { name: 'History' }).click() + cy.findByRole('complementary', { + name: 'Project history and labels', + }).within(() => { + cy.findAllByTestId('history-version-metadata-users') + .first() + .should('contain.text', author) // might have other edits in the same group + }) } - describe('via email', function () { const email = 'collaborator-email@example.com' ensureUserExists({ email }) diff --git a/server-ce/test/sandboxed-compiles.spec.ts b/server-ce/test/sandboxed-compiles.spec.ts index 06f516c765..8599809c7c 100644 --- a/server-ce/test/sandboxed-compiles.spec.ts +++ b/server-ce/test/sandboxed-compiles.spec.ts @@ -30,30 +30,35 @@ describe('SandboxedCompiles', function () { createProject('sandboxed') const recompile = throttledRecompile() cy.log('wait for compile') - cy.get('.pdf-viewer').should('contain.text', 'sandboxed') + cy.findByRole('region', { name: 'PDF preview and logs' }).should( + 'contain.text', + 'sandboxed' + ) cy.log('Check which compiler version was used, expect 2023') - cy.get('[aria-label="View logs"]').click() + cy.findByRole('button', { name: 'View logs' }).click() cy.findByText(/This is pdfTeX, Version .+ \(TeX Live 2023\) /) cy.log('Switch TeXLive version from 2023 to 2022') cy.findByRole('navigation', { - name: /Project actions/i, + name: 'Project actions', }) - .findByRole('button', { name: /Menu/i }) + .findByRole('button', { name: 'Menu' }) .click() - cy.findByText(LABEL_TEX_LIVE_VERSION) - .parent() - .findByText('2023') - .parent() - .select('2022') - cy.get('.left-menu-modal-backdrop').click() + cy.findByRole('dialog').within(() => { + cy.findByRole('option', { name: '2023' }).should('be.selected') + cy.findByRole('combobox', { name: LABEL_TEX_LIVE_VERSION }).select( + '2022' + ) + }) + cy.get('body').type('{esc}') + cy.findByRole('dialog').should('not.exist') cy.log('Trigger compile with other TeX Live version') recompile() cy.log('Check which compiler version was used, expect 2022') - cy.get('[aria-label="View logs"]').click() + cy.findByRole('button', { name: 'View logs' }).click() cy.findByText(/This is pdfTeX, Version .+ \(TeX Live 2022\) /) }) @@ -101,39 +106,43 @@ describe('SandboxedCompiles', function () { projectName = `Project ${uuid()}` createProject(projectName) const recompile = throttledRecompile() - cy.findByText('\\maketitle').parent().click() - cy.findByText('\\maketitle') - .parent() - .type( - `\n\\pagebreak\n\\section{{}Section A}\n\\pagebreak\n\\section{{}Section B}\n\\pagebreak` - ) + cy.findByRole('textbox', { name: 'Source Editor editing' }).within( + () => { + cy.findByText('\\maketitle').parent().click() + cy.findByText('\\maketitle') + .parent() + .type( + `\n\\pagebreak\n\\section{{}Section A}\n\\pagebreak\n\\section{{}Section B}\n\\pagebreak` + ) + } + ) recompile() cy.log('wait for pdf-rendering') - cy.get('.pdf-viewer').within(() => { - cy.findByText(projectName) - }) + cy.findByRole('region', { name: 'PDF preview and logs' }).findByText( + projectName + ) }) it('should sync to code', function () { cy.log('navigate to \\maketitle using double click in PDF') - cy.get('.pdf-viewer').within(() => { - cy.findByText(projectName).dblclick() - }) + cy.findByRole('region', { name: 'PDF preview and logs' }) + .findByText(projectName) + .dblclick() cy.get('.cm-activeLine').should('have.text', '\\maketitle') cy.log('navigate to Section A using double click in PDF') - cy.get('.pdf-viewer').within(() => { - cy.findByText('Section A').dblclick() - }) + cy.findByRole('region', { name: 'PDF preview and logs' }) + .findByText('Section A') + .dblclick() cy.get('.cm-activeLine').should('have.text', '\\section{Section A}') cy.log('navigate to Section B using arrow button') - cy.get('.pdfjs-viewer-inner') + cy.findByTestId('pdfjs-viewer-inner') .should('have.prop', 'scrollTop') .as('start') - cy.get('.pdf-viewer').within(() => { - cy.findByText('Section B').scrollIntoView() - }) + cy.findByRole('region', { name: 'PDF preview and logs' }) + .findByText('Section B') + .scrollIntoView() cy.get('@start').then((start: any) => { waitUntilScrollingFinished('.pdfjs-viewer-inner', start) }) @@ -141,21 +150,27 @@ describe('SandboxedCompiles', function () { // Cypress appears to click on a button that references a stale position. // Adding a cy.wait() statement is the most reliable "fix" so far :/ cy.wait(1000) - cy.get('[aria-label^="Go to PDF location in code"]').click() + cy.findByRole('button', { + name: 'Go to PDF location in code (Tip: double click on the PDF for best results)', + }).click() cy.get('.cm-activeLine').should('have.text', '\\section{Section B}') }) it('should sync to pdf', function () { cy.log('zoom in') - cy.findByText('45%').click() - cy.findByText('400%').click() + cy.findByRole('button', { name: /^\d+%$/ }).click() // TODO: ARIA label + cy.findByRole('menuitem', { name: '400%' }).click() cy.log('scroll to top') - cy.get('.pdfjs-viewer-inner').scrollTo('top') + cy.findByTestId('pdfjs-viewer-inner').scrollTo('top') waitUntilScrollingFinished('.pdfjs-viewer-inner', -1).as('start') cy.log('navigate to title') - cy.findByText('\\maketitle').parent().click() - cy.get('[aria-label="Go to code location in PDF"]').click() + cy.findByRole('textbox', { name: 'Source Editor editing' }).within( + () => { + cy.findByText('\\maketitle').parent().click() + } + ) + cy.findByRole('button', { name: 'Go to code location in PDF' }).click() cy.get('@start').then((start: any) => { waitUntilScrollingFinished('.pdfjs-viewer-inner', start) .as('title') @@ -163,10 +178,10 @@ describe('SandboxedCompiles', function () { }) cy.log('navigate to Section A') - cy.findByRole('textbox', { name: /Source Editor editing/i }).within( - () => cy.findByText('Section A').click() + cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => + cy.findByText('Section A').click() ) - cy.get('[aria-label="Go to code location in PDF"]').click() + cy.findByRole('button', { name: 'Go to code location in PDF' }).click() cy.get('@title').then((title: any) => { waitUntilScrollingFinished('.pdfjs-viewer-inner', title) .as('sectionA') @@ -174,10 +189,10 @@ describe('SandboxedCompiles', function () { }) cy.log('navigate to Section B') - cy.findByRole('textbox', { name: /Source Editor editing/i }).within( - () => cy.findByText('Section B').click() + cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => + cy.findByText('Section B').click() ) - cy.get('[aria-label="Go to code location in PDF"]').click() + cy.findByRole('button', { name: 'Go to code location in PDF' }).click() cy.get('@sectionA').then((title: any) => { waitUntilScrollingFinished('.pdfjs-viewer-inner', title) .as('sectionB') @@ -192,14 +207,18 @@ describe('SandboxedCompiles', function () { login('user@example.com') createProject('test-project') const recompile = throttledRecompile() - cy.findByText('\\maketitle').parent().click() - cy.findByText('\\maketitle') - .parent() - .type('\n\\fakeCommand{} \n\\section{{}Test Section}') + cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => { + cy.findByText('\\maketitle').parent().click() + cy.findByText('\\maketitle') + .parent() + .type('\n\\fakeCommand{} \n\\section{{}Test Section}') + }) recompile() recompile() - cy.get('.pdf-viewer').should('contain.text', 'Test Section') - cy.get('.logs-pane').should('not.contain.text', 'No PDF') + 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') }) } @@ -208,30 +227,32 @@ describe('SandboxedCompiles', function () { createProject('XeLaTeX') const recompile = throttledRecompile() cy.log('wait for compile') - cy.get('.pdf-viewer').should('contain.text', 'XeLaTeX') - + cy.findByRole('region', { name: 'PDF preview and logs' }).should( + 'contain.text', + 'XeLaTeX' + ) cy.log('Check which compiler was used, expect pdfLaTeX') - cy.get('[aria-label="View logs"]').click() + cy.findByRole('button', { name: 'View logs' }).click() cy.findByText(/This is pdfTeX/) cy.log('Switch compiler to from pdfLaTeX to XeLaTeX') cy.findByRole('navigation', { - name: /Project actions/i, + name: 'Project actions', }) - .findByRole('button', { name: /Menu/i }) + .findByRole('button', { name: 'Menu' }) .click() - cy.findByText('Compiler') - .parent() - .findByText('pdfLaTeX') - .parent() - .select('XeLaTeX') - cy.get('.left-menu-modal-backdrop').click() + cy.findByRole('dialog').within(() => { + cy.findByRole('option', { name: 'pdfLaTeX' }).should('be.selected') + cy.findByRole('combobox', { name: 'Compiler' }).select('XeLaTeX') + }) + cy.get('body').type('{esc}') + cy.findByRole('dialog').should('not.exist') cy.log('Trigger compile with other compiler') recompile() cy.log('Check which compiler was used, expect XeLaTeX') - cy.get('[aria-label="View logs"]').click() + cy.findByRole('button', { name: 'View logs' }).click() cy.findByText(/This is XeTeX/) }) } @@ -252,9 +273,9 @@ describe('SandboxedCompiles', function () { cy.log('Check that there is no TeX Live version toggle') cy.findByRole('navigation', { - name: /Project actions/i, + name: 'Project actions', }) - .findByRole('button', { name: /Menu/i }) + .findByRole('button', { name: 'Menu' }) .click() cy.findByText('Word Count') // wait for lazy loading cy.findByText(LABEL_TEX_LIVE_VERSION).should('not.exist') diff --git a/server-ce/test/templates.spec.ts b/server-ce/test/templates.spec.ts index 2d6287d0d8..2e61c67954 100644 --- a/server-ce/test/templates.spec.ts +++ b/server-ce/test/templates.spec.ts @@ -1,6 +1,6 @@ import { isExcludedBySharding, startWith } from './helpers/config' import { ensureUserExists, login } from './helpers/login' -import { createProject } from './helpers/project' +import { createProject, NEW_PROJECT_BUTTON_MATCHER } from './helpers/project' const WITHOUT_PROJECTS_USER = 'user-without-projects@example.com' const ADMIN_USER = 'admin@example.com' @@ -43,7 +43,9 @@ describe('Templates', () => { it('should show templates link on welcome page', () => { login(WITHOUT_PROJECTS_USER) cy.visit('/') - cy.findByText(LABEL_BROWSE_TEMPLATES).click() + cy.findByRole('link', { name: LABEL_BROWSE_TEMPLATES }) + .should('have.attr', 'href', '/templates') + .click() cy.url().should('match', /\/templates$/) }) @@ -56,9 +58,9 @@ describe('Templates', () => { createProject(name, { type: 'Example project' }).as('templateProjectId') cy.findByRole('navigation', { - name: /Project actions/i, + name: 'Project actions', }) - .findByRole('button', { name: /Menu/i }) + .findByRole('button', { name: 'Menu' }) .click() cy.findByText('Manage Template').click() @@ -139,9 +141,9 @@ describe('Templates', () => { cy.visit(`/project/${projectId}`) ) cy.findByRole('navigation', { - name: /Project actions/i, + name: 'Project actions', }) - .findByRole('button', { name: /Menu/i }) + .findByRole('button', { name: 'Menu' }) .click() cy.findByText('Manage Template').click() cy.findByText('Publish').click() @@ -168,9 +170,9 @@ describe('Templates', () => { cy.url().should('match', /\/project\/[a-f0-9]{24}$/) cy.get('.project-name').should('contain.text', 'Your Paper') // might have (1) suffix cy.findByRole('navigation', { - name: /Project actions/i, + name: 'Project actions', }) - .findByRole('button', { name: /Menu/i }) + .findByRole('button', { name: 'Menu' }) .click() cy.findByText('Word Count') // wait for lazy loading cy.findByText('Manage Template').should('not.exist') @@ -191,9 +193,9 @@ describe('Templates', () => { cy.visit(`/project/${projectId}`) ) cy.findByRole('navigation', { - name: /Project actions/i, + name: 'Project actions', }) - .findByRole('button', { name: /Menu/i }) + .findByRole('button', { name: 'Menu' }) .click() cy.findByText('Manage Template').click() cy.findByText('Unpublish') @@ -206,9 +208,9 @@ describe('Templates', () => { cy.visit(`/project/${projectId}`) ) cy.findByRole('navigation', { - name: /Project actions/i, + name: 'Project actions', }) - .findByRole('button', { name: /Menu/i }) + .findByRole('button', { name: 'Menu' }) .click() cy.findByText('Manage Template').click() cy.findByText('Unpublish').click() @@ -218,9 +220,7 @@ describe('Templates', () => { // check for template links, after creating the first project cy.visit('/') - cy.findAllByRole('button') - .contains(/new project/i) - .click() + cy.findAllByRole('button', { name: NEW_PROJECT_BUTTON_MATCHER }).click() cy.findAllByText('All Templates') .first() .parent() @@ -236,9 +236,9 @@ describe('Templates', () => { createProject('maybe templates') cy.findByRole('navigation', { - name: /Project actions/i, + name: 'Project actions', }) - .findByRole('button', { name: /Menu/i }) + .findByRole('button', { name: 'Menu' }) .click() cy.findByText('Word Count') // wait for lazy loading cy.findByText('Manage Template').should('not.exist') @@ -250,16 +250,14 @@ describe('Templates', () => { // check for template links, after creating the first project cy.visit('/') - cy.findAllByRole('button') - .contains(/new project/i) - .click() + cy.findAllByRole('button', { name: NEW_PROJECT_BUTTON_MATCHER }).click() cy.findAllByText('All Templates').should('not.exist') }) it('should not show templates link on welcome page', () => { login(WITHOUT_PROJECTS_USER) cy.visit('/') - cy.findByText(/new project/i) // wait for lazy loading + cy.findByText(NEW_PROJECT_BUTTON_MATCHER) // wait for lazy loading cy.findByText(LABEL_BROWSE_TEMPLATES).should('not.exist') }) } diff --git a/server-ce/test/upgrading.spec.ts b/server-ce/test/upgrading.spec.ts index 5edc260d5f..ace62c1386 100644 --- a/server-ce/test/upgrading.spec.ts +++ b/server-ce/test/upgrading.spec.ts @@ -56,9 +56,9 @@ describe('Upgrading', function () { cy.log('Trigger full flush') recompile() cy.findByRole('navigation', { - name: /Project Layout, Sharing, and Submission/i, + name: 'Project Layout, Sharing, and Submission', }) - .findByRole('button', { name: /Menu/i }) + .findByRole('button', { name: 'Menu' }) .click() cy.findByText('Source').click() cy.get('.left-menu-modal-backdrop').click({ force: true }) @@ -121,7 +121,7 @@ describe('Upgrading', function () { cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/) cy.findByRole('navigation', { - name: /Project actions/i, + name: 'Project actions', }).within(() => { cy.findByText(PROJECT_NAME) }) diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 11d23fe9ee..1e965a6a73 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -665,7 +665,7 @@ "git_bridge_modal_review_access": "", "git_bridge_modal_see_once": "", "git_bridge_modal_use_previous_token": "", - "git_clone_project": "", + "git_clone_project_command": "", "git_clone_this_project": "", "git_integration": "", "git_integration_info": "", diff --git a/services/web/frontend/js/features/settings/components/linking/integration-widget.tsx b/services/web/frontend/js/features/settings/components/linking/integration-widget.tsx index d38e79d92e..70fe6fdf57 100644 --- a/services/web/frontend/js/features/settings/components/linking/integration-widget.tsx +++ b/services/web/frontend/js/features/settings/components/linking/integration-widget.tsx @@ -121,7 +121,9 @@ function ActionButton({ titleId, }: ActionButtonProps) { const { t } = useTranslation() + const upgradeTextId = `${titleId}-upgrade` const linkTextId = `${titleId}-link` + const unlinkTextId = `${titleId}-unlink` if (!hasFeature) { return ( @@ -129,17 +131,19 @@ function ActionButton({ variant="primary" href="/user/subscription/plans" onClick={() => trackUpgradeClick(integration)} - aria-labelledby={`${titleId} ${linkTextId}`} + aria-labelledby={`${titleId} ${upgradeTextId}`} > - {t('upgrade')} + {t('upgrade')} ) } else if (linked) { return ( {t('unlink')} @@ -148,7 +152,12 @@ function ActionButton({ return ( <> {disabled ? ( - + {t('link')} ) : ( @@ -156,6 +165,8 @@ function ActionButton({ variant="secondary" href={linkPath} onClick={() => trackLinkingClick(integration)} + aria-labelledby={`${linkTextId} ${titleId}`} + id={linkTextId} > {t('link')} diff --git a/services/web/locales/en.json b/services/web/locales/en.json index c93e9cb013..241f61291f 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -868,7 +868,7 @@ "git_bridge_modal_review_access": "<0>You have review access to this project. This means you can pull from __appName__ but you can’t push any changes you make back to this project.", "git_bridge_modal_see_once": "You’ll only see this token once. To delete it or generate a new one, visit Account settings. For detailed instructions and troubleshooting, read our <0>help page.", "git_bridge_modal_use_previous_token": "If you’re prompted for a password, you can use a previously generated Git authentication token. Or you can generate a new one in Account settings. For more support, read our <0>help page.", - "git_clone_project": "Git clone project", + "git_clone_project_command": "Git clone project command", "git_clone_this_project": "Git clone this project.", "git_gitHub_dropbox_mendeley_papers_and_zotero_integrations": "Git, GitHub, Dropbox, Papers, Zotero, and Mendeley integrations", "git_integration": "Git integration", diff --git a/services/web/test/frontend/features/history/components/change-list.spec.tsx b/services/web/test/frontend/features/history/components/change-list.spec.tsx index e27291ff1e..a2928a8212 100644 --- a/services/web/test/frontend/features/history/components/change-list.spec.tsx +++ b/services/web/test/frontend/features/history/components/change-list.spec.tsx @@ -148,7 +148,7 @@ describe('change list', function () { name: /Project history and labels/i, }).within(() => { cy.findByRole('group', { - name: /Show all of the project history/i, + name: 'Show all of the project history or only labelled versions.', }).within(() => { cy.findByText(/Labels/i).click() }) @@ -353,7 +353,7 @@ describe('change list', function () { name: /Project history and labels/i, }).within(() => { cy.findByRole('group', { - name: /Show all of the project history/i, + name: 'Show all of the project history or only labelled versions.', }).within(() => { cy.findByText(/Labels/i).click() }) @@ -716,7 +716,7 @@ describe('change list', function () { name: /Project history and labels/i, }).within(() => { cy.findByRole('group', { - name: /Show all of the project history/i, + name: 'Show all of the project history or only labelled versions.', }).within(() => { cy.findByText(/Labels/i).click() }) diff --git a/services/web/test/frontend/features/settings/components/account-info-section.test.tsx b/services/web/test/frontend/features/settings/components/account-info-section.test.tsx index fc6adebcda..cec460bae1 100644 --- a/services/web/test/frontend/features/settings/components/account-info-section.test.tsx +++ b/services/web/test/frontend/features/settings/components/account-info-section.test.tsx @@ -47,7 +47,7 @@ describe('', function () { }) fireEvent.click( screen.getByRole('button', { - name: /update/i, + name: 'Update account info', }) ) expect(updateMock.callHistory.called()).to.be.true @@ -68,7 +68,7 @@ describe('', function () { target: { value: 'john' }, }) const button = screen.getByRole('button', { - name: /update/i, + name: 'Update account info', }) as HTMLButtonElement expect(button.disabled).to.be.true diff --git a/services/web/test/frontend/features/settings/components/emails/emails-section-add-new-email.test.tsx b/services/web/test/frontend/features/settings/components/emails/emails-section-add-new-email.test.tsx index 1fd4542f76..3458ab4747 100644 --- a/services/web/test/frontend/features/settings/components/emails/emails-section-add-new-email.test.tsx +++ b/services/web/test/frontend/features/settings/components/emails/emails-section-add-new-email.test.tsx @@ -63,17 +63,16 @@ function resetFetchMock() { } async function confirmCodeForEmail(email: string) { - await screen.findByText( - `Enter the 6-digit confirmation code sent to ${email}.` - ) - const inputCode = screen.getByLabelText(/6-digit confirmation code/i) + const inputCode = await screen.findByRole('textbox', { + name: `Enter the 6-digit confirmation code sent to ${email}.`, + }) fireEvent.change(inputCode, { target: { value: '123456' } }) const submitCodeBtn = screen.getByRole('button', { name: 'Confirm', }) fireEvent.click(submitCodeBtn) await waitForElementToBeRemoved(() => - screen.getByRole('button', { name: /confirming/i }) + screen.getByRole('button', { name: 'Confirming' }) ) } @@ -96,7 +95,7 @@ describe('', function () { fetchMock.get('/user/emails?ensureAffiliation=true', []) render() - await screen.findByRole('button', { name: /add another email/i }) + await screen.findByRole('button', { name: 'Add another email' }) }) it('renders input', async function () { @@ -105,11 +104,11 @@ describe('', function () { await fetchMock.callHistory.flush(true) const button = await screen.findByRole('button', { - name: /add another email/i, + name: 'Add another email', }) fireEvent.click(button) - await screen.findByLabelText(/email/i, { selector: 'input' }) + await screen.findByRole('textbox', { name: 'Email' }) }) it('renders "Start adding your address" until a valid email is typed', async function () { @@ -120,17 +119,17 @@ describe('', function () { await fetchMock.callHistory.flush(true) const button = await screen.findByRole('button', { - name: /add another email/i, + name: 'Add another email', }) fireEvent.click(button) - const input = screen.getByLabelText(/email/i, { selector: 'input' }) + const input = screen.getByRole('textbox', { name: 'Email' }) // initially the text is displayed and the "add email" button disabled screen.getByText('Start by adding your email address.') expect( screen.getByRole('button', { - name: /add new email/i, + name: 'Add new email', }).disabled ).to.be.true @@ -141,7 +140,7 @@ describe('', function () { screen.getByText('Start by adding your email address.') expect( screen.getByRole('button', { - name: /add new email/i, + name: 'Add new email', }).disabled ).to.be.true @@ -152,7 +151,7 @@ describe('', function () { expect(screen.queryByText('Start by adding your email address.')).to.be.null expect( screen.getByRole('button', { - name: /add new email/i, + name: 'Add new email', }).disabled ).to.be.false }) @@ -162,11 +161,11 @@ describe('', function () { render() const button = await screen.findByRole('button', { - name: /add another email/i, + name: 'Add another email', }) fireEvent.click(button) - screen.getByRole('button', { name: /add new email/i }) + screen.getByRole('button', { name: 'Add new email' }) }) it('prevent users from adding new emails when the limit is reached', async function () { @@ -182,7 +181,7 @@ describe('', function () { 'You can have a maximum of 10 email addresses on this account. To add another email address, please delete an existing one.' ) - expect(screen.queryByRole('button', { name: /add another email/i })).to.not + expect(screen.queryByRole('button', { name: 'Add another email' })).to.not .exist }) @@ -192,7 +191,7 @@ describe('', function () { const addAnotherEmailBtn = await screen.findByRole( 'button', - { name: /add another email/i } + { name: 'Add another email' } ) await fetchMock.callHistory.flush(true) @@ -203,14 +202,14 @@ describe('', function () { .post('/user/emails/confirm-secondary', 200) fireEvent.click(addAnotherEmailBtn) - const input = screen.getByLabelText(/email/i, { selector: 'input' }) + const input = screen.getByRole('textbox', { name: 'Email' }) fireEvent.change(input, { target: { value: userEmailData.email }, }) const submitBtn = screen.getByRole('button', { - name: /add new email/i, + name: 'Add new email', }) expect(submitBtn.disabled).to.be.false @@ -221,7 +220,7 @@ describe('', function () { await waitForElementToBeRemoved(() => screen.getByRole('button', { - name: /Loading/i, + name: 'Loading', }) ) @@ -235,7 +234,7 @@ describe('', function () { const addAnotherEmailBtn = await screen.findByRole( 'button', - { name: /add another email/i } + { name: 'Add another email' } ) await fetchMock.callHistory.flush(true) @@ -245,14 +244,14 @@ describe('', function () { .post('/user/emails/secondary', 400) fireEvent.click(addAnotherEmailBtn) - const input = screen.getByLabelText(/email/i, { selector: 'input' }) + const input = screen.getByRole('textbox', { name: 'Email' }) fireEvent.change(input, { target: { value: userEmailData.email }, }) const submitBtn = screen.getByRole('button', { - name: /add new email/i, + name: 'Add new email', }) expect(submitBtn.disabled).to.be.false @@ -262,7 +261,7 @@ describe('', function () { expect(submitBtn.disabled).to.be.true await screen.findByText( - /Invalid Request. Please correct the data and try again./i + 'Invalid Request. Please correct the data and try again.' ) expect(submitBtn).to.not.be.null expect(submitBtn.disabled).to.be.false @@ -273,7 +272,7 @@ describe('', function () { render() const button = await screen.findByRole('button', { - name: /add another email/i, + name: 'Add another email', }) await fetchMock.callHistory.flush(true) @@ -282,7 +281,7 @@ describe('', function () { await userEvent.click(button) - const input = screen.getByLabelText(/email/i, { selector: 'input' }) + const input = screen.getByRole('textbox', { name: 'Email' }) fireEvent.change(input, { target: { value: 'user@autocomplete.edu' }, }) @@ -297,7 +296,7 @@ describe('', function () { render() const button = await screen.findByRole('button', { - name: /add another email/i, + name: 'Add another email', }) await fetchMock.callHistory.flush(true) @@ -306,14 +305,14 @@ describe('', function () { await userEvent.click(button) await userEvent.type( - screen.getByLabelText(/email/i, { selector: 'input' }), + screen.getByRole('textbox', { name: 'Email' }), userEmailData.email ) - await userEvent.click(screen.getByRole('button', { name: /let us know/i })) + await userEvent.click(screen.getByRole('button', { name: 'Let us know' })) const universityInput = screen.getByRole('combobox', { - name: /university/i, + name: 'University', }) expect(universityInput.disabled).to.be.true @@ -330,7 +329,7 @@ describe('', function () { // Select the country from dropdown await userEvent.type( screen.getByRole('combobox', { - name: /country/i, + name: 'Country', }), country ) @@ -347,10 +346,10 @@ describe('', function () { await screen.findByText(userEmailData.affiliation.institution.name) ) - const roleInput = screen.getByRole('combobox', { name: /role/i }) + const roleInput = screen.getByRole('combobox', { name: 'Role' }) await userEvent.type(roleInput, userEmailData.affiliation.role!) const departmentInput = screen.getByRole('combobox', { - name: /department/i, + name: 'Department', }) await userEvent.click(departmentInput) await userEvent.click(screen.getByText(customDepartment)) @@ -370,7 +369,7 @@ describe('', function () { await userEvent.click( screen.getByRole('button', { - name: /add new email/i, + name: 'Add new email', }) ) @@ -402,7 +401,7 @@ describe('', function () { render() const button = await screen.findByRole('button', { - name: /add another email/i, + name: 'Add another email', }) await fetchMock.callHistory.flush(true) @@ -422,14 +421,14 @@ describe('', function () { // open "add new email" section and click "let us know" to open the Country/University form await userEvent.click(button) await userEvent.type( - screen.getByLabelText(/email/i, { selector: 'input' }), + screen.getByRole('textbox', { name: 'Email' }), userEmailData.email ) - await userEvent.click(screen.getByRole('button', { name: /let us know/i })) + await userEvent.click(screen.getByRole('button', { name: 'Let us know' })) // select a country const countryInput = screen.getByRole('combobox', { - name: /country/i, + name: 'Country', }) await userEvent.click(countryInput) await userEvent.type(countryInput, 'Germ') @@ -437,7 +436,7 @@ describe('', function () { // match several universities on initial typing const universityInput = screen.getByRole('combobox', { - name: /university/i, + name: 'University', }) await userEvent.click(universityInput) await userEvent.type(universityInput, 'bo') @@ -458,7 +457,7 @@ describe('', function () { render() const button = await screen.findByRole('button', { - name: /add another email/i, + name: 'Add another email', }) await fetchMock.callHistory.flush(true) @@ -467,14 +466,14 @@ describe('', function () { await userEvent.click(button) await userEvent.type( - screen.getByLabelText(/email/i, { selector: 'input' }), + screen.getByRole('textbox', { name: 'Email' }), userEmailData.email ) - await userEvent.click(screen.getByRole('button', { name: /let us know/i })) + await userEvent.click(screen.getByRole('button', { name: 'Let us know' })) const universityInput = screen.getByRole('combobox', { - name: /university/i, + name: 'University', }) expect(universityInput.disabled).to.be.true @@ -491,7 +490,7 @@ describe('', function () { // Select the country from dropdown await userEvent.type( screen.getByRole('combobox', { - name: /country/i, + name: 'Country', }), country ) @@ -505,10 +504,10 @@ describe('', function () { // Enter the university manually await userEvent.type(universityInput, newUniversity) - const roleInput = screen.getByRole('combobox', { name: /role/i }) + const roleInput = screen.getByRole('combobox', { name: 'Role' }) await userEvent.type(roleInput, userEmailData.affiliation.role!) const departmentInput = screen.getByRole('combobox', { - name: /department/i, + name: 'Department', }) await userEvent.type(departmentInput, userEmailData.affiliation.department!) @@ -530,7 +529,7 @@ describe('', function () { await userEvent.click( screen.getByRole('button', { - name: /add new email/i, + name: 'Add new email', }) ) @@ -573,7 +572,7 @@ describe('', function () { render() const button = await screen.findByRole('button', { - name: /add another email/i, + name: 'Add another email', }) await fetchMock.callHistory.flush(true) @@ -586,7 +585,7 @@ describe('', function () { await userEvent.click(button) await userEvent.type( - screen.getByLabelText(/email/i, { selector: 'input' }), + screen.getByRole('textbox', { name: 'Email' }), `user@${hostnameFirstChar}` ) @@ -596,37 +595,37 @@ describe('', function () { expect( screen.queryByRole('combobox', { - name: /country/i, + name: 'Country', }) ).to.be.null expect( screen.queryByRole('combobox', { - name: /university/i, + name: 'University', }) ).to.be.null screen.getByRole('combobox', { - name: /role/i, + name: 'Role', }) screen.getByRole('combobox', { - name: /department/i, + name: 'Department', }) - await userEvent.click(screen.getByRole('button', { name: /change/i })) + await userEvent.click(screen.getByRole('button', { name: 'Change' })) screen.getByRole('combobox', { - name: /country/i, + name: 'Country', }) screen.getByRole('combobox', { - name: /university/i, + name: 'University', }) expect( screen.queryByRole('combobox', { - name: /role/i, + name: 'Role', }) ).to.be.null expect( screen.queryByRole('combobox', { - name: /department/i, + name: 'Department', }) ).to.be.null }) @@ -646,7 +645,7 @@ describe('', function () { render() const button = await screen.findByRole('button', { - name: /add another email/i, + name: 'Add another email', }) await fetchMock.callHistory.flush(true) @@ -659,7 +658,7 @@ describe('', function () { await userEvent.click(button) await userEvent.type( - screen.getByLabelText(/email/i, { selector: 'input' }), + screen.getByRole('textbox', { name: 'Email' }), `user@${hostnameFirstChar}` ) @@ -686,16 +685,16 @@ describe('', function () { .post('/user/emails/confirm-secondary', 200) await userEvent.type( - screen.getByRole('combobox', { name: /role/i }), + screen.getByRole('combobox', { name: 'Role' }), userEmailData.affiliation.role! ) await userEvent.type( - screen.getByRole('combobox', { name: /department/i }), + screen.getByRole('combobox', { name: 'Department' }), userEmailData.affiliation.department! ) await userEvent.click( screen.getByRole('button', { - name: /add new email/i, + name: 'Add new email', }) ) @@ -743,12 +742,12 @@ describe('', function () { render() const button = await screen.findByRole('button', { - name: /add another email/i, + name: 'Add another email', }) await userEvent.click(button) - const input = screen.getByLabelText(/email/i, { selector: 'input' }) + const input = screen.getByRole('textbox', { name: 'Email' }) fireEvent.change(input, { target: { value: 'user@autocomplete.edu' }, }) @@ -786,12 +785,12 @@ describe('', function () { render() const button = await screen.findByRole('button', { - name: /add another email/i, + name: 'Add another email', }) await userEvent.click(button) - const input = screen.getByLabelText(/email/i, { selector: 'input' }) + const input = screen.getByRole('textbox', { name: 'Email' }) fireEvent.change(input, { target: { value: 'user@autocomplete.edu' }, }) @@ -833,12 +832,12 @@ describe('', function () { render() const button = await screen.findByRole('button', { - name: /add another email/i, + name: 'Add another email', }) await userEvent.click(button) - const input = screen.getByLabelText(/email/i, { selector: 'input' }) + const input = screen.getByRole('textbox', { name: 'Email' }) fireEvent.change(input, { target: { value: 'user@autocomplete.edu' }, }) diff --git a/services/web/test/frontend/features/settings/components/linking-section.test.tsx b/services/web/test/frontend/features/settings/components/linking-section.test.tsx index 49970cc2f3..ecd0098014 100644 --- a/services/web/test/frontend/features/settings/components/linking-section.test.tsx +++ b/services/web/test/frontend/features/settings/components/linking-section.test.tsx @@ -61,17 +61,17 @@ describe('', function () { screen.getByText('Google') screen.getByText('Log in with Google.') - screen.getByRole('button', { name: /unlink/i }) + screen.getByRole('button', { name: 'Unlink Google' }) screen.getByText('ORCID') screen.getByText( /Securely establish your identity by linking your ORCID iD/ ) const helpLink = screen.getByRole('link', { - name: /learn more about orcid/i, + name: 'Learn more about ORCID', }) expect(helpLink.getAttribute('href')).to.equal('/blog/434') - const linkButton = screen.getByRole('link', { name: /link orcid/i }) + const linkButton = screen.getByRole('link', { name: 'Link ORCID' }) expect(linkButton.getAttribute('href')).to.equal('/auth/orcid?intent=link') }) diff --git a/services/web/test/frontend/features/settings/components/linking/integration-widget.test.tsx b/services/web/test/frontend/features/settings/components/linking/integration-widget.test.tsx index 1a623ba7b7..e8e60c80ba 100644 --- a/services/web/test/frontend/features/settings/components/linking/integration-widget.test.tsx +++ b/services/web/test/frontend/features/settings/components/linking/integration-widget.test.tsx @@ -33,7 +33,9 @@ describe('', function () { }) it('should render an upgrade link and track clicks', function () { - const upgradeLink = screen.getByRole('link', { name: /upgrade/i }) + const upgradeLink = screen.getByRole('link', { + name: 'Integration Upgrade', + }) expect(upgradeLink.getAttribute('href')).to.equal( '/user/subscription/plans' ) @@ -52,7 +54,9 @@ describe('', function () { it('should render a link to initiate integration linking', function () { expect( - screen.getByRole('link', { name: 'Link' }).getAttribute('href') + screen + .getByRole('link', { name: 'Link Integration' }) + .getAttribute('href') ).to.equal('/link') }) @@ -86,11 +90,13 @@ describe('', function () { }) it('should display an `unlink` button', function () { - screen.getByRole('button', { name: 'Unlink' }) + screen.getByRole('button', { name: 'Unlink Integration' }) }) it('should open a modal with a link to confirm integration unlinking', function () { - fireEvent.click(screen.getByRole('button', { name: 'Unlink' })) + fireEvent.click( + screen.getByRole('button', { name: 'Unlink Integration' }) + ) const withinModal = within(screen.getByRole('dialog')) withinModal.getByText('confirm unlink') withinModal.getByText('you will be unlinked') @@ -99,7 +105,9 @@ describe('', function () { }) it('should cancel unlinking when clicking "cancel" in the confirmation modal', async function () { - fireEvent.click(screen.getByRole('button', { name: 'Unlink' })) + fireEvent.click( + screen.getByRole('button', { name: 'Unlink Integration' }) + ) screen.getByText('confirm unlink') const cancelBtn = screen.getByRole('button', { name: 'Cancel', diff --git a/services/web/test/frontend/features/settings/components/linking/sso-widget.test.tsx b/services/web/test/frontend/features/settings/components/linking/sso-widget.test.tsx index 68c3f35c27..b5d2fe5500 100644 --- a/services/web/test/frontend/features/settings/components/linking/sso-widget.test.tsx +++ b/services/web/test/frontend/features/settings/components/linking/sso-widget.test.tsx @@ -13,8 +13,8 @@ import { SSOLinkingWidget } from '../../../../../../frontend/js/features/setting describe('', function () { const defaultProps = { providerId: 'integration_id', - title: 'integration', - description: 'integration description', + title: 'Integration', + description: 'Integration description', helpPath: '/help/integration', linkPath: '/integration/link', onUnlink: () => Promise.resolve(), @@ -22,10 +22,12 @@ describe('', function () { it('should render', function () { render() - screen.getByText('integration') - screen.getByText('integration description') + screen.getByText('Integration') + screen.getByText('Integration description') expect( - screen.getByRole('link', { name: /learn more/i }).getAttribute('href') + screen + .getByRole('link', { name: 'Learn more about Integration' }) + .getAttribute('href') ).to.equal('/help/integration') }) @@ -33,7 +35,9 @@ describe('', function () { it('should render a link to `linkPath`', function () { render() expect( - screen.getByRole('link', { name: /link/i }).getAttribute('href') + screen + .getByRole('link', { name: 'Link Integration' }) + .getAttribute('href') ).to.equal('/integration/link?intent=link') }) }) @@ -49,19 +53,23 @@ describe('', function () { }) it('should display an `unlink` button', function () { - screen.getByRole('button', { name: /unlink/i }) + screen.getByRole('button', { name: 'Unlink Integration' }) }) it('should open a modal to confirm integration unlinking', function () { - fireEvent.click(screen.getByRole('button', { name: /unlink/i })) - screen.getByText('Unlink integration Account') + fireEvent.click( + screen.getByRole('button', { name: 'Unlink Integration' }) + ) + screen.getByText('Unlink Integration Account') screen.getByText( - 'Warning: When you unlink your account from integration you will not be able to sign in using integration anymore.' + 'Warning: When you unlink your account from Integration you will not be able to sign in using Integration anymore.' ) }) it('should cancel unlinking when clicking cancel in the confirmation modal', async function () { - fireEvent.click(screen.getByRole('button', { name: /unlink/i })) + fireEvent.click( + screen.getByRole('button', { name: 'Unlink Integration' }) + ) const cancelBtn = screen.getByRole('button', { name: 'Cancel', hidden: false, @@ -80,9 +88,11 @@ describe('', function () { render( ) - fireEvent.click(screen.getByRole('button', { name: /unlink/i })) + fireEvent.click( + screen.getByRole('button', { name: 'Unlink Integration' }) + ) confirmBtn = within(screen.getByRole('dialog')).getByRole('button', { - name: /unlink/i, + name: 'Unlink', hidden: false, }) }) @@ -114,11 +124,13 @@ describe('', function () { render( ) - fireEvent.click(screen.getByRole('button', { name: /unlink/i })) + fireEvent.click( + screen.getByRole('button', { name: 'Unlink Integration' }) + ) const confirmBtn = within(screen.getByRole('dialog')).getByRole( 'button', { - name: /unlink/i, + name: 'Unlink', hidden: false, } ) @@ -130,7 +142,7 @@ describe('', function () { }) it('should display the unlink button ', async function () { - await screen.findByRole('button', { name: /unlink/i }) + await screen.findByRole('button', { name: 'Unlink Integration' }) }) }) })