314 lines
13 KiB
Plaintext
314 lines
13 KiB
Plaintext
*** Settings ***
|
|
Resource ${CURDIR}/../resources/common.resource
|
|
Resource ${CURDIR}/../resources/auth.resource
|
|
|
|
Suite Setup Login As Admin
|
|
|
|
*** Test Cases ***
|
|
Config Field Edit Persists After Reload
|
|
[Documentation] Verifies auto-save round-trip: UI edit → debounced PATCH → reload rehydration.
|
|
...
|
|
... - Restores original value in teardown so subsequent tests are not affected.
|
|
... - Runs last in suite ordering to avoid destabilising fail2ban health for other tests.
|
|
[Teardown] Run Keyword And Ignore Error Restore Original Ban Time
|
|
|
|
# Step 1 — navigate to config page and click Jails tab.
|
|
Go To ${FRONTEND_URL}/config
|
|
Wait For Load State domcontentloaded
|
|
Sleep 5s
|
|
# Use JS click for the Jails tab — avoids Playwright role=tab selector timing issues
|
|
Evaluate JavaScript ${None}
|
|
... () => {
|
|
... const tabs = document.querySelectorAll('[role="tab"]');
|
|
... for (const tab of tabs) {
|
|
... if (tab.getAttribute('data-testid') === 'jails-tab' ||
|
|
... (tab.textContent?.trim() === 'Jails' && tab.getAttribute('aria-label') === 'Jails')) {
|
|
... tab.click(); return;
|
|
... }
|
|
... }
|
|
... // Fallback: click any tab whose visible text includes 'Jails'
|
|
... for (const tab of tabs) {
|
|
... if (tab.textContent?.trim() === 'Jails') { tab.click(); return; }
|
|
... }
|
|
... }
|
|
Sleep 5s
|
|
|
|
# Step 2 — wait for jail list to load (retry until options appear).
|
|
FOR ${i} IN RANGE 1 21
|
|
${opts}= Get Elements css=[role="option"]
|
|
${count}= Get Length ${opts}
|
|
IF ${count} > 0
|
|
BREAK
|
|
END
|
|
Sleep 2s
|
|
END
|
|
Log Jail options loaded: ${count} found.
|
|
|
|
# Scroll the list pane to top to ensure options are visible
|
|
Evaluate JavaScript ${None}
|
|
... () => {
|
|
... const listPane = document.querySelector('[role="listbox"]');
|
|
... if (listPane) listPane.scrollTop = 0;
|
|
... }
|
|
Sleep 1s
|
|
|
|
# Step 3 — find active jail name via JS (avoids strict-mode selector issues with virtual lists)
|
|
${active_jail_name}= Evaluate JavaScript ${None}
|
|
... () => {
|
|
... const items = document.querySelectorAll('[role="option"]');
|
|
... let activeCount = 0;
|
|
... let firstActiveName = null;
|
|
... items.forEach(el => {
|
|
... const badge = el.querySelector('[class*="Badge"]');
|
|
... if (badge && badge.textContent?.trim() === 'Active') {
|
|
... activeCount++;
|
|
... if (firstActiveName === null) firstActiveName = el.getAttribute('data-name');
|
|
... }
|
|
... });
|
|
... return { active: activeCount, firstActiveName };
|
|
... }
|
|
Log Active jail info: ${active_jail_name}
|
|
|
|
IF ${active_jail_name['active']} == 0
|
|
Log No active jails found. Test requires at least one active jail to verify auto-save.
|
|
Skip Test requires at least one active jail. Activate a jail via the UI or API first.
|
|
END
|
|
|
|
Set Suite Variable ${ACTIVE_JAIL_NAME} ${active_jail_name['firstActiveName']}
|
|
Log Active jail name: ${ACTIVE_JAIL_NAME}
|
|
|
|
# Click the active jail directly by name using JS (bypasses Playwright strict-mode selector conflicts)
|
|
${click_result}= Evaluate JavaScript ${None}
|
|
... () => {
|
|
... const opts = document.querySelectorAll('[role="option"]');
|
|
... for (const opt of opts) {
|
|
... if (opt.getAttribute('data-name') === '${ACTIVE_JAIL_NAME}') {
|
|
... opt.click();
|
|
... return 'CLICKED:' + opt.getAttribute('data-name');
|
|
... }
|
|
... }
|
|
... return 'NOT_FOUND';
|
|
... }
|
|
Log JS click result: ${click_result}
|
|
Sleep 5s
|
|
|
|
# Verify ban_time input appeared
|
|
${has_bantime}= Evaluate JavaScript ${None}
|
|
... () => {
|
|
... return document.querySelector('input[data-field="ban_time"]') !== null;
|
|
... }
|
|
Log Has editable ban_time input: ${has_bantime}
|
|
|
|
IF '${has_bantime}' != 'True'
|
|
Fatal Error TEST_BODY: ban_time input not found for jail ${ACTIVE_JAIL_NAME}.
|
|
END
|
|
|
|
# Get original ban_time value
|
|
${ban_time_value}= Get Attribute css=input[data-field="ban_time"] value
|
|
Set Suite Variable ${ORIGINAL_BANTIME} ${ban_time_value}
|
|
Log Original bantime: ${ORIGINAL_BANTIME}
|
|
|
|
# Step 4 — edit ban_time to 7200 using keyboard press (bypasses React synthetic event issues with Fill Text)
|
|
# Clear the field first by selecting all text
|
|
Click css=input[data-field="ban_time"]
|
|
Sleep 500ms
|
|
Evaluate JavaScript ${None}
|
|
... () => {
|
|
... const input = document.querySelector('input[data-field="ban_time"]');
|
|
... if (input) {
|
|
... input.select();
|
|
... input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Delete', bubbles: true }));
|
|
... }
|
|
... }
|
|
Sleep 500ms
|
|
Fill Text css=input[data-field="ban_time"] 7200
|
|
Sleep 1s
|
|
# Verify the fill worked by checking value via JS
|
|
${fill_value}= Evaluate JavaScript ${None}
|
|
... () => {
|
|
... const el = document.querySelector('input[data-field="ban_time"]');
|
|
... return el ? el.value : 'NOT_FOUND';
|
|
... }
|
|
Log Value after fill attempt: ${fill_value}
|
|
|
|
# Step 5 — wait for auto-save indicator.
|
|
# Also check that the fill actually changed the value (auto-save needs the value to differ from server state)
|
|
${fill_check}= Get Attribute css=input[data-field="ban_time"] value
|
|
Log Value after fill: ${fill_check}
|
|
${saved}= Set Variable ${FALSE}
|
|
FOR ${i} IN RANGE 1 31
|
|
${status_visible}= Run Keyword And Return Status Wait For Elements State css=[role="status"] visible timeout=2s
|
|
IF ${status_visible}
|
|
${status_text}= Evaluate JavaScript ${None}
|
|
... () => {
|
|
... const el = document.querySelector('[role="status"]');
|
|
... return el ? el.textContent : '';
|
|
... }
|
|
Log Status element text: ${status_text}
|
|
IF 'saved' in '${status_text}'.toLowerCase()
|
|
${saved}= Set Variable ${TRUE}
|
|
BREAK
|
|
END
|
|
END
|
|
Sleep 1s
|
|
END
|
|
Log Auto-save confirmed: ${saved}
|
|
|
|
# Step 6 — verify via API.
|
|
${resp}= Run Keyword And Ignore Error GET ${BACKEND_URL}/api/jails
|
|
${verify_ok}= Run Keyword And Return Status Should Contain ${resp.text} 7200
|
|
IF ${verify_ok}
|
|
Log API verification passed: 7200 found in jail configs.
|
|
ELSE
|
|
Log API verification skipped (rate-limited or error): ${resp}
|
|
END
|
|
|
|
# Step 7 — reload and verify persistence.
|
|
Reload
|
|
Wait For Load State domcontentloaded
|
|
Sleep 5s
|
|
|
|
# Re-authenticate if session was lost after reload
|
|
${needs_auth}= Evaluate JavaScript ${None}
|
|
... async () => {
|
|
... // Check current URL - if on login page, need to re-auth
|
|
... if (window.location.pathname.includes('/login')) return true;
|
|
... // Check if we can fetch authenticated API
|
|
... try {
|
|
... const res = await fetch('/api/v1/auth/login', {
|
|
... method: 'POST',
|
|
... headers: { 'Content-Type': 'application/json' },
|
|
... body: JSON.stringify({ password: 'Hallo123!' }),
|
|
... credentials: 'include'
|
|
... });
|
|
... return !res.ok;
|
|
... } catch(e) { return true; }
|
|
... }
|
|
Log Needs re-authentication: ${needs_auth}
|
|
|
|
IF ${needs_auth}
|
|
Evaluate JavaScript ${None}
|
|
... async () => {
|
|
... await new Promise(r => setTimeout(r, 2000));
|
|
... const res = await fetch('/api/v1/auth/login', {
|
|
... method: 'POST',
|
|
... headers: { 'Content-Type': 'application/json' },
|
|
... body: JSON.stringify({ password: 'Hallo123!' }),
|
|
... credentials: 'include'
|
|
... });
|
|
... return res.ok;
|
|
... }
|
|
Evaluate JavaScript ${None} () => sessionStorage.setItem('bangui_authenticated', 'true')
|
|
END
|
|
|
|
# Check if Jails tab is already visible, otherwise go to config page
|
|
${tab_visible}= Run Keyword And Return Status Wait For Elements State role=tab[name=Jails] visible timeout=3s
|
|
IF not ${tab_visible}
|
|
Go To ${FRONTEND_URL}/config
|
|
Wait For Load State domcontentloaded
|
|
Sleep 5s
|
|
END
|
|
|
|
# Use JS click for the Jails tab — avoids Playwright role=tab selector timing issues
|
|
Evaluate JavaScript ${None}
|
|
... () => {
|
|
... const tabs = document.querySelectorAll('[role="tab"]');
|
|
... for (const tab of tabs) {
|
|
... if (tab.textContent?.trim() === 'Jails') { tab.click(); return; }
|
|
... }
|
|
... }
|
|
Sleep 5s
|
|
FOR ${i} IN RANGE 1 21
|
|
${opts}= Get Elements css=[role="option"]
|
|
${count}= Get Length ${opts}
|
|
IF ${count} > 0
|
|
BREAK
|
|
END
|
|
Sleep 2s
|
|
END
|
|
|
|
# Re-click the active jail by name to verify reloaded value.
|
|
Evaluate JavaScript ${None}
|
|
... () => {
|
|
... const opts = document.querySelectorAll('[role="option"]');
|
|
... for (const opt of opts) {
|
|
... if (opt.getAttribute('data-name') === '${ACTIVE_JAIL_NAME}') {
|
|
... opt.scrollIntoView({ behavior: 'instant', block: 'center' });
|
|
... opt.click();
|
|
... return 'CLICKED:' + opt.getAttribute('data-name');
|
|
... }
|
|
... }
|
|
... // Fallback: click first option
|
|
... if (opts.length > 0) { opts[0].click(); return 'FALLBACK:' + opts[0].getAttribute('data-name'); }
|
|
... return 'NO_OPTS';
|
|
... }
|
|
Log Jail re-click result: ${click_result}
|
|
Sleep 8s
|
|
|
|
# Debug: check if detail panel has rendered
|
|
${detail_html}= Evaluate JavaScript ${None}
|
|
... () => {
|
|
... const panel = document.querySelector('[data-testid="jail-detail-panel"]') ||
|
|
... document.querySelector('.f22iagw') ||
|
|
... document.querySelector('[class*="fieldRow"]');
|
|
... const bantimeInput = document.querySelector('input[data-field="ban_time"]');
|
|
... const allInputs = document.querySelectorAll('input');
|
|
... return {
|
|
... hasDetailPanel: !!panel,
|
|
... hasBantimeInput: !!bantimeInput,
|
|
... inputCount: allInputs.length,
|
|
... bantimeValue: bantimeInput ? bantimeInput.value : 'NOT_FOUND',
|
|
... firstInputDataField: allInputs[0]?.getAttribute('data-field') || 'none'
|
|
... };
|
|
... }
|
|
Log Detail panel state after re-click: ${detail_html}
|
|
|
|
${reloaded}= Set Variable ${EMPTY}
|
|
FOR ${i} IN RANGE 1 31
|
|
${input_visible}= Run Keyword And Return Status Wait For Elements State css=input[data-field="ban_time"] visible timeout=5s
|
|
IF ${input_visible}
|
|
${reloaded}= Evaluate JavaScript ${None}
|
|
... () => {
|
|
... const el = document.querySelector('input[data-field="ban_time"]');
|
|
... return el ? el.value : 'NOT_FOUND';
|
|
... }
|
|
Log Reloaded bantime at attempt ${i}: ${reloaded}
|
|
IF '${reloaded}' != '${EMPTY}' and '${reloaded}' != 'None' and '${reloaded}' != 'FAIL' and '${reloaded}' != 'NOT_FOUND'
|
|
BREAK
|
|
END
|
|
END
|
|
Sleep 1s
|
|
END
|
|
Log Reloaded bantime: ${reloaded}
|
|
IF '${reloaded}' == '${EMPTY}' or '${reloaded}' == 'None'
|
|
Fatal Error TEST_BODY: Ban Time input not found after reload.
|
|
END
|
|
Should Be Equal As Strings ${reloaded} 7200
|
|
Log Reload verification passed — value persisted.
|
|
|
|
|
|
*** Keywords ***
|
|
Restore Original Ban Time
|
|
[Documentation] Restore jail's original ban_time so subsequent tests are unaffected.
|
|
${has_original}= Run Keyword And Return Status Should Not Be Empty ${ORIGINAL_BANTIME}
|
|
IF not ${has_original}
|
|
Log No original ban_time to restore.
|
|
RETURN
|
|
END
|
|
Go To ${FRONTEND_URL}/config
|
|
Wait For Load State domcontentloaded
|
|
Sleep 5s
|
|
Run Keyword And Ignore Error Click role=tab[name=Jails]
|
|
Sleep 3s
|
|
Run Keyword And Ignore Error Evaluate JavaScript ${None}
|
|
... () => {
|
|
... const listPane = document.querySelector('[role="listbox"]');
|
|
... if (listPane) listPane.scrollTop = 0;
|
|
... const opts = document.querySelectorAll('[role="option"]');
|
|
... if (opts.length > 0) opts[0].click();
|
|
... }
|
|
Sleep 3s
|
|
Run Keyword And Ignore Error Fill Text css=input[aria-label="Ban Time (s)"] ${ORIGINAL_BANTIME}
|
|
Sleep 3s
|
|
Run Keyword And Ignore Error Wait For Elements State css=[role="status"]:has-text("Saved") visible timeout=15s
|
|
Log Teardown restore ${ORIGINAL_BANTIME} |