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',