main
ts 402 lines 19.5 KB
Raw
1 import { test, expect, Page } from '@playwright/test'
2 import fs from 'fs'
3 import { wait } from '../src/cross'
4 import {
5 clickAdminMenu, clickIconBtn, forwardConsole, loginAdmin, password, resetTimestamp, FRONTEND_URL, username, TEST_PORT
6 } from './common'
7
8 const screenshotStyle = fs.readFileSync('e2e/screenshot.css', 'utf8')
9 const screenshotCounters = new WeakMap<object, number>()
10
11 // a generic test touch several parts
12 test('around1', async ({ page }) => {
13 forwardConsole(page)
14 resetTimestamp()
15 await page.goto(FRONTEND_URL)
16 await expect(page).toHaveTitle(/File server/)
17 await screenshot(page)
18 await page.getByRole('button', { name: 'Login' }).click()
19 await expect(page.getByRole('dialog', {})).toBeVisible()
20 await screenshot(page)
21
22 await page.getByRole('textbox', { name: 'Username' }).fill(username + '!') // wrong username
23 await page.getByRole('textbox', { name: 'Password' }).fill(password)
24 await page.getByRole('button', { name: 'Continue' }).click()
25 await expect(page.getByText('x!ErrorInvalid credentials')).toBeVisible()
26 await page.getByRole('alertdialog').getByRole('button', { name: 'Close' }).click()
27
28 resetTimestamp()
29 await page.getByRole('textbox', { name: 'Username' }).fill(username)
30 await page.getByRole('button', { name: 'Continue' }).click()
31 await page.locator('div').filter({ hasText: 'Logged in' }).nth(3).click()
32 await screenshot(page)
33
34 // selecting in this folder should enable bulk delete button
35 await page.getByRole('link', { name: 'for-admins, Folder' }).click()
36 await page.getByRole('link', { name: 'upload, Folder' }).click()
37 await page.getByRole('link', { name: 'alfa.txt' }).click()
38 await expect(page.getByText('Delete')).toBeVisible() // first check single-delete command
39 await page.getByRole('button', { name: 'Close' }).click()
40 await page.getByRole('button', { name: 'Select' }).click()
41 await page.getByRole('checkbox', { name: 'alfa.txt' }).check()
42 await expect(page.getByRole('button', { name: 'Delete' })).toBeEnabled()
43 await page.getByRole('button', { name: 'Select' }).click()
44
45 await page.getByRole('link', { name: 'home' }).click()
46 await page.getByRole('button', { name: username }).click()
47 await page.getByRole('button', { name: 'Logout' }).click()
48 await page.getByText('Logged out').click()
49 await page.getByRole('link', { name: 'cantListBut, Folder' }).click()
50 await page.getByText('x!WarningForbidden').click()
51 await page.getByRole('button', { name: 'Close' }).click()
52 await page.getByRole('link', { name: 'cantListPage, Folder' }).click()
53 await page.getByRole('link', { name: 'alfa.txt' }).click()
54 await expect(page.getByRole('dialog')).toMatchAriaSnapshot(`
55 - dialog:
56 - button "Close"
57 - heading "File menu" [level=1]
58 - term: Name
59 - definition: alfa.txt
60 - term: Size
61 - definition: 6 B
62 - term: Timestamp
63 - definition: /\\d+\\/\\d+\\/\\d+, \\d+:\\d+:\\d+/
64 - term: Creation
65 - definition: /\\d+\\/\\d+\\/\\d+, \\d+:\\d+:\\d+/
66 - link "Download"
67 - link "Open"
68 `)
69 await page.getByRole('link', { name: 'Download' }).click() // this also closes the dialog
70 await page.getByRole('link', { name: 'alfa.txt', exact: true }).click()
71 await screenshot(page)
72 await page.getByRole('button', { name: 'Close' }).click()
73
74 await page.getByRole('link', { name: 'cantListPage' }).click()
75 await page.getByRole('button', { name: 'Calculate' }).click()
76 await page.getByText('KB / 2 files').click()
77 await page.locator('#menu-prop-name').getByText('cantListPage').click()
78 const downloadPromise = page.waitForEvent('download')
79 await page.getByRole('link', { name: 'Download' }).click()
80 await downloadPromise
81 await page.getByRole('link', { name: 'cantListPage' }).click()
82 const pageListPromise = page.waitForEvent('popup')
83 await page.getByRole('link', { name: 'Get list' }).click()
84 const pageList = await pageListPromise
85 await expect(pageList.getByText('::1')).toBeVisible()
86 await pageList.close()
87
88 await page.getByRole('link', { name: 'home' }).click()
89 await page.getByRole('button', { name: 'Select' }).click()
90 await page.getByRole('textbox', { name: 'Type here to filter the list' }).click()
91 await page.getByRole('textbox', { name: 'Type here to filter the list' }).fill('x')
92 await page.getByText('filtered').click()
93 await screenshot(page)
94 await page.getByRole('button', { name: 'Select' }).click()
95 await page.getByText('file, 10 folders, 6 B').click()
96 await page.getByRole('link', { name: 'cantListPageAlt, Folder' }).click()
97 await page.getByRole('link', { name: 'home' }).click()
98 await page.getByRole('link', { name: 'f1, Folder' }).click()
99 await page.getByRole('link', { name: 'page, Folder' }).click()
100 await page.getByRole('img', { name: 'gpl logo' }).click()
101 await page.getByRole('heading', { name: 'This is a test' }).click()
102 await page.goBack()
103 await page.getByRole('link', { name: 'home' }).click()
104
105 const isPhone = await page.evaluate(() => window.matchMedia("(max-width: 600px)").matches)
106 if (isPhone)
107 await page.getByRole('listitem').filter({ hasText: 'for-disabled' }).getByRole('button').click()
108 else
109 await page.getByRole('listitem').filter({ hasText: 'for-disabledMenu' }).getByRole('button').click()
110 await expect(page.getByText('Missing permission')).toBeVisible()
111 await page.getByRole('button', { name: 'Close' }).click()
112 await page.getByRole('link', { name: 'cantSearchForMasks, Folder' }).click()
113 await expect(page.getByRole('link', { name: 'cantSearchForMasks' })).toBeVisible()
114 await page.getByRole('link', { name: 'cantSearchForMasks' }).click()
115 await expect(page.locator('#menu-prop-name').getByText('cantSearchForMasks')).toBeVisible()
116 await page.getByRole('button', { name: 'Close' }).click()
117 await page.getByRole('link', { name: 'cantSearchForMasks' }).click()
118 await expect(page.getByText('xFolder')).toBeVisible()
119 await page.getByRole('button', { name: 'Close' }).click()
120 await page.getByRole('link', { name: 'home' }).click()
121
122 await page.getByRole('button', { name: 'Options' }).click()
123 await page.getByRole('slider').fill('6')
124 await page.getByRole('button', { name: 'Close' }).click()
125 await expect(page.locator('.list-wrapper')).toHaveClass(/tiles-mode/)
126
127 const item = page.locator('.list-wrapper li').filter({ has: page.locator('.link-wrapper > a') }).first()
128 const link = item.locator('.link-wrapper > a')
129 const name = item.locator('.entry-name')
130 await expect(link).toBeVisible()
131 await expect(name).toBeVisible()
132
133 // safari can shrink the inline link box, so compare the geometry directly against the name
134 const [linkBox, nameBox] = await Promise.all([link.boundingBox(), name.boundingBox()])
135 expect(linkBox).not.toBeNull()
136 expect(nameBox).not.toBeNull()
137 expect(linkBox!.y + linkBox!.height).toBeGreaterThanOrEqual(nameBox!.y + nameBox!.height - 1)
138 })
139
140 test('search1', async ({ page }) => {
141 resetTimestamp()
142 await page.goto(FRONTEND_URL)
143 await page.getByRole('button', { name: 'Search' }).click()
144 await page.locator('input[name="name"]').fill('a')
145 await page.getByRole('button', { name: 'Continue' }).click()
146 await page.getByText('12 folders').click()
147 await page.getByRole('link', { name: 'cantListPage/ alfa.txt' }).click()
148 await page.getByRole('button', { name: 'Close' }).click()
149 await page.getByRole('button', { name: 'Clear search' }).click()
150
151 await page.getByRole('button', { name: 'Search' }).click()
152 await page.locator('input[name="name"]').fill('a*')
153 await page.locator('input[name="name"]').press('Enter')
154 await page.getByText('files, 42 B').click()
155
156 await page.getByRole('link', { name: 'home' }).click()
157 await page.getByRole('button', { name: 'Close' }).click()
158 await page.getByRole('link', { name: 'home' }).click()
159 await page.getByRole('button', { name: 'Close' }).click()
160 await page.getByRole('button', { name: 'Clear search' }).click()
161 await page.getByRole('link', { name: 'home' }).click()
162 await expect(page.getByText('xFolder')).toBeVisible()
163 await page.getByRole('button', { name: 'Close' }).click()
164 await page.getByRole('button', { name: 'Options' }).click()
165 await expect(page.locator('#option-sort-by')).toBeVisible()
166 await expect(page.locator('#option-sort-by')).toBeVisible()
167 await page.getByRole('dialog').locator('div').nth(2).click()
168 await page.locator('#option-sort-by').selectOption('size')
169 await page.getByRole('checkbox', { name: 'Invert order' }).check()
170 await page.getByRole('slider').fill('6')
171 await page.locator('#option-theme').selectOption('dark')
172 await page.getByRole('button', { name: 'Close' }).click()
173 await page.getByRole('link', { name: 'cantListPageAlt, Folder' }).click()
174 await expect(page.getByText('alfa.txt')).toBeVisible() // be sure the folder is loaded
175 await page.mouse.click(1, 1) // avoid focus inconsistencies
176 await screenshot(page)
177
178 await page.getByRole('button', { name: 'Zip' }).click()
179 await expect(page.getByRole('dialog')).toMatchAriaSnapshot(`
180 - dialog:
181 - button "Close"
182 - heading "Confirm" [level=1]
183 - paragraph: Download WHOLE folder as ZIP archive?
184 - link "Yes":
185 - button "Yes"
186 - button "Don't"
187 - button "Select some files"
188 `)
189 await page.getByRole('button', { name: "Don't" }).click()
190 await page.getByRole('button', { name: 'Zip' }).click()
191 await page.getByRole('button', { name: 'Select some files' }).click()
192 await page.getByText('Use checkboxes to select the').click()
193 await page.getByRole('button', { name: 'Close' }).click()
194 await page.getByRole('textbox', { name: 'Type here to filter the list' }).click()
195 })
196
197 test('frontend-admin', async ({ page }) => {
198 await page.goto(FRONTEND_URL, { waitUntil: 'networkidle' })
199 await page.evaluate(() => document.fonts.ready) // aspetta i font
200 await page.getByRole('button', { name: 'Options' }).click()
201 // no admin button yet,
202 await expect(page.getByRole('dialog')).toMatchAriaSnapshot(`
203 - dialog:
204 - button "Close"
205 - heading "Options" [level=1]
206 - combobox:
207 - 'option "Sort by: name" [selected]'
208 - 'option "Sort by: extension"'
209 - 'option "Sort by: size"'
210 - 'option "Sort by: time"'
211 - 'option "Sort by: creation"'
212 - checkbox "Invert order"
213 - text: Invert order
214 - checkbox "Folders first" [checked]
215 - text: Folders first
216 - checkbox "Numeric names"
217 - text: "Numeric names Tiles mode: off"
218 - slider: "0"
219 - combobox:
220 - 'option "Theme: auto" [selected]'
221 - 'option "Theme: light"'
222 - 'option "Theme: dark"'
223 `)
224 await page.getByRole('button', { name: 'Close' }).click()
225 await page.getByRole('button', { name: 'Login' }).click()
226 await page.getByRole('textbox', { name: 'Username' }).fill(username)
227 await page.getByRole('textbox', { name: 'Password' }).fill(password)
228 await page.getByRole('textbox', { name: 'Password' }).press('Enter')
229 await page.getByRole('button', { name: 'Options' }).click()
230 const page1Promise = page.waitForEvent('popup')
231 await page.getByRole('button', { name: 'Admin-panel' }).click()
232 const page1 = await page1Promise
233 await expect(page1).toHaveTitle(/HFS Admin-panel/)
234 })
235
236 test('admin1', async ({ page }) => {
237 await fs.promises.rm('tests/work/logs', { force: true, recursive: true }) // clear logs to have consistent screenshots
238 const isPhone = await loginAdmin(page)
239
240 function dataTableLoading() {
241 return expect(page.getByRole('grid').getByRole('img')).toBeVisible({ visible: false })
242 }
243
244 await clickAdminMenu(page, 'Internet') // initiate the get_nat process, so we'll have to wait less, later
245 await clickAdminMenu(page, 'Shared files')
246 await expect(page.getByText('cantListBut')).toBeVisible() // wait for data
247 if (!isPhone)
248 await expect(page.getByText('Your shared files')).toBeVisible() // wait for data
249 await screenshot(page)
250 await clickAdminMenu(page, 'Accounts')
251 await expect(page.getByText('admins', { exact: true })).toBeVisible() // wait for data
252 await screenshot(page)
253 await page.getByText('rejetto(admins,').click()
254 await screenshot(page)
255 if (isPhone)
256 await clickIconBtn('Close', page)
257 await clickAdminMenu(page, 'Options')
258 await expect(page.getByText('Correctly working on port')).toBeVisible() // wait for data
259 if (!isPhone)
260 await expect(page.getByText('Expire', { exact: true })).toBeVisible() // wait for layout of 'block' table
261 await page.mouse.click(1, 1) // avoid focus inconsistencies
262 await screenshot(page)
263
264 await clickAdminMenu(page, 'Logs')
265 await dataTableLoading()
266 await screenshot(page)
267 await clickIconBtn('Options', page)
268 await page.getByRole('textbox', { name: 'Served', exact: true }).click()
269 await clickIconBtn('Close', page)
270 await clickAdminMenu(page, 'Language')
271 await dataTableLoading()
272 if (!isPhone)
273 await expect(page.getByText('author', { exact: true })).toBeVisible() // wait for the layout to be stable
274 await screenshot(page, '.MuiDataGrid-root')
275 await clickAdminMenu(page, 'Plugins')
276 await expect(page.getByText('antibrute')).toBeVisible() // wait for data
277 // ensure Test plugin is running
278 const stopTest = page.getByRole('button', { name: 'Stop test' })
279 if (!await stopTest.isVisible()) {
280 await page.getByRole('button', { name: 'Start test' }).click()
281 await expect(stopTest).toBeVisible()
282 }
283 // reload the list from the server so the plugin screenshot doesn't depend on SSE timing
284 await page.reload()
285 await expect(page.getByRole('heading', { name: 'Plugins', exact: true })).toBeVisible()
286 await expect(page.getByRole('button', { name: 'Stop test' })).toBeVisible()
287
288 await screenshot(page)
289 await page.getByRole('tab', { name: 'Get more' }).click()
290 await page.getByRole('tab', { name: 'updates' }).click()
291 await clickAdminMenu(page, 'Custom HTML')
292 await expect(page.getByRole('combobox', { name: 'Section Style' })).toBeVisible() // wait for data to be loaded
293 await screenshot(page)
294 await page.getByRole('main').click()
295 await clickAdminMenu(page, 'Internet')
296 await expect(page.getByText(`port ${TEST_PORT}`)).toBeVisible({ timeout: 15000 }) // wait for data (get_nat can be very slow)
297 await page.mouse.click(1, 1) // avoid focus inconsistencies
298 await screenshot(page)
299 await clickAdminMenu(page, 'Logout')
300 await screenshot(page)
301 })
302
303 async function screenshot(page: Page, selectorForMask = '') {
304 if (process.env.NO_SS) return
305 const testInfo = test.info()
306 const snapshotName = nextScreenshotName(testInfo)
307 const snapshotPath = testInfo.snapshotPath(snapshotName, { kind: 'screenshot' })
308 if (selectorForMask)
309 selectorForMask = ',' + selectorForMask
310 await wait(200) // this accounts especially for our DataTable component which takes time to set the layout
311 const mask = [page.locator(`.maskInTests${selectorForMask}`)]
312 // write the missing baseline ourselves so Playwright does not turn the first run into a failure
313 if (!fs.existsSync(snapshotPath)) {
314 await page.screenshot({
315 path: snapshotPath,
316 fullPage: true,
317 mask,
318 animations: 'disabled',
319 caret: 'hide',
320 scale: 'css',
321 style: screenshotStyle,
322 })
323 return
324 }
325 return expect(page).toHaveScreenshot(snapshotName, { fullPage: true, mask })
326 }
327
328 function nextScreenshotName(testInfo: ReturnType<typeof test.info>) {
329 const nextIndex = (screenshotCounters.get(testInfo) ?? 0) + 1
330 screenshotCounters.set(testInfo, nextIndex)
331 const testName = testInfo.titlePath.slice(1).join(' ')
332 return `${testName}-${nextIndex}.png`
333 }
334
335 test('anew', async ({ page, browserName }) => {
336 forwardConsole(page)
337 if (page.viewportSize()?.width! < 1000 || browserName !== 'chromium') return // test only for desktop chromium
338 // reset config so each run starts from the same default workspace state
339 const port = 8082
340 do {
341 fs.writeFileSync('tests/work2/config.yaml', `port: ${port}\nopen_browser_at_start: false\n`)
342 await wait(500)
343 } while (await page.goto(`http://localhost:${port}/`).then(() => 0, () => 1))
344 await expect(page.getByText('Nothing here')).toBeVisible()
345 await page.getByRole('button', { name: 'Options' }).click()
346 const page1Promise = page.waitForEvent('popup')
347 await page.getByRole('button', { name: 'Admin-panel' }).click()
348 const adminPage = await page1Promise
349 await adminPage.getByRole('link', { name: 'add some' }).click()
350 const addBtn = adminPage.getByRole('button', { name: 'Add item to virtual file system' })
351 await addBtn.click()
352 await adminPage.getByRole('menuitem', { name: 'from disk' }).click()
353 await expect(adminPage.getByText('data.kv')).toBeVisible()
354 await adminPage.getByRole('textbox', { name: /Filter results/ }).fill('data')
355 await expect(adminPage.getByText('Filter results (1/')).toBeVisible()
356 await adminPage.getByRole('checkbox').first().check()
357 await adminPage.getByText('data.kv').first().click()
358 await addBtn.click()
359 await adminPage.getByRole('menuitem', { name: 'from disk' }).click()
360 await adminPage.getByRole('button', { name: 'Select this folder' }).click()
361 await addBtn.click()
362 await adminPage.getByRole('menuitem', { name: 'virtual folder' }).click()
363 await adminPage.getByRole('textbox').fill('folder1')
364 await adminPage.getByRole('textbox').press('Enter')
365 await adminPage.locator('#vfs').click()
366 await adminPage.getByText('folder1', { exact: true }).click()
367 await clickIconBtn('Cut', adminPage)
368 await adminPage.locator('div').filter({ hasText: 'InfoNow that this is marked' }).nth(1).click()
369 await clickIconBtn('Close', adminPage)
370 await adminPage.getByRole('treeitem', { name: 'Home folder', exact: true })
371 .getByText('Home folder', { exact: true }).click()
372 await clickIconBtn('/work2/folder1/', adminPage) // paste button
373 await adminPage.getByText('data.kv').click()
374 await clickIconBtn('Cut', adminPage)
375 await clickIconBtn('Close', adminPage)
376 await adminPage.getByText('folder1').click()
377 await clickIconBtn('/data.kv', adminPage) // paste
378 await clickIconBtn('Save', adminPage)
379 await page.getByRole('button', { name: 'Close' }).click()
380 await page.getByRole('link', { name: 'home' }).click()
381 await page.getByRole('link', { name: 'Reload' }).click()
382 await page.getByRole('link', { name: 'folder1, Folder' }).click()
383 await page.getByRole('link', { name: 'data.kv' }).click()
384 await page.getByRole('button', { name: 'Close' }).click()
385 await page.locator('.list-wrapper > div').press('Control+Backspace')
386 await page.getByRole('link', { name: 'work2, Folder' }).click()
387 await page.getByRole('link', { name: 'config.yaml', exact: true }).click()
388 const page2Promise = page.waitForEvent('popup')
389 await page.getByRole('link', { name: 'Open' }).click()
390 const page2 = await page2Promise
391 await page2.getByText(/folder1/).click()
392 })
393
394 test('order field', async ({ page }) => {
395 await page.goto(FRONTEND_URL + 'renameChild/orderTest/')
396 await expect(page.getByText('orderTest')).toBeVisible()
397 await expect(page.locator('.entry-name')).toHaveText(['B', 'A', 'C'])
398 await page.goto(FRONTEND_URL + 'renameChild/')
399 await page.getByRole('link', { name: 'gui#%2, Folder' }).click()
400 await expect(page.getByRole('link', { name: 'alfa.txt' })).toBeVisible()
401 await expect(page.getByText('Not found')).not.toBeVisible()
402 })