test(cypress): split helpers for files actions to make tests less flaky

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
pull/54237/head
Ferdinand Thiessen 2 months ago
parent 4ce1980eff
commit 88be308b06
No known key found for this signature in database
GPG Key ID: 45FAE7268762B400
  1. 83
      cypress/e2e/files/FilesUtils.ts
  2. 18
      cypress/e2e/files/files-actions.cy.ts
  3. 9
      cypress/e2e/files/files-renaming.cy.ts
  4. 11
      cypress/e2e/files_external/files-user-credentials.cy.ts
  5. 4
      cypress/e2e/files_sharing/FilesSharingUtils.ts
  6. 27
      cypress/e2e/files_sharing/files-download.cy.ts
  7. 2
      cypress/e2e/files_sharing/note-to-recipient.cy.ts
  8. 15
      cypress/e2e/files_sharing/share-status-action.cy.ts

@ -15,65 +15,63 @@ export const getActionsForFile = (filename: string) => getRowForFile(filename).f
export const getActionButtonForFileId = (fileid: number) => getActionsForFileId(fileid).findByRole('button', { name: 'Actions' })
export const getActionButtonForFile = (filename: string) => getActionsForFile(filename).findByRole('button', { name: 'Actions' })
const searchForActionInRow = (row: JQuery<HTMLElement>, actionId: string): Cypress.Chainable<JQuery<HTMLElement>> => {
const action = row.find(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`)
if (action.length > 0) {
cy.log('Found action in row')
return cy.wrap(action)
}
// Else look in the action menu
const menuButtonId = row.find('button[aria-controls]').attr('aria-controls')
if (menuButtonId === undefined) {
return cy.wrap(Cypress.$())
}
export const getActionEntryForFileId = (fileid: number, actionId: string) => {
return getActionButtonForFileId(fileid)
.should('have.attr', 'aria-controls')
.then((menuId) => cy.get(`#${menuId}`).find(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`))
}
// eslint-disable-next-line no-unused-expressions
expect(menuButtonId).not.to.be.undefined
return cy.get(`#${menuButtonId} [data-cy-files-list-row-action="${CSS.escape(actionId)}"]`)
export const getActionEntryForFile = (file: string, actionId: string) => {
return getActionButtonForFile(file)
.should('have.attr', 'aria-controls')
.then((menuId) => cy.get(`#${menuId}`).find(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`))
}
export const getActionEntryForFileId = (fileid: number, actionId: string): Cypress.Chainable<JQuery<HTMLElement>> => {
// If we cannot find the action in the row, it might be in the action menu
return getRowForFileId(fileid).should('be.visible')
.then((row) => searchForActionInRow(row, actionId))
export const getInlineActionEntryForFileId = (fileid: number, actionId: string) => {
return getActionsForFileId(fileid)
.find(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`)
}
export const getActionEntryForFile = (filename: string, actionId: string): Cypress.Chainable<JQuery<HTMLElement>> => {
// If we cannot find the action in the row, it might be in the action menu
return getRowForFile(filename).should('be.visible')
.then((row) => searchForActionInRow(row, actionId))
export const getInlineActionEntryForFile = (file: string, actionId: string) => {
return getActionsForFile(file)
.find(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`)
}
export const triggerActionForFileId = (fileid: number, actionId: string) => {
// Even if it's inline, we open the action menu to get all actions visible
getActionButtonForFileId(fileid).click({ force: true })
// wait for the actions menu to be visible
cy.findByRole('menu').findAllByRole('menuitem').first().should('be.visible')
getActionEntryForFileId(fileid, actionId)
.find('button').last().as('actionButton')
getActionButtonForFileId(fileid)
.as('actionButton')
.scrollIntoView()
cy.get('@actionButton')
.click({ force: true }) // force to avoid issues with overlaying file list header
getActionEntryForFileId(fileid, actionId)
.find('button')
.should('be.visible')
.click({ force: true })
.click()
}
export const triggerActionForFile = (filename: string, actionId: string) => {
// Even if it's inline, we open the action menu to get all actions visible
getActionButtonForFile(filename).click({ force: true })
// wait for the actions menu to be visible
cy.findByRole('menu').findAllByRole('menuitem').first().should('be.visible')
getActionEntryForFile(filename, actionId)
.find('button').last().as('actionButton')
getActionButtonForFile(filename)
.as('actionButton')
.scrollIntoView()
cy.get('@actionButton')
.click({ force: true }) // force to avoid issues with overlaying file list header
getActionEntryForFile(filename, actionId)
.find('button')
.should('be.visible')
.click({ force: true })
.click()
}
export const triggerInlineActionForFileId = (fileid: number, actionId: string) => {
getActionsForFileId(fileid).find(`button[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`).should('exist').click()
getActionsForFileId(fileid)
.find(`button[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`)
.should('exist')
.click()
}
export const triggerInlineActionForFile = (filename: string, actionId: string) => {
getActionsForFile(filename).find(`button[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`).should('exist').click()
getActionsForFile(filename)
.find(`button[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`)
.should('exist')
.click()
}
export const selectAllFiles = () => {
@ -176,12 +174,17 @@ export const copyFile = (fileName: string, dirPath: string) => {
export const renameFile = (fileName: string, newFileName: string) => {
getRowForFile(fileName)
.should('exist')
.scrollIntoView()
triggerActionForFile(fileName, 'rename')
// intercept the move so we can wait for it
cy.intercept('MOVE', /\/(remote|public)\.php\/dav\/files\//).as('moveFile')
getRowForFile(fileName).find('[data-cy-files-list-row-name] input').type(`{selectAll}${newFileName}{enter}`)
getRowForFile(fileName)
.find('[data-cy-files-list-row-name] input')
.type(`{selectAll}${newFileName}{enter}`)
cy.wait('@moveFile')
}

@ -10,7 +10,6 @@ import { getActionButtonForFileId, getActionEntryForFileId, getRowForFile, getSe
import { ACTION_COPY_MOVE } from '../../../apps/files/src/actions/moveOrCopyAction'
import { ACTION_DELETE } from '../../../apps/files/src/actions/deleteAction'
import { ACTION_DETAILS } from '../../../apps/files/src/actions/sidebarAction'
import { ACTION_SHARING_STATUS } from '../../../apps/files_sharing/src/files_actions/sharingStatusAction'
declare global {
interface Window {
@ -24,7 +23,6 @@ const expectedDefaultActionsIDs = [
ACTION_COPY_MOVE,
ACTION_DELETE,
ACTION_DETAILS,
ACTION_SHARING_STATUS,
]
const expectedDefaultSelectionActionsIDs = [
ACTION_COPY_MOVE,
@ -90,11 +88,13 @@ describe('Files: Actions', { testIsolation: true }, () => {
win._nc_fileactions.push(parent)
win._nc_fileactions.push(child1)
win._nc_fileactions.push(child2)
}
},
})
// Open the menu
getActionButtonForFileId(fileId).click({ force: true })
getActionButtonForFileId(fileId)
.scrollIntoView()
.click({ force: true })
// Check we have the parent action but not the children
getActionEntryForFileId(fileId, 'nested-action').should('be.visible')
@ -104,8 +104,8 @@ describe('Files: Actions', { testIsolation: true }, () => {
// Click on the parent action
getActionEntryForFileId(fileId, 'nested-action')
.find('button').last()
.should('exist').click({ force: true })
.should('be.visible')
.click()
// Check we have the children and the back button but not the parent
getActionEntryForFileId(fileId, 'nested-action').should('not.exist')
@ -115,8 +115,8 @@ describe('Files: Actions', { testIsolation: true }, () => {
// Click on the back button
getActionEntryForFileId(fileId, 'menu-back')
.find('button').last()
.should('exist').click({ force: true })
.should('be.visible')
.click()
// Check we have the parent action but not the children
getActionEntryForFileId(fileId, 'nested-action').should('be.visible')
@ -177,7 +177,7 @@ describe('Files: Actions', { testIsolation: true }, () => {
win._nc_fileactions.push(parent)
win._nc_fileactions.push(child1)
win._nc_fileactions.push(child2)
}
},
})
selectRowForFile('image.jpg')

@ -181,13 +181,16 @@ describe('files: Rename nodes', { testIsolation: true }, () => {
cy.visit('/apps/files')
getRowForFile('file.txt').should('be.visible')
getRowForFile('file.txt')
.should('be.visible')
// Z so it is shown last
renameFile('file.txt', 'zzz.txt')
// not visible any longer
getRowForFile('zzz.txt').should('not.exist')
getRowForFile('zzz.txt')
.should('not.exist')
// scroll file list to bottom
cy.get('[data-cy-files-list]').scrollTo('bottom')
cy.get('[data-cy-files-list]')
.scrollTo('bottom')
cy.screenshot()
// The file is no longer in rename state
getRowForFile('zzz.txt')

@ -5,7 +5,7 @@
import { User } from '@nextcloud/cypress'
import { AuthBackend, createStorageWithConfig, StorageBackend } from './StorageUtils'
import { getActionEntryForFile, getRowForFile, navigateToFolder, triggerInlineActionForFile } from '../files/FilesUtils'
import { getInlineActionEntryForFile, getRowForFile, navigateToFolder, triggerInlineActionForFile } from '../files/FilesUtils'
import { ACTION_CREDENTIALS_EXTERNAL_STORAGE } from '../../../apps/files_external/src/actions/enterCredentialsAction'
import { handlePasswordConfirmation } from '../settings/usersUtils'
@ -72,7 +72,8 @@ describe('Files user credentials', { testIsolation: true }, () => {
// Auth dialog should be closed and the set credentials button should be gone
cy.get('@authDialog').should('not.exist', { timeout: 2000 })
getActionEntryForFile(storageUser.userId, ACTION_CREDENTIALS_EXTERNAL_STORAGE).should('not.exist')
getInlineActionEntryForFile(storageUser.userId, ACTION_CREDENTIALS_EXTERNAL_STORAGE)
.should('not.exist')
// Finally, the storage should be accessible
cy.visit('/apps/files')
@ -112,7 +113,7 @@ describe('Files user credentials', { testIsolation: true }, () => {
// Auth dialog should be closed and the set credentials button should be gone
cy.get('@authDialog').should('not.exist', { timeout: 2000 })
getActionEntryForFile('storage1', ACTION_CREDENTIALS_EXTERNAL_STORAGE).should('not.exist')
getInlineActionEntryForFile('storage1', ACTION_CREDENTIALS_EXTERNAL_STORAGE).should('not.exist')
// Finally, the storage should be accessible
cy.visit('/apps/files')
@ -131,8 +132,8 @@ describe('Files user credentials', { testIsolation: true }, () => {
getRowForFile('storage2').should('be.visible')
// Since we already have set the credentials, the action should not be present
getActionEntryForFile('storage1', ACTION_CREDENTIALS_EXTERNAL_STORAGE).should('not.exist')
getActionEntryForFile('storage2', ACTION_CREDENTIALS_EXTERNAL_STORAGE).should('not.exist')
getInlineActionEntryForFile('storage1', ACTION_CREDENTIALS_EXTERNAL_STORAGE).should('not.exist')
getInlineActionEntryForFile('storage2', ACTION_CREDENTIALS_EXTERNAL_STORAGE).should('not.exist')
// Finally, the storage should be accessible
cy.visit('/apps/files')

@ -125,8 +125,8 @@ export function updateShare(fileName: string, index: number, shareSettings: Part
export function openSharingPanel(fileName: string) {
triggerActionForFile(fileName, 'details')
cy.get('#app-sidebar-vue')
.get('[aria-controls="tab-sharing"]')
cy.get('[data-cy-sidebar]')
.find('[aria-controls="tab-sharing"]')
.click()
}

@ -5,6 +5,7 @@
import type { User } from '@nextcloud/cypress'
import { createShare } from './FilesSharingUtils.ts'
import {
getActionButtonForFile,
getActionEntryForFile,
getRowForFile,
} from '../files/FilesUtils.ts'
@ -41,8 +42,13 @@ describe('files_sharing: Download forbidden', { testIsolation: true }, () => {
// visit shared files view
cy.visit('/apps/files')
// see the shared folder
getRowForFile('folder').should('be.visible')
getActionEntryForFile('folder', 'download').should('not.exist')
getActionButtonForFile('folder')
.should('be.visible')
// open the action menu
.click({ force: true })
// see no download action
getActionEntryForFile('folder', 'download')
.should('not.exist')
// Disable view without download option
cy.runOccCommand('config:app:set --value no core shareapi_allow_view_without_download')
@ -51,6 +57,10 @@ describe('files_sharing: Download forbidden', { testIsolation: true }, () => {
cy.visit('/apps/files')
// see the shared folder
getRowForFile('folder').should('be.visible')
getActionButtonForFile('folder')
.should('be.visible')
// open the action menu
.click({ force: true })
getActionEntryForFile('folder', 'download').should('not.exist')
})
@ -68,8 +78,13 @@ describe('files_sharing: Download forbidden', { testIsolation: true }, () => {
// visit shared files view
cy.visit('/apps/files')
// see the shared folder
getRowForFile('file.txt').should('be.visible')
getActionEntryForFile('file.txt', 'download').should('not.exist')
getActionButtonForFile('file.txt')
.should('be.visible')
// open the action menu
.click({ force: true })
// see no download action
getActionEntryForFile('file.txt', 'download')
.should('not.exist')
// Disable view without download option
cy.runOccCommand('config:app:set --value no core shareapi_allow_view_without_download')
@ -78,6 +93,10 @@ describe('files_sharing: Download forbidden', { testIsolation: true }, () => {
cy.visit('/apps/files')
// see the shared folder
getRowForFile('file.txt').should('be.visible')
getActionButtonForFile('file.txt')
.should('be.visible')
// open the action menu
.click({ force: true })
getActionEntryForFile('file.txt', 'download').should('not.exist')
})
})

@ -72,7 +72,7 @@ describe('files_sharing: Note to recipient', { testIsolation: true }, () => {
createShare('folder', sharee.userId, { read: true, download: true, note: 'Hello, this is the note.' })
// reload just to be sure
cy.reload()
cy.visit('/apps/files')
// open the sharing tab
openSharingPanel('folder')

@ -4,7 +4,7 @@
*/
import type { User } from '@nextcloud/cypress'
import { createShare } from './FilesSharingUtils.ts'
import { closeSidebar, enableGridMode, getActionButtonForFile, getRowForFile } from '../files/FilesUtils.ts'
import { closeSidebar, enableGridMode, getActionButtonForFile, getInlineActionEntryForFile, getRowForFile } from '../files/FilesUtils.ts'
describe('files_sharing: Sharing status action', { testIsolation: true }, () => {
/**
@ -78,10 +78,9 @@ describe('files_sharing: Sharing status action', { testIsolation: true }, () =>
cy.login(user)
cy.visit('/apps/files')
getRowForFile('folder')
.should('be.visible')
.find('[data-cy-files-list-row-actions]')
.findByRole('button', { name: /^Shared with/i })
getInlineActionEntryForFile('folder', 'sharing-status')
.should('have.attr', 'aria-label', `Shared with ${sharee.userId}`)
.should('have.attr', 'title', `Shared with ${sharee.userId}`)
.should('be.visible')
})
@ -103,10 +102,8 @@ describe('files_sharing: Sharing status action', { testIsolation: true }, () =>
cy.login(sharee)
cy.visit('/apps/files')
getRowForFile('folder')
.should('be.visible')
.find('[data-cy-files-list-row-actions]')
.findByRole('button', { name: `Shared by ${user.userId}` })
getInlineActionEntryForFile('folder', 'sharing-status')
.should('have.attr', 'aria-label', `Shared by ${user.userId}`)
.should('be.visible')
})

Loading…
Cancel
Save