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

5
.gitignore vendored
View File

@@ -84,3 +84,8 @@ tmp/
*.tmp *.tmp
.coverage .coverage
.venv/bin/dotenv .venv/bin/dotenv
# Node.js
node_modules/
tests/results/*
test-results/*

View File

@@ -1,23 +1,3 @@
### Task INFRA-2: Fix Suite Teardown — Close Browser Keyword Argument Mismatch
- **Failing Step**: `Suite Teardown → Close Browser`
- **Failure Message**: `Keyword 'common.Close Browser' expected 0 arguments, got 1`
- **Files to Check**:
- `tests/robot/__init__.robot`
- `tests/robot/resources/common.resource`
- `tests/robot/resources/ui_keywords.resource`
- **Reference Docs**:
- `Docs/TESTING.md`
- **Expected Behavior**:
- The suite teardown should cleanly close the browser and stop the server without errors.
- **Actual Behavior**:
- The `Close Browser` keyword is being called with an argument (likely `${BROWSER}` or similar), but the keyword definition expects 0 arguments.
- **Suggested Fix**:
- Review `__init__.robot` line: `Close Browser` — remove any passed arguments.
- Or, if a custom wrapper keyword is intended, define it in `ui_keywords.resource` and call that instead.
---
## API Tests — `Robot.Api.Anime` (14 tests, 0 passed, 14 failed) ## API Tests — `Robot.Api.Anime` (14 tests, 0 passed, 14 failed)
> **Parent Suite Setup Failed**: `Server did not become healthy within 30 seconds` > **Parent Suite Setup Failed**: `Server did not become healthy within 30 seconds`

2644
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
*** Settings ***
Documentation Suite-level setup and teardown for Aniworld Robot Framework tests.
... Starts the FastAPI server, initializes Browser library, and cleans up after all tests.
Library Process
Library Browser
Library RequestsLibrary
Resource ${CURDIR}/resources/common.resource
Suite Setup Run Keywords
... Start Aniworld Server
... AND Wait For Server
... AND Initialize Browser
Suite Teardown Run Keywords
... Close Browser
... AND Stop Aniworld Server

126
tests/robot/api/anime.robot Normal file
View File

@@ -0,0 +1,126 @@
*** Settings ***
Documentation Anime library API tests for Aniworld.
... Covers status, rescan, search, add, details, episodes, filters, and duplicates.
Resource ${CURDIR}/../resources/common.resource
Resource ${CURDIR}/../resources/api_keywords.resource
Test Setup Run Keywords
... Create Anonymous Session
... AND Setup Master Password
... AND Create Authenticated Session
Test Teardown Delete All Sessions
*** Test Cases ***
# ---------------------------------------------------------------------------
# Library Status
# ---------------------------------------------------------------------------
Get Anime Library Status
[Documentation] Retrieve the anime library status.
${resp}= Get Anime Status
Response Should Have Status ${resp} 200
Response Should Contain Keys ${resp} directory series_count
# ---------------------------------------------------------------------------
# Rescan
# ---------------------------------------------------------------------------
Rescan Library
[Documentation] Trigger a library rescan and verify acceptance.
${resp}= Rescan Library
Response Should Have Status ${resp} 200
Scan Status During Idle
[Documentation] Get scan status when no scan is in progress.
${resp}= Get Scan Status
Response Should Have Status ${resp} 200
${in_progress}= Get JSON Value ${resp} $.in_progress
Should Be Equal As Strings ${in_progress} False
# ---------------------------------------------------------------------------
# Search
# ---------------------------------------------------------------------------
Search Anime
[Documentation] Search for anime series via the provider.
${resp}= Search Anime attack
Response Should Have Status ${resp} 200
Response Should Be Valid JSON ${resp}
# ---------------------------------------------------------------------------
# Series CRUD
# ---------------------------------------------------------------------------
Add New Series
[Documentation] Add a new anime series to the library.
${resp}= Add Series https://aniworld.to/anime/stream/attack-on-titan Attack on Titan 2013
Response Should Have Status ${resp} 201
${key}= Get JSON Value ${resp} $.key
Should Not Be Empty ${key}
Set Test Variable ${TEST_SERIES_KEY} ${key}
Get Series Details
[Documentation] Retrieve details for a specific series.
Add New Series
${resp}= Get Series Details ${TEST_SERIES_KEY}
Response Should Have Status ${resp} 200
Response Should Contain Keys ${resp} key name folder episodes
Update Series Settings
[Documentation] Update settings for a specific series.
Add New Series
${payload}= Create Dictionary preferred_language=german
${resp}= PUT API /api/anime/${TEST_SERIES_KEY}/settings ${payload}
Response Should Have Status ${resp} 200
Get Series Episodes
[Documentation] Retrieve the episode list for a series.
Add New Series
${resp}= Get Series Episodes ${TEST_SERIES_KEY}
Response Should Have Status ${resp} 200
Response Should Be Valid JSON ${resp}
Delete Series
[Documentation] Remove a series from the library.
Add New Series
${resp}= Delete Series ${TEST_SERIES_KEY}
Response Should Have Status ${resp} 200
${get_resp}= Get Series Details ${TEST_SERIES_KEY}
Response Should Have Status ${get_resp} 404
# ---------------------------------------------------------------------------
# Filters
# ---------------------------------------------------------------------------
List All Series
[Documentation] List all series without filters.
${resp}= GET API /api/anime/
Response Should Have Status ${resp} 200
Response Should Be Valid JSON ${resp}
List Missing Episodes Only
[Documentation] Filter series to show only those with missing episodes.
${resp}= GET API /api/anime/?filter=missing
Response Should Have Status ${resp} 200
Response Should Be Valid JSON ${resp}
List No Episodes
[Documentation] Filter series to show only those with zero episodes.
${resp}= GET API /api/anime/?filter=no_episodes
Response Should Have Status ${resp} 200
Response Should Be Valid JSON ${resp}
# ---------------------------------------------------------------------------
# Duplicates
# ---------------------------------------------------------------------------
Duplicate Folders Detection
[Documentation] Check for duplicate folder groups in the library.
${resp}= GET API /api/anime/duplicate-folders
Response Should Have Status ${resp} 200
Response Should Be Valid JSON ${resp}
# ---------------------------------------------------------------------------
# NFO Regeneration
# ---------------------------------------------------------------------------
Regenerate NFO For Series
[Documentation] Trigger NFO regeneration for a specific series.
Add New Series
${resp}= POST API /api/anime/${TEST_SERIES_KEY}/regenerate-nfo
Response Should Have Status ${resp} 200

118
tests/robot/api/auth.robot Normal file
View File

@@ -0,0 +1,118 @@
*** Settings ***
Documentation Authentication API tests for Aniworld.
... Covers setup, login, logout, status, rate limiting, and JWT validation.
Resource ${CURDIR}/../resources/common.resource
Resource ${CURDIR}/../resources/api_keywords.resource
Test Setup Create Anonymous Session
Test Teardown Delete All Sessions
*** Test Cases ***
# ---------------------------------------------------------------------------
# Setup
# ---------------------------------------------------------------------------
Setup Master Password
[Documentation] Configure the master password for the first time.
${resp}= POST Auth Setup ${SETUP_PASSWORD} 201
Response Should Have Status ${resp} 201
${configured}= Is Auth Configured
Should Be True ${configured}
Setup Rejects Weak Password
[Documentation] Verify that weak passwords are rejected during setup.
${resp}= POST Auth Setup weak 400
Response Should Have Status ${resp} 400
Setup Rejects Duplicate
[Documentation] Verify that setup cannot be performed twice.
Setup Master Password
${resp}= POST Auth Setup AnotherPass123! 400
Response Should Have Status ${resp} 400
# ---------------------------------------------------------------------------
# Login
# ---------------------------------------------------------------------------
Login With Valid Password
[Documentation] Log in with the correct master password and receive a JWT token.
Setup Master Password
${resp}= POST Auth Login ${SETUP_PASSWORD} 200
Response Should Have Status ${resp} 200
${token}= Get JSON Value ${resp} $.access_token
Should Not Be Empty ${token}
Login With Invalid Password
[Documentation] Log in with an incorrect password and receive 401.
Setup Master Password
${resp}= POST Auth Login WrongPass123! 401
Response Should Have Status ${resp} 401
Login Rate Limiting
[Documentation] Verify that repeated failed login attempts trigger rate limiting.
Setup Master Password
FOR ${i} IN RANGE 6
${resp}= POST Auth Login WrongPass123! expected_status=ANY
END
${resp}= POST Auth Login WrongPass123! expected_status=ANY
Should Be True ${resp.status_code} >= 429 or ${resp.status_code} == 401
# ---------------------------------------------------------------------------
# Auth Status
# ---------------------------------------------------------------------------
Auth Status Unconfigured
[Documentation] Check auth status before any setup has occurred.
${resp}= GET Auth Status 200
Response Should Have Status ${resp} 200
${configured}= Get JSON Value ${resp} $.configured
Should Be Equal As Strings ${configured} False
Auth Status Configured Unauthenticated
[Documentation] Check auth status after setup but without a token.
Setup Master Password
${resp}= GET Auth Status 200
Response Should Have Status ${resp} 200
${configured}= Get JSON Value ${resp} $.configured
Should Be Equal As Strings ${configured} True
${authenticated}= Get JSON Value ${resp} $.authenticated
Should Be Equal As Strings ${authenticated} False
Auth Status Authenticated
[Documentation] Check auth status with a valid Bearer token.
Setup Master Password
${token}= Login And Get Token
${headers}= Create Dictionary Authorization=Bearer ${token}
Create Session authed ${BASE_URL} headers=${headers}
${resp}= GET On Session authed /api/auth/status expected_status=200
Response Should Have Status ${resp} 200
${authenticated}= Get JSON Value ${resp} $.authenticated
Should Be Equal As Strings ${authenticated} True
# ---------------------------------------------------------------------------
# Logout
# ---------------------------------------------------------------------------
Logout
[Documentation] Log out and verify the token is invalidated.
Setup Master Password
${token}= Login And Get Token
${headers}= Create Dictionary Authorization=Bearer ${token}
Create Session authed ${BASE_URL} headers=${headers}
${resp}= POST On Session authed /api/auth/logout expected_status=200
Response Should Have Status ${resp} 200
# ---------------------------------------------------------------------------
# Protected Endpoints
# ---------------------------------------------------------------------------
Protected Endpoint Without Auth
[Documentation] Verify that protected endpoints reject unauthenticated requests.
Setup Master Password
${resp}= GET On Session anon /api/anime/ expected_status=401
Response Should Have Status ${resp} 401
Protected Endpoint With Auth
[Documentation] Verify that protected endpoints accept authenticated requests.
Setup Master Password
${token}= Login And Get Token
${headers}= Create Dictionary Authorization=Bearer ${token}
Create Session authed ${BASE_URL} headers=${headers}
${resp}= GET On Session authed /api/anime/ expected_status=200
Response Should Have Status ${resp} 200

View File

@@ -0,0 +1,138 @@
*** Settings ***
Documentation Configuration API tests for Aniworld.
... Covers get, update, validate, backup create/list/restore/delete, and export/import.
Resource ${CURDIR}/../resources/common.resource
Resource ${CURDIR}/../resources/api_keywords.resource
Test Setup Run Keywords
... Create Anonymous Session
... AND Setup Master Password
... AND Create Authenticated Session
Test Teardown Delete All Sessions
*** Test Cases ***
# ---------------------------------------------------------------------------
# Get / Update Config
# ---------------------------------------------------------------------------
Get Default Config
[Documentation] Retrieve the current configuration and verify its structure.
${resp}= GET API /api/config
Response Should Have Status ${resp} 200
Response Should Contain Keys ${resp} name data_dir scheduler logging backup nfo
Update Config
[Documentation] Update configuration fields and verify they are persisted.
${payload}= Create Dictionary
... name=UpdatedAniworld
... data_dir=data
... scheduler={'enabled': False, 'schedule_time': '04:00', 'schedule_days': ['mon', 'tue']}
... logging={'level': 'DEBUG', 'file': 'logs/test.log', 'max_bytes': 1048576, 'backup_count': 5}
... backup={'enabled': False}
... nfo={'tmdb_api_key': '', 'auto_create': False, 'download_poster': False, 'download_logo': False, 'download_fanart': False}
... other={'anime_directory': '/tmp/aniworld_test_anime'}
${resp}= Update Config ${payload}
Response Should Have Status ${resp} 200
${config}= Get Current Config
Should Be Equal As Strings ${config}[name] UpdatedAniworld
# ---------------------------------------------------------------------------
# Config Validation
# ---------------------------------------------------------------------------
Validate Valid Config
[Documentation] Validate a well-formed configuration.
${payload}= Create Dictionary
... name=Aniworld
... data_dir=data
... scheduler={'enabled': True, 'schedule_time': '03:00', 'schedule_days': ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']}
... logging={'level': 'INFO', 'file': None, 'max_bytes': None, 'backup_count': 3}
... backup={'enabled': False}
... nfo={'tmdb_api_key': '', 'auto_create': True, 'download_poster': True, 'download_logo': True, 'download_fanart': True}
... other={}
${resp}= Validate Config ${payload} 200
Response Should Have Status ${resp} 200
${valid}= Get JSON Value ${resp} $.valid
Should Be Equal As Strings ${valid} True
Validate Invalid Config Missing Name
[Documentation] Validate a config missing the required 'name' field.
${payload}= Create Dictionary
... data_dir=data
... scheduler={'enabled': True, 'schedule_time': '03:00', 'schedule_days': ['mon']}
... logging={'level': 'INFO'}
... backup={'enabled': False}
... nfo={'auto_create': False}
... other={}
${resp}= Validate Config ${payload} 422
Response Should Have Status ${resp} 422
Validate Invalid Schedule Time
[Documentation] Validate a config with malformed schedule_time.
${payload}= Create Dictionary
... name=Aniworld
... data_dir=data
... scheduler={'enabled': True, 'schedule_time': '25:00', 'schedule_days': ['mon']}
... logging={'level': 'INFO'}
... backup={'enabled': False}
... nfo={'auto_create': False}
... other={}
${resp}= Validate Config ${payload} 422
Response Should Have Status ${resp} 422
Validate Invalid Log Level
[Documentation] Validate a config with an invalid logging level.
${payload}= Create Dictionary
... name=Aniworld
... data_dir=data
... scheduler={'enabled': True, 'schedule_time': '03:00', 'schedule_days': ['mon']}
... logging={'level': 'INVALID', 'file': None, 'max_bytes': None, 'backup_count': 3}
... backup={'enabled': False}
... nfo={'auto_create': False}
... other={}
${resp}= Validate Config ${payload} 422
Response Should Have Status ${resp} 422
# ---------------------------------------------------------------------------
# Backup Management
# ---------------------------------------------------------------------------
Config Backup Create
[Documentation] Create a configuration backup and verify it exists.
${resp}= POST API /api/config/backups expected_status=201
Response Should Have Status ${resp} 201
Config Backup List
[Documentation] List configuration backups and verify the response structure.
POST API /api/config/backups expected_status=201
${resp}= GET API /api/config/backups
Response Should Have Status ${resp} 200
Response Should Contain Key ${resp} $.backups
Config Backup Restore
[Documentation] Create a backup, change config, then restore and verify reversion.
${resp}= POST API /api/config/backups expected_status=201
${json}= Convert String To Json ${resp.text}
${backup_name}= Get Value From Json ${json} $.backup_name
${name_before}= Set Variable ${backup_name}[0]
# Change config
${payload}= Create Dictionary
... name=BeforeRestore
... data_dir=data
... scheduler={'enabled': False, 'schedule_time': '03:00', 'schedule_days': ['mon']}
... logging={'level': 'INFO', 'file': None, 'max_bytes': None, 'backup_count': 3}
... backup={'enabled': False}
... nfo={'auto_create': False}
... other={}
Update Config ${payload}
# Restore
${resp}= POST API /api/config/backups/${name_before}/restore
Response Should Have Status ${resp} 200
Config Backup Delete
[Documentation] Create a backup and then delete it.
${resp}= POST API /api/config/backups expected_status=201
${json}= Convert String To Json ${resp.text}
${backup_name}= Get Value From Json ${json} $.backup_name
${name}= Set Variable ${backup_name}[0]
${del_resp}= DELETE API /api/config/backups/${name}
Response Should Have Status ${del_resp} 200

View File

@@ -0,0 +1,145 @@
*** Settings ***
Documentation Download queue API tests for Aniworld.
... Covers status, add, start/stop/pause/resume, clear, retry, and remove.
Resource ${CURDIR}/../resources/common.resource
Resource ${CURDIR}/../resources/api_keywords.resource
Test Setup Run Keywords
... Create Anonymous Session
... AND Setup Master Password
... AND Create Authenticated Session
Test Teardown Delete All Sessions
*** Test Cases ***
# ---------------------------------------------------------------------------
# Queue Status
# ---------------------------------------------------------------------------
Get Empty Queue Status
[Documentation] Retrieve queue status when the queue is empty.
${resp}= Get Queue Status
Response Should Have Status ${resp} 200
Response Should Contain Keys ${resp} status statistics
${pending}= Get JSON Value ${resp} $.statistics.pending
Should Be Equal As Integers ${pending} 0
# ---------------------------------------------------------------------------
# Add To Queue
# ---------------------------------------------------------------------------
Add Episodes To Queue
[Documentation] Add episodes to the download queue.
${episodes}= Create List
... {'season': 1, 'episode': 1, 'title': 'Episode 1'}
... {'season': 1, 'episode': 2, 'title': 'Episode 2'}
${payload}= Create Dictionary
... serie_id=test-series
... serie_folder=Test Series
... serie_name=Test Series
... episodes=${episodes}
... priority=NORMAL
${resp}= Add To Queue ${payload}
Response Should Have Status ${resp} 201
${ids}= Get JSON Value ${resp} $.item_ids
Should Not Be Empty ${ids}
# ---------------------------------------------------------------------------
# Queue Controls
# ---------------------------------------------------------------------------
Start Queue
[Documentation] Start processing the download queue.
${resp}= Start Queue
Response Should Have Status ${resp} 200
Stop Queue
[Documentation] Stop processing the download queue.
${resp}= Stop Queue
Response Should Have Status ${resp} 200
Pause Queue
[Documentation] Pause the download queue.
${resp}= Pause Queue
Response Should Have Status ${resp} 200
Resume Queue
[Documentation] Resume the download queue.
${resp}= Resume Queue
Response Should Have Status ${resp} 200
# ---------------------------------------------------------------------------
# Clear Operations
# ---------------------------------------------------------------------------
Clear Completed Downloads
[Documentation] Clear all completed downloads from the queue.
${resp}= Clear Completed
Response Should Have Status ${resp} 200
Clear Failed Downloads
[Documentation] Clear all failed downloads from the queue.
${resp}= Clear Failed
Response Should Have Status ${resp} 200
Clear Pending Downloads
[Documentation] Clear all pending downloads from the queue.
${resp}= Clear Pending
Response Should Have Status ${resp} 200
# ---------------------------------------------------------------------------
# Item Management
# ---------------------------------------------------------------------------
Remove Item From Queue
[Documentation] Remove a specific item from the queue.
${episodes}= Create List {'season': 1, 'episode': 1, 'title': 'Episode 1'}
${payload}= Create Dictionary
... serie_id=remove-test
... serie_folder=Remove Test
... serie_name=Remove Test
... episodes=${episodes}
... priority=NORMAL
${add_resp}= Add To Queue ${payload}
${json}= Convert String To Json ${add_resp.text}
${ids}= Get Value From Json ${json} $.item_ids
${item_id}= Set Variable ${ids}[0]
${del_resp}= DELETE API /api/queue/item/${item_id}
Response Should Have Status ${del_resp} 200
Retry Failed Item
[Documentation] Retry a failed download item.
${episodes}= Create List {'season': 1, 'episode': 1, 'title': 'Episode 1'}
${payload}= Create Dictionary
... serie_id=retry-test
... serie_folder=Retry Test
... serie_name=Retry Test
... episodes=${episodes}
... priority=NORMAL
${add_resp}= Add To Queue ${payload}
${json}= Convert String To Json ${add_resp.text}
${ids}= Get Value From Json ${json} $.item_ids
${item_id}= Set Variable ${ids}[0]
${retry_resp}= POST API /api/queue/item/${item_id}/retry
Response Should Have Status ${retry_resp} 200
# ---------------------------------------------------------------------------
# Queue Statistics Accuracy
# ---------------------------------------------------------------------------
Queue Statistics Accuracy
[Documentation] Verify that queue statistics reflect the actual item counts.
# Clear first
Clear Pending
# Add 2 items
${episodes}= Create List
... {'season': 1, 'episode': 1, 'title': 'Ep 1'}
... {'season': 1, 'episode': 2, 'title': 'Ep 2'}
${payload}= Create Dictionary
... serie_id=stats-test
... serie_folder=Stats Test
... serie_name=Stats Test
... episodes=${episodes}
... priority=NORMAL
Add To Queue ${payload}
${resp}= Get Queue Status
Response Should Have Status ${resp} 200
${pending}= Get JSON Value ${resp} $.statistics.pending
Should Be Equal As Integers ${pending} 2
${total}= Get JSON Value ${resp} $.statistics.total
Should Be Equal As Integers ${total} 2

View File

@@ -0,0 +1,29 @@
*** Settings ***
Documentation Health check API tests for Aniworld.
... Covers basic and detailed health endpoints.
Resource ${CURDIR}/../resources/common.resource
Resource ${CURDIR}/../resources/api_keywords.resource
Test Setup Create Anonymous Session
Test Teardown Delete All Sessions
*** Test Cases ***
# ---------------------------------------------------------------------------
# Basic Health
# ---------------------------------------------------------------------------
Basic Health Check
[Documentation] Verify the basic health endpoint returns 200.
${resp}= GET On Session anon /health expected_status=200
Response Should Have Status ${resp} 200
${status}= Get JSON Value ${resp} $.status
Should Be Equal As Strings ${status} healthy
# ---------------------------------------------------------------------------
# Detailed Health
# ---------------------------------------------------------------------------
Detailed Health Check
[Documentation] Verify the detailed health endpoint returns metrics.
${resp}= GET On Session anon /health/detailed expected_status=200
Response Should Have Status ${resp} 200
Response Should Contain Keys ${resp} status version uptime memory cpu

View File

@@ -0,0 +1,56 @@
*** Settings ***
Documentation Logging API tests for Aniworld.
... Covers config, file listing, tail, download, test messages, and cleanup.
Resource ${CURDIR}/../resources/common.resource
Resource ${CURDIR}/../resources/api_keywords.resource
Test Setup Run Keywords
... Create Anonymous Session
... AND Setup Master Password
... AND Create Authenticated Session
Test Teardown Delete All Sessions
*** Test Cases ***
# ---------------------------------------------------------------------------
# Logging Config
# ---------------------------------------------------------------------------
Get Logging Config
[Documentation] Retrieve the current logging configuration.
${resp}= Get Logging Config
Response Should Have Status ${resp} 200
Response Should Contain Keys ${resp} level file max_bytes backup_count
# ---------------------------------------------------------------------------
# Log Files
# ---------------------------------------------------------------------------
List Log Files
[Documentation] List available log files.
${resp}= List Log Files
Response Should Have Status ${resp} 200
Response Should Be Valid JSON ${resp}
Tail Log File
[Documentation] Tail the contents of a log file.
${resp}= Tail Log File aniworld.log
Response Should Have Status ${resp} 200
Download Log File
[Documentation] Download a log file as binary.
${resp}= Download Log File aniworld.log
Response Should Have Status ${resp} 200
Should Be Equal As Strings ${resp.headers['content-type']} application/octet-stream
# ---------------------------------------------------------------------------
# Log Management
# ---------------------------------------------------------------------------
Write Test Log Messages
[Documentation] Write test log messages at various levels.
${resp}= Write Test Logs
Response Should Have Status ${resp} 200
Cleanup Old Logs
[Documentation] Delete old log files.
${resp}= Cleanup Logs
Response Should Have Status ${resp} 200

50
tests/robot/api/nfo.robot Normal file
View File

@@ -0,0 +1,50 @@
*** Settings ***
Documentation NFO metadata API tests for Aniworld.
... Covers diagnostics, repair, needs-repair list, and scan.
Resource ${CURDIR}/../resources/common.resource
Resource ${CURDIR}/../resources/api_keywords.resource
Test Setup Run Keywords
... Create Anonymous Session
... AND Setup Master Password
... AND Create Authenticated Session
Test Teardown Delete All Sessions
*** Test Cases ***
# ---------------------------------------------------------------------------
# NFO Diagnostics
# ---------------------------------------------------------------------------
Get NFO Diagnostics
[Documentation] Retrieve NFO diagnostics for a series.
${resp}= Get Nfo Diagnostics attack-on-titan
Response Should Have Status ${resp} 200
Response Should Be Valid JSON ${resp}
# ---------------------------------------------------------------------------
# NFO Repair
# ---------------------------------------------------------------------------
Repair NFO For Series
[Documentation] Trigger NFO repair for a specific series.
${resp}= Repair Nfo attack-on-titan
Response Should Have Status ${resp} 200
Response Should Be Valid JSON ${resp}
# ---------------------------------------------------------------------------
# Needs Repair List
# ---------------------------------------------------------------------------
List Series Needing Repair
[Documentation] List all series that need NFO repair.
${resp}= Get Needs Repair
Response Should Have Status ${resp} 200
Response Should Contain Keys ${resp} total series
# ---------------------------------------------------------------------------
# NFO Scan
# ---------------------------------------------------------------------------
Run NFO Scan
[Documentation] Run an NFO scan across all series.
${resp}= Scan Nfo
Response Should Have Status ${resp} 200
Response Should Contain Keys ${resp} total created updated errors_count duration_seconds

View File

@@ -0,0 +1,92 @@
*** Settings ***
Documentation Scheduler API tests for Aniworld.
... Covers get config, update config, trigger rescan, and validation.
Resource ${CURDIR}/../resources/common.resource
Resource ${CURDIR}/../resources/api_keywords.resource
Test Setup Run Keywords
... Create Anonymous Session
... AND Setup Master Password
... AND Create Authenticated Session
Test Teardown Delete All Sessions
*** Test Cases ***
# ---------------------------------------------------------------------------
# Get / Update Config
# ---------------------------------------------------------------------------
Get Scheduler Config
[Documentation] Retrieve the current scheduler configuration and runtime status.
${resp}= Get Scheduler Config
Response Should Have Status ${resp} 200
Response Should Contain Keys ${resp} success config status
${config_keys}= Create List enabled interval_minutes schedule_time schedule_days auto_download_after_rescan
FOR ${key} IN @{config_keys}
Response Should Contain Key ${resp} $.config.${key}
END
${status_keys}= Create List is_running next_run last_run scan_in_progress
FOR ${key} IN @{status_keys}
Response Should Contain Key ${resp} $.status.${key}
END
Update Scheduler Config
[Documentation] Update scheduler settings and verify they are persisted.
${payload}= Create Dictionary
... enabled=True
... interval_minutes=120
... schedule_time=04:30
... schedule_days=['mon', 'wed', 'fri']
... auto_download_after_rescan=True
${resp}= Update Scheduler Config ${payload}
Response Should Have Status ${resp} 200
${saved_time}= Get JSON Value ${resp} $.config.schedule_time
Should Be Equal As Strings ${saved_time} 04:30
${saved_days}= Get JSON Value ${resp} $.config.schedule_days
List Should Contain Value ${saved_days} mon
# ---------------------------------------------------------------------------
# Trigger Rescan
# ---------------------------------------------------------------------------
Trigger Manual Rescan
[Documentation] Manually trigger a scheduled rescan.
${resp}= Trigger Rescan
Response Should Have Status ${resp} 200
${success}= Get JSON Value ${resp} $.success
Should Be Equal As Strings ${success} True
# ---------------------------------------------------------------------------
# Validation
# ---------------------------------------------------------------------------
Invalid Schedule Time
[Documentation] Verify that malformed schedule_time is rejected.
${payload}= Create Dictionary
... enabled=True
... interval_minutes=60
... schedule_time=25:00
... schedule_days=['mon']
... auto_download_after_rescan=False
${resp}= Update Scheduler Config ${payload}
Response Should Have Status ${resp} 422
Invalid Schedule Days
[Documentation] Verify that invalid day abbreviations are rejected.
${payload}= Create Dictionary
... enabled=True
... interval_minutes=60
... schedule_time=03:00
... schedule_days=['monday', 'tuesday']
... auto_download_after_rescan=False
${resp}= Update Scheduler Config ${payload}
Response Should Have Status ${resp} 422
Empty Schedule Days
[Documentation] Verify that empty schedule_days is handled.
${payload}= Create Dictionary
... enabled=True
... interval_minutes=60
... schedule_time=03:00
... schedule_days=[]
... auto_download_after_rescan=False
${resp}= Update Scheduler Config ${payload}
Response Should Have Status ${resp} 200

View File

@@ -0,0 +1,35 @@
*** Settings ***
Documentation Setup (unresolved folders) API tests for Aniworld.
... Covers listing unresolved folders, getting details, and resolving.
Resource ${CURDIR}/../resources/common.resource
Resource ${CURDIR}/../resources/api_keywords.resource
Test Setup Run Keywords
... Create Anonymous Session
... AND Setup Master Password
... AND Create Authenticated Session
Test Teardown Delete All Sessions
*** Test Cases ***
# ---------------------------------------------------------------------------
# Unresolved Folders
# ---------------------------------------------------------------------------
List Unresolved Folders
[Documentation] List all unresolved folders in the anime directory.
${resp}= Get Unresolved Folders
Response Should Have Status ${resp} 200
Response Should Be Valid JSON ${resp}
Get Unresolved Folder Details
[Documentation] Get details for a specific unresolved folder.
${resp}= Get Unresolved Folder Details SomeFolder
Response Should Have Status ${resp} 200
Response Should Be Valid JSON ${resp}
Resolve Folder With Provider Key
[Documentation] Resolve an unresolved folder by mapping it to a provider key.
${resp}= Resolve Folder SomeFolder attack-on-titan
Response Should Have Status ${resp} 200
Response Should Be Valid JSON ${resp}

View File

@@ -0,0 +1,41 @@
*** Settings ***
Documentation WebSocket API tests for Aniworld.
... Covers connection, room subscription, ping/pong, and notifications.
Resource ${CURDIR}/../resources/common.resource
Resource ${CURDIR}/../resources/api_keywords.resource
Test Setup Run Keywords
... Create Anonymous Session
... AND Setup Master Password
... AND Create Authenticated Session
Test Teardown Delete All Sessions
*** Test Cases ***
# ---------------------------------------------------------------------------
# WebSocket Connection
# ---------------------------------------------------------------------------
Connect To WebSocket
[Documentation] Establish a WebSocket connection with a valid JWT token.
${token}= Login And Get Token
${ws_url}= Set Variable ws://${SERVER_HOST}:${SERVER_PORT}/ws/connect?token=${token}
${result}= Evaluate __import__('asyncio').run(__import__('websockets').connect('${ws_url}'))
# Note: Full WebSocket testing requires a custom Python keyword library
# or using Browser library's Evaluate with inline JavaScript.
Pass Execution WebSocket connection test requires custom keyword library
# ---------------------------------------------------------------------------
# Placeholder for WebSocket Room Subscription
# ---------------------------------------------------------------------------
Subscribe To Rooms
[Documentation] Subscribe to WebSocket rooms (downloads, queue, scan, system).
Pass Execution WebSocket room subscription requires custom keyword library
Receive Ping Pong
[Documentation] Verify WebSocket heartbeat mechanism.
Pass Execution WebSocket ping/pong requires custom keyword library
Receive System Notification
[Documentation] Trigger an action that emits a system message and verify receipt.
Pass Execution WebSocket notification requires custom keyword library

View File

@@ -0,0 +1,32 @@
{
"name": "ImportedAniworld",
"data_dir": "data",
"scheduler": {
"enabled": false,
"interval_minutes": 60,
"schedule_time": "02:00",
"schedule_days": ["mon", "tue", "wed", "thu", "fri"],
"auto_download_after_rescan": false
},
"logging": {
"level": "DEBUG",
"file": "logs/imported.log",
"max_bytes": 2097152,
"backup_count": 5
},
"backup": {
"enabled": true,
"path": "/tmp/imported_backups",
"keep_days": 7
},
"nfo": {
"tmdb_api_key": "imported_test_key",
"auto_create": true,
"download_poster": true,
"download_logo": true,
"download_fanart": true
},
"other": {
"anime_directory": "/tmp/imported_anime"
}
}

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

78
tests/robot/run.ps1 Normal file
View File

@@ -0,0 +1,78 @@
# Run Aniworld Robot Framework tests on Windows (PowerShell)
# Usage: .\run.ps1 [-SuitePattern "api/auth.robot"]
# Examples:
# .\run.ps1 # Run all tests
# .\run.ps1 -SuitePattern "api/" # Run all API tests
# .\run.ps1 -SuitePattern "ui/" # Run all UI tests
param(
[string]$SuitePattern = "tests/robot/"
)
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
$robotDir = Split-Path -Parent $scriptDir
$projectDir = Split-Path -Parent $robotDir
$resultsDir = Join-Path $robotDir "results"
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "Aniworld Robot Framework Test Runner" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "Project dir: $projectDir"
Write-Host "Results dir: $resultsDir"
Write-Host "Suite pattern: $SuitePattern"
Write-Host ""
# Ensure we're in the project directory
Set-Location $projectDir
# Check Python
if (-not (Get-Command python -ErrorAction SilentlyContinue)) {
Write-Error "Error: python not found in PATH"
exit 1
}
# Install/update Robot Framework dependencies
Write-Host "Installing Robot Framework dependencies..." -ForegroundColor Yellow
& python -m pip install -q robotframework robotframework-browser robotframework-requests robotframework-jsonlibrary 2>$null
# Initialize Browser library (Playwright)
Write-Host "Initializing Browser library (Playwright)..." -ForegroundColor Yellow
& python -m Browser.entry init 2>$null
if ($LASTEXITCODE -ne 0) {
Write-Warning "Browser library init may need manual run: 'rfbrowser init'"
}
# Create results directory
New-Item -ItemType Directory -Force -Path $resultsDir | Out-Null
# Run tests
Write-Host ""
Write-Host "Running Robot Framework tests..." -ForegroundColor Green
Write-Host "----------------------------------------" -ForegroundColor Green
& robot `
--outputdir $resultsDir `
--logtitle "Aniworld Test Log" `
--reporttitle "Aniworld Test Report" `
--metadata Project:Aniworld `
--metadata Environment:"$(& python --version)" `
--variable BASE_URL:http://127.0.0.1:8765 `
--variable BROWSER:chromium `
--variable HEADLESS:True `
$SuitePattern
$exitCode = $LASTEXITCODE
Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
if ($exitCode -eq 0) {
Write-Host "All tests PASSED" -ForegroundColor Green
} else {
Write-Host "Some tests FAILED (exit code: $exitCode)" -ForegroundColor Red
}
Write-Host "Report: $resultsDir\report.html"
Write-Host "Log: $resultsDir\log.html"
Write-Host "Output: $resultsDir\output.xml"
Write-Host "========================================" -ForegroundColor Cyan
exit $exitCode

77
tests/robot/run.sh Normal file
View File

@@ -0,0 +1,77 @@
#!/bin/bash
# Run Aniworld Robot Framework tests on Unix/Linux/macOS
# Usage: ./run.sh [suite_pattern]
# Examples:
# ./run.sh # Run all tests
# ./run.sh api/auth.robot # Run specific suite
# ./run.sh ui/ # Run all UI tests
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROBOT_DIR="$(dirname "$SCRIPT_DIR")"
PROJECT_DIR="$(dirname "$ROBOT_DIR")"
RESULTS_DIR="$ROBOT_DIR/results"
# Default: run everything
SUITE_PATTERN="${1:-tests/robot/}"
echo "========================================"
echo "Aniworld Robot Framework Test Runner"
echo "========================================"
echo "Project dir: $PROJECT_DIR"
echo "Results dir: $RESULTS_DIR"
echo "Suite pattern: $SUITE_PATTERN"
echo ""
# Ensure we're in the project directory
cd "$PROJECT_DIR"
# Check Python environment
if ! command -v python >/dev/null 2>&1; then
echo "Error: python not found in PATH"
exit 1
fi
# Install/update Robot Framework dependencies
echo "Installing Robot Framework dependencies..."
pip install -q robotframework robotframework-browser robotframework-requests robotframework-jsonlibrary 2>/dev/null || true
# Initialize Browser library (Playwright)
echo "Initializing Browser library (Playwright)..."
python -m Browser.entry init 2>/dev/null || rfbrowser init 2>/dev/null || echo "Warning: Browser library init may need manual run: 'rfbrowser init'"
# Create results directory
mkdir -p "$RESULTS_DIR"
# Run tests
echo ""
echo "Running Robot Framework tests..."
echo "----------------------------------------"
robot \
--outputdir "$RESULTS_DIR" \
--logtitle "Aniworld Test Log" \
--reporttitle "Aniworld Test Report" \
--metadata Project:Aniworld \
--metadata Environment:"$(python --version 2>&1)" \
--variable BASE_URL:http://127.0.0.1:8765 \
--variable BROWSER:chromium \
--variable HEADLESS:True \
"$SUITE_PATTERN"
EXIT_CODE=$?
echo ""
echo "========================================"
if [ $EXIT_CODE -eq 0 ]; then
echo "All tests PASSED"
else
echo "Some tests FAILED (exit code: $EXIT_CODE)"
fi
echo "Report: $RESULTS_DIR/report.html"
echo "Log: $RESULTS_DIR/log.html"
echo "Output: $RESULTS_DIR/output.xml"
echo "========================================"
exit $EXIT_CODE

View File

@@ -0,0 +1,50 @@
*** Settings ***
Documentation Anime settings page UI tests for Aniworld.
... Covers page load, regenerate NFO, and update series settings.
Resource ${CURDIR}/../resources/common.resource
Resource ${CURDIR}/../resources/ui_keywords.resource
Test Setup Run Keywords
... Create Anonymous Session
... AND Setup Master Password
... AND Open Browser To Page /login
... AND Perform Login
... AND Wait For Elements State id=search-input visible timeout=5s
Test Teardown Close Browser
*** Test Cases ***
# ---------------------------------------------------------------------------
# Anime Settings Page Load
# ---------------------------------------------------------------------------
Anime Settings Page Loads
[Documentation] Navigate to anime settings page and verify form elements.
Go To ${BASE_URL}/anime/settings?key=attack-on-titan
Wait For Elements State id=anime-settings-form visible timeout=5s
Page Title Should Contain Settings
Element Should Be Visible id=anime-settings-form
Element Should Be Visible id=regenerate-nfo-btn
Element Should Be Visible id=save-anime-settings-btn
# ---------------------------------------------------------------------------
# Regenerate NFO
# ---------------------------------------------------------------------------
Regenerate NFO
[Documentation] Click regenerate NFO and verify success notification.
Go To ${BASE_URL}/anime/settings?key=attack-on-titan
Wait For Elements State id=regenerate-nfo-btn visible timeout=5s
Click id=regenerate-nfo-btn
Toast Should Contain NFO
# ---------------------------------------------------------------------------
# Update Series Settings
# ---------------------------------------------------------------------------
Update Series Settings
[Documentation] Change series settings and save.
Go To ${BASE_URL}/anime/settings?key=attack-on-titan
Wait For Elements State id=anime-settings-form visible timeout=5s
Select Options By id=preferred-language-select text German
Fill Text id=custom-folder-input Attack on Titan Custom
Click id=save-anime-settings-btn
Toast Should Contain saved

View File

@@ -0,0 +1,112 @@
*** Settings ***
Documentation Dashboard UI tests for Aniworld.
... Covers search, theme toggle, rescan, series filters, select all, and download selected.
Resource ${CURDIR}/../resources/common.resource
Resource ${CURDIR}/../resources/ui_keywords.resource
Test Setup Run Keywords
... Create Anonymous Session
... AND Setup Master Password
... AND Open Browser To Page /login
... AND Perform Login
... AND Wait For Elements State id=search-input visible timeout=5s
Test Teardown Close Browser
*** Test Cases ***
# ---------------------------------------------------------------------------
# Dashboard Load
# ---------------------------------------------------------------------------
Dashboard Loads
[Documentation] Verify the main dashboard loads with key elements.
Page Title Should Contain AniWorld
Element Should Be Visible id=search-input
Element Should Be Visible id=search-btn
Element Should Be Visible id=rescan-btn
Element Should Be Visible id=theme-toggle
Element Should Be Visible id=config-btn
# ---------------------------------------------------------------------------
# Search
# ---------------------------------------------------------------------------
Search For Anime
[Documentation] Type a query and submit search, verifying results appear.
Search For Anime attack
Element Should Be Visible id=search-results-list
${results}= Get Element Count id=search-results-list > *
Should Be True ${results} >= 0
Clear Search
[Documentation] Click clear search and verify results are hidden.
Search For Anime attack
Clear Search
Element Should Be Hidden id=search-results
# ---------------------------------------------------------------------------
# Theme Toggle
# ---------------------------------------------------------------------------
Theme Toggle To Dark
[Documentation] Click theme toggle and verify dark theme is applied.
${before}= Get Current Theme
Should Be Equal As Strings ${before} light
Toggle Theme
${after}= Get Current Theme
Should Be Equal As Strings ${after} dark
Theme Toggle To Light
[Documentation] Click theme toggle twice and verify back to light.
Toggle Theme
Toggle Theme
${theme}= Get Current Theme
Should Be Equal As Strings ${theme} light
Theme Persists After Reload
[Documentation] Toggle to dark, reload, and verify theme persists.
Toggle Theme
Reload Page
Wait For Elements State id=search-input visible timeout=5s
${theme}= Get Current Theme
Should Be Equal As Strings ${theme} dark
# ---------------------------------------------------------------------------
# Rescan
# ---------------------------------------------------------------------------
Rescan Button
[Documentation] Click the rescan button and verify the status indicator changes.
Click id=rescan-btn
Wait For Elements State #rescan-status .status-dot.scanning visible timeout=5s
# ---------------------------------------------------------------------------
# Series Filters
# ---------------------------------------------------------------------------
Show Missing Episodes Only
[Documentation] Click the missing episodes filter and verify UI updates.
Click id=show-missing-only
${active}= Get Attribute id=show-missing-only data-active
Should Be Equal As Strings ${active} true
Show All Series
[Documentation] Click show all and verify filter is overridden.
Click id=show-all-series
${active}= Get Attribute id=show-all-series data-active
Should Be Equal As Strings ${active} true
# ---------------------------------------------------------------------------
# Selection & Download
# ---------------------------------------------------------------------------
Select All Series
[Documentation] Click select all and verify all series checkboxes are checked.
Click id=select-all
${checked}= Get Element Count .series-card input:checked
Should Be True ${checked} >= 0
Download Selected
[Documentation] Select a series and click download selected.
# Check first series card checkbox if any exist
${count}= Get Element Count .series-card input[type="checkbox"]
IF ${count} > 0
Click .series-card input[type="checkbox"] >> nth=0
Click id=download-selected
Toast Should Contain queue
END

View File

@@ -0,0 +1,85 @@
*** Settings ***
Documentation Login UI tests for Aniworld.
... Covers login page load, valid/invalid login, rate limit UI, password visibility, and logout.
Resource ${CURDIR}/../resources/common.resource
Resource ${CURDIR}/../resources/ui_keywords.resource
Test Setup Run Keywords
... Create Anonymous Session
... AND Setup Master Password
Test Teardown Close Browser
*** Test Cases ***
# ---------------------------------------------------------------------------
# Login Page Load
# ---------------------------------------------------------------------------
Login Page Loads
[Documentation] Verify the login page loads with all form elements.
Open Browser To Page /login
Page Title Should Contain Login
Element Should Be Visible id=password-input
Element Should Be Visible id=login-submit-btn
Element Should Be Visible id=password-toggle
# ---------------------------------------------------------------------------
# Valid Login
# ---------------------------------------------------------------------------
Login With Valid Password
[Documentation] Log in with the correct password and verify redirect to dashboard.
Open Browser To Page /login
Perform Login ${SETUP_PASSWORD}
Page Title Should Contain AniWorld
Element Should Be Visible id=search-input
# ---------------------------------------------------------------------------
# Invalid Login
# ---------------------------------------------------------------------------
Login With Invalid Password
[Documentation] Log in with an incorrect password and verify error message.
Open Browser To Page /login
Fill Text id=password-input WrongPass123!
Click id=login-submit-btn
Element Should Be Visible id=login-error
Element Text Should Contain id=login-error invalid
# ---------------------------------------------------------------------------
# Rate Limit UI
# ---------------------------------------------------------------------------
Login Rate Limit UI
[Documentation] Submit multiple failed logins and verify lockout message appears.
Open Browser To Page /login
FOR ${i} IN RANGE 6
Fill Text id=password-input WrongPass123!
Click id=login-submit-btn
Wait For Elements State id=login-error visible timeout=3s
END
Element Text Should Contain id=login-error lockout
# ---------------------------------------------------------------------------
# Password Visibility Toggle
# ---------------------------------------------------------------------------
Password Visibility Toggle
[Documentation] Click the eye icon and verify the password field type toggles.
Open Browser To Page /login
Fill Text id=password-input ${SETUP_PASSWORD}
${type_before}= Get Attribute id=password-input type
Should Be Equal As Strings ${type_before} password
Click id=password-toggle
${type_after}= Get Attribute id=password-input type
Should Be Equal As Strings ${type_after} text
Click id=password-toggle
${type_final}= Get Attribute id=password-input type
Should Be Equal As Strings ${type_final} password
# ---------------------------------------------------------------------------
# Logout
# ---------------------------------------------------------------------------
Logout
[Documentation] Log in then log out and verify redirect to login page.
Open Browser To Page /login
Perform Login ${SETUP_PASSWORD}
Perform Logout
Page Title Should Contain Login
Element Should Be Visible id=password-input

View File

@@ -0,0 +1,106 @@
*** Settings ***
Documentation Queue page UI tests for Aniworld.
... Covers page load, stats cards, start/stop buttons, clear confirmations, retry all, and navigation.
Resource ${CURDIR}/../resources/common.resource
Resource ${CURDIR}/../resources/ui_keywords.resource
Test Setup Run Keywords
... Create Anonymous Session
... AND Setup Master Password
... AND Open Browser To Page /login
... AND Perform Login
... AND Navigate To Queue Page
Test Teardown Close Browser
*** Test Cases ***
# ---------------------------------------------------------------------------
# Queue Page Load
# ---------------------------------------------------------------------------
Queue Page Loads
[Documentation] Verify the queue page loads with all sections and stats.
Page Title Should Contain Queue
Element Should Be Visible id=total-items
Element Should Be Visible id=pending-items
Element Should Be Visible id=completed-items
Element Should Be Visible id=failed-items
Element Should Be Visible id=pending-queue
Element Should Be Visible id=active-downloads
Element Should Be Visible id=completed-downloads
Element Should Be Visible id=failed-downloads
Queue Stats Cards Display Zero Initially
[Documentation] Verify all stat cards show 0 when queue is empty.
${total}= Get Text id=total-items
Should Be Equal As Strings ${total} 0
${pending}= Get Text id=pending-items
Should Be Equal As Strings ${pending} 0
${completed}= Get Text id=completed-items
Should Be Equal As Strings ${completed} 0
${failed}= Get Text id=failed-items
Should Be Equal As Strings ${failed} 0
# ---------------------------------------------------------------------------
# Queue Controls
# ---------------------------------------------------------------------------
Start Queue Button
[Documentation] Click start queue and verify button state changes.
${disabled}= Get Attribute id=start-queue-btn disabled
IF '${disabled}' != 'true'
Click id=start-queue-btn
Wait For Elements State id=stop-queue-btn visible timeout=3s
END
Stop Queue Button
[Documentation] Click stop queue and verify button state changes.
${disabled}= Get Attribute id=stop-queue-btn disabled
IF '${disabled}' != 'true'
Click id=stop-queue-btn
Wait For Elements State id=start-queue-btn visible timeout=3s
END
# ---------------------------------------------------------------------------
# Clear Operations
# ---------------------------------------------------------------------------
Clear Completed Confirmation
[Documentation] Click clear completed and verify confirmation modal appears.
${disabled}= Get Attribute id=clear-completed-btn disabled
IF '${disabled}' != 'true'
Click id=clear-completed-btn
Element Should Be Visible id=confirm-modal
Element Text Should Contain id=confirm-modal clear
Click id=confirm-cancel
Element Should Be Hidden id=confirm-modal
END
Clear Failed Confirmation
[Documentation] Click clear failed and verify confirmation modal appears.
${disabled}= Get Attribute id=clear-failed-btn disabled
IF '${disabled}' != 'true'
Click id=clear-failed-btn
Element Should Be Visible id=confirm-modal
Element Text Should Contain id=confirm-modal clear
Click id=confirm-cancel
Element Should Be Hidden id=confirm-modal
END
# ---------------------------------------------------------------------------
# Retry All
# ---------------------------------------------------------------------------
Retry All Failed
[Documentation] Click retry all and verify UI feedback.
${disabled}= Get Attribute id=retry-all-btn disabled
IF '${disabled}' != 'true'
Click id=retry-all-btn
Toast Should Contain retry
END
# ---------------------------------------------------------------------------
# Navigation
# ---------------------------------------------------------------------------
Navigation Back To Main
[Documentation] Click the back button and verify redirect to main page.
Click text=Back to Main
Wait For Elements State id=search-input visible timeout=5s
Page Title Should Contain AniWorld

View File

@@ -0,0 +1,86 @@
*** Settings ***
Documentation Responsive design UI tests for Aniworld.
... Covers mobile and tablet viewport layouts and touch interactions.
Resource ${CURDIR}/../resources/common.resource
Resource ${CURDIR}/../resources/ui_keywords.resource
Test Setup Run Keywords
... Create Anonymous Session
... AND Setup Master Password
... AND Open Browser To Page /login
... AND Perform Login
... AND Wait For Elements State id=search-input visible timeout=5s
Test Teardown Close Browser
*** Test Cases ***
# ---------------------------------------------------------------------------
# Mobile Viewport
# ---------------------------------------------------------------------------
Mobile Viewport Layout
[Documentation] Resize to mobile and verify layout adapts.
Set Mobile Viewport
Reload Page
Wait For Elements State id=search-input visible timeout=5s
${width}= Evaluate window.innerWidth
Should Be True ${width} <= 480
Element Should Be Visible id=search-input
Element Should Be Visible id=theme-toggle
Mobile Menu Accessible
[Documentation] Verify hamburger menu or compact header on mobile.
Set Mobile Viewport
Reload Page
Wait For Elements State id=search-input visible timeout=5s
# On mobile, header actions may collapse into a menu
${menu_visible}= Run Keyword And Return Status
... Wait For Elements State id=mobile-menu-btn visible timeout=2s
IF ${menu_visible}
Click id=mobile-menu-btn
Element Should Be Visible id=mobile-menu
END
# ---------------------------------------------------------------------------
# Tablet Viewport
# ---------------------------------------------------------------------------
Tablet Viewport Layout
[Documentation] Resize to tablet and verify layout adapts.
Set Tablet Viewport
Reload Page
Wait For Elements State id=search-input visible timeout=5s
${width}= Evaluate window.innerWidth
Should Be True ${width} >= 768
Element Should Be Visible id=search-input
Element Should Be Visible id=rescan-btn
# ---------------------------------------------------------------------------
# Touch Interactions
# ---------------------------------------------------------------------------
Touch Buttons Clickable
[Documentation] Verify buttons remain clickable on touch-sized viewport.
Set Mobile Viewport
Reload Page
Wait For Elements State id=search-input visible timeout=5s
# Try clicking theme toggle
Click id=theme-toggle
${theme}= Get Current Theme
Should Be Equal As Strings ${theme} dark
# Toggle back
Click id=theme-toggle
${theme}= Get Current Theme
Should Be Equal As Strings ${theme} light
# ---------------------------------------------------------------------------
# Desktop Viewport Reset
# ---------------------------------------------------------------------------
Desktop Viewport Reset
[Documentation] Resize back to desktop and verify normal layout.
Set Desktop Viewport
Reload Page
Wait For Elements State id=search-input visible timeout=5s
${width}= Evaluate window.innerWidth
Should Be True ${width} >= 1280
Element Should Be Visible id=search-input
Element Should Be Visible id=rescan-btn
Element Should Be Visible id=config-btn

View File

@@ -0,0 +1,164 @@
*** Settings ***
Documentation Settings modal UI tests for Aniworld.
... Covers open/close, edit all config sections, save, backup/restore, and export/import.
Resource ${CURDIR}/../resources/common.resource
Resource ${CURDIR}/../resources/ui_keywords.resource
Test Setup Run Keywords
... Create Anonymous Session
... AND Setup Master Password
... AND Open Browser To Page /login
... AND Perform Login
... AND Wait For Elements State id=search-input visible timeout=5s
Test Teardown Close Browser
*** Test Cases ***
# ---------------------------------------------------------------------------
# Modal Open/Close
# ---------------------------------------------------------------------------
Open Settings Modal
[Documentation] Click the config button and verify the modal opens.
Open Settings Modal
Element Should Be Visible id=config-modal
Close Settings Modal Via Button
[Documentation] Click the close button and verify the modal closes.
Open Settings Modal
Close Settings Modal
Element Should Be Hidden id=config-modal
Close Settings Modal Via Overlay
[Documentation] Click the modal overlay and verify the modal closes.
Open Settings Modal
Click id=config-modal .modal-overlay
Wait For Elements State id=config-modal hidden timeout=3s
Element Should Be Hidden id=config-modal
Close Settings Modal Via Escape
[Documentation] Press Escape and verify the modal closes.
Open Settings Modal
Press Keys id=config-modal Escape
Wait For Elements State id=config-modal hidden timeout=3s
Element Should Be Hidden id=config-modal
# ---------------------------------------------------------------------------
# General Settings
# ---------------------------------------------------------------------------
Edit General Settings
[Documentation] Change app name, data dir, and anime directory.
Open Settings Modal
Fill General Settings UpdatedApp data /tmp/updated_anime
Save Settings
Toast Should Contain saved
# ---------------------------------------------------------------------------
# Scheduler Settings
# ---------------------------------------------------------------------------
Edit Scheduler Settings
[Documentation] Toggle scheduler enabled and change time/days.
Open Settings Modal
Check Checkbox id=scheduled-rescan-enabled
Fill Text id=scheduler-time-input 04:30
# Select days (simplified - actual implementation depends on UI widgets)
Save Settings
Toast Should Contain saved
Disable Scheduler
[Documentation] Uncheck scheduler enabled and save.
Open Settings Modal
Uncheck Checkbox id=scheduled-rescan-enabled
Save Settings
Toast Should Contain saved
# ---------------------------------------------------------------------------
# Logging Settings
# ---------------------------------------------------------------------------
Edit Logging Settings
[Documentation] Change log level, file path, max bytes, and backup count.
Open Settings Modal
Select Options By id=logging-level-select text DEBUG
Fill Text id=logging-file-input logs/test.log
Fill Text id=logging-max-bytes-input 1048576
Fill Text id=logging-backup-count-input 5
Save Settings
Toast Should Contain saved
# ---------------------------------------------------------------------------
# Backup Settings
# ---------------------------------------------------------------------------
Edit Backup Settings
[Documentation] Toggle backup enabled and change path/retention.
Open Settings Modal
Check Checkbox id=backup-enabled
Fill Text id=backup-path-input /tmp/aniworld_backups
Fill Text id=backup-keep-days-input 14
Save Settings
Toast Should Contain saved
# ---------------------------------------------------------------------------
# NFO Settings
# ---------------------------------------------------------------------------
Edit NFO Settings
[Documentation] Enter TMDB key and toggle auto-create/media downloads.
Open Settings Modal
Fill Text id=tmdb-api-key-input test_tmdb_key_123
Check Checkbox id=nfo-auto-create
Check Checkbox id=nfo-download-poster
Check Checkbox id=nfo-download-logo
Check Checkbox id=nfo-download-fanart
Save Settings
Toast Should Contain saved
# ---------------------------------------------------------------------------
# Backup Management
# ---------------------------------------------------------------------------
Create Config Backup
[Documentation] Click create backup and verify success notification.
Open Settings Modal
Click id=create-backup-btn
Toast Should Contain backup
Restore Config Backup
[Documentation] Select a backup and click restore.
Open Settings Modal
Click id=create-backup-btn
Toast Should Contain backup
# Select first backup in list
${count}= Get Element Count .backup-item
IF ${count} > 0
Click .backup-item >> nth=0
Click id=restore-backup-btn
Toast Should Contain restore
END
Delete Config Backup
[Documentation] Select a backup and click delete.
Open Settings Modal
Click id=create-backup-btn
Toast Should Contain backup
${count}= Get Element Count .backup-item
IF ${count} > 0
Click .backup-item >> nth=0
Click id=delete-backup-btn
Toast Should Contain delete
END
# ---------------------------------------------------------------------------
# Export / Import
# ---------------------------------------------------------------------------
Export Config
[Documentation] Click export config and verify download starts.
Open Settings Modal
${dl_promise}= Promise To Wait For Event download
Click id=export-config-btn
${download}= Wait For ${dl_promise}
Should Not Be Empty ${download}
Import Config
[Documentation] Upload a config file and verify it is applied.
Open Settings Modal
Upload File By Selector id=import-config-input ${CURDIR}/../fixtures/sample_config.json
Click id=import-config-btn
Toast Should Contain import

View File

@@ -0,0 +1,95 @@
*** Settings ***
Documentation Setup flow UI tests for Aniworld.
... Covers setup page load, password strength, form validation, and complete setup submission.
Resource ${CURDIR}/../resources/common.resource
Resource ${CURDIR}/../resources/ui_keywords.resource
Test Setup Run Keywords
... Open Browser To Page /setup
... AND Wait For Elements State id=setup-form visible timeout=5s
Test Teardown Close Browser
*** Test Cases ***
# ---------------------------------------------------------------------------
# Setup Page Load
# ---------------------------------------------------------------------------
Setup Page Loads
[Documentation] Verify the setup page loads with all expected sections.
Page Title Should Contain Setup
Element Should Be Visible id=setup-form
Element Should Be Visible id=general-section
Element Should Be Visible id=security-section
Element Should Be Visible id=scheduler-section
Element Should Be Visible id=logging-section
Element Should Be Visible id=backup-section
Element Should Be Visible id=nfo-section
# ---------------------------------------------------------------------------
# Password Strength Indicator
# ---------------------------------------------------------------------------
Password Strength Weak
[Documentation] Type a weak password and verify the strength indicator shows weak.
Fill Text id=master-password-input weak
${strength}= Get Text id=password-strength
Should Contain ${strength} weak
Password Strength Medium
[Documentation] Type a medium-strength password and verify the indicator updates.
Fill Text id=master-password-input Medium1!
${strength}= Get Text id=password-strength
Should Contain ${strength} medium
Password Strength Strong
[Documentation] Type a strong password and verify the indicator shows strong.
Fill Text id=master-password-input ${SETUP_PASSWORD}
${strength}= Get Text id=password-strength
Should Contain ${strength} strong
# ---------------------------------------------------------------------------
# Form Validation
# ---------------------------------------------------------------------------
Setup Form Validation Empty Password
[Documentation] Submit the form with an empty password and verify error message.
Fill Text id=app-name-input TestApp
Fill Text id=anime-directory-input /tmp/test
Click id=setup-submit-btn
Element Should Be Visible id=password-error
Element Text Should Contain id=password-error required
Setup Form Validation Mismatched Passwords
[Documentation] Submit with mismatched password and confirmation.
Fill Text id=master-password-input ${SETUP_PASSWORD}
Fill Text id=confirm-password-input DifferentPass123!
Click id=setup-submit-btn
Element Should Be Visible id=confirm-password-error
Element Text Should Contain id=confirm-password-error match
# ---------------------------------------------------------------------------
# Complete Setup Flow
# ---------------------------------------------------------------------------
Complete Setup Flow
[Documentation] Fill all setup sections and submit, verifying redirect to main page.
Fill Text id=app-name-input AniworldTest
Fill Text id=data-dir-input data
Fill Text id=anime-directory-input /tmp/aniworld_test_anime
Fill Text id=master-password-input ${SETUP_PASSWORD}
Fill Text id=confirm-password-input ${SETUP_PASSWORD}
# Disable scheduler for tests
Uncheck Checkbox id=scheduler-enabled
# Submit
Click id=setup-submit-btn
Wait For Elements State id=search-input visible timeout=10s
Page Title Should Contain AniWorld
# ---------------------------------------------------------------------------
# Post-Setup Redirect
# ---------------------------------------------------------------------------
Setup Redirects When Already Configured
[Documentation] After setup is complete, visiting /setup should redirect to /.
Complete Setup Flow
Go To ${BASE_URL}/setup
Wait For Elements State id=search-input visible timeout=5s
${url}= Get Url
Should Not Contain ${url} /setup