tests: more on admin-panel
Massimo Melina committed
Feb 22, 2026 at 15:36 UTC
d915830ea24bd80b2f87b16e87556259e3795b26
1 file changed
+151
-40
e2e/frontend.spec.ts
+151
-40
@@ -212,60 +212,32 @@ test('frontend-admin', async ({ page }) => {
212
213
test('admin1', async ({ page }) => {
214
await fs.promises.rm('tests/work/logs', { force: true, recursive: true }) // clear logs to have consistent screenshots
215
- await page.goto(URL + '~/admin/')
216
- await page.getByRole('textbox', { name: 'Username' }).fill(username)
217
- await page.getByRole('textbox', { name: 'Password' }).fill(password)
218
- await page.getByRole('textbox', { name: 'Password' }).press('Enter')
219
-
220
- const isPhone = await page.evaluate(() => window.matchMedia("(max-width: 600px)").matches)
221
-
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
-
229
- async function closePhoneDialog() { // on phone, some content is displayed in dialogs that need to be closed before having access to the outer content
230
- if (isPhone)
231
- await page.getByRole('button', { name: 'Close' }).click()
232
- }
215
+ const isPhone = await loginAdmin(page)
216
217
function dataTableLoading() {
218
return expect(page.getByRole('grid').getByRole('img')).toBeVisible({ visible: false })
219
}
220
238
- await clickMenu('Internet') // initiate the get_nat process, so we'll have to wait less, later
239
- await clickMenu('Shared files')
221
+ await clickAdminMenu(page, 'Internet') // initiate the get_nat process, so we'll have to wait less, later
222
+ await clickAdminMenu(page, 'Shared files')
223
await expect(page.getByText('cantListBut')).toBeVisible() // wait for data
224
if (!isPhone)
225
await expect(page.getByText('Your shared files')).toBeVisible() // wait for data
226
await screenshot(page)
244
- await clickMenu('Accounts')
227
+ await clickAdminMenu(page, 'Accounts')
228
await expect(page.getByText('admins', { exact: true })).toBeVisible() // wait for data
229
await screenshot(page)
230
await page.getByText('rejetto(admins,').click()
231
await screenshot(page)
249
- await closePhoneDialog()
250
- await clickMenu('Options')
232
+ await closeAdminPhoneDialog(page, isPhone)
233
+ await clickAdminMenu(page, 'Options')
234
await expect(page.getByText('Correctly working on port')).toBeVisible() // wait for data
235
if (!isPhone)
236
await expect(page.getByText('Expire', { exact: true })).toBeVisible() // wait for layout of 'block' table
237
await page.mouse.click(1, 1) // avoid focus inconsistencies
238
await screenshot(page)
256
- { // regression test on dialog navigation not properly working
257
- const blockTable = page.getByRole('grid').filter({ has: page.getByText('Blocked IP', { exact: true }) }).first()
258
- await blockTable.getByRole('button', { name: 'Add' }).click()
259
- const addDialog = page.getByRole('dialog').filter({ hasText: 'Add' })
260
- await addDialog.getByRole('textbox', { name: 'Blocked IP' }).fill('1.2.3.4')
261
- await addDialog.getByRole('button').last().click()
262
- await expect(addDialog).not.toBeVisible()
263
- await expect(page.getByRole('heading', { name: 'Options', exact: true })).toBeVisible() // still on the same page
264
- await expect(blockTable.getByText('1.2.3.4')).toBeVisible()
265
- await page.getByRole('button', { name: 'Reload' }).click() // cancel
266
- }
239
268
- await clickMenu('Logs')
240
+ await clickAdminMenu(page, 'Logs')
241
await dataTableLoading()
242
await screenshot(page)
243
await page.getByRole('tab').nth(2).click()
@@ -275,28 +247,167 @@ test('admin1', async ({ page }) => {
247
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()
248
await page.getByRole('button', { name: '(Close)' }).click()
249
await expect(page.getByText('LogsServedNot')).toBeVisible()
278
- await clickMenu('Language')
250
+ await clickAdminMenu(page, 'Language')
251
await dataTableLoading()
252
if (!isPhone)
253
await expect(page.getByText('author', { exact: true })).toBeVisible() // wait for layout to be stable
254
await screenshot(page, '.MuiDataGrid-root')
283
- await clickMenu('Plugins')
255
+ await clickAdminMenu(page, 'Plugins')
256
await expect(page.getByText('antibrute')).toBeVisible() // wait for data
257
await screenshot(page)
258
await page.getByRole('tab', { name: 'Search' }).click()
259
await page.getByRole('tab', { name: 'updates' }).click()
288
- await clickMenu('Custom HTML')
260
+ await clickAdminMenu(page, 'Custom HTML')
261
await expect(page.getByRole('combobox', { name: 'Section Style' })).toBeVisible() // wait for data to be loaded
262
await screenshot(page)
263
await page.getByRole('main').click()
292
- await clickMenu('Internet')
264
+ await clickAdminMenu(page, 'Internet')
265
await expect(page.getByText('Server')).toBeVisible({ timeout: 10000 }) // wait for data (get_nat can be very slow)
266
await page.mouse.click(1, 1) // avoid focus inconsistencies
267
await screenshot(page)
296
- await clickMenu('Logout')
268
+ await clickAdminMenu(page, 'Logout')
269
+ await screenshot(page)
270
+})
271
+
272
+// some interactions, no screenshots
273
+test('admin2', async ({ page }) => {
274
+ const isPhone = await loginAdmin(page)
275
+
276
+ await clickAdminMenu(page, 'Accounts')
277
+ await expect(page.getByText('admins', { exact: true })).toBeVisible()
278
+ await page.getByRole('button', { name: 'Add' }).click()
279
+ await page.getByRole('menuitem', { name: 'user' }).click()
280
+ const usernameField = page.getByRole('textbox', { name: 'Username' })
281
+ const passwordField = page.getByRole('textbox', { name: 'Password', exact: true })
282
+ await usernameField.fill('admin2-temp-user')
283
+ await passwordField.fill('admin2-temp-pass')
284
+ await page.getByRole('textbox', { name: 'Repeat password' }).fill('admin2-temp-pass')
285
+ await expect(usernameField).toHaveValue('admin2-temp-user')
286
+ const adminAccess = page.getByRole('checkbox', { name: 'Admin-panel access' })
287
+ await adminAccess.check()
288
+ await expect(adminAccess).toBeChecked()
289
+ await page.getByRole('textbox', { name: 'Notes' }).fill('admin2 expanded interactions')
290
+ await closeAdminPhoneDialog(page, isPhone)
291
+
292
+ await clickAdminMenu(page, 'Options')
293
+ await expect(page.getByText('Correctly working on port')).toBeVisible()
294
+ await page.getByRole('button', { name: 'Reload' }).click()
295
+ const blockTable = page.getByRole('grid').filter({ has: page.getByText('Blocked IP', { exact: true }) }).first()
296
+ await blockTable.getByRole('button', { name: 'Add' }).click()
297
+ const addDialog = page.getByRole('dialog').filter({ hasText: 'Add' })
298
+ await addDialog.getByRole('textbox', { name: 'Blocked IP' }).fill('5.6.7.8')
299
+ if (!isPhone) {
300
+ // This field uses a masked input: selecting from picker is more reliable than typing.
301
+ await addDialog.getByRole('button', { name: 'Choose date' }).click()
302
+ const picker = page.locator('.MuiPickersPopper-root[role="dialog"]')
303
+ await picker.locator('button.MuiPickersDay-root:not([disabled])').first().click()
304
+ // Close the popper so it doesn't intercept clicks on the Add dialog buttons.
305
+ await page.keyboard.press('Escape')
306
+ await expect(addDialog.getByRole('textbox', { name: 'Expire' })).not.toHaveValue('MM/DD/YYYY hh:mm aa')
307
+ }
308
+ await addDialog.getByRole('button').last().click()
309
+ await expect(addDialog).not.toBeVisible()
310
+ await expect(page.getByRole('heading', { name: 'Options', exact: true })).toBeVisible() // still on the same page
311
+ await expect(blockTable.getByText('5.6.7.8')).toBeVisible()
312
+ await page.getByRole('button', { name: 'Reload' }).click()
313
+ await expect(blockTable.getByText('5.6.7.8')).not.toBeVisible()
314
+
315
+ await clickAdminMenu(page, 'Logs')
316
+ await expect(page.getByRole('tab', { name: 'Served', exact: true })).toBeVisible()
317
+ const logTabs = page.getByRole('tab')
318
+ await logTabs.nth(1).click()
319
+ await logTabs.nth(2).click()
320
+ await logTabs.nth(3).click()
321
+ await logTabs.nth(4).click()
322
+ const pauseBtn = page.getByRole('button', { name: 'Pause' })
323
+ await expect(pauseBtn).toHaveAttribute('aria-pressed', 'true')
324
+ await pauseBtn.click()
325
+ await expect(pauseBtn).toHaveAttribute('aria-pressed', 'false')
326
+ await pauseBtn.click()
327
+ await expect(pauseBtn).toHaveAttribute('aria-pressed', 'true')
328
+ const showApisBtn = page.getByRole('button', { name: 'Show APIs' })
329
+ await expect(showApisBtn).toHaveAttribute('aria-pressed', 'true')
330
+ await showApisBtn.click()
331
+ await expect(showApisBtn).toHaveAttribute('aria-pressed', 'false')
332
+ await showApisBtn.click()
333
+ await expect(showApisBtn).toHaveAttribute('aria-pressed', 'true')
334
+ await page.getByRole('button', { name: '(Options)' }).click()
335
+ if (!isPhone) {
336
+ const logsDialog = page.getByRole('dialog', { name: /Log options/ })
337
+ const logApisToggle = logsDialog.getByRole('checkbox', { name: 'Log API requests' })
338
+ await expect(logApisToggle).toBeChecked()
339
+ await logApisToggle.click()
340
+ await expect(logApisToggle).not.toBeChecked()
341
+ await logApisToggle.click()
342
+ await expect(logApisToggle).toBeChecked()
343
+ }
344
+ await page.getByRole('button', { name: '(Close)' }).click()
345
+
346
+ await clickAdminMenu(page, 'Plugins')
347
+ await expect(page.getByText('antibrute')).toBeVisible()
348
+ // Tab labels vary by layout/version ("Search", "Get more"), but the order is stable.
349
+ const pluginTabs = page.getByRole('tab')
350
+ await pluginTabs.nth(1).click()
351
+ await page.getByRole('textbox', { name: 'Search text' }).fill('download')
352
+ await pluginTabs.nth(2).click()
353
+ await pluginTabs.nth(0).click()
354
+ if (!isPhone) {
355
+ // Keep this non-invasive: if all plugins are stopped by baseline config, skip plugin-options editing.
356
+ const pluginOptionsBtn = page.getByRole('button', { name: '(Options)' }).first()
357
+ if (await pluginOptionsBtn.isVisible()) {
358
+ await pluginOptionsBtn.click()
359
+ const whereField = page.getByRole('combobox', { name: 'Where to display counter' })
360
+ await whereField.click()
361
+ await page.getByRole('option', { name: 'list', exact: true }).click()
362
+ const pluginOptionsDialog = page.getByRole('dialog')
363
+ // Same reason as log options: accessible name may include shortcut hints.
364
+ const pluginSaveBtn = pluginOptionsDialog.locator('button:has-text("Save")').first()
365
+ await expect(pluginSaveBtn).toBeEnabled()
366
+ await page.getByRole('button', { name: '(Close)' }).click()
367
+ }
368
+ }
369
+
370
+ await clickAdminMenu(page, 'Custom HTML')
371
+ const sectionStyle = page.getByRole('combobox', { name: 'Section Style' })
372
+ await expect(sectionStyle).toBeVisible()
373
+ await sectionStyle.click()
374
+ await page.getByRole('option').nth(1).click()
375
+
376
+ await clickAdminMenu(page, 'Internet')
377
+ await expect(page.getByText('Server')).toBeVisible()
378
+
379
+ await clickAdminMenu(page, 'Logout')
380
await screenshot(page)
381
})
382
383
+async function loginAdmin(page: Page) {
384
+ await page.goto(URL + '~/admin/')
385
+ await page.getByRole('textbox', { name: 'Username' }).fill(username)
386
+ await page.getByRole('textbox', { name: 'Password' }).fill(password)
387
+ await page.getByRole('textbox', { name: 'Password' }).press('Enter')
388
+ const isPhone = await page.evaluate(() => window.matchMedia("(max-width: 600px)").matches)
389
+ ;(page as AdminPage).isPhone = isPhone
390
+ return isPhone
391
+}
392
+
393
+async function clickAdminMenu(page: Page, text: string) {
394
+ if ((page as AdminPage).isPhone)
395
+ await page.getByRole('button', { name: 'menu' }).nth(0).click() // on phones the menu is popup
396
+ await page.getByRole('link', { name: text }).click()
397
+ await page.waitForTimeout(100)
398
+}
399
+
400
+async function closeAdminPhoneDialog(page: Page, isPhone: boolean) {
401
+ // On phones, detail pages are shown in dialogs and block the menu behind them.
402
+ if (isPhone)
403
+ // Mobile close button is icon-only, so we target the first button in the dialog header.
404
+ await page.getByRole('dialog').getByRole('button').first().click()
405
+}
406
+
407
+type AdminPage = Page & {
408
+ isPhone?: boolean
409
+}
410
+
411
async function screenshot(page: Page, selectorForMask = '') {
412
if (selectorForMask)
413
selectorForMask = ',' + selectorForMask