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