backup
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -84,3 +84,8 @@ tmp/
|
|||||||
*.tmp
|
*.tmp
|
||||||
.coverage
|
.coverage
|
||||||
.venv/bin/dotenv
|
.venv/bin/dotenv
|
||||||
|
|
||||||
|
# Node.js
|
||||||
|
node_modules/
|
||||||
|
tests/results/*
|
||||||
|
test-results/*
|
||||||
|
|||||||
@@ -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
2644
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
tests/robot/__init__.robot
Normal file
17
tests/robot/__init__.robot
Normal 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
126
tests/robot/api/anime.robot
Normal 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
118
tests/robot/api/auth.robot
Normal 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
|
||||||
138
tests/robot/api/config.robot
Normal file
138
tests/robot/api/config.robot
Normal 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
|
||||||
145
tests/robot/api/download.robot
Normal file
145
tests/robot/api/download.robot
Normal 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
|
||||||
29
tests/robot/api/health.robot
Normal file
29
tests/robot/api/health.robot
Normal 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
|
||||||
56
tests/robot/api/logging.robot
Normal file
56
tests/robot/api/logging.robot
Normal 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
50
tests/robot/api/nfo.robot
Normal 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
|
||||||
92
tests/robot/api/scheduler.robot
Normal file
92
tests/robot/api/scheduler.robot
Normal 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
|
||||||
35
tests/robot/api/setup.robot
Normal file
35
tests/robot/api/setup.robot
Normal 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}
|
||||||
41
tests/robot/api/websocket.robot
Normal file
41
tests/robot/api/websocket.robot
Normal 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
|
||||||
32
tests/robot/fixtures/sample_config.json
Normal file
32
tests/robot/fixtures/sample_config.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
306
tests/robot/resources/api_keywords.resource
Normal file
306
tests/robot/resources/api_keywords.resource
Normal 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}
|
||||||
146
tests/robot/resources/common.resource
Normal file
146
tests/robot/resources/common.resource
Normal 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}
|
||||||
204
tests/robot/resources/ui_keywords.resource
Normal file
204
tests/robot/resources/ui_keywords.resource
Normal 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
78
tests/robot/run.ps1
Normal 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
77
tests/robot/run.sh
Normal 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
|
||||||
50
tests/robot/ui/anime_settings.robot
Normal file
50
tests/robot/ui/anime_settings.robot
Normal 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
|
||||||
112
tests/robot/ui/dashboard.robot
Normal file
112
tests/robot/ui/dashboard.robot
Normal 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
|
||||||
85
tests/robot/ui/login.robot
Normal file
85
tests/robot/ui/login.robot
Normal 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
|
||||||
106
tests/robot/ui/queue_page.robot
Normal file
106
tests/robot/ui/queue_page.robot
Normal 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
|
||||||
86
tests/robot/ui/responsive.robot
Normal file
86
tests/robot/ui/responsive.robot
Normal 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
|
||||||
164
tests/robot/ui/settings_modal.robot
Normal file
164
tests/robot/ui/settings_modal.robot
Normal 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
|
||||||
95
tests/robot/ui/setup_flow.robot
Normal file
95
tests/robot/ui/setup_flow.robot
Normal 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
|
||||||
Reference in New Issue
Block a user