This commit is contained in:
2026-06-21 12:32:44 +02:00
parent 107158eb04
commit be3e180137
27 changed files with 5037 additions and 20 deletions

View File

@@ -0,0 +1,306 @@
*** Settings ***
Documentation API-specific keywords for Aniworld Robot Framework tests.
... Wraps common HTTP patterns and JSON assertions for REST API testing.
Resource ${CURDIR}/common.resource
*** Keywords ***
# ---------------------------------------------------------------------------
# Generic API Helpers
# ---------------------------------------------------------------------------
GET API
[Arguments] ${endpoint} ${expected_status}=200 ${session}=auth
[Documentation] Perform an authenticated GET request and return the response.
${resp}= GET On Session ${session} ${endpoint} expected_status=${expected_status}
RETURN ${resp}
POST API
[Arguments] ${endpoint} ${payload}=${EMPTY} ${expected_status}=200 ${session}=auth
[Documentation] Perform an authenticated POST request with optional JSON payload.
IF '${payload}' == '${EMPTY}'
${resp}= POST On Session ${session} ${endpoint} expected_status=${expected_status}
ELSE
${resp}= POST On Session ${session} ${endpoint} json=${payload} expected_status=${expected_status}
END
RETURN ${resp}
PUT API
[Arguments] ${endpoint} ${payload} ${expected_status}=200 ${session}=auth
[Documentation] Perform an authenticated PUT request with JSON payload.
${resp}= PUT On Session ${session} ${endpoint} json=${payload} expected_status=${expected_status}
RETURN ${resp}
DELETE API
[Arguments] ${endpoint} ${expected_status}=200 ${session}=auth
[Documentation] Perform an authenticated DELETE request.
${resp}= DELETE On Session ${session} ${endpoint} expected_status=${expected_status}
RETURN ${resp}
# ---------------------------------------------------------------------------
# JSON Response Helpers
# ---------------------------------------------------------------------------
Get JSON Value
[Arguments] ${response} ${json_path}
[Documentation] Extract a value from a JSON response using JSONPath.
${json}= Convert String To Json ${response.text}
${values}= Get Value From Json ${json} ${json_path}
RETURN ${values}[0]
Response Should Contain Keys
[Arguments] ${response} @{keys}
[Documentation] Assert that the JSON response contains all the specified top-level keys.
${json}= Convert String To Json ${response.text}
FOR ${key} IN @{keys}
Dictionary Should Contain Key ${json} ${key}
END
Response Should Be Valid JSON
[Arguments] ${response}
[Documentation] Assert that the response body is valid JSON.
${json}= Convert String To Json ${response.text}
Should Not Be Empty ${json}
# ---------------------------------------------------------------------------
# Auth-Specific API Helpers
# ---------------------------------------------------------------------------
POST Auth Setup
[Arguments] ${password} ${expected_status}=201
[Documentation] POST to /api/auth/setup with the given password.
${payload}= Create Dictionary master_password=${password}
${resp}= POST On Session anon /api/auth/setup json=${payload} expected_status=${expected_status}
RETURN ${resp}
POST Auth Login
[Arguments] ${password} ${expected_status}=200
[Documentation] POST to /api/auth/login with the given password.
${payload}= Create Dictionary password=${password}
${resp}= POST On Session anon /api/auth/login json=${payload} expected_status=${expected_status}
RETURN ${resp}
GET Auth Status
[Arguments] ${expected_status}=200
[Documentation] GET /api/auth/status.
${resp}= GET On Session anon /api/auth/status expected_status=${expected_status}
RETURN ${resp}
# ---------------------------------------------------------------------------
# Config-Specific API Helpers
# ---------------------------------------------------------------------------
Get Current Config
[Documentation] GET /api/config and return the parsed JSON.
${resp}= GET API /api/config
${json}= Convert String To Json ${resp.text}
RETURN ${json}
Update Config
[Arguments] ${payload}
[Documentation] PUT /api/config with the given payload.
${resp}= PUT API /api/config ${payload}
RETURN ${resp}
Validate Config
[Arguments] ${payload} ${expected_status}=200
[Documentation] POST /api/config/validate with the given payload.
${resp}= POST API /api/config/validate ${payload} ${expected_status}
RETURN ${resp}
# ---------------------------------------------------------------------------
# Queue-Specific API Helpers
# ---------------------------------------------------------------------------
Get Queue Status
[Documentation] GET /api/queue/status and return the response.
${resp}= GET API /api/queue/status
RETURN ${resp}
Add To Queue
[Arguments] ${payload}
[Documentation] POST /api/queue/add with the given payload.
${resp}= POST API /api/queue/add ${payload} 201
RETURN ${resp}
Start Queue
[Documentation] POST /api/queue/start.
${resp}= POST API /api/queue/start
RETURN ${resp}
Stop Queue
[Documentation] POST /api/queue/stop.
${resp}= POST API /api/queue/stop
RETURN ${resp}
Pause Queue
[Documentation] POST /api/queue/pause.
${resp}= POST API /api/queue/pause
RETURN ${resp}
Resume Queue
[Documentation] POST /api/queue/resume.
${resp}= POST API /api/queue/resume
RETURN ${resp}
Clear Completed
[Documentation] DELETE /api/queue/completed.
${resp}= DELETE API /api/queue/completed
RETURN ${resp}
Clear Failed
[Documentation] DELETE /api/queue/failed.
${resp}= DELETE API /api/queue/failed
RETURN ${resp}
Clear Pending
[Documentation] DELETE /api/queue/pending.
${resp}= DELETE API /api/queue/pending
RETURN ${resp}
# ---------------------------------------------------------------------------
# Anime-Specific API Helpers
# ---------------------------------------------------------------------------
Get Anime Status
[Documentation] GET /api/anime/status.
${resp}= GET API /api/anime/status
RETURN ${resp}
Rescan Library
[Documentation] POST /api/anime/rescan.
${resp}= POST API /api/anime/rescan
RETURN ${resp}
Get Scan Status
[Documentation] GET /api/anime/scan/status.
${resp}= GET API /api/anime/scan/status
RETURN ${resp}
Search Anime
[Arguments] ${query}
[Documentation] GET /api/anime/search?q={query}.
${resp}= GET API /api/anime/search?q=${query}
RETURN ${resp}
Add Series
[Arguments] ${link} ${name} ${year}=${EMPTY}
[Documentation] POST /api/anime/add with series details.
${payload}= Create Dictionary link=${link} name=${name}
IF '${year}' != '${EMPTY}'
Set To Dictionary ${payload} year=${year}
END
${resp}= POST API /api/anime/add ${payload} 201
RETURN ${resp}
Get Series Details
[Arguments] ${key}
[Documentation] GET /api/anime/{key}.
${resp}= GET API /api/anime/${key}
RETURN ${resp}
Delete Series
[Arguments] ${key}
[Documentation] DELETE /api/anime/{key}.
${resp}= DELETE API /api/anime/${key}
RETURN ${resp}
Get Series Episodes
[Arguments] ${key}
[Documentation] GET /api/anime/{key}/episodes.
${resp}= GET API /api/anime/${key}/episodes
RETURN ${resp}
# ---------------------------------------------------------------------------
# Scheduler-Specific API Helpers
# ---------------------------------------------------------------------------
Get Scheduler Config
[Documentation] GET /api/scheduler/config.
${resp}= GET API /api/scheduler/config
RETURN ${resp}
Update Scheduler Config
[Arguments] ${payload}
[Documentation] POST /api/scheduler/config.
${resp}= POST API /api/scheduler/config ${payload}
RETURN ${resp}
Trigger Rescan
[Documentation] POST /api/scheduler/trigger-rescan.
${resp}= POST API /api/scheduler/trigger-rescan
RETURN ${resp}
# ---------------------------------------------------------------------------
# NFO-Specific API Helpers
# ---------------------------------------------------------------------------
Get Nfo Diagnostics
[Arguments] ${key}
[Documentation] GET /api/nfo/{key}/diagnostics.
${resp}= GET API /api/nfo/${key}/diagnostics
RETURN ${resp}
Repair Nfo
[Arguments] ${key}
[Documentation] POST /api/nfo/{key}/repair.
${resp}= POST API /api/nfo/${key}/repair
RETURN ${resp}
Get Needs Repair
[Documentation] GET /api/nfo/needs-repair.
${resp}= GET API /api/nfo/needs-repair
RETURN ${resp}
Scan Nfo
[Documentation] POST /api/nfo/scan.
${resp}= POST API /api/nfo/scan
RETURN ${resp}
# ---------------------------------------------------------------------------
# Logging-Specific API Helpers
# ---------------------------------------------------------------------------
Get Logging Config
[Documentation] GET /api/logging/config.
${resp}= GET API /api/logging/config
RETURN ${resp}
List Log Files
[Documentation] GET /api/logging/files.
${resp}= GET API /api/logging/files
RETURN ${resp}
Tail Log File
[Arguments] ${filename}
[Documentation] GET /api/logging/files/{filename}/tail.
${resp}= GET API /api/logging/files/${filename}/tail
RETURN ${resp}
Download Log File
[Arguments] ${filename}
[Documentation] GET /api/logging/files/{filename}/download.
${resp}= GET API /api/logging/files/${filename}/download
RETURN ${resp}
Write Test Logs
[Documentation] POST /api/logging/test.
${resp}= POST API /api/logging/test
RETURN ${resp}
Cleanup Logs
[Documentation] POST /api/logging/cleanup.
${resp}= POST API /api/logging/cleanup
RETURN ${resp}
# ---------------------------------------------------------------------------
# Setup-Specific API Helpers
# ---------------------------------------------------------------------------
Get Unresolved Folders
[Documentation] GET /api/setup/unresolved.
${resp}= GET API /api/setup/unresolved
RETURN ${resp}
Get Unresolved Folder Details
[Arguments] ${folder_name}
[Documentation] GET /api/setup/unresolved/{folder_name}.
${resp}= GET API /api/setup/unresolved/${folder_name}
RETURN ${resp}
Resolve Folder
[Arguments] ${folder_name} ${provider_key}
[Documentation] POST /api/setup/unresolved/{folder_name}/resolve.
${payload}= Create Dictionary provider_key=${provider_key}
${resp}= POST API /api/setup/unresolved/${folder_name}/resolve ${payload}
RETURN ${resp}

