fix: add aria-labels to SetupPage inputs and update e2e selectors
- Add aria-label attributes to all form inputs in SetupPage.tsx (Master Password, Confirm Password, Database Path, fail2ban Socket Path, Timezone, Session Duration) for accessibility and test stability - Update e2e tests to use xpath selectors with role=alert instead of class-based selectors for validation messages - Add New Context / New Page per test for browser isolation - Fix API endpoint from /api/setup/status to /api/v1/setup - Fix response field from setup_complete to completed - Simplify password strength test to check aria-live text instead of DOM class traversal - Remove completed task docs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2236
Docs/Tasks.md
2236
Docs/Tasks.md
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,8 @@ Suite Setup Wait For Backend Health
|
|||||||
Setup Page Renders All Form Fields
|
Setup Page Renders All Form Fields
|
||||||
[Documentation] Verify all setup wizard fields are present and labelled correctly.
|
[Documentation] Verify all setup wizard fields are present and labelled correctly.
|
||||||
New Browser chromium headless=${TRUE}
|
New Browser chromium headless=${TRUE}
|
||||||
|
New Context
|
||||||
|
New Page
|
||||||
Go To ${FRONTEND_URL}/setup
|
Go To ${FRONTEND_URL}/setup
|
||||||
Wait For Elements State css=form visible timeout=15s
|
Wait For Elements State css=form visible timeout=15s
|
||||||
|
|
||||||
@@ -31,37 +33,31 @@ Setup Page Renders All Form Fields
|
|||||||
Password Strength Indicator Updates On Input
|
Password Strength Indicator Updates On Input
|
||||||
[Documentation] The four-segment strength bar and rule count reflect password complexity.
|
[Documentation] The four-segment strength bar and rule count reflect password complexity.
|
||||||
New Browser chromium headless=${TRUE}
|
New Browser chromium headless=${TRUE}
|
||||||
|
New Context
|
||||||
|
New Page
|
||||||
Go To ${FRONTEND_URL}/setup
|
Go To ${FRONTEND_URL}/setup
|
||||||
Wait For Elements State css=input[aria-label="Master Password"] visible timeout=15s
|
Wait For Elements State css=input[aria-label="Master Password"] visible timeout=15s
|
||||||
|
|
||||||
# Initially no segments are active — no rules satisfied.
|
# Verify initial strength text shows "0 of 4 rules satisfied".
|
||||||
${segments}= Get Elements css=.passwordStrengthSegment
|
${text_0}= Get Text xpath=//div[@aria-live="polite"]
|
||||||
${active_count}= Set Variable 0
|
Should Contain ${text_0} 0 of 4 rules satisfied
|
||||||
FOR ${seg} IN @{segments}
|
Log Initial strength: ${text_0}
|
||||||
${classes}= Get Attribute ${seg} class
|
|
||||||
IF "Active" in """${classes}"""
|
|
||||||
${active_count}= Evaluate ${active_count} + 1
|
|
||||||
END
|
|
||||||
END
|
|
||||||
Should Be Equal As Integers ${active_count} 0
|
|
||||||
|
|
||||||
# Type a weak password — only length (>=8) rule satisfied.
|
# Type a weak password — only length (>=8) rule satisfied.
|
||||||
Fill Text css=input[aria-label="Master Password"] WeakPass
|
Fill Text css=input[aria-label="Master Password"] longpassword
|
||||||
${active_count}= Set Variable 0
|
|
||||||
${segments}= Get Elements css=.passwordStrengthSegment
|
# Verify strength text updates to "1 of 4 rules satisfied" (only length rule, no uppercase/number/special).
|
||||||
FOR ${seg} IN @{segments}
|
${text_1}= Get Text xpath=//div[@aria-live="polite"]
|
||||||
${classes}= Get Attribute ${seg} class
|
Should Contain ${text_1} 1 of 4 rules satisfied
|
||||||
IF "Active" in """${classes}"""
|
Log After longpassword: ${text_1}
|
||||||
${active_count}= Evaluate ${active_count} + 1
|
|
||||||
END
|
|
||||||
END
|
|
||||||
Should Be Equal As Integers ${active_count} 1
|
|
||||||
|
|
||||||
Close Browser
|
Close Browser
|
||||||
|
|
||||||
Password Mismatch Shows Validation Error
|
Password Mismatch Shows Validation Error
|
||||||
[Documentation] Submitting with non-matching passwords surfaces an error on Confirm Password.
|
[Documentation] Submitting with non-matching passwords surfaces an error on Confirm Password.
|
||||||
New Browser chromium headless=${TRUE}
|
New Browser chromium headless=${TRUE}
|
||||||
|
New Context
|
||||||
|
New Page
|
||||||
Go To ${FRONTEND_URL}/setup
|
Go To ${FRONTEND_URL}/setup
|
||||||
Wait For Elements State css=input[aria-label="Master Password"] visible timeout=15s
|
Wait For Elements State css=input[aria-label="Master Password"] visible timeout=15s
|
||||||
|
|
||||||
@@ -70,7 +66,7 @@ Password Mismatch Shows Validation Error
|
|||||||
Click css=button[type="submit"]
|
Click css=button[type="submit"]
|
||||||
|
|
||||||
Wait For Elements State css=[aria-label="Confirm Password"] attached timeout=5s
|
Wait For Elements State css=[aria-label="Confirm Password"] attached timeout=5s
|
||||||
${msg}= Get Text css=[aria-label="Confirm Password"]/ancestor::*[contains(@class,"field")]//*[contains(@class,"validationMessage")]
|
${msg}= Get Text xpath=//*[@aria-label="Confirm Password"]/ancestor::*[contains(@class,"field")]//*[@role="alert"]
|
||||||
Should Be Equal As Strings ${msg} Passwords do not match.
|
Should Be Equal As Strings ${msg} Passwords do not match.
|
||||||
|
|
||||||
Close Browser
|
Close Browser
|
||||||
@@ -78,21 +74,23 @@ Password Mismatch Shows Validation Error
|
|||||||
Empty Required Fields Show Validation Errors
|
Empty Required Fields Show Validation Errors
|
||||||
[Documentation] Submitting with blank required fields shows field-level error messages.
|
[Documentation] Submitting with blank required fields shows field-level error messages.
|
||||||
New Browser chromium headless=${TRUE}
|
New Browser chromium headless=${TRUE}
|
||||||
|
New Context
|
||||||
|
New Page
|
||||||
Go To ${FRONTEND_URL}/setup
|
Go To ${FRONTEND_URL}/setup
|
||||||
Wait For Elements State css=input[aria-label="Master Password"] visible timeout=15s
|
Wait For Elements State css=input[aria-label="Master Password"] visible timeout=15s
|
||||||
|
|
||||||
Click css=button[type="submit"]
|
Click css=button[type="submit"]
|
||||||
|
|
||||||
Wait For Elements State css=[aria-label="Master Password"] attached timeout=5s
|
Wait For Elements State css=[aria-label="Master Password"] attached timeout=5s
|
||||||
${msg}= Get Text css=[aria-label="Master Password"]/ancestor::*[contains(@class,"field")]//*[contains(@class,"validationMessage")]
|
${msg}= Get Text xpath=//*[@aria-label="Master Password"]/ancestor::*[contains(@class,"field")]//*[@role="alert"]
|
||||||
Should Be Equal As Strings ${msg} Password is required.
|
Should Be Equal As Strings ${msg} Password is required.
|
||||||
|
|
||||||
Wait For Elements State css=[aria-label="Database Path"] attached timeout=5s
|
Wait For Elements State css=[aria-label="Database Path"] attached timeout=5s
|
||||||
${msg}= Get Text css=[aria-label="Database Path"]/ancestor::*[contains(@class,"field")]//*[contains(@class,"validationMessage")]
|
${msg}= Get Text xpath=//*[@aria-label="Database Path"]/ancestor::*[contains(@class,"field")]//*[@role="alert"]
|
||||||
Should Be Equal As Strings ${msg} Database path is required.
|
Should Be Equal As Strings ${msg} Database path is required.
|
||||||
|
|
||||||
Wait For Elements State css=[aria-label="fail2ban Socket Path"] attached timeout=5s
|
Wait For Elements State css=[aria-label="fail2ban Socket Path"] attached timeout=5s
|
||||||
${msg}= Get Text css=[aria-label="fail2ban Socket Path"]/ancestor::*[contains(@class,"field")]//*[contains(@class,"validationMessage")]
|
${msg}= Get Text xpath=//*[@aria-label="fail2ban Socket Path"]/ancestor::*[contains(@class,"field")]//*[@role="alert"]
|
||||||
Should Be Equal As Strings ${msg} Socket path is required.
|
Should Be Equal As Strings ${msg} Socket path is required.
|
||||||
|
|
||||||
Close Browser
|
Close Browser
|
||||||
@@ -100,6 +98,8 @@ Empty Required Fields Show Validation Errors
|
|||||||
Invalid Session Duration Shows Validation Error
|
Invalid Session Duration Shows Validation Error
|
||||||
[Documentation] Session duration below 1 minute triggers a validation error.
|
[Documentation] Session duration below 1 minute triggers a validation error.
|
||||||
New Browser chromium headless=${TRUE}
|
New Browser chromium headless=${TRUE}
|
||||||
|
New Context
|
||||||
|
New Page
|
||||||
Go To ${FRONTEND_URL}/setup
|
Go To ${FRONTEND_URL}/setup
|
||||||
Wait For Elements State css=input[aria-label="Master Password"] visible timeout=15s
|
Wait For Elements State css=input[aria-label="Master Password"] visible timeout=15s
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ Invalid Session Duration Shows Validation Error
|
|||||||
Click css=button[type="submit"]
|
Click css=button[type="submit"]
|
||||||
|
|
||||||
Wait For Elements State css=[aria-label="Session Duration (minutes)"] attached timeout=5s
|
Wait For Elements State css=[aria-label="Session Duration (minutes)"] attached timeout=5s
|
||||||
${msg}= Get Text css=[aria-label="Session Duration (minutes)"]/ancestor::*[contains(@class,"field")]//*[contains(@class,"validationMessage")]
|
${msg}= Get Text xpath=//*[@aria-label="Session Duration (minutes)"]/ancestor::*[contains(@class,"field")]//*[@role="alert"]
|
||||||
Should Be Equal As Strings ${msg} Session duration must be at least 1 minute.
|
Should Be Equal As Strings ${msg} Session duration must be at least 1 minute.
|
||||||
|
|
||||||
Close Browser
|
Close Browser
|
||||||
@@ -120,6 +120,8 @@ Invalid Session Duration Shows Validation Error
|
|||||||
Incomplete Password Shows Complexity Error
|
Incomplete Password Shows Complexity Error
|
||||||
[Documentation] Submitting a password that meets length but not all rules shows complexity error.
|
[Documentation] Submitting a password that meets length but not all rules shows complexity error.
|
||||||
New Browser chromium headless=${TRUE}
|
New Browser chromium headless=${TRUE}
|
||||||
|
New Context
|
||||||
|
New Page
|
||||||
Go To ${FRONTEND_URL}/setup
|
Go To ${FRONTEND_URL}/setup
|
||||||
Wait For Elements State css=input[aria-label="Master Password"] visible timeout=15s
|
Wait For Elements State css=input[aria-label="Master Password"] visible timeout=15s
|
||||||
|
|
||||||
@@ -127,7 +129,7 @@ Incomplete Password Shows Complexity Error
|
|||||||
Click css=button[type="submit"]
|
Click css=button[type="submit"]
|
||||||
|
|
||||||
Wait For Elements State css=[aria-label="Master Password"] attached timeout=5s
|
Wait For Elements State css=[aria-label="Master Password"] attached timeout=5s
|
||||||
${msg}= Get Text css=[aria-label="Master Password"]/ancestor::*[contains(@class,"field")]//*[contains(@class,"validationMessage")]
|
${msg}= Get Text xpath=//*[@aria-label="Master Password"]/ancestor::*[contains(@class,"field")]//*[@role="alert"]
|
||||||
Should Contain ${msg} Password must meet all complexity requirements.
|
Should Contain ${msg} Password must meet all complexity requirements.
|
||||||
|
|
||||||
Close Browser
|
Close Browser
|
||||||
@@ -135,11 +137,13 @@ Incomplete Password Shows Complexity Error
|
|||||||
Setup Completes Successfully And Redirects To Login
|
Setup Completes Successfully And Redirects To Login
|
||||||
[Documentation] Filling all fields and submitting completes setup and navigates to /login.
|
[Documentation] Filling all fields and submitting completes setup and navigates to /login.
|
||||||
New Browser chromium headless=${TRUE}
|
New Browser chromium headless=${TRUE}
|
||||||
|
New Context
|
||||||
|
New Page
|
||||||
|
|
||||||
# Use API to check if setup is already complete; reset if needed.
|
# Use API to check if setup is already complete; reset if needed.
|
||||||
${status_resp}= GET ${BACKEND_URL}/api/setup/status
|
${status_resp}= GET ${BACKEND_URL}/api/v1/setup
|
||||||
${status_body}= Set Variable ${status_resp.json()}
|
${status_body}= Set Variable ${status_resp.json()}
|
||||||
Log Setup complete: ${status_body}[setup_complete]
|
Log Setup complete: ${status_body}[completed]
|
||||||
|
|
||||||
Go To ${FRONTEND_URL}/setup
|
Go To ${FRONTEND_URL}/setup
|
||||||
Wait For Elements State css=input[aria-label="Master Password"] visible timeout=15s
|
Wait For Elements State css=input[aria-label="Master Password"] visible timeout=15s
|
||||||
@@ -168,8 +172,8 @@ Setup Completes Successfully And Redirects To Login
|
|||||||
END
|
END
|
||||||
|
|
||||||
# Verify setup is now marked complete.
|
# Verify setup is now marked complete.
|
||||||
${new_status_resp}= GET ${BACKEND_URL}/api/setup/status
|
${new_status_resp}= GET ${BACKEND_URL}/api/v1/setup
|
||||||
${new_status_body}= Set Variable ${new_status_resp.json()}
|
${new_status_body}= Set Variable ${new_status_resp.json()}
|
||||||
Should Be True ${new_status_body}[setup_complete]
|
Should Be True ${new_status_body}[completed]
|
||||||
|
|
||||||
Close Browser
|
Close Browser
|
||||||
|
|||||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "bangui-frontend",
|
"name": "bangui-frontend",
|
||||||
"version": "0.9.19-rc.4",
|
"version": "0.9.19-rc.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "bangui-frontend",
|
"name": "bangui-frontend",
|
||||||
"version": "0.9.19-rc.4",
|
"version": "0.9.19-rc.5",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fluentui/react-components": "^9.55.0",
|
"@fluentui/react-components": "^9.55.0",
|
||||||
"@fluentui/react-icons": "^2.0.257",
|
"@fluentui/react-icons": "^2.0.257",
|
||||||
|
|||||||
@@ -332,6 +332,7 @@ export function SetupPage(): React.JSX.Element {
|
|||||||
value={values.masterPassword}
|
value={values.masterPassword}
|
||||||
onChange={handleChange("masterPassword")}
|
onChange={handleChange("masterPassword")}
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
|
aria-label="Master Password"
|
||||||
/>
|
/>
|
||||||
<div className={styles.passwordStrength} aria-live="polite">
|
<div className={styles.passwordStrength} aria-live="polite">
|
||||||
<div className={styles.passwordStrengthBar}>
|
<div className={styles.passwordStrengthBar}>
|
||||||
@@ -363,6 +364,7 @@ export function SetupPage(): React.JSX.Element {
|
|||||||
value={values.confirmPassword}
|
value={values.confirmPassword}
|
||||||
onChange={handleChange("confirmPassword")}
|
onChange={handleChange("confirmPassword")}
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
|
aria-label="Confirm Password"
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
@@ -375,6 +377,7 @@ export function SetupPage(): React.JSX.Element {
|
|||||||
<Input
|
<Input
|
||||||
value={values.databasePath}
|
value={values.databasePath}
|
||||||
onChange={handleChange("databasePath")}
|
onChange={handleChange("databasePath")}
|
||||||
|
aria-label="Database Path"
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
@@ -387,6 +390,7 @@ export function SetupPage(): React.JSX.Element {
|
|||||||
<Input
|
<Input
|
||||||
value={values.fail2banSocket}
|
value={values.fail2banSocket}
|
||||||
onChange={handleChange("fail2banSocket")}
|
onChange={handleChange("fail2banSocket")}
|
||||||
|
aria-label="fail2ban Socket Path"
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
@@ -397,6 +401,7 @@ export function SetupPage(): React.JSX.Element {
|
|||||||
<Input
|
<Input
|
||||||
value={values.timezone}
|
value={values.timezone}
|
||||||
onChange={handleChange("timezone")}
|
onChange={handleChange("timezone")}
|
||||||
|
aria-label="Timezone"
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
@@ -411,6 +416,7 @@ export function SetupPage(): React.JSX.Element {
|
|||||||
value={values.sessionDurationMinutes}
|
value={values.sessionDurationMinutes}
|
||||||
onChange={handleChange("sessionDurationMinutes")}
|
onChange={handleChange("sessionDurationMinutes")}
|
||||||
min={1}
|
min={1}
|
||||||
|
aria-label="Session Duration (minutes)"
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user