Skip to content

Commit 47eb503

Browse files
authored
Merge pull request #9073 from nextcloud/backport/9041/stable33
[stable33] test(playwright): migrate propfind tests from Cypress to Playwright
2 parents 5b11165 + 499cef9 commit 47eb503

7 files changed

Lines changed: 297 additions & 184 deletions

File tree

cypress/e2e/propfind.spec.js

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

cypress/support/commands.js

Lines changed: 0 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -243,76 +243,6 @@ Cypress.Commands.add('getFileContent', (path) => {
243243
.then((response) => response.data)
244244
})
245245

246-
Cypress.Commands.add('propfindFolder', (path, depth = 0, properties = null) => {
247-
const defaultProperties = `
248-
<nc:rich-workspace />
249-
<nc:rich-workspace-file />`
250-
251-
const propsXml = properties
252-
? properties.map((p) => `<${p} />`).join('\n')
253-
: defaultProperties
254-
255-
const rootPath = `${url}/remote.php/webdav/`
256-
const requestPath = path === '/' ? rootPath : `${rootPath}${path}`
257-
258-
return axios
259-
.request({
260-
method: 'PROPFIND',
261-
url: requestPath,
262-
headers: {
263-
Depth: depth,
264-
'Content-Type': 'application/xml',
265-
},
266-
data: `<?xml version="1.0"?>
267-
<d:propfind xmlns:d="DAV:"
268-
xmlns:oc="http://owncloud.org/ns"
269-
xmlns:nc="http://nextcloud.org/ns">
270-
<d:prop>
271-
${propsXml}
272-
</d:prop>
273-
</d:propfind>`,
274-
})
275-
.then((response) => {
276-
const parser = new DOMParser()
277-
const xmlDoc = parser.parseFromString(response.data, 'text/xml')
278-
const responses = xmlDoc.querySelectorAll('d\\:response, response')
279-
const results = Array.from(responses).map((resp) => {
280-
const props = {}
281-
const propStats = resp.querySelectorAll('d\\:propstat, propstat')
282-
propStats.forEach((propStat) => {
283-
const status =
284-
propStat.querySelector('d\\:status, status')?.textContent
285-
286-
// Skip properties with 404 status ( not found)
287-
if (status?.includes('404')) {
288-
return
289-
}
290-
291-
const propElements = resp.querySelectorAll(
292-
'd\\:prop > *, prop > * ',
293-
)
294-
295-
propElements.forEach((prop) => {
296-
const tagName = prop.localName
297-
const namespace = prop.namespaceURI
298-
299-
let key = tagName
300-
if (namespace === 'http://nextcloud.org/ns') {
301-
key = `nc:${tagName}`
302-
} else if (namespace === 'http://owncloud.org/ns') {
303-
key = `oc:${tagName}`
304-
}
305-
306-
props[key] = prop.textContent || ''
307-
})
308-
})
309-
return props
310-
})
311-
312-
return depth > 0 ? results : results[0] || {}
313-
})
314-
})
315-
316246
Cypress.Commands.add('reloadFileList', () => {
317247
cy.get('[title="Reload current directory"] button').click()
318248
return cy.get('button').contains('Reload content').click()

package-lock.json

Lines changed: 28 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@
113113
"@nextcloud/prettier-config": "^1.2.0",
114114
"@nextcloud/vite-config": "^1.7.2",
115115
"@playwright/test": "^1.60.0",
116+
"@types/jsdom": "^30.0.0",
116117
"@types/markdown-it": "^14.1.2",
117118
"@types/node": "^26.2.0",
118119
"@vitejs/plugin-vue2": "^2.3.4",

playwright/e2e/propfind.spec.ts

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { expect } from '@playwright/test'
7+
import { createFolder, uploadFile } from '../support/fixtures/Node'
8+
import { test } from '../support/fixtures/random-user'
9+
import { setTextSetting } from '../support/fixtures/settings'
10+
import {
11+
deleteWebDAVResource,
12+
PROPERTY_WORKSPACE,
13+
PROPERTY_WORKSPACE_FILE,
14+
PROPERTY_WORKSPACE_FILE_FLAT,
15+
PROPERTY_WORKSPACE_FLAT,
16+
propfindFolder,
17+
} from '../support/fixtures/webdav'
18+
19+
test.describe('Text PROPFIND extension', () => {
20+
test.describe('with workspaces enabled', () => {
21+
test.beforeEach(async ({ user }) => {
22+
await setTextSetting(user, 'workspace_enabled', 1)
23+
})
24+
25+
test('always adds rich workspace property', async ({ page, user }) => {
26+
const properties = [
27+
PROPERTY_WORKSPACE_FLAT,
28+
PROPERTY_WORKSPACE_FILE_FLAT,
29+
]
30+
const fiveSecondsAgo = Math.floor(Date.now() / 1000) - 5
31+
32+
await page.goto('/apps/dashboard')
33+
await user.uploadFile({
34+
name: 'Readme.md',
35+
content: '',
36+
mtime: fiveSecondsAgo,
37+
})
38+
39+
const [root1] = await propfindFolder(user, '/', 0, properties)
40+
expect(root1).toHaveProperty(PROPERTY_WORKSPACE_FLAT, '')
41+
42+
await user.uploadFile({ name: 'Readme.md', content: '## Hello world\n' })
43+
const [root2] = await propfindFolder(user, '/', 0, properties)
44+
expect(root2).toHaveProperty(PROPERTY_WORKSPACE_FLAT, '## Hello world\n')
45+
46+
await deleteWebDAVResource(user, '/Readme.md')
47+
const [root3] = await propfindFolder(user, '/', 0, properties)
48+
expect(root3).toHaveProperty(PROPERTY_WORKSPACE_FLAT, '')
49+
})
50+
51+
test('never adds rich workspace property to nested folders for flat properties', async ({
52+
page,
53+
user,
54+
}) => {
55+
const properties = [
56+
PROPERTY_WORKSPACE_FLAT,
57+
PROPERTY_WORKSPACE_FILE_FLAT,
58+
]
59+
60+
await page.goto('/apps/dashboard')
61+
await createFolder({ name: 'workspace-flat', owner: user })
62+
63+
const results1 = await propfindFolder(user, '/', 1, properties)
64+
const folder1 = results1.find((r) =>
65+
r['d:href']?.endsWith('/workspace-flat/'),
66+
)
67+
expect(folder1).toHaveProperty(PROPERTY_WORKSPACE_FLAT, '')
68+
69+
await uploadFile({
70+
name: 'workspace-flat/Readme.md',
71+
content: '## Hello world\n',
72+
owner: user,
73+
})
74+
const results2 = await propfindFolder(user, '/', 1, properties)
75+
const folder2 = results2.find((r) =>
76+
r['d:href']?.endsWith('/workspace-flat/'),
77+
)
78+
expect(folder2).toHaveProperty(PROPERTY_WORKSPACE_FLAT, '')
79+
})
80+
81+
// Android app relies on this to detect rich workspace availability in subfolders properly
82+
test('adds rich workspace property to nested folders for the default properties', async ({
83+
page,
84+
user,
85+
}) => {
86+
const properties = [PROPERTY_WORKSPACE, PROPERTY_WORKSPACE_FILE]
87+
88+
await page.goto('/apps/dashboard')
89+
await createFolder({ name: 'workspace', owner: user })
90+
91+
const results1 = await propfindFolder(user, '/', 1, properties)
92+
const folder1 = results1.find((r) =>
93+
r['d:href']?.endsWith('/workspace/'),
94+
)
95+
expect(folder1).toHaveProperty(PROPERTY_WORKSPACE, '')
96+
97+
await uploadFile({
98+
name: 'workspace/Readme.md',
99+
content: '## Hello world\n',
100+
owner: user,
101+
})
102+
const results2 = await propfindFolder(user, '/', 1, properties)
103+
const folder2 = results2.find((r) =>
104+
r['d:href']?.endsWith('/workspace/'),
105+
)
106+
expect(folder2).toHaveProperty(PROPERTY_WORKSPACE, '## Hello world\n')
107+
})
108+
})
109+
110+
test.describe('with workspaces disabled', () => {
111+
test.beforeEach(async ({ user }) => {
112+
await setTextSetting(user, 'workspace_enabled', 0)
113+
})
114+
115+
test('does not return a rich workspace property', async ({ page, user }) => {
116+
await page.goto('/apps/dashboard')
117+
118+
const results1 = await propfindFolder(user, '/', 1, [
119+
PROPERTY_WORKSPACE_FLAT,
120+
PROPERTY_WORKSPACE_FILE_FLAT,
121+
])
122+
for (const result of results1) {
123+
expect(result).not.toHaveProperty(PROPERTY_WORKSPACE_FLAT)
124+
}
125+
126+
await user.uploadFile({ name: 'Readme.md', content: '## Hello world\n' })
127+
const results2 = await propfindFolder(user, '/', 1, [
128+
PROPERTY_WORKSPACE_FLAT,
129+
PROPERTY_WORKSPACE_FILE_FLAT,
130+
])
131+
for (const result of results2) {
132+
expect(result).not.toHaveProperty(PROPERTY_WORKSPACE_FLAT)
133+
}
134+
135+
await createFolder({ name: 'without-workspace', owner: user })
136+
const results3 = await propfindFolder(user, '/', 1)
137+
const folder = results3.find((r) =>
138+
r['d:href']?.endsWith('/without-workspace/'),
139+
)
140+
expect(folder).not.toHaveProperty(PROPERTY_WORKSPACE)
141+
})
142+
})
143+
})

0 commit comments

Comments
 (0)