*** Settings *** Documentation Common shared resources for Aniworld Robot Framework tests. ... Provides keywords for server lifecycle, authentication, sessions, and cleanup. Library Process Library Browser Library RequestsLibrary Library JSONLibrary Library OperatingSystem Library String Library Collections Library DateTime *** Variables *** ${BASE_URL} http://127.0.0.1:8765 ${SERVER_HOST} 127.0.0.1 ${SERVER_PORT} 8765 ${SETUP_PASSWORD} TestPass123! ${BROWSER} chromium ${HEADLESS} True ${SERVER_PROCESS} ${EMPTY} ${TOKEN} ${EMPTY} *** Keywords *** # --------------------------------------------------------------------------- # Server Lifecycle # --------------------------------------------------------------------------- Start Aniworld Server [Documentation] Start the FastAPI uvicorn server as a background process. ${handle}= Start Process bash /home/lukas/Volume/repo/Aniworld/tests/robot/start_server.sh ... cwd=/home/lukas/Volume/repo/Aniworld ... stdout=${CURDIR}/../fixtures/server_stdout.log ... stderr=${CURDIR}/../fixtures/server_stderr.log Set Suite Variable ${SERVER_PROCESS} ${handle} Sleep 5s Stop Aniworld Server [Documentation] Terminate the uvicorn server process and clean up log files. Run Keyword And Ignore Error Terminate Process ${SERVER_PROCESS} Run Keyword And Ignore Error Remove File ${CURDIR}/../fixtures/server_stdout.log Run Keyword And Ignore Error Remove File ${CURDIR}/../fixtures/server_stderr.log Wait For Server [Documentation] Poll the health endpoint until the server responds. ... Accepts 200 or 503 (not configured yet) as valid responses. ... Retries every 1 second for up to 30 seconds. FOR ${i} IN RANGE 30 ${resp}= Run Keyword And Ignore Error ... GET On Session anon /health expected_status=any IF '${resp}[0]' == 'PASS' ${status_code}= Evaluate $resp[1].status_code IF $status_code == 200 or $status_code == 503 RETURN END END Sleep 1s END Fail Server did not become healthy within 30 seconds Initialize Browser [Documentation] Initialize Browser library with the configured browser and headless mode. New Browser ${BROWSER} headless=${HEADLESS} New Context viewport={'width': 1280, 'height': 720} # --------------------------------------------------------------------------- # HTTP Session Management # --------------------------------------------------------------------------- Create Anonymous Session [Documentation] Create an unauthenticated HTTP session. Create Session anon ${BASE_URL} Create Authenticated Session [Documentation] Create an authenticated HTTP session. Performs setup if needed, then logs in. ... Skips setup/login if already authenticated (suite-level setup done). Create Anonymous Session ${configured}= Is Auth Configured IF not ${configured} Setup Master Password ${token}= Login And Get Token Create Session auth ${BASE_URL} headers={'Authorization': 'Bearer ${token}'} ELSE IF '${TOKEN}' == '${EMPTY}' ${token}= Login And Get Token Create Session auth ${BASE_URL} headers={'Authorization': 'Bearer ${token}'} ELSE Create Session auth ${BASE_URL} headers={'Authorization': 'Bearer ${TOKEN}'} END # --------------------------------------------------------------------------- # Authentication Helpers # --------------------------------------------------------------------------- Is Auth Configured [Documentation] Check if the master password has already been configured. ${resp}= GET On Session anon /api/auth/status expected_status=200 ${json}= Convert String To Json ${resp.text} ${configured}= Get Value From Json ${json} $.configured ${configured_val}= Set Variable ${configured}[0] RETURN ${configured_val} Verify App Is Unconfigured [Documentation] Verify the app is in unconfigured state, retrying reset if needed. FOR ${i} IN RANGE 3 ${configured}= Is Auth Configured IF not ${configured} RETURN END # Not configured but Is Auth Configured returned True - reset again Reset Application State Sleep 1s END Fail App still configured after 3 reset attempts Setup Master Password [Documentation] Configure the master password via the setup endpoint. ${payload}= Create Dictionary ... master_password=${SETUP_PASSWORD} ... anime_directory=/tmp/aniworld_test_anime ... name=AniworldTest ... data_dir=data ... scheduler_enabled=False ... logging_level=INFO ... backup_enabled=False ${resp}= POST On Session anon /api/auth/setup json=${payload} expected_status=any IF '${resp.status_code}' == '429' # Rate limited - wait and retry Sleep 45s ${resp}= POST On Session anon /api/auth/setup json=${payload} expected_status=any END IF '${resp.status_code}' == '429' # Still rate limited - wait longer and retry again Sleep 45s ${resp}= POST On Session anon /api/auth/setup json=${payload} expected_status=any END IF '${resp.status_code}' == '400' # Already configured - this is OK ${json}= Convert String To Json ${resp.text} ${detail}= Get Value From Json ${json} $.detail IF '${detail}[0]' == 'Master password already configured' RETURN END Fail Setup failed with 400: ${detail} END IF '${resp.status_code}' == '201' Should Be Equal As Integers ${resp.status_code} 201 RETURN END Fail Unexpected status ${resp.status_code} from /api/auth/setup Login And Get Token [Documentation] Log in with the master password and return the JWT access token. ${payload}= Create Dictionary password=${SETUP_PASSWORD} ${resp}= POST On Session anon /api/auth/login json=${payload} expected_status=any IF '${resp.status_code}' == '429' # Rate limited - wait and retry once Sleep 6s ${resp}= POST On Session anon /api/auth/login json=${payload} expected_status=any END IF '${resp.status_code}' != '200' ${json}= Convert String To Json ${resp.text} ${detail}= Get Value From Json ${json} $.detail ${detail_val}= Set Variable ${detail}[0] Fail Login failed with ${resp.status_code}: ${detail_val} END ${json}= Convert String To Json ${resp.text} ${token}= Get Value From Json ${json} $.access_token ${token_str}= Set Variable ${token}[0] Set Suite Variable ${TOKEN} ${token_str} RETURN ${token_str} # --------------------------------------------------------------------------- # State Reset # --------------------------------------------------------------------------- Reset Application State [Documentation] Reset auth, config, and database state for test isolation. ... Calls internal cleanup endpoints if available; otherwise warns. POST On Session anon /api/config/admin/reset expected_status=200 # Fallback: clear local files Run Keyword And Ignore Error Remove Directory /tmp/aniworld_test_anime recursive=True Run Keyword And Ignore Error Create Directory /tmp/aniworld_test_anime # --------------------------------------------------------------------------- # Browser Navigation Helpers # --------------------------------------------------------------------------- Open Browser To Page [Arguments] ${path}=/ [Documentation] Open a new page at the given path relative to BASE_URL. New Page ${BASE_URL}${path} Close Browser [Documentation] Close all browser contexts and the browser itself. Browser.Close Browser ALL # --------------------------------------------------------------------------- # Assertion Helpers # --------------------------------------------------------------------------- Response Should Contain Key [Arguments] ${response} ${json_path} [Documentation] Assert that the JSON response contains the given JSONPath key. ${json}= Convert String To Json ${response.text} ${values}= Get Value From Json ${json} ${json_path} Should Not Be Empty ${values} Expected JSON path '${json_path}' not found in response Response Should Have Status [Arguments] ${response} ${expected_status} [Documentation] Assert that the response status code matches the expected value. Should Be Equal As Integers ${response.status_code} ${expected_status}