Skip to content

Commit a1e6e43

Browse files
committed
test(files_trashbin): migrate trashbin e2e from Cypress to Playwright
Signed-off-by: Peter Ringelmann <peter.ringelmann@nextcloud.com>
1 parent 0684698 commit a1e6e43

8 files changed

Lines changed: 334 additions & 223 deletions

File tree

cypress/e2e/files_trashbin/files-trash-action.cy.ts

Lines changed: 0 additions & 69 deletions
This file was deleted.

cypress/e2e/files_trashbin/files.cy.ts

Lines changed: 0 additions & 137 deletions
This file was deleted.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*!
2+
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { expect, test } from '../../support/fixtures/files-page.ts'
7+
import { rm, uploadContent } from '../../support/utils/dav.ts'
8+
9+
const FILE_COUNT = 5
10+
11+
test.describe('files_trashbin: empty trashbin action', () => {
12+
test.beforeEach(async ({ page, user }) => {
13+
// Create FILE_COUNT files and move them all to the trash
14+
for (let index = 0; index < FILE_COUNT; index++) {
15+
await uploadContent(page.request, user, '<content>', 'text/plain', `/file${index}.txt`)
16+
await rm(page.request, user, `/file${index}.txt`)
17+
}
18+
})
19+
20+
test('can empty trashbin', async ({ page, filesListPage }) => {
21+
await filesListPage.open()
22+
// Home holds only the default welcome file and offers no empty-trash action
23+
await expect(filesListPage.getRows()).toHaveCount(1)
24+
await expect(filesListPage.getListActionButton('empty-trash')).toHaveCount(0)
25+
26+
await filesListPage.open('trashbin')
27+
await expect(filesListPage.getRows()).toHaveCount(FILE_COUNT)
28+
29+
const emptied = page.waitForResponse(
30+
(r) => r.request().method() === 'DELETE' && r.url().includes('/remote.php/dav/trashbin/'),
31+
)
32+
await filesListPage.triggerListAction('empty-trash')
33+
34+
// Confirm in the dialog
35+
await page.getByRole('dialog')
36+
.getByRole('button', { name: 'Empty deleted files' })
37+
.click()
38+
39+
expect((await emptied).status()).toBe(204)
40+
await expect(filesListPage.getRows()).toHaveCount(0)
41+
})
42+
43+
test('cancelling the empty trashbin action does not delete anything', async ({ page, filesListPage }) => {
44+
await filesListPage.open('trashbin')
45+
await expect(filesListPage.getRows()).toHaveCount(FILE_COUNT)
46+
47+
await filesListPage.triggerListAction('empty-trash')
48+
49+
// Cancel the dialog: no request is sent and the files remain
50+
await page.getByRole('dialog')
51+
.getByRole('button', { name: 'Cancel' })
52+
.click()
53+
54+
await expect(filesListPage.getRows()).toHaveCount(FILE_COUNT)
55+
})
56+
})
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*!
2+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import type { Locator, Page } from '@playwright/test'
7+
import { readFile } from 'node:fs/promises'
8+
import { expect, test } from '../../support/fixtures/files-trashbin-page.ts'
9+
import { mkdir, rm, uploadContent } from '../../support/utils/dav.ts'
10+
import { ALL_PERMISSIONS, ShareType, createShare } from '../../support/utils/sharing.ts'
11+
import { setUserDisplayName } from '../../support/utils/users.ts'
12+
13+
// Trashbin custom columns, keyed by their product-owned column ids
14+
const ORIGINAL_LOCATION = '[data-cy-files-list-row-column-custom="files_trashbin--original-location"]'
15+
const DELETED_BY = '[data-cy-files-list-row-column-custom="files_trashbin--deleted-by"]'
16+
const DELETED_AT = '[data-cy-files-list-row-column-custom="files_trashbin--deleted"]'
17+
18+
/** Run `trigger`, then assert it downloaded `file.txt` with the expected content. */
19+
async function expectFileDownload(page: Page, trigger: () => Promise<void>) {
20+
const downloadPromise = page.waitForEvent('download')
21+
await trigger()
22+
const download = await downloadPromise
23+
expect(download.suggestedFilename()).toBe('file.txt')
24+
expect(await readFile(await download.path(), 'utf-8')).toBe('<content>')
25+
}
26+
27+
/** Assert a trashbin row's name and custom columns (the deleted time is always recent). */
28+
async function expectTrashbinRow(row: Locator, name: string, location: string, deletedBy: string) {
29+
await expect(row).toBeVisible()
30+
// Name and extension render as separate spans, so the composed text has a space
31+
await expect(row.locator('[data-cy-files-list-row-name]')).toHaveText(name)
32+
await expect(row.locator(ORIGINAL_LOCATION)).toHaveText(location)
33+
await expect(row.locator(DELETED_BY)).toHaveText(deletedBy)
34+
await expect(row.locator(DELETED_AT)).toHaveText('few seconds ago')
35+
}
36+
37+
test.describe('files_trashbin: download files', () => {
38+
let fileIds: [number, number]
39+
40+
test.beforeEach(async ({ page, user, filesListPage }) => {
41+
const first = await uploadContent(page.request, user, '<content>', 'text/plain', '/file.txt')
42+
await rm(page.request, user, '/file.txt')
43+
const second = await uploadContent(page.request, user, '<content>', 'text/plain', '/other-file.txt')
44+
await rm(page.request, user, '/other-file.txt')
45+
fileIds = [Number(first), Number(second)]
46+
47+
await filesListPage.open('trashbin')
48+
})
49+
50+
test('can download a file', async ({ page, filesListPage }) => {
51+
await expect(filesListPage.getRowForFileId(fileIds[0])).toBeVisible()
52+
await expect(filesListPage.getRowForFileId(fileIds[1])).toBeVisible()
53+
54+
await expectFileDownload(page, () => filesListPage.triggerActionForFileId(fileIds[0], 'download'))
55+
})
56+
57+
test('can download a file using the default action', async ({ page, filesListPage }) => {
58+
await expectFileDownload(page, () =>
59+
// The inline "Download" button is the row's default action; force past the sticky header
60+
filesListPage.getRowForFileId(fileIds[0])
61+
.getByRole('button', { name: 'Download' })
62+
.click({ force: true }),
63+
)
64+
})
65+
66+
// Trashbin has no bulk download: the webdav zip-folder plugin does not work for
67+
// the trashbin (and never did with the legacy ajax download either).
68+
test('does not offer bulk download', async ({ page, filesListPage }) => {
69+
await expect(filesListPage.getRowCheckboxes()).toHaveCount(2)
70+
await filesListPage.selectAll()
71+
await expect(page.getByText('2 selected')).toBeVisible()
72+
73+
await expect(filesListPage.getSelectionActionEntry('restore')).toBeVisible()
74+
await expect(filesListPage.getSelectionActionEntry('download')).toHaveCount(0)
75+
})
76+
})
77+
78+
test.describe('files_trashbin: file row', () => {
79+
test('shows data for a file deleted by the owner', async ({ user, aliceRequest, filesListPage }) => {
80+
const fileId = Number(await uploadContent(aliceRequest, user, '<content>', 'text/plain', '/test-file.txt'))
81+
await rm(aliceRequest, user, '/test-file.txt')
82+
83+
await filesListPage.open('trashbin')
84+
85+
// The owner's own deletions render as "You" regardless of display name
86+
await expectTrashbinRow(filesListPage.getRowForFileId(fileId), 'test-file .txt', 'All files', 'You')
87+
})
88+
89+
test('shows data for a file deleted by a sharee in a group share', async ({ user, aliceRequest, bob, bobRequest, group, filesListPage }) => {
90+
await setUserDisplayName(bobRequest, bob.userId, 'Bob')
91+
await mkdir(aliceRequest, user, '/Shared')
92+
await createShare(aliceRequest, '/Shared', group, ALL_PERMISSIONS, ShareType.GROUP)
93+
94+
const fileId = Number(await uploadContent(aliceRequest, user, '<content>', 'text/plain', '/Shared/test-file.txt'))
95+
// Bob (the sharee) deletes the file from his view of the shared folder
96+
await rm(bobRequest, bob, '/Shared/test-file.txt')
97+
98+
await filesListPage.open('trashbin')
99+
100+
await expectTrashbinRow(filesListPage.getRowForFileId(fileId), 'test-file .txt', 'Shared', 'Bob')
101+
})
102+
})

0 commit comments

Comments
 (0)