View File

@@ -0,0 +1,146 @@
*** 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}
*** Keywords ***
# ---------------------------------------------------------------------------
# Server Lifecycle
# ---------------------------------------------------------------------------
Start Aniworld Server
[Documentation] Start the FastAPI uvicorn server as a background process.
${result}= Run Process conda run -n AniWorld python -m uvicorn src.server.fastapi_app:app
... --host ${SERVER_HOST} --port ${SERVER_PORT}
... --no-access-log
... cwd=/home/lukas/Volume/repo/Aniworld
... stdout=${CURDIR}/../fixtures/server_stdout.log
... stderr=${CURDIR}/../fixtures/server_stderr.log
Set Suite Variable ${SERVER_PROCESS} ${result}
Sleep 3s
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 with 200.
... 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=200
IF '${resp}[0]' == 'PASS'
RETURN
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.
Create Anonymous Session
${configured}= Is Auth Configured
IF not ${configured}
Setup Master Password
END
${token}= Login And Get Token
Create Session auth ${BASE_URL} headers={'Authorization': 'Bearer ${token}'}
# ---------------------------------------------------------------------------
# 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
RETURN ${configured}[0]
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=201
Should Be Equal As Integers ${resp.status_code} 201
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=200
${json}= Convert String To Json ${resp.text}
${token}= Get Value From Json ${json} $.access_token
RETURN ${token}[0]
# ---------------------------------------------------------------------------
# State Reset
# ---------------------------------------------------------------------------
Reset Application State
[Documentation] Reset auth, config, and database state for test isolation.
... Calls internal cleanup endpoints if available; otherwise warns.
Run Keyword And Ignore Error POST On Session auth /api/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}

