From ad93216416f4522060f7c7d3c15c3857fb3222a3 Mon Sep 17 00:00:00 2001 From: Peter Ringelmann Date: Wed, 10 Jun 2026 12:42:48 +0200 Subject: [PATCH] test(files): migrate files actions e2e from Cypress to Playwright Signed-off-by: Peter Ringelmann --- cypress/e2e/files/files-actions.cy.ts | 70 ------------------- .../e2e/files/files-actions.spec.ts | 41 +++++++++++ .../support/sections/FilesListPage.ts | 41 +++++++++-- 3 files changed, 78 insertions(+), 74 deletions(-) delete mode 100644 cypress/e2e/files/files-actions.cy.ts create mode 100644 tests/playwright/e2e/files/files-actions.spec.ts diff --git a/cypress/e2e/files/files-actions.cy.ts b/cypress/e2e/files/files-actions.cy.ts deleted file mode 100644 index f717213c15a..00000000000 --- a/cypress/e2e/files/files-actions.cy.ts +++ /dev/null @@ -1,70 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import type { User } from '@nextcloud/e2e-test-server/cypress' - -import { getActionButtonForFileId, getActionEntryForFileId, getRowForFile, getSelectionActionButton, getSelectionActionEntry, selectRowForFile } from './FilesUtils.ts' - -const ACTION_DELETE = 'delete' -const ACTION_COPY_MOVE = 'move-copy' -const ACTION_DETAILS = 'details' - -// Those two arrays doesn't represent the full list of actions -// the goal is to test a few, we're not trying to match the full feature set -const expectedDefaultActionsIDs = [ - ACTION_COPY_MOVE, - ACTION_DELETE, - ACTION_DETAILS, -] -const expectedDefaultSelectionActionsIDs = [ - ACTION_COPY_MOVE, - ACTION_DELETE, -] - -describe('Files: Actions', { testIsolation: true }, () => { - let user: User - let fileId: number = 0 - - beforeEach(() => cy.createRandomUser().then(($user) => { - user = $user - - cy.uploadContent(user, new Blob([]), 'image/jpeg', '/image.jpg').then((response) => { - fileId = Number.parseInt(response.headers['oc-fileid'] ?? '0') - }) - cy.login(user) - })) - - it('Show some standard actions', () => { - cy.visit('/apps/files') - getRowForFile('image.jpg').should('be.visible') - - expectedDefaultActionsIDs.forEach((actionId) => { - // Open the menu - getActionButtonForFileId(fileId).click({ force: true }) - // Check the action is visible - getActionEntryForFileId(fileId, actionId).should('be.visible') - // Close the menu - cy.get('body').click({ force: true }) - }) - }) - - it('Show some actions for a selection', () => { - cy.visit('/apps/files') - getRowForFile('image.jpg').should('be.visible') - - selectRowForFile('image.jpg') - - cy.get('[data-cy-files-list-selection-actions]').should('be.visible') - getSelectionActionButton().should('be.visible') - - // Open the menu - getSelectionActionButton().click({ force: true }) - - // Check the action is visible - expectedDefaultSelectionActionsIDs.forEach((actionId) => { - getSelectionActionEntry(actionId).should('be.visible') - }) - }) -}) diff --git a/tests/playwright/e2e/files/files-actions.spec.ts b/tests/playwright/e2e/files/files-actions.spec.ts new file mode 100644 index 00000000000..ed26cdede89 --- /dev/null +++ b/tests/playwright/e2e/files/files-actions.spec.ts @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { test, expect } from '../../support/fixtures/files-page.ts' +import { rm, uploadContent } from '../../support/utils/dav.ts' + +// A representative subset of the default actions, not the full feature set. +const expectedRowActions = ['move-copy', 'delete', 'details'] +const expectedSelectionActions = ['move-copy', 'delete'] + +test.describe('Files: Actions', () => { + test.beforeEach(async ({ page, user, filesListPage }) => { + // New users get welcome.txt — remove it so the list contains only our test file + await rm(page.request, user, '/welcome.txt') + await uploadContent(page.request, user, Buffer.alloc(0), 'image/jpeg', '/image.jpg') + await filesListPage.open() + }) + + test('shows the standard row actions', async ({ filesListPage }) => { + await expect(filesListPage.getRowForFile('image.jpg')).toBeVisible() + + const menu = await filesListPage.openActionsMenuForFile('image.jpg') + for (const actionId of expectedRowActions) { + await expect(filesListPage.getActionButtonInMenu(menu, actionId)).toBeVisible() + } + }) + + test('shows the standard actions for a selection', async ({ filesListPage }) => { + await expect(filesListPage.getRowForFile('image.jpg')).toBeVisible() + + await filesListPage.selectRowForFile('image.jpg') + await expect(filesListPage.getSelectionActionsToolbar()).toBeVisible() + + await filesListPage.openSelectionActionsMenu() + for (const actionId of expectedSelectionActions) { + await expect(filesListPage.getSelectionActionEntry(actionId)).toBeVisible() + } + }) +}) diff --git a/tests/playwright/support/sections/FilesListPage.ts b/tests/playwright/support/sections/FilesListPage.ts index 282583b4a39..18b9fc25b33 100644 --- a/tests/playwright/support/sections/FilesListPage.ts +++ b/tests/playwright/support/sections/FilesListPage.ts @@ -68,12 +68,45 @@ export class FilesListPage { .click({ force: true }) } + async selectRowForFile(filename: string): Promise { + // The checkbox is visually hidden inside NcCheckboxRadioSwitch, so force the check + await this.getRowForFile(filename) + .getByRole('checkbox', { name: /Toggle selection/ }) + .check({ force: true }) + } + + /** + * The toolbar that replaces the list header once one or more rows are selected. + */ + getSelectionActionsToolbar(): Locator { + return this.page.locator('[data-cy-files-list-selection-actions]') + } + + private getSelectionActionsButton(): Locator { + return this.getSelectionActionsToolbar().getByRole('button', { name: 'Actions' }) + } + + /** + * Open the bulk-selection actions menu. Pair with {@link getSelectionActionEntry} + * to inspect an entry (e.g. assert it is visible) before acting; for a plain + * "open and click" use {@link triggerSelectionAction}. + */ + async openSelectionActionsMenu(): Promise { + await this.getSelectionActionsButton().click({ force: true }) + } + + /** + * A selection action entry. Matched at page level on the product-owned + * attribute because selection actions can render inline or inside the menu popover. + */ + getSelectionActionEntry(actionId: string): Locator { + return this.page.locator(`[data-cy-files-list-selection-action="${actionId}"]`) + } + async triggerSelectionAction(actionId: string): Promise { - const actionsButton = this.page.locator('[data-cy-files-list-selection-actions]') - .getByRole('button', { name: 'Actions' }) - await actionsButton.click({ force: true }) + await this.openSelectionActionsMenu() // NcActionButton renders as