dx: steadier tests, working also offline and changing timezone

Massimo Melina committed Sep 29, 2025 at 13:08 UTC 1a913c2469c02c88ce59357143ad5a2a28cbf59f
5 files changed +284 -277
admin/src/InternetPage.ts
+3 -2
@@ -179,7 +179,7 @@ export default function InternetPage({ setTitleSide }: PageProps) {
179 const { https } = status.data ||{}
180 const disabled = https?.port === PORT_DISABLED
181 const error = https?.error
182 - return nat.element || status.element || h(TitleCard, { title: "HTTPS", icon: Lock, color: https?.listening && !error ? 'success' : 'warning' },
182 + return status.element || h(TitleCard, { title: "HTTPS", icon: Lock, color: https?.listening && !error ? 'success' : 'warning' },
183 error ? h(Alert, { severity: 'warning' }, error) :
184 (disabled && h(LinkBtn, { onClick: notEnabled }, "Not enabled")),
185 cert.element || with_(cert.data, c => c.none ? h(LinkBtn, { onClick: () => suggestMakingCert().then(cert.reload) }, "No certificate configured") : h(Box, {},
@@ -322,11 +322,12 @@ export default function InternetPage({ setTitleSide }: PageProps) {
322 h(DataLine),
323 h(Device, { name: "Internet", icon: PublicTwoTone, ip: publicIps,
324 color: checkResult ? 'success' : checkResult === false ? 'error' : doubleNat ? 'warning' : undefined,
325 - below: checking ? h(LinearProgress, { sx: { height: '1em' } }) : h(Box, { fontSize: 'smaller' },
325 + below: checking ? h(LinearProgress, { sx: { height: '1em' } }) : h(Box, { fontSize: 'smaller', className: HIDE_IN_TESTS },
326 doubleNat && h(LinkBtn, { display: 'block', onClick: () => alertDialog(MSG_ISP, 'warning') }, "Double NAT"),
327 checkResult ? "Working!" : checkResult === false ? "Failed!" : '',
328 ' ',
329 (baseUrl > '' || publicIps.length > 0) && data.internalPort && h(LinkBtn, { onClick: () => verify() }, "Verify")
330 + || ' ' // steadier layout, mainly for testing
331 )
332 }),
333 )
admin/src/MainMenu.ts
+2 -2
@@ -17,7 +17,7 @@ import LogoutPage from './LogoutPage';
17 import LangPage from './LangPage'
18 import LogsPage from './LogsPage';
19 import PluginsPage from './PluginsPage';
20 -import { getHFS, replaceStringToReact, WEBSITE } from '@hfs/shared'
20 +import { getHFS, HIDE_IN_TESTS, replaceStringToReact, WEBSITE } from '@hfs/shared'
21 import CustomHtmlPage from './CustomHtmlPage';
22 import InternetPage from './InternetPage'
23 import { useWindowSize } from 'usehooks-ts'
@@ -64,7 +64,7 @@ export default function Menu({ onSelect, itemTitle }: { onSelect: ()=>void, item
64 h(Box, { id: 'hfs-name', display: 'flex', px: 2, py: .5, gap: 2, alignItems: 'center' },
65 h('a', { href: WEBSITE, target: 'website', style: { textDecoration: 'none' } },
66 h(Box, { color: 'primary.contrastText', fontSize: 'min(3rem, max(5vw, 4vh))' }, 'HFS')),
67 - h(Box, { fontSize: 'small', className: 'hideInTests' }, replaceStringToReact(VERSION||'', /-/, () => h('br'))),
67 + h(Box, { fontSize: 'small', className: HIDE_IN_TESTS }, replaceStringToReact(VERSION||'', /-/, () => h('br'))),
68 short && h('img', { src: logo, style: { height: '2.5em' } }),
69 ),
70 mainMenu.map((it, idx) => hTooltip( itemTitle(idx), getMenuLabel(it) + ' ' + itemTitle(idx),
admin/src/VfsPage.ts
+4 -2
@@ -5,7 +5,9 @@ import { apiCall, useApiEx } from './api'
5 import { Alert, Box, Button, Card, CardContent, Grid, Link, List, ListItem, ListItemText, Typography } from '@mui/material'
6 import { state, useSnapState } from './state'
7 import VfsTree, { vfsNodeIcon } from './VfsTree'
8 -import { CFG, matches, newDialog, normalizeHost, onlyTruthy, pathEncode, prefix, VfsNodeAdminSend } from './misc'
8 +import {
9 + CFG, matches, newDialog, normalizeHost, onlyTruthy, pathEncode, prefix, VfsNodeAdminSend, HIDE_IN_TESTS
10 +} from './misc'
11 import { Flex, useBreakpoint } from './mui'
12 import { reactJoin } from '@hfs/shared'
13 import _ from 'lodash'
@@ -57,7 +59,7 @@ export default function VfsPage({ setTitleSide }: PageProps) {
59 severity: 'info',
60 children: [
61 "Your shared files can be browsed from ",
60 - h('span', { className: 'hideInTests', key: 0 },
62 + h('span', { className: HIDE_IN_TESTS, key: 0 },
63 reactJoin(" or ", urls.slice(0,3).map(href => h(Link, { href, target: 'frontend' }, href))) )
64 ]
65 }), [nothingShared, urls])
e2e/frontend.spec.ts
+274 -271
@@ -5,47 +5,47 @@ import { password, resetTimestamp, URL, username } from './common'
5
6 // a generic test touch several parts
7 test('around1', async ({ page }) => {
8 - resetTimestamp();
9 - await page.goto(URL);
10 - await expect(page).toHaveTitle(/File server/);
11 - await screenshot(page);
12 - await page.getByRole('button', { name: 'Login' }).click();
13 - await expect(page.getByRole('dialog', {})).toBeVisible();
14 - await screenshot(page);
8 + resetTimestamp()
9 + await page.goto(URL)
10 + await expect(page).toHaveTitle(/File server/)
11 + await screenshot(page)
12 + await page.getByRole('button', { name: 'Login' }).click()
13 + await expect(page.getByRole('dialog', {})).toBeVisible()
14 + await screenshot(page)
15
16 - await page.getByRole('textbox', { name: 'Username' }).fill(username + '!'); // wrong username
17 - await page.getByRole('textbox', { name: 'Password' }).fill(password);
18 - await page.getByRole('button', { name: 'Continue' }).click();
19 - await expect(page.getByText('x!ErrorInvalid credentials')).toBeVisible();
20 - await page.getByRole('alertdialog').getByRole('button', { name: 'Close' }).click();
16 + await page.getByRole('textbox', { name: 'Username' }).fill(username + '!') // wrong username
17 + await page.getByRole('textbox', { name: 'Password' }).fill(password)
18 + await page.getByRole('button', { name: 'Continue' }).click()
19 + await expect(page.getByText('x!ErrorInvalid credentials')).toBeVisible()
20 + await page.getByRole('alertdialog').getByRole('button', { name: 'Close' }).click()
21
22 - resetTimestamp();
23 - await page.getByRole('textbox', { name: 'Username' }).fill(username);
24 - await page.getByRole('button', { name: 'Continue' }).click();
25 - await page.locator('div').filter({ hasText: 'Logged in' }).nth(3).click();
26 - await screenshot(page);
22 + resetTimestamp()
23 + await page.getByRole('textbox', { name: 'Username' }).fill(username)
24 + await page.getByRole('button', { name: 'Continue' }).click()
25 + await page.locator('div').filter({ hasText: 'Logged in' }).nth(3).click()
26 + await screenshot(page)
27
28 - // selecting in this folder should enable bulk delete button
29 - await page.getByRole('link', { name: 'for-admins, Folder' }).click();
30 - await page.getByRole('link', { name: 'upload, Folder' }).click();
31 - await page.getByRole('link', { name: 'alfa.txt' }).click();
32 - await expect(page.getByText('Delete')).toBeVisible(); // first check single-delete command
33 - await page.getByRole('button', { name: 'Close' }).click();
34 - await page.getByRole('button', { name: 'Select' }).click();
35 - await page.getByRole('checkbox', { name: 'alfa.txt' }).check();
36 - await expect(page.getByRole('button', { name: 'Delete' })).toBeEnabled();
37 - await page.getByRole('button', { name: 'Select' }).click();
28 + // selecting in this folder should enable bulk delete button
29 + await page.getByRole('link', { name: 'for-admins, Folder' }).click()
30 + await page.getByRole('link', { name: 'upload, Folder' }).click()
31 + await page.getByRole('link', { name: 'alfa.txt' }).click()
32 + await expect(page.getByText('Delete')).toBeVisible() // first check single-delete command
33 + await page.getByRole('button', { name: 'Close' }).click()
34 + await page.getByRole('button', { name: 'Select' }).click()
35 + await page.getByRole('checkbox', { name: 'alfa.txt' }).check()
36 + await expect(page.getByRole('button', { name: 'Delete' })).toBeEnabled()
37 + await page.getByRole('button', { name: 'Select' }).click()
38
39 - await page.getByRole('link', { name: 'home' }).click();
40 - await page.getByRole('button', { name: username }).click();
41 - await page.getByRole('button', { name: 'Logout' }).click();
42 - await page.getByText('Logged out').click();
43 - await page.getByRole('link', { name: 'cantListBut, Folder' }).click();
44 - await page.getByText('x!WarningForbidden').click();
45 - await page.getByRole('button', { name: 'Close' }).click();
46 - await page.getByRole('link', { name: 'cantListPage, Folder' }).click();
47 - await page.getByRole('link', { name: 'alfa.txt' }).click();
48 - await expect(page.getByRole('dialog')).toMatchAriaSnapshot(`
39 + await page.getByRole('link', { name: 'home' }).click()
40 + await page.getByRole('button', { name: username }).click()
41 + await page.getByRole('button', { name: 'Logout' }).click()
42 + await page.getByText('Logged out').click()
43 + await page.getByRole('link', { name: 'cantListBut, Folder' }).click()
44 + await page.getByText('x!WarningForbidden').click()
45 + await page.getByRole('button', { name: 'Close' }).click()
46 + await page.getByRole('link', { name: 'cantListPage, Folder' }).click()
47 + await page.getByRole('link', { name: 'alfa.txt' }).click()
48 + await expect(page.getByRole('dialog')).toMatchAriaSnapshot(`
49 - dialog:
50 - button "Close"
51 - heading "File menu" [level=1]
@@ -59,102 +59,102 @@ test('around1', async ({ page }) => {
59 - definition: /\\d+\\/\\d+\\/\\d+, \\d+:\\d+:\\d+/
60 - link "Download"
61 - link "Open"
62 - `);
63 - await page.getByRole('link', { name: 'Download' }).click(); // this also closes the dialog
64 - await page.getByRole('link', { name: 'alfa.txt', exact: true }).click();
65 - await screenshot(page);
66 - await page.getByRole('button', { name: 'Close' }).click();
62 + `)
63 + await page.getByRole('link', { name: 'Download' }).click() // this also closes the dialog
64 + await page.getByRole('link', { name: 'alfa.txt', exact: true }).click()
65 + await screenshot(page)
66 + await page.getByRole('button', { name: 'Close' }).click()
67
68 - await page.getByRole('link', { name: 'cantListPage' }).click();
69 - await page.getByRole('button', { name: 'Calculate' }).click();
70 - await page.getByText('KB / 2 files').click();
71 - await page.locator('#menu-prop-name').getByText('cantListPage').click();
72 - const downloadPromise = page.waitForEvent('download');
73 - await page.getByRole('link', { name: 'Download' }).click();
74 - await downloadPromise;
75 - await page.getByRole('link', { name: 'cantListPage' }).click();
76 - const pageListPromise = page.waitForEvent('popup');
77 - await page.getByRole('link', { name: 'Get list' }).click();
78 - const pageList = await pageListPromise;
79 - await expect(pageList.getByText('::1')).toBeVisible();
80 - await pageList.close()
68 + await page.getByRole('link', { name: 'cantListPage' }).click()
69 + await page.getByRole('button', { name: 'Calculate' }).click()
70 + await page.getByText('KB / 2 files').click()
71 + await page.locator('#menu-prop-name').getByText('cantListPage').click()
72 + const downloadPromise = page.waitForEvent('download')
73 + await page.getByRole('link', { name: 'Download' }).click()
74 + await downloadPromise
75 + await page.getByRole('link', { name: 'cantListPage' }).click()
76 + const pageListPromise = page.waitForEvent('popup')
77 + await page.getByRole('link', { name: 'Get list' }).click()
78 + const pageList = await pageListPromise
79 + await expect(pageList.getByText('::1')).toBeVisible()
80 + await pageList.close()
81
82 - await page.getByRole('link', { name: 'home' }).click();
83 - await page.getByRole('button', { name: 'Select' }).click();
84 - await page.getByRole('textbox', { name: 'Type here to filter the list' }).click();
85 - await page.getByRole('textbox', { name: 'Type here to filter the list' }).fill('x');
86 - await page.getByText('filtered').click();
87 - await screenshot(page);
88 - await page.getByRole('button', { name: 'Select' }).click();
89 - await page.getByText('file, 10 folders, 6 B').click();
90 - await page.getByRole('link', { name: 'cantListPageAlt, Folder' }).click();
91 - await page.getByRole('link', { name: 'home' }).click();
92 - await page.getByRole('link', { name: 'f1, Folder' }).click();
93 - await page.getByRole('link', { name: 'page, Folder' }).click();
94 - await page.getByRole('img', { name: 'gpl logo' }).click();
95 - await page.getByRole('heading', { name: 'This is a test' }).click();
96 - await page.goBack();
97 - await page.getByRole('link', { name: 'home' }).click();
82 + await page.getByRole('link', { name: 'home' }).click()
83 + await page.getByRole('button', { name: 'Select' }).click()
84 + await page.getByRole('textbox', { name: 'Type here to filter the list' }).click()
85 + await page.getByRole('textbox', { name: 'Type here to filter the list' }).fill('x')
86 + await page.getByText('filtered').click()
87 + await screenshot(page)
88 + await page.getByRole('button', { name: 'Select' }).click()
89 + await page.getByText('file, 10 folders, 6 B').click()
90 + await page.getByRole('link', { name: 'cantListPageAlt, Folder' }).click()
91 + await page.getByRole('link', { name: 'home' }).click()
92 + await page.getByRole('link', { name: 'f1, Folder' }).click()
93 + await page.getByRole('link', { name: 'page, Folder' }).click()
94 + await page.getByRole('img', { name: 'gpl logo' }).click()
95 + await page.getByRole('heading', { name: 'This is a test' }).click()
96 + await page.goBack()
97 + await page.getByRole('link', { name: 'home' }).click()
98
99 - const isPhone = await page.evaluate(() => window.matchMedia("(max-width: 600px)").matches);
100 - if (isPhone)
101 - await page.getByRole('listitem').filter({ hasText: 'for-disabled' }).getByRole('button').click();
102 - else
103 - await page.getByRole('listitem').filter({ hasText: 'for-disabledMenu' }).getByRole('button').click();
104 - await expect(page.getByText('Missing permission')).toBeVisible();
105 - await page.getByRole('button', { name: 'Close' }).click();
106 - await page.getByRole('link', { name: 'cantSearchForMasks, Folder' }).click();
107 - await expect(page.getByRole('link', { name: 'cantSearchForMasks' })).toBeVisible();
108 - await page.getByRole('link', { name: 'cantSearchForMasks' }).click();
109 - await expect(page.locator('#menu-prop-name').getByText('cantSearchForMasks')).toBeVisible();
110 - await page.getByRole('button', { name: 'Close' }).click();
111 - await page.getByRole('link', { name: 'cantSearchForMasks' }).click();
112 - await expect(page.getByText('xFolder')).toBeVisible();
113 - await page.getByRole('button', { name: 'Close' }).click();
114 - await page.getByRole('link', { name: 'home' }).click();
115 - await expect(page.getByText('file, 10 folders, 6 B')).toBeVisible();
116 -});
99 + const isPhone = await page.evaluate(() => window.matchMedia("(max-width: 600px)").matches)
100 + if (isPhone)
101 + await page.getByRole('listitem').filter({ hasText: 'for-disabled' }).getByRole('button').click()
102 + else
103 + await page.getByRole('listitem').filter({ hasText: 'for-disabledMenu' }).getByRole('button').click()
104 + await expect(page.getByText('Missing permission')).toBeVisible()
105 + await page.getByRole('button', { name: 'Close' }).click()
106 + await page.getByRole('link', { name: 'cantSearchForMasks, Folder' }).click()
107 + await expect(page.getByRole('link', { name: 'cantSearchForMasks' })).toBeVisible()
108 + await page.getByRole('link', { name: 'cantSearchForMasks' }).click()
109 + await expect(page.locator('#menu-prop-name').getByText('cantSearchForMasks')).toBeVisible()
110 + await page.getByRole('button', { name: 'Close' }).click()
111 + await page.getByRole('link', { name: 'cantSearchForMasks' }).click()
112 + await expect(page.getByText('xFolder')).toBeVisible()
113 + await page.getByRole('button', { name: 'Close' }).click()
114 + await page.getByRole('link', { name: 'home' }).click()
115 + await expect(page.getByText('file, 10 folders, 6 B')).toBeVisible()
116 +})
117
118 test('search1', async ({ page }) => {
119 - resetTimestamp()
120 - await page.goto(URL);
121 - await page.getByRole('button', { name: 'Search' }).click();
122 - await page.locator('input[name="name"]').fill('a');
123 - await page.getByRole('button', { name: 'Continue' }).click();
124 - await page.getByText('files, 12 folders, 29.3 KB').click();
125 - await page.getByRole('link', { name: 'cantListPage/ alfa.txt' }).click();
126 - await page.getByRole('button', { name: 'Close' }).click();
127 - await page.getByRole('button', { name: 'Clear search' }).click();
119 + resetTimestamp()
120 + await page.goto(URL)
121 + await page.getByRole('button', { name: 'Search' }).click()
122 + await page.locator('input[name="name"]').fill('a')
123 + await page.getByRole('button', { name: 'Continue' }).click()
124 + await page.getByText('files, 12 folders, 29.3 KB').click()
125 + await page.getByRole('link', { name: 'cantListPage/ alfa.txt' }).click()
126 + await page.getByRole('button', { name: 'Close' }).click()
127 + await page.getByRole('button', { name: 'Clear search' }).click()
128
129 - await page.getByRole('button', { name: 'Search' }).click();
130 - await page.locator('input[name="name"]').fill('a*');
131 - await page.locator('input[name="name"]').press('Enter');
132 - await page.getByText('files, 36 B').click();
129 + await page.getByRole('button', { name: 'Search' }).click()
130 + await page.locator('input[name="name"]').fill('a*')
131 + await page.locator('input[name="name"]').press('Enter')
132 + await page.getByText('files, 36 B').click()
133
134 - await page.getByRole('link', { name: 'home' }).click();
135 - await page.getByRole('button', { name: 'Close' }).click();
136 - await page.getByRole('link', { name: 'home' }).click();
137 - await page.getByRole('button', { name: 'Close' }).click();
138 - await page.getByRole('button', { name: 'Clear search' }).click();
139 - await page.getByRole('link', { name: 'home' }).click();
140 - await expect(page.getByText('xFolder')).toBeVisible();
141 - await page.getByRole('button', { name: 'Close' }).click();
142 - await page.getByRole('button', { name: 'Options' }).click();
143 - await expect(page.locator('#option-sort-by')).toBeVisible();
144 - await expect(page.locator('#option-sort-by')).toBeVisible();
145 - await page.getByRole('dialog').locator('div').nth(2).click();
146 - await page.locator('#option-sort-by').selectOption('size');
147 - await page.getByRole('checkbox', { name: 'Invert order' }).check();
148 - await page.getByRole('slider').fill('6');
149 - await page.locator('#option-theme').selectOption('dark');
150 - await page.getByRole('button', { name: 'Close' }).click();
151 - await page.getByRole('link', { name: 'cantListPageAlt, Folder' }).click();
152 - await expect(page.getByText('alfa.txt')).toBeVisible() // be sure the folder is loaded
153 - await page.mouse.click(1, 1); // avoid focus inconsistencies
154 - await screenshot(page);
134 + await page.getByRole('link', { name: 'home' }).click()
135 + await page.getByRole('button', { name: 'Close' }).click()
136 + await page.getByRole('link', { name: 'home' }).click()
137 + await page.getByRole('button', { name: 'Close' }).click()
138 + await page.getByRole('button', { name: 'Clear search' }).click()
139 + await page.getByRole('link', { name: 'home' }).click()
140 + await expect(page.getByText('xFolder')).toBeVisible()
141 + await page.getByRole('button', { name: 'Close' }).click()
142 + await page.getByRole('button', { name: 'Options' }).click()
143 + await expect(page.locator('#option-sort-by')).toBeVisible()
144 + await expect(page.locator('#option-sort-by')).toBeVisible()
145 + await page.getByRole('dialog').locator('div').nth(2).click()
146 + await page.locator('#option-sort-by').selectOption('size')
147 + await page.getByRole('checkbox', { name: 'Invert order' }).check()
148 + await page.getByRole('slider').fill('6')
149 + await page.locator('#option-theme').selectOption('dark')
150 + await page.getByRole('button', { name: 'Close' }).click()
151 + await page.getByRole('link', { name: 'cantListPageAlt, Folder' }).click()
152 + await expect(page.getByText('alfa.txt')).toBeVisible() // be sure the folder is loaded
153 + await page.mouse.click(1, 1) // avoid focus inconsistencies
154 + await screenshot(page)
155
156 - await page.getByRole('button', { name: 'Zip' }).click();
157 - await expect(page.getByRole('dialog')).toMatchAriaSnapshot(`
156 + await page.getByRole('button', { name: 'Zip' }).click()
157 + await expect(page.getByRole('dialog')).toMatchAriaSnapshot(`
158 - dialog:
159 - button "Close"
160 - heading "Confirm" [level=1]
@@ -163,21 +163,21 @@ test('search1', async ({ page }) => {
163 - button "Yes"
164 - button "Don't"
165 - button "Select some files"
166 - `);
167 - await page.getByRole('button', { name: "Don't" }).click();
168 - await page.getByRole('button', { name: 'Zip' }).click();
169 - await page.getByRole('button', { name: 'Select some files' }).click();
170 - await page.getByText('Use checkboxes to select the').click();
171 - await page.getByRole('button', { name: 'Close' }).click();
172 - await page.getByRole('textbox', { name: 'Type here to filter the list' }).click();
173 -});
166 + `)
167 + await page.getByRole('button', { name: "Don't" }).click()
168 + await page.getByRole('button', { name: 'Zip' }).click()
169 + await page.getByRole('button', { name: 'Select some files' }).click()
170 + await page.getByText('Use checkboxes to select the').click()
171 + await page.getByRole('button', { name: 'Close' }).click()
172 + await page.getByRole('textbox', { name: 'Type here to filter the list' }).click()
173 +})
174
175 test('frontend-admin', async ({ page }) => {
176 - await page.goto(URL, { waitUntil: 'networkidle' });
177 - await page.evaluate(() => document.fonts.ready); // aspetta i font
178 - await page.getByRole('button', { name: 'Options' }).click();
179 - // no admin button yet,
180 - await expect(page.getByRole('dialog')).toMatchAriaSnapshot(`
176 + await page.goto(URL, { waitUntil: 'networkidle' })
177 + await page.evaluate(() => document.fonts.ready) // aspetta i font
178 + await page.getByRole('button', { name: 'Options' }).click()
179 + // no admin button yet,
180 + await expect(page.getByRole('dialog')).toMatchAriaSnapshot(`
181 - dialog:
182 - button "Close"
183 - heading "Options" [level=1]
@@ -198,151 +198,154 @@ test('frontend-admin', async ({ page }) => {
198 - 'option "Theme: auto" [selected]'
199 - 'option "Theme: light"'
200 - 'option "Theme: dark"'
201 - `);
202 - await page.getByRole('button', { name: 'Close' }).click();
203 - await page.getByRole('button', { name: 'Login' }).click();
204 - await page.getByRole('textbox', { name: 'Username' }).fill(username);
205 - await page.getByRole('textbox', { name: 'Password' }).fill(password);
206 - await page.getByRole('textbox', { name: 'Password' }).press('Enter');
207 - await page.getByRole('button', { name: 'Options' }).click();
208 - const page1Promise = page.waitForEvent('popup');
209 - await page.getByRole('button', { name: 'Admin-panel' }).click();
210 - const page1 = await page1Promise;
211 - await expect(page1).toHaveTitle(/HFS Admin-panel/);
201 + `)
202 + await page.getByRole('button', { name: 'Close' }).click()
203 + await page.getByRole('button', { name: 'Login' }).click()
204 + await page.getByRole('textbox', { name: 'Username' }).fill(username)
205 + await page.getByRole('textbox', { name: 'Password' }).fill(password)
206 + await page.getByRole('textbox', { name: 'Password' }).press('Enter')
207 + await page.getByRole('button', { name: 'Options' }).click()
208 + const page1Promise = page.waitForEvent('popup')
209 + await page.getByRole('button', { name: 'Admin-panel' }).click()
210 + const page1 = await page1Promise
211 + await expect(page1).toHaveTitle(/HFS Admin-panel/)
212 })
213
214 test('admin1', async ({ page }) => {
215 - await fs.promises.rm('tests/work/logs', {force: true, recursive: true}); // clear logs to have consistent screenshots
216 - await page.goto(URL + '~/admin/');
217 - await page.getByRole('textbox', { name: 'Username' }).fill(username);
218 - await page.getByRole('textbox', { name: 'Password' }).fill(password);
219 - await page.getByRole('textbox', { name: 'Password' }).press('Enter');
215 + await fs.promises.rm('tests/work/logs', { force: true, recursive: true }) // clear logs to have consistent screenshots
216 + await page.goto(URL + '~/admin/')
217 + await page.getByRole('textbox', { name: 'Username' }).fill(username)
218 + await page.getByRole('textbox', { name: 'Password' }).fill(password)
219 + await page.getByRole('textbox', { name: 'Password' }).press('Enter')
220
221 - const isPhone = await page.evaluate(() => window.matchMedia("(max-width: 600px)").matches);
222 - async function clickMenu(text: string) {
223 - if (isPhone)
224 - await page.getByRole('button', { name: 'menu' }).nth(0).click(); // on phones the menu is popup
225 - await page.getByRole('link', { name: text }).click();
226 - await page.waitForTimeout(100);
227 - }
228 - async function closePhoneDialog() { // on phone, some content is displayed in dialogs that need to be closed before having access to the outer content
229 - if (isPhone)
230 - await page.getByRole('button', { name: 'Close' }).click();
231 - }
221 + const isPhone = await page.evaluate(() => window.matchMedia("(max-width: 600px)").matches)
222
233 - function dataTableLoading() {
234 - return expect(page.getByRole('grid').getByRole('img')).toBeVisible({ visible: false });
235 - }
223 + async function clickMenu(text: string) {
224 + if (isPhone)
225 + await page.getByRole('button', { name: 'menu' }).nth(0).click() // on phones the menu is popup
226 + await page.getByRole('link', { name: text }).click()
227 + await page.waitForTimeout(100)
228 + }
229
237 - await clickMenu('Internet'); // initiate the get_nat process, so we'll have to wait less, later
238 - await clickMenu('Shared files')
239 - await expect(page.getByText('cantListBut')).toBeVisible(); // wait for data
240 - await screenshot(page)
241 - await clickMenu('Accounts');
242 - await expect(page.getByText('admins', { exact: true })).toBeVisible(); // wait for data
243 - await screenshot(page)
244 - await page.getByText('rejetto(admins,').click();
245 - await screenshot(page)
246 - await closePhoneDialog();
247 - await clickMenu('Options');
248 - await expect(page.getByText('Correctly working on port')).toBeVisible(); // wait for data
249 - if (!isPhone)
250 - await expect(page.getByText('Expire', { exact: true })).toBeVisible(); // wait for layout of 'block' table
251 - await page.mouse.click(1, 1); // avoid focus inconsistencies
252 - await screenshot(page)
230 + async function closePhoneDialog() { // on phone, some content is displayed in dialogs that need to be closed before having access to the outer content
231 + if (isPhone)
232 + await page.getByRole('button', { name: 'Close' }).click()
233 + }
234
254 - await clickMenu('Logs');
255 - await dataTableLoading()
256 - await screenshot(page);
257 - await page.getByRole('tab').nth(2).click();
258 - await page.getByRole('tab').nth(3).click();
259 - await page.getByRole('tab').nth(4).click();
260 - await page.getByRole('button', { name: '(Options)' }).click();
261 - await page.locator('div').filter({ hasText: 'ServedRequests are logged here. Empty to disable it.Not servedWrite errors in a different file. Empty to use same file.' }).nth(3).click();
262 - await page.getByRole('button', { name: '(Close)' }).click();
263 - await expect(page.getByText('LogsServedNot')).toBeVisible();
264 - await clickMenu('Language');
265 - await dataTableLoading()
266 - if (!isPhone)
267 - await expect(page.getByText('author', { exact: true })).toBeVisible(); // wait for layout to be stable
268 - await screenshot(page, '.MuiDataGrid-root');
269 - await clickMenu('Plugins');
270 - await expect(page.getByText('antibrute')).toBeVisible(); // wait for data
271 - await screenshot(page);
272 - await page.getByRole('tab', { name: 'Search' }).click();
273 - await page.getByRole('tab', { name: 'updates' }).click();
274 - await clickMenu('Custom HTML');
275 - await expect(page.getByRole('combobox', { name: 'Section Style' })).toBeVisible(); // wait for data to be loaded
276 - await screenshot(page);
277 - await page.getByRole('main').click();
278 - await clickMenu('Internet');
279 - await expect(page.getByRole('button', { name: 'Verify' })).toBeVisible({ timeout: 10000 }); // wait for data (get_nat can be very slow)
280 - await page.mouse.click(1, 1); // avoid focus inconsistencies
281 - await screenshot(page)
282 - await clickMenu('Logout');
283 - await screenshot(page);
284 -});
235 + function dataTableLoading() {
236 + return expect(page.getByRole('grid').getByRole('img')).toBeVisible({ visible: false })
237 + }
238
286 -async function screenshot(page: Page, selectorForMask='') {
287 - if (selectorForMask)
288 - selectorForMask = ',' + selectorForMask
289 - await wait(1000) // this accounts especially for our DataTable component which takes time to set the layout
290 - return expect(page).toHaveScreenshot({ fullPage: true, mask: [page.locator(`.maskInTests${selectorForMask}`)] });
239 + await clickMenu('Internet') // initiate the get_nat process, so we'll have to wait less, later
240 + await clickMenu('Shared files')
241 + await expect(page.getByText('cantListBut')).toBeVisible() // wait for data
242 + if (!isPhone)
243 + await expect(page.getByText('Your shared files')).toBeVisible() // wait for data
244 + await screenshot(page)
245 + await clickMenu('Accounts')
246 + await expect(page.getByText('admins', { exact: true })).toBeVisible() // wait for data
247 + await screenshot(page)
248 + await page.getByText('rejetto(admins,').click()
249 + await screenshot(page)
250 + await closePhoneDialog()
251 + await clickMenu('Options')
252 + await expect(page.getByText('Correctly working on port')).toBeVisible() // wait for data
253 + if (!isPhone)
254 + await expect(page.getByText('Expire', { exact: true })).toBeVisible() // wait for layout of 'block' table
255 + await page.mouse.click(1, 1) // avoid focus inconsistencies
256 + await screenshot(page)
257 +
258 + await clickMenu('Logs')
259 + await dataTableLoading()
260 + await screenshot(page)
261 + await page.getByRole('tab').nth(2).click()
262 + await page.getByRole('tab').nth(3).click()
263 + await page.getByRole('tab').nth(4).click()
264 + await page.getByRole('button', { name: '(Options)' }).click()
265 + await page.locator('div').filter({ hasText: 'ServedRequests are logged here. Empty to disable it.Not servedWrite errors in a different file. Empty to use same file.' }).nth(3).click()
266 + await page.getByRole('button', { name: '(Close)' }).click()
267 + await expect(page.getByText('LogsServedNot')).toBeVisible()
268 + await clickMenu('Language')
269 + await dataTableLoading()
270 + if (!isPhone)
271 + await expect(page.getByText('author', { exact: true })).toBeVisible() // wait for layout to be stable
272 + await screenshot(page, '.MuiDataGrid-root')
273 + await clickMenu('Plugins')
274 + await expect(page.getByText('antibrute')).toBeVisible() // wait for data
275 + await screenshot(page)
276 + await page.getByRole('tab', { name: 'Search' }).click()
277 + await page.getByRole('tab', { name: 'updates' }).click()
278 + await clickMenu('Custom HTML')
279 + await expect(page.getByRole('combobox', { name: 'Section Style' })).toBeVisible() // wait for data to be loaded
280 + await screenshot(page)
281 + await page.getByRole('main').click()
282 + await clickMenu('Internet')
283 + await expect(page.getByText('Server')).toBeVisible({ timeout: 10000 }) // wait for data (get_nat can be very slow)
284 + await page.mouse.click(1, 1) // avoid focus inconsistencies
285 + await screenshot(page)
286 + await clickMenu('Logout')
287 + await screenshot(page)
288 +})
289 +
290 +async function screenshot(page: Page, selectorForMask = '') {
291 + if (selectorForMask)
292 + selectorForMask = ',' + selectorForMask
293 + await wait(1000) // this accounts especially for our DataTable component which takes time to set the layout
294 + return expect(page).toHaveScreenshot({ fullPage: true, mask: [page.locator(`.maskInTests${selectorForMask}`)] })
295 }
296
297 test('anew', async ({ page, browserName }) => {
298 if (page.viewportSize()?.width! < 1000 || browserName !== 'chromium') return // test only for desktop chromium
295 - await fs.promises.writeFile('tests/work2/config.yaml', "port: 82\nopen_browser_at_start: false"); // reset config
299 + await fs.promises.writeFile('tests/work2/config.yaml', "port: 82\nopen_browser_at_start: false") // reset config
300 await wait(500) // ensure new config is aplied
297 - await page.goto('http://localhost:82/');
298 - await expect(page.getByText('Nothing here')).toBeVisible();
299 - await page.getByRole('button', { name: 'Options' }).click();
300 - const page1Promise = page.waitForEvent('popup');
301 - await page.getByRole('button', { name: 'Admin-panel' }).click();
302 - const adminPage = await page1Promise;
303 - await adminPage.getByRole('link', { name: 'add some' }).click();
301 + await page.goto('http://localhost:82/')
302 + await expect(page.getByText('Nothing here')).toBeVisible()
303 + await page.getByRole('button', { name: 'Options' }).click()
304 + const page1Promise = page.waitForEvent('popup')
305 + await page.getByRole('button', { name: 'Admin-panel' }).click()
306 + const adminPage = await page1Promise
307 + await adminPage.getByRole('link', { name: 'add some' }).click()
308 const addBtn = adminPage.getByRole('button').nth(1)
305 - await addBtn.click();
306 - await adminPage.getByRole('menuitem', { name: 'from disk' }).click();
307 - await adminPage.getByRole('textbox', { name: /Filter results/ }).fill('data');
308 - await expect(adminPage.getByText('Filter results (1/')).toBeVisible();
309 - await adminPage.getByRole('checkbox').check();
310 - await adminPage.getByText('data.kv').click();
311 - await addBtn.click();
312 - await adminPage.getByRole('menuitem', { name: 'from disk' }).click();
313 - await adminPage.getByRole('button', { name: 'Select this folder' }).click();
314 - await addBtn.click();
315 - await adminPage.getByRole('menuitem', { name: 'virtual folder' }).click();
316 - await adminPage.getByRole('textbox').fill('folder1');
317 - await adminPage.getByRole('textbox').press('Enter');
318 - await adminPage.getByRole('dialog').locator('div').nth(1).click();
309 + await addBtn.click()
310 + await adminPage.getByRole('menuitem', { name: 'from disk' }).click()
311 + await adminPage.getByRole('textbox', { name: /Filter results/ }).fill('data')
312 + await expect(adminPage.getByText('Filter results (1/')).toBeVisible()
313 + await adminPage.getByRole('checkbox').check()
314 + await adminPage.getByText('data.kv').click()
315 + await addBtn.click()
316 + await adminPage.getByRole('menuitem', { name: 'from disk' }).click()
317 + await adminPage.getByRole('button', { name: 'Select this folder' }).click()
318 + await addBtn.click()
319 + await adminPage.getByRole('menuitem', { name: 'virtual folder' }).click()
320 + await adminPage.getByRole('textbox').fill('folder1')
321 + await adminPage.getByRole('textbox').press('Enter')
322 + await adminPage.getByRole('dialog').locator('div').nth(1).click()
323 await adminPage.locator('.MuiDialog-container').press('Escape')
320 - await adminPage.locator('#vfs').click();
321 - await adminPage.getByText('folder1', { exact: true }).click();
322 - await adminPage.getByRole('treeitem', { name: 'folder1' }).locator('path').first().click();
323 - await adminPage.getByText('folder1', { exact: true }).click();
324 - await adminPage.getByRole('button', { name: 'Cut' }).click();
325 - await adminPage.locator('div').filter({ hasText: 'InfoNow that this is marked' }).nth(1).click();
326 - await adminPage.getByRole('button', { name: '(Close)' }).click();
327 - await adminPage.getByText('Home folder').click();
328 - await adminPage.getByRole('button', { name: '(/work2/folder1/)' }).click(); // paste button
329 - await adminPage.getByText('data.kv').click();
330 - await adminPage.getByRole('button', { name: 'Cut' }).click();
331 - await adminPage.getByRole('button', { name: '(Close)' }).click();
332 - await adminPage.getByText('folder1').click();
333 - await adminPage.getByRole('button', { name: '(/data.kv)' }).click();
334 - await adminPage.locator('[id="vfs-/"] div').filter({ hasText: /work2\/data\.kv$/ }).nth(4).click();
335 - await page.getByRole('button', { name: 'Close' }).click();
336 - await page.getByRole('link', { name: 'home' }).click();
337 - await page.getByRole('link', { name: 'Reload' }).click();
338 - await page.getByRole('link', { name: 'folder1, Folder' }).click();
339 - await page.getByRole('link', { name: 'data.kv' }).click();
340 - await page.getByRole('button', { name: 'Close' }).click();
341 - await page.locator('.list-wrapper > div').press('Control+Backspace');
342 - await page.getByRole('link', { name: 'work2, Folder' }).click();
343 - await page.getByRole('link', { name: 'config.yaml', exact: true }).click();
344 - const page2Promise = page.waitForEvent('popup');
345 - await page.getByRole('link', { name: 'Open' }).click();
346 - const page2 = await page2Promise;
347 - await page2.getByText(/folder1/).click();
348 -});
324 + await adminPage.locator('#vfs').click()
325 + await adminPage.getByText('folder1', { exact: true }).click()
326 + await adminPage.getByRole('treeitem', { name: 'folder1' }).locator('path').first().click()
327 + await adminPage.getByText('folder1', { exact: true }).click()
328 + await adminPage.getByRole('button', { name: 'Cut' }).click()
329 + await adminPage.locator('div').filter({ hasText: 'InfoNow that this is marked' }).nth(1).click()
330 + await adminPage.getByRole('button', { name: '(Close)' }).click()
331 + await adminPage.getByText('Home folder').click()
332 + await adminPage.getByRole('button', { name: '(/work2/folder1/)' }).click() // paste button
333 + await adminPage.getByText('data.kv').click()
334 + await adminPage.getByRole('button', { name: 'Cut' }).click()
335 + await adminPage.getByRole('button', { name: '(Close)' }).click()
336 + await adminPage.getByText('folder1').click()
337 + await adminPage.getByRole('button', { name: '(/data.kv)' }).click() // paste
338 + await page.getByRole('button', { name: 'Close' }).click()
339 + await page.getByRole('link', { name: 'home' }).click()
340 + await page.getByRole('link', { name: 'Reload' }).click()
341 + await page.getByRole('link', { name: 'folder1, Folder' }).click()
342 + await page.getByRole('link', { name: 'data.kv' }).click()
343 + await page.getByRole('button', { name: 'Close' }).click()
344 + await page.locator('.list-wrapper > div').press('Control+Backspace')
345 + await page.getByRole('link', { name: 'work2, Folder' }).click()
346 + await page.getByRole('link', { name: 'config.yaml', exact: true }).click()
347 + const page2Promise = page.waitForEvent('popup')
348 + await page.getByRole('link', { name: 'Open' }).click()
349 + const page2 = await page2Promise
350 + await page2.getByText(/folder1/).click()
351 +})
playwright.config.ts
+1
@@ -25,6 +25,7 @@ export default defineConfig({
25
26 /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
27 trace: 'on-first-retry',
28 + timezoneId: 'Europe/Rome',
29 launchOptions: {
30 args: [
31 '--disable-font-subpixel-positioning',