View File

@@ -0,0 +1,204 @@
*** Settings ***
Documentation UI-specific keywords for Aniworld Robot Framework tests.
... Wraps Browser library interactions for page navigation, form filling,
... and common UI assertions.
Resource ${CURDIR}/common.resource
*** Keywords ***
# ---------------------------------------------------------------------------
# Navigation
# ---------------------------------------------------------------------------
Navigate To
[Arguments] ${path}
[Documentation] Navigate the browser to the given path relative to BASE_URL.
Go To ${BASE_URL}${path}
Reload Page
[Documentation] Reload the current page.
Reload
# ---------------------------------------------------------------------------
# Authentication UI Flows
# ---------------------------------------------------------------------------
Perform Login
[Arguments] ${password}=${SETUP_PASSWORD}
[Documentation] Fill the login form and submit it.
Fill Text id=password-input ${password}
Click id=login-submit-btn
Wait For Elements State id=search-input visible timeout=5s
Perform Logout
[Documentation] Click the logout button and wait for redirect to login.
Click id=logout-btn
Wait For Elements State id=password-input visible timeout=5s
# ---------------------------------------------------------------------------
# Setup UI Flow
# ---------------------------------------------------------------------------
Complete Setup Flow
[Arguments] ${password}=${SETUP_PASSWORD}
... ${anime_dir}=/tmp/aniworld_test_anime
... ${app_name}=AniworldTest
[Documentation] Fill the entire setup form and submit it.
# General section
Fill Text id=app-name-input ${app_name}
Fill Text id=data-dir-input data
Fill Text id=anime-directory-input ${anime_dir}
# Security section
Fill Text id=master-password-input ${password}
Fill Text id=confirm-password-input ${password}
# Scheduler section
Uncheck Checkbox id=scheduler-enabled
# Submit
Click id=setup-submit-btn
Wait For Elements State id=search-input visible timeout=10s
# ---------------------------------------------------------------------------
# Settings Modal
# ---------------------------------------------------------------------------
Open Settings Modal
[Documentation] Click the config button and wait for the modal to appear.
Click id=config-btn
Wait For Elements State id=config-modal visible timeout=5s
Close Settings Modal
[Documentation] Click the close button and wait for the modal to disappear.
Click id=close-config
Wait For Elements State id=config-modal hidden timeout=5s
Save Settings
[Documentation] Click the save button inside the settings modal.
Click id=save-main-config
Wait For Elements State .toast, .notification visible timeout=5s
Fill General Settings
[Arguments] ${app_name} ${data_dir} ${anime_dir}
[Documentation] Fill the general settings section in the modal.
Fill Text id=app-name-input ${app_name}
Fill Text id=data-dir-input ${data_dir}
Fill Text id=anime-directory-input ${anime_dir}
Fill Scheduler Settings
[Arguments] ${enabled}=False ${time}=03:00 @{days}
[Documentation] Fill the scheduler settings section.
IF ${enabled}
Check Checkbox id=scheduled-rescan-enabled
ELSE
Uncheck Checkbox id=scheduled-rescan-enabled
END
Fill Text id=scheduler-time-input ${time}
# Days selection would be handled by multi-select UI elements
Fill NFO Settings
[Arguments] ${tmdb_key}=${EMPTY} ${auto_create}=False
[Documentation] Fill the NFO settings section.
Fill Text id=tmdb-api-key-input ${tmdb_key}
IF ${auto_create}
Check Checkbox id=nfo-auto-create
ELSE
Uncheck Checkbox id=nfo-auto-create
END
# ---------------------------------------------------------------------------
# Search
# ---------------------------------------------------------------------------
Search For Anime
[Arguments] ${query}
[Documentation] Type a query into the search box and submit.
Fill Text id=search-input ${query}
Click id=search-btn
Wait For Elements State id=search-results visible timeout=5s
Clear Search
[Documentation] Click the clear search button.
Click id=clear-search
Wait For Elements State id=search-results hidden timeout=3s
# ---------------------------------------------------------------------------
# Theme
# ---------------------------------------------------------------------------
Toggle Theme
[Documentation] Click the theme toggle button.
Click id=theme-toggle
Get Current Theme
[Documentation] Return the current theme attribute from the HTML element.
${theme}= Get Attribute html data-theme
RETURN ${theme}
# ---------------------------------------------------------------------------
# Queue Page
# ---------------------------------------------------------------------------
Navigate To Queue Page
[Documentation] Navigate to the download queue page.
Navigate To /queue
Wait For Elements State id=pending-queue visible timeout=5s
Start Queue From UI
[Documentation] Click the start queue button on the queue page.
Click id=start-queue-btn
Stop Queue From UI
[Documentation] Click the stop queue button on the queue page.
Click id=stop-queue-btn
Clear Completed From UI
[Documentation] Click clear completed and confirm the modal.
Click id=clear-completed-btn
Wait For Elements State id=confirm-modal visible timeout=3s
Click id=confirm-ok
Wait For Elements State id=confirm-modal hidden timeout=3s
Clear Failed From UI
[Documentation] Click clear failed and confirm the modal.
Click id=clear-failed-btn
Wait For Elements State id=confirm-modal visible timeout=3s
Click id=confirm-ok
Wait For Elements State id=confirm-modal hidden timeout=3s
# ---------------------------------------------------------------------------
# Common UI Assertions
# ---------------------------------------------------------------------------
Page Title Should Contain
[Arguments] ${text}
[Documentation] Assert that the page title contains the given text.
${title}= Get Title
Should Contain ${title} ${text}
Element Should Be Visible
[Arguments] ${selector}
[Documentation] Assert that the element matching the selector is visible.
Wait For Elements State ${selector} visible timeout=5s
Element Should Be Hidden
[Arguments] ${selector}
[Documentation] Assert that the element matching the selector is hidden.
Wait For Elements State ${selector} hidden timeout=5s
Element Text Should Contain
[Arguments] ${selector} ${text}
[Documentation] Assert that the element's text contains the given substring.
${element_text}= Get Text ${selector}
Should Contain ${element_text} ${text}
Toast Should Contain
[Arguments] ${text}
[Documentation] Assert that a toast/notification contains the given text.
${toast}= Get Text .toast, .notification
Should Contain ${toast} ${text}
# ---------------------------------------------------------------------------
# Responsive Helpers
# ---------------------------------------------------------------------------
Set Mobile Viewport
[Documentation] Resize the browser viewport to mobile dimensions.
Set Viewport Size 375 667
Set Tablet Viewport
[Documentation] Resize the browser viewport to tablet dimensions.
Set Viewport Size 768 1024
Set Desktop Viewport
[Documentation] Resize the browser viewport to desktop dimensions.
Set Viewport Size 1280 720