diff --git a/Docs/tasks.md b/Docs/tasks.md index 8cbe642..fdc0ed4 100644 --- a/Docs/tasks.md +++ b/Docs/tasks.md @@ -1,19 +1,3 @@ -### Task 17: Fix `Login With Valid Password` UI Test -**Test Result:** FAIL — Same as Task 16 (`id=password-input` not found) -**File:** `tests/robot/ui/login.robot` and `src/server/web/templates/login.html` -**Instructions:** -Fix the element ID mismatches in `login.html` as described in Task 16. The `Perform Login` keyword in `tests/robot/resources/ui_keywords.resource` fills `id=password-input` and clicks `id=login-submit-btn`. Update the HTML template IDs to match. - ---- - -### Task 18: Fix `Login With Invalid Password` UI Test -**Test Result:** FAIL — Same as Task 16 -**File:** `tests/robot/ui/login.robot` and `src/server/web/templates/login.html` -**Instructions:** -Fix the element ID mismatches in `login.html` as described in Task 16. Also verify that the error message element `id=login-error` exists in the template and is shown when login fails. - ---- - ### Task 19: Fix `Login Rate Limit UI` UI Test **Test Result:** FAIL — Same as Task 16 **File:** `tests/robot/ui/login.robot` and `src/server/web/templates/login.html` diff --git a/log.html b/log.html new file mode 100644 index 0000000..4fefd19 --- /dev/null +++ b/log.html @@ -0,0 +1,2467 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Opening Robot Framework log failed

+ +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/output.xml b/output.xml new file mode 100644 index 0000000..8647a7c --- /dev/null +++ b/output.xml @@ -0,0 +1,676 @@ + + + + + + + +anon +${BASE_URL} +Create Session: create a HTTP session to a server + + +Create an unauthenticated HTTP session. + + + + +${payload} +master_password=${SETUP_PASSWORD} +anime_directory=/tmp/aniworld_test_anime +name=AniworldTest +data_dir=data +scheduler_enabled=False +logging_level=INFO +backup_enabled=False +Creates and returns a dictionary based on the given ``items``. + + + +${resp} +anon +/api/auth/setup +json=${payload} +expected_status=any +Sends a POST request on a previously created HTTP Session. + + + + + +6s +Pauses the test executed for the given time. + + + +${resp} +anon +/api/auth/setup +json=${payload} +expected_status=any +Sends a POST request on a previously created HTTP Session. + + + + + + + + + +${json} +${resp.text} +Convert String to JSON object + + + +${detail} +${json} +$.detail +Get Value From JSON using JSONPath + + + + + + + + + + + + +Setup failed with 400: ${detail} +Fails the test with the given message and optionally alters its tags. + + + + + + + + + +${resp.status_code} +201 +Fails if objects are unequal after converting them to integers. + + + + + + + + + + +Unexpected status ${resp.status_code} from /api/auth/setup +Fails the test with the given message and optionally alters its tags. + + +Configure the master password via the setup endpoint. + + + + + +anon +${BASE_URL} +Create Session: create a HTTP session to a server + + +Create an unauthenticated HTTP session. + + + + +${resp} +anon +/api/auth/status +expected_status=200 +Sends a GET request on a previously created HTTP Session. + + + +${json} +${resp.text} +Convert String to JSON object + + + +${configured} +${json} +$.configured +Get Value From JSON using JSONPath + + + +${configured_val} +${configured}[0] +Returns the given values which can then be assigned to a variables. + + + +${configured_val} + + +${configured} +Check if the master password has already been configured. + + + + + + +${payload} +master_password=${SETUP_PASSWORD} +anime_directory=/tmp/aniworld_test_anime +name=AniworldTest +data_dir=data +scheduler_enabled=False +logging_level=INFO +backup_enabled=False +Creates and returns a dictionary based on the given ``items``. + + + +${resp} +anon +/api/auth/setup +json=${payload} +expected_status=any +Sends a POST request on a previously created HTTP Session. + + + + + +6s +Pauses the test executed for the given time. + + + +${resp} +anon +/api/auth/setup +json=${payload} +expected_status=any +Sends a POST request on a previously created HTTP Session. + + + + + + + + + +${json} +${resp.text} +Convert String to JSON object + + + +${detail} +${json} +$.detail +Get Value From JSON using JSONPath + + + + + + + + + + + + +Setup failed with 400: ${detail} +Fails the test with the given message and optionally alters its tags. + + + + + + + + + +${resp.status_code} +201 +Fails if objects are unequal after converting them to integers. + + + + + + + + + + +Unexpected status ${resp.status_code} from /api/auth/setup +Fails the test with the given message and optionally alters its tags. + + +Configure the master password via the setup endpoint. + + + + +${payload} +password=${SETUP_PASSWORD} +Creates and returns a dictionary based on the given ``items``. + + + +${resp} +anon +/api/auth/login +json=${payload} +expected_status=any +Sends a POST request on a previously created HTTP Session. + + + + + +6s +Pauses the test executed for the given time. + + + +${resp} +anon +/api/auth/login +json=${payload} +expected_status=any +Sends a POST request on a previously created HTTP Session. + + + + + + + + + +${json} +${resp.text} +Convert String to JSON object + + + +${detail} +${json} +$.detail +Get Value From JSON using JSONPath + + + +${detail_val} +${detail}[0] +Returns the given values which can then be assigned to a variables. + + + +Login failed with ${resp.status_code}: ${detail_val} +Fails the test with the given message and optionally alters its tags. + + + + + + + +${json} +${resp.text} +Convert String to JSON object + + + +${token} +${json} +$.access_token +Get Value From JSON using JSONPath + + + +${token_str} +${token}[0] +Returns the given values which can then be assigned to a variables. + + + +${TOKEN} +${token_str} +Makes a variable available everywhere within the scope of the current suite. + + + +${token_str} + + +${token} +Log in with the master password and return the JWT access token. + + + +auth +${BASE_URL} +headers={'Authorization': 'Bearer ${token}'} +Create Session: create a HTTP session to a server + + + + + + + +${payload} +password=${SETUP_PASSWORD} +Creates and returns a dictionary based on the given ``items``. + + + +${resp} +anon +/api/auth/login +json=${payload} +expected_status=any +Sends a POST request on a previously created HTTP Session. + + + + + +6s +Pauses the test executed for the given time. + + + +${resp} +anon +/api/auth/login +json=${payload} +expected_status=any +Sends a POST request on a previously created HTTP Session. + + + + + + + + + +${json} +${resp.text} +Convert String to JSON object + + + +${detail} +${json} +$.detail +Get Value From JSON using JSONPath + + + +${detail_val} +${detail}[0] +Returns the given values which can then be assigned to a variables. + + + +Login failed with ${resp.status_code}: ${detail_val} +Fails the test with the given message and optionally alters its tags. + + + + + + + +${json} +${resp.text} +Convert String to JSON object + + + +${token} +${json} +$.access_token +Get Value From JSON using JSONPath + + + +${token_str} +${token}[0] +Returns the given values which can then be assigned to a variables. + + + +${TOKEN} +${token_str} +Makes a variable available everywhere within the scope of the current suite. + + + +${token_str} + + +${token} +Log in with the master password and return the JWT access token. + + + +auth +${BASE_URL} +headers={'Authorization': 'Bearer ${token}'} +Create Session: create a HTTP session to a server + + + + + + +auth +${BASE_URL} +headers={'Authorization': 'Bearer ${TOKEN}'} +Create Session: create a HTTP session to a server + + + + + + +Create an authenticated HTTP session. Performs setup if needed, then logs in. +Skips setup/login if already authenticated (suite-level setup done). + + +Create Anonymous Session +AND +Setup Master Password +AND +Create Authenticated Session +Executes all the given keywords in a sequence. + + + +${episodes} +{'season': 1, 'episode': 1, 'title': 'Episode 1'} +Returns a list containing given items. + + + +${payload} +serie_id=retry-test +serie_folder=Retry Test +serie_name=Retry Test +episodes=${episodes} +priority=NORMAL +Creates and returns a dictionary based on the given ``items``. + + + + + + + +${resp} +${session} +${endpoint} +expected_status=${expected_status} +Sends a POST request on a previously created HTTP Session. + + + + + + +${resp} +${session} +${endpoint} +json=${payload} +expected_status=${expected_status} +Sends a POST request on a previously created HTTP Session. + + + + + + + +${resp} + + +${resp} +/api/queue/add +${payload} +201 +Perform an authenticated POST request with optional JSON payload. + + + +${resp} + + +${add_resp} +${payload} +POST /api/queue/add with the given payload. + + + +${json} +${add_resp.text} +Convert String to JSON object + + + +${ids} +${json} +$.item_ids +Get Value From JSON using JSONPath + + + +${item_id} +${ids}[0] +Returns the given values which can then be assigned to a variables. + + + +${retry_payload} +item_ids=${item_id} +Creates and returns a dictionary based on the given ``items``. + + + + + + +${resp} +${session} +${endpoint} +expected_status=${expected_status} +Sends a POST request on a previously created HTTP Session. + + + + + + +${resp} +${session} +${endpoint} +json=${payload} +expected_status=${expected_status} +Sends a POST request on a previously created HTTP Session. + + + + + + + +${resp} + + +${retry_resp} +/api/queue/retry +${retry_payload} +Perform an authenticated POST request with optional JSON payload. + + + + +${response.status_code} +${expected_status} +Fails if objects are unequal after converting them to integers. + + +${retry_resp} +200 +Assert that the response status code matches the expected value. + + + +${retry_json} +${retry_resp.text} +Convert String to JSON object + + + +${retry_json} +retried_count +Fails if ``key`` is not found from ``dictionary``. + + + +Removes all the session objects + + +Starting Browser process /home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/index.js using at 127.0.0.1:35183 +Retry a failed download item. + + +Download queue API tests for Aniworld. +Covers status, add, start/stop/pause/resume, clear, retry, and remove. + + + + +All Tests + + + + +Download + + + + + diff --git a/playwright-log.txt b/playwright-log.txt new file mode 100644 index 0000000..3d22a0d --- /dev/null +++ b/playwright-log.txt @@ -0,0 +1,9 @@ +{"level":"info","time":"2026-06-26T16:51:25.695Z","seq":1,"component":"browser-library","msg":"Listening on 127.0.0.1:35183"} +{"level":"error","time":"2026-06-26T16:51:25.731Z","seq":2,"component":"browser-library","event_kind":"grpc_error","status":"failed","error_type":"Error","msg":"Error: Browser has been closed.\n at PlaywrightState.getActiveBrowser (/home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/index.js:12594:13)\n at PlaywrightServer.getActiveBrowser (/home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/index.js:13516:52)\n at PlaywrightServer.setTimeout (/home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/index.js:13734:56)\n at Object.onReceiveHalfClose (/home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/node_modules/@grpc/grpc-js/build/src/server.js:1478:25)\n at BaseServerInterceptingCall.maybePushNextMessage (/home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/node_modules/@grpc/grpc-js/build/src/server-interceptors.js:594:31)\n at BaseServerInterceptingCall.handleEndEvent (/home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/node_modules/@grpc/grpc-js/build/src/server-interceptors.js:634:14)\n at ServerHttp2Stream. (/home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/node_modules/@grpc/grpc-js/build/src/server-interceptors.js:387:18)\n at ServerHttp2Stream.emit (node:events:531:35)\n at endReadableNT (node:internal/streams/readable:1698:12)\n at process.processTicksAndRejections (node:internal/process/task_queues:89:21)"} +{"level":"info","time":"2026-06-26T16:51:25.738Z","seq":3,"suite_id":"s1","suite_name":"Download","component":"browser-library","event_kind":"grpc","action":"getBrowserCatalog","status":"started"} +{"level":"info","time":"2026-06-26T16:51:25.739Z","seq":4,"suite_id":"s1","suite_name":"Download","component":"browser-library","event_kind":"grpc","action":"getBrowserCatalog","status":"succeeded"} +{"level":"error","time":"2026-06-26T16:51:25.741Z","seq":5,"suite_id":"s1","suite_name":"Download","component":"browser-library","event_kind":"grpc_error","status":"failed","error_type":"Error","msg":"Error: Browser has been closed.\n at PlaywrightState.getActiveBrowser (/home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/index.js:12594:13)\n at PlaywrightServer.getActiveBrowser (/home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/index.js:13516:52)\n at PlaywrightServer.setTimeout (/home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/index.js:13734:56)\n at Object.onReceiveHalfClose (/home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/node_modules/@grpc/grpc-js/build/src/server.js:1478:25)\n at BaseServerInterceptingCall.maybePushNextMessage (/home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/node_modules/@grpc/grpc-js/build/src/server-interceptors.js:594:31)\n at BaseServerInterceptingCall.handleEndEvent (/home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/node_modules/@grpc/grpc-js/build/src/server-interceptors.js:634:14)\n at ServerHttp2Stream. (/home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/node_modules/@grpc/grpc-js/build/src/server-interceptors.js:387:18)\n at ServerHttp2Stream.emit (node:events:531:35)\n at endReadableNT (node:internal/streams/readable:1698:12)\n at process.processTicksAndRejections (node:internal/process/task_queues:89:21)"} +{"level":"info","time":"2026-06-26T16:51:25.746Z","seq":6,"component":"browser-library","event_kind":"grpc","action":"getBrowserCatalog","status":"started"} +{"level":"info","time":"2026-06-26T16:51:25.747Z","seq":7,"component":"browser-library","event_kind":"grpc","action":"getBrowserCatalog","status":"succeeded"} +{"level":"info","time":"2026-06-26T16:51:25.784Z","seq":8,"component":"browser-library","event_kind":"grpc","action":"closeAllBrowsers","status":"started"} +{"level":"info","time":"2026-06-26T16:51:25.784Z","seq":9,"component":"browser-library","event_kind":"grpc","action":"closeAllBrowsers","status":"succeeded"} diff --git a/report.html b/report.html new file mode 100644 index 0000000..4c5a384 --- /dev/null +++ b/report.html @@ -0,0 +1,2729 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Opening Robot Framework report failed

+
    +
  • Verify that you have JavaScript enabled in your browser.
  • +
  • Make sure you are using a modern enough browser. If using Internet Explorer, version 11 is required.
  • +
  • Check are there messages in your browser's JavaScript error log. Please report the problem if you suspect you have encountered a bug.
  • +
+
+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/robot_output/browser/screenshot/fail-screenshot-1.png b/robot_output/browser/screenshot/fail-screenshot-1.png new file mode 100644 index 0000000..86245af Binary files /dev/null and b/robot_output/browser/screenshot/fail-screenshot-1.png differ diff --git a/robot_output/log.html b/robot_output/log.html new file mode 100644 index 0000000..a3ba436 --- /dev/null +++ b/robot_output/log.html @@ -0,0 +1,2467 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Opening Robot Framework log failed

+
    +
  • Verify that you have JavaScript enabled in your browser.
  • +
  • Make sure you are using a modern enough browser. If using Internet Explorer, version 11 is required.
  • +
  • Check are there messages in your browser's JavaScript error log. Please report the problem if you suspect you have encountered a bug.
  • +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/robot_output/output.xml b/robot_output/output.xml new file mode 100644 index 0000000..0975527 --- /dev/null +++ b/robot_output/output.xml @@ -0,0 +1,1679 @@ + + + + + + + +Creating Session using : alias=anon, url=http://127.0.0.1:8765, headers={}, cookies={}, auth=None, timeout=None, proxies=None, verify=False, debug=0 +anon +${BASE_URL} +Create Session: create a HTTP session to a server + + +Create an unauthenticated HTTP session. + + + + +${payload} = {'master_password': 'TestPass123!', 'anime_directory': '/tmp/aniworld_test_anime', 'name': 'AniworldTest', 'data_dir': 'data', 'scheduler_enabled': 'False', 'logging_level': 'INFO', 'backup_enabled': ... +${payload} +master_password=${SETUP_PASSWORD} +anime_directory=/tmp/aniworld_test_anime +name=AniworldTest +data_dir=data +scheduler_enabled=False +logging_level=INFO +backup_enabled=False +Creates and returns a dictionary based on the given ``items``. + + + +POST Request : url=http://127.0.0.1:8765/api/auth/setup + path_url=/api/auth/setup + headers={'User-Agent': 'python-requests/2.33.1', 'Accept-Encoding': 'gzip, deflate, br', 'Accept': '*/*', 'Connection': 'keep-alive', 'Content-Length': '208', 'Content-Type': 'application/json'} + body=b'{"master_password": "TestPass123!", "anime_directory": "/tmp/aniworld_test_anime", "name": "AniworldTest", "data_dir": "data", "scheduler_enabled": "False", "logging_level": "INFO", "backup_enabled": "False"}' + +POST Response : url=http://127.0.0.1:8765/api/auth/setup + status=429, reason=Too Many Requests + headers={'date': 'Fri, 26 Jun 2026 19:00:40 GMT', 'server': 'uvicorn', 'content-length': '74', 'content-type': 'application/json'} + body={"detail":"Too many authentication attempts, try again later. IP lockout"} + +${resp} = <Response [429]> +${resp} +anon +/api/auth/setup +json=${payload} +expected_status=any +Sends a POST request on a previously created HTTP Session. + + + + + +Slept 45 seconds. +45s +Pauses the test executed for the given time. + + + +POST Request : url=http://127.0.0.1:8765/api/auth/setup + path_url=/api/auth/setup + headers={'User-Agent': 'python-requests/2.33.1', 'Accept-Encoding': 'gzip, deflate, br', 'Accept': '*/*', 'Connection': 'keep-alive', 'Content-Length': '208', 'Content-Type': 'application/json'} + body=b'{"master_password": "TestPass123!", "anime_directory": "/tmp/aniworld_test_anime", "name": "AniworldTest", "data_dir": "data", "scheduler_enabled": "False", "logging_level": "INFO", "backup_enabled": "False"}' + +POST Response : url=http://127.0.0.1:8765/api/auth/setup + status=400, reason=Bad Request + headers={'date': 'Fri, 26 Jun 2026 19:01:25 GMT', 'server': 'uvicorn', 'content-length': '47', 'content-type': 'application/json'} + body={"detail":"Master password already configured"} + +${resp} = <Response [400]> +${resp} +anon +/api/auth/setup +json=${payload} +expected_status=any +Sends a POST request on a previously created HTTP Session. + + + + + + + + + +45s +Pauses the test executed for the given time. + + + +${resp} +anon +/api/auth/setup +json=${payload} +expected_status=any +Sends a POST request on a previously created HTTP Session. + + + + + + + + + +${json} = {'detail': 'Master password already configured'} +${json} +${resp.text} +Convert String to JSON object + + + +${detail} = ['Master password already configured'] +${detail} +${json} +$.detail +Get Value From JSON using JSONPath + + + + + + + + + + + + +Setup failed with 400: ${detail} +Fails the test with the given message and optionally alters its tags. + + + + + + + + + +${resp.status_code} +201 +Fails if objects are unequal after converting them to integers. + + + + + + + + + + +Unexpected status ${resp.status_code} from /api/auth/setup +Fails the test with the given message and optionally alters its tags. + + +Configure the master password via the setup endpoint. + + +Create Anonymous Session +AND +Setup Master Password +Executes all the given keywords in a sequence. + + + + +Starting Browser process /home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/index.js using at 127.0.0.1:40597 +Successfully initialized new page object and opened url: http://127.0.0.1:8765/login +No browser and context was open. New browser and context was automatically opened when page is created. +${BASE_URL}${path} +BrowserControl +Setter +Open a new Page. + + +/login +Open a new page at the given path relative to BASE_URL. + + + + +Title: 'AniWorld Manager - Login' +${title} = AniWorld Manager - Login +${title} +Assertion +Getter +PageContent +Returns the title of the current page. + + + +${title} +${text} +Fails if ``container`` does not contain ``item`` one or more times. + + +Login +Assert that the page title contains the given text. + + + + +Waited for Element with selector id=password-input at state visible +${selector} +visible +timeout=5s +PageContent +Wait +Waits for the element found by ``selector`` to satisfy state option. + + +id=password-input +Assert that the element matching the selector is visible. + + + + +Waited for Element with selector id=login-submit-btn at state visible +${selector} +visible +timeout=5s +PageContent +Wait +Waits for the element found by ``selector`` to satisfy state option. + + +id=login-submit-btn +Assert that the element matching the selector is visible. + + + + +Waited for Element with selector id=password-toggle at state visible +${selector} +visible +timeout=5s +PageContent +Wait +Waits for the element found by ``selector`` to satisfy state option. + + +id=password-toggle +Assert that the element matching the selector is visible. + + + + +Closed all browsers +ALL +BrowserControl +Setter +Closes the current browser. + + +Close all browser contexts and the browser itself. + + +Verify the login page loads with all form elements. + + + + + + +Creating Session using : alias=anon, url=http://127.0.0.1:8765, headers={}, cookies={}, auth=None, timeout=None, proxies=None, verify=False, debug=0 +anon +${BASE_URL} +Create Session: create a HTTP session to a server + + +Create an unauthenticated HTTP session. + + + + +${payload} = {'master_password': 'TestPass123!', 'anime_directory': '/tmp/aniworld_test_anime', 'name': 'AniworldTest', 'data_dir': 'data', 'scheduler_enabled': 'False', 'logging_level': 'INFO', 'backup_enabled': ... +${payload} +master_password=${SETUP_PASSWORD} +anime_directory=/tmp/aniworld_test_anime +name=AniworldTest +data_dir=data +scheduler_enabled=False +logging_level=INFO +backup_enabled=False +Creates and returns a dictionary based on the given ``items``. + + + +POST Request : url=http://127.0.0.1:8765/api/auth/setup + path_url=/api/auth/setup + headers={'User-Agent': 'python-requests/2.33.1', 'Accept-Encoding': 'gzip, deflate, br', 'Accept': '*/*', 'Connection': 'keep-alive', 'Content-Length': '208', 'Content-Type': 'application/json'} + body=b'{"master_password": "TestPass123!", "anime_directory": "/tmp/aniworld_test_anime", "name": "AniworldTest", "data_dir": "data", "scheduler_enabled": "False", "logging_level": "INFO", "backup_enabled": "False"}' + +POST Response : url=http://127.0.0.1:8765/api/auth/setup + status=400, reason=Bad Request + headers={'date': 'Fri, 26 Jun 2026 19:01:26 GMT', 'server': 'uvicorn', 'content-length': '47', 'content-type': 'application/json'} + body={"detail":"Master password already configured"} + +${resp} = <Response [400]> +${resp} +anon +/api/auth/setup +json=${payload} +expected_status=any +Sends a POST request on a previously created HTTP Session. + + + + + +45s +Pauses the test executed for the given time. + + + +${resp} +anon +/api/auth/setup +json=${payload} +expected_status=any +Sends a POST request on a previously created HTTP Session. + + + + + + + + + +45s +Pauses the test executed for the given time. + + + +${resp} +anon +/api/auth/setup +json=${payload} +expected_status=any +Sends a POST request on a previously created HTTP Session. + + + + + + + + + +${json} = {'detail': 'Master password already configured'} +${json} +${resp.text} +Convert String to JSON object + + + +${detail} = ['Master password already configured'] +${detail} +${json} +$.detail +Get Value From JSON using JSONPath + + + + + + + + + + + + +Setup failed with 400: ${detail} +Fails the test with the given message and optionally alters its tags. + + + + + + + + + +${resp.status_code} +201 +Fails if objects are unequal after converting them to integers. + + + + + + + + + + +Unexpected status ${resp.status_code} from /api/auth/setup +Fails the test with the given message and optionally alters its tags. + + +Configure the master password via the setup endpoint. + + +Create Anonymous Session +AND +Setup Master Password +Executes all the given keywords in a sequence. + + + + +Successfully initialized new page object and opened url: http://127.0.0.1:8765/login +No browser and context was open. New browser and context was automatically opened when page is created. +${BASE_URL}${path} +BrowserControl +Setter +Open a new Page. + + +/login +Open a new page at the given path relative to BASE_URL. + + + + +Fills the text 'TestPass123!' in the given field. +id=password-input +${password} +PageContent +Setter +Clears and fills the given ``txt`` into the text field found by ``selector``. + + + +Clicks the element 'id=login-submit-btn'. +id=login-submit-btn +PageContent +Setter +Simulates mouse click on the element found by ``selector``. + + + +Waited for Element with selector id=search-input at state visible +id=search-input +visible +timeout=5s +PageContent +Wait +Waits for the element found by ``selector`` to satisfy state option. + + +${SETUP_PASSWORD} +Fill the login form and submit it. + + + + +Title: 'AniWorld Manager' +${title} = AniWorld Manager +${title} +Assertion +Getter +PageContent +Returns the title of the current page. + + + +${title} +${text} +Fails if ``container`` does not contain ``item`` one or more times. + + +AniWorld +Assert that the page title contains the given text. + + + + +Waited for Element with selector id=search-input at state visible +${selector} +visible +timeout=5s +PageContent +Wait +Waits for the element found by ``selector`` to satisfy state option. + + +id=search-input +Assert that the element matching the selector is visible. + + + + +Closed all browsers +ALL +BrowserControl +Setter +Closes the current browser. + + +Close all browser contexts and the browser itself. + + +Log in with the correct password and verify redirect to dashboard. + + + + + + +Creating Session using : alias=anon, url=http://127.0.0.1:8765, headers={}, cookies={}, auth=None, timeout=None, proxies=None, verify=False, debug=0 +anon +${BASE_URL} +Create Session: create a HTTP session to a server + + +Create an unauthenticated HTTP session. + + + + +${payload} = {'master_password': 'TestPass123!', 'anime_directory': '/tmp/aniworld_test_anime', 'name': 'AniworldTest', 'data_dir': 'data', 'scheduler_enabled': 'False', 'logging_level': 'INFO', 'backup_enabled': ... +${payload} +master_password=${SETUP_PASSWORD} +anime_directory=/tmp/aniworld_test_anime +name=AniworldTest +data_dir=data +scheduler_enabled=False +logging_level=INFO +backup_enabled=False +Creates and returns a dictionary based on the given ``items``. + + + +POST Request : url=http://127.0.0.1:8765/api/auth/setup + path_url=/api/auth/setup + headers={'User-Agent': 'python-requests/2.33.1', 'Accept-Encoding': 'gzip, deflate, br', 'Accept': '*/*', 'Connection': 'keep-alive', 'Content-Length': '208', 'Content-Type': 'application/json'} + body=b'{"master_password": "TestPass123!", "anime_directory": "/tmp/aniworld_test_anime", "name": "AniworldTest", "data_dir": "data", "scheduler_enabled": "False", "logging_level": "INFO", "backup_enabled": "False"}' + +POST Response : url=http://127.0.0.1:8765/api/auth/setup + status=400, reason=Bad Request + headers={'date': 'Fri, 26 Jun 2026 19:01:28 GMT', 'server': 'uvicorn', 'content-length': '47', 'content-type': 'application/json'} + body={"detail":"Master password already configured"} + +${resp} = <Response [400]> +${resp} +anon +/api/auth/setup +json=${payload} +expected_status=any +Sends a POST request on a previously created HTTP Session. + + + + + +45s +Pauses the test executed for the given time. + + + +${resp} +anon +/api/auth/setup +json=${payload} +expected_status=any +Sends a POST request on a previously created HTTP Session. + + + + + + + + + +45s +Pauses the test executed for the given time. + + + +${resp} +anon +/api/auth/setup +json=${payload} +expected_status=any +Sends a POST request on a previously created HTTP Session. + + + + + + + + + +${json} = {'detail': 'Master password already configured'} +${json} +${resp.text} +Convert String to JSON object + + + +${detail} = ['Master password already configured'] +${detail} +${json} +$.detail +Get Value From JSON using JSONPath + + + + + + + + + + + + +Setup failed with 400: ${detail} +Fails the test with the given message and optionally alters its tags. + + + + + + + + + +${resp.status_code} +201 +Fails if objects are unequal after converting them to integers. + + + + + + + + + + +Unexpected status ${resp.status_code} from /api/auth/setup +Fails the test with the given message and optionally alters its tags. + + +Configure the master password via the setup endpoint. + + +Create Anonymous Session +AND +Setup Master Password +Executes all the given keywords in a sequence. + + + + +Successfully initialized new page object and opened url: http://127.0.0.1:8765/login +No browser and context was open. New browser and context was automatically opened when page is created. +${BASE_URL}${path} +BrowserControl +Setter +Open a new Page. + + +/login +Open a new page at the given path relative to BASE_URL. + + + +Fills the text 'WrongPass123!' in the given field. +id=password-input +WrongPass123! +PageContent +Setter +Clears and fills the given ``txt`` into the text field found by ``selector``. + + + +Clicks the element 'id=login-submit-btn'. +id=login-submit-btn +PageContent +Setter +Simulates mouse click on the element found by ``selector``. + + + + +Waited for Element with selector id=login-error at state visible +${selector} +visible +timeout=5s +PageContent +Wait +Waits for the element found by ``selector`` to satisfy state option. + + +id=login-error +Assert that the element matching the selector is visible. + + + + +Text: ['invalid credentials'] +${element_text} = invalid credentials +${element_text} +${selector} +Assertion +Getter +PageContent +Returns text attribute of the element found by ``selector``. + + + +${element_text} +${text} +Fails if ``container`` does not contain ``item`` one or more times. + + +id=login-error +invalid +Assert that the element's text contains the given substring. + + + + +Closed all browsers +ALL +BrowserControl +Setter +Closes the current browser. + + +Close all browser contexts and the browser itself. + + +Log in with an incorrect password and verify error message. + + + + + + +Creating Session using : alias=anon, url=http://127.0.0.1:8765, headers={}, cookies={}, auth=None, timeout=None, proxies=None, verify=False, debug=0 +anon +${BASE_URL} +Create Session: create a HTTP session to a server + + +Create an unauthenticated HTTP session. + + + + +${payload} = {'master_password': 'TestPass123!', 'anime_directory': '/tmp/aniworld_test_anime', 'name': 'AniworldTest', 'data_dir': 'data', 'scheduler_enabled': 'False', 'logging_level': 'INFO', 'backup_enabled': ... +${payload} +master_password=${SETUP_PASSWORD} +anime_directory=/tmp/aniworld_test_anime +name=AniworldTest +data_dir=data +scheduler_enabled=False +logging_level=INFO +backup_enabled=False +Creates and returns a dictionary based on the given ``items``. + + + +POST Request : url=http://127.0.0.1:8765/api/auth/setup + path_url=/api/auth/setup + headers={'User-Agent': 'python-requests/2.33.1', 'Accept-Encoding': 'gzip, deflate, br', 'Accept': '*/*', 'Connection': 'keep-alive', 'Content-Length': '208', 'Content-Type': 'application/json'} + body=b'{"master_password": "TestPass123!", "anime_directory": "/tmp/aniworld_test_anime", "name": "AniworldTest", "data_dir": "data", "scheduler_enabled": "False", "logging_level": "INFO", "backup_enabled": "False"}' + +POST Response : url=http://127.0.0.1:8765/api/auth/setup + status=400, reason=Bad Request + headers={'date': 'Fri, 26 Jun 2026 19:01:28 GMT', 'server': 'uvicorn', 'content-length': '47', 'content-type': 'application/json'} + body={"detail":"Master password already configured"} + +${resp} = <Response [400]> +${resp} +anon +/api/auth/setup +json=${payload} +expected_status=any +Sends a POST request on a previously created HTTP Session. + + + + + +45s +Pauses the test executed for the given time. + + + +${resp} +anon +/api/auth/setup +json=${payload} +expected_status=any +Sends a POST request on a previously created HTTP Session. + + + + + + + + + +45s +Pauses the test executed for the given time. + + + +${resp} +anon +/api/auth/setup +json=${payload} +expected_status=any +Sends a POST request on a previously created HTTP Session. + + + + + + + + + +${json} = {'detail': 'Master password already configured'} +${json} +${resp.text} +Convert String to JSON object + + + +${detail} = ['Master password already configured'] +${detail} +${json} +$.detail +Get Value From JSON using JSONPath + + + + + + + + + + + + +Setup failed with 400: ${detail} +Fails the test with the given message and optionally alters its tags. + + + + + + + + + +${resp.status_code} +201 +Fails if objects are unequal after converting them to integers. + + + + + + + + + + +Unexpected status ${resp.status_code} from /api/auth/setup +Fails the test with the given message and optionally alters its tags. + + +Configure the master password via the setup endpoint. + + +Create Anonymous Session +AND +Setup Master Password +Executes all the given keywords in a sequence. + + + + +Successfully initialized new page object and opened url: http://127.0.0.1:8765/login +No browser and context was open. New browser and context was automatically opened when page is created. +${BASE_URL}${path} +BrowserControl +Setter +Open a new Page. + + +/login +Open a new page at the given path relative to BASE_URL. + + + + + +Fills the text 'WrongPass123!' in the given field. +id=password-input +WrongPass123! +PageContent +Setter +Clears and fills the given ``txt`` into the text field found by ``selector``. + + + +Clicks the element 'id=login-submit-btn'. +id=login-submit-btn +PageContent +Setter +Simulates mouse click on the element found by ``selector``. + + + +Waited for Element with selector id=login-error at state visible +id=login-error +visible +timeout=3s +PageContent +Wait +Waits for the element found by ``selector`` to satisfy state option. + + +0 + + + + +Fills the text 'WrongPass123!' in the given field. +id=password-input +WrongPass123! +PageContent +Setter +Clears and fills the given ``txt`` into the text field found by ``selector``. + + + +Clicks the element 'id=login-submit-btn'. +id=login-submit-btn +PageContent +Setter +Simulates mouse click on the element found by ``selector``. + + + +Waited for Element with selector id=login-error at state visible +id=login-error +visible +timeout=3s +PageContent +Wait +Waits for the element found by ``selector`` to satisfy state option. + + +1 + + + + +Fills the text 'WrongPass123!' in the given field. +id=password-input +WrongPass123! +PageContent +Setter +Clears and fills the given ``txt`` into the text field found by ``selector``. + + + +Clicks the element 'id=login-submit-btn'. +id=login-submit-btn +PageContent +Setter +Simulates mouse click on the element found by ``selector``. + + + +Waited for Element with selector id=login-error at state visible +id=login-error +visible +timeout=3s +PageContent +Wait +Waits for the element found by ``selector`` to satisfy state option. + + +2 + + + + +Fills the text 'WrongPass123!' in the given field. +id=password-input +WrongPass123! +PageContent +Setter +Clears and fills the given ``txt`` into the text field found by ``selector``. + + + +Clicks the element 'id=login-submit-btn'. +id=login-submit-btn +PageContent +Setter +Simulates mouse click on the element found by ``selector``. + + + +Waited for Element with selector id=login-error at state visible +id=login-error +visible +timeout=3s +PageContent +Wait +Waits for the element found by ``selector`` to satisfy state option. + + +3 + + + + +Fills the text 'WrongPass123!' in the given field. +id=password-input +WrongPass123! +PageContent +Setter +Clears and fills the given ``txt`` into the text field found by ``selector``. + + + +Clicks the element 'id=login-submit-btn'. +id=login-submit-btn +PageContent +Setter +Simulates mouse click on the element found by ``selector``. + + + +Waited for Element with selector id=login-error at state visible +id=login-error +visible +timeout=3s +PageContent +Wait +Waits for the element found by ``selector`` to satisfy state option. + + +4 + + + + +Fills the text 'WrongPass123!' in the given field. +id=password-input +WrongPass123! +PageContent +Setter +Clears and fills the given ``txt`` into the text field found by ``selector``. + + + +Clicks the element 'id=login-submit-btn'. +id=login-submit-btn +PageContent +Setter +Simulates mouse click on the element found by ``selector``. + + + +Waited for Element with selector id=login-error at state visible +id=login-error +visible +timeout=3s +PageContent +Wait +Waits for the element found by ``selector`` to satisfy state option. + + +5 + + +${i} +6 + + + + +Text: ['Too many authentication attempts, try again later. IP lockout'] +${element_text} = Too many authentication attempts, try again later. IP lockout +${element_text} +${selector} +Assertion +Getter +PageContent +Returns text attribute of the element found by ``selector``. + + + +${element_text} +${text} +Fails if ``container`` does not contain ``item`` one or more times. + + +id=login-error +lockout +Assert that the element's text contains the given substring. + + + + +Closed all browsers +ALL +BrowserControl +Setter +Closes the current browser. + + +Close all browser contexts and the browser itself. + + +Submit multiple failed logins and verify lockout message appears. + + + + + + +Creating Session using : alias=anon, url=http://127.0.0.1:8765, headers={}, cookies={}, auth=None, timeout=None, proxies=None, verify=False, debug=0 +anon +${BASE_URL} +Create Session: create a HTTP session to a server + + +Create an unauthenticated HTTP session. + + + + +${payload} = {'master_password': 'TestPass123!', 'anime_directory': '/tmp/aniworld_test_anime', 'name': 'AniworldTest', 'data_dir': 'data', 'scheduler_enabled': 'False', 'logging_level': 'INFO', 'backup_enabled': ... +${payload} +master_password=${SETUP_PASSWORD} +anime_directory=/tmp/aniworld_test_anime +name=AniworldTest +data_dir=data +scheduler_enabled=False +logging_level=INFO +backup_enabled=False +Creates and returns a dictionary based on the given ``items``. + + + +POST Request : url=http://127.0.0.1:8765/api/auth/setup + path_url=/api/auth/setup + headers={'User-Agent': 'python-requests/2.33.1', 'Accept-Encoding': 'gzip, deflate, br', 'Accept': '*/*', 'Connection': 'keep-alive', 'Content-Length': '208', 'Content-Type': 'application/json'} + body=b'{"master_password": "TestPass123!", "anime_directory": "/tmp/aniworld_test_anime", "name": "AniworldTest", "data_dir": "data", "scheduler_enabled": "False", "logging_level": "INFO", "backup_enabled": "False"}' + +POST Response : url=http://127.0.0.1:8765/api/auth/setup + status=429, reason=Too Many Requests + headers={'date': 'Fri, 26 Jun 2026 19:01:30 GMT', 'server': 'uvicorn', 'content-length': '74', 'content-type': 'application/json'} + body={"detail":"Too many authentication attempts, try again later. IP lockout"} + +${resp} = <Response [429]> +${resp} +anon +/api/auth/setup +json=${payload} +expected_status=any +Sends a POST request on a previously created HTTP Session. + + + + + +Slept 45 seconds. +45s +Pauses the test executed for the given time. + + + +POST Request : url=http://127.0.0.1:8765/api/auth/setup + path_url=/api/auth/setup + headers={'User-Agent': 'python-requests/2.33.1', 'Accept-Encoding': 'gzip, deflate, br', 'Accept': '*/*', 'Connection': 'keep-alive', 'Content-Length': '208', 'Content-Type': 'application/json'} + body=b'{"master_password": "TestPass123!", "anime_directory": "/tmp/aniworld_test_anime", "name": "AniworldTest", "data_dir": "data", "scheduler_enabled": "False", "logging_level": "INFO", "backup_enabled": "False"}' + +POST Response : url=http://127.0.0.1:8765/api/auth/setup + status=429, reason=Too Many Requests + headers={'date': 'Fri, 26 Jun 2026 19:02:15 GMT', 'server': 'uvicorn', 'content-length': '74', 'content-type': 'application/json'} + body={"detail":"Too many authentication attempts, try again later. IP lockout"} + +${resp} = <Response [429]> +${resp} +anon +/api/auth/setup +json=${payload} +expected_status=any +Sends a POST request on a previously created HTTP Session. + + + + + + + + + +Slept 45 seconds. +45s +Pauses the test executed for the given time. + + + +POST Request : url=http://127.0.0.1:8765/api/auth/setup + path_url=/api/auth/setup + headers={'User-Agent': 'python-requests/2.33.1', 'Accept-Encoding': 'gzip, deflate, br', 'Accept': '*/*', 'Connection': 'keep-alive', 'Content-Length': '208', 'Content-Type': 'application/json'} + body=b'{"master_password": "TestPass123!", "anime_directory": "/tmp/aniworld_test_anime", "name": "AniworldTest", "data_dir": "data", "scheduler_enabled": "False", "logging_level": "INFO", "backup_enabled": "False"}' + +POST Response : url=http://127.0.0.1:8765/api/auth/setup + status=400, reason=Bad Request + headers={'date': 'Fri, 26 Jun 2026 19:03:00 GMT', 'server': 'uvicorn', 'content-length': '47', 'content-type': 'application/json'} + body={"detail":"Master password already configured"} + +${resp} = <Response [400]> +${resp} +anon +/api/auth/setup +json=${payload} +expected_status=any +Sends a POST request on a previously created HTTP Session. + + + + + + + + + +${json} = {'detail': 'Master password already configured'} +${json} +${resp.text} +Convert String to JSON object + + + +${detail} = ['Master password already configured'] +${detail} +${json} +$.detail +Get Value From JSON using JSONPath + + + + + + + + + + + + +Setup failed with 400: ${detail} +Fails the test with the given message and optionally alters its tags. + + + + + + + + + +${resp.status_code} +201 +Fails if objects are unequal after converting them to integers. + + + + + + + + + + +Unexpected status ${resp.status_code} from /api/auth/setup +Fails the test with the given message and optionally alters its tags. + + +Configure the master password via the setup endpoint. + + +Create Anonymous Session +AND +Setup Master Password +Executes all the given keywords in a sequence. + + + + +Successfully initialized new page object and opened url: http://127.0.0.1:8765/login +No browser and context was open. New browser and context was automatically opened when page is created. +${BASE_URL}${path} +BrowserControl +Setter +Open a new Page. + + +/login +Open a new page at the given path relative to BASE_URL. + + + +Fills the text 'TestPass123!' in the given field. +id=password-input +${SETUP_PASSWORD} +PageContent +Setter +Clears and fills the given ``txt`` into the text field found by ``selector``. + + + +Attribute is: 'password' +${type_before} = password +${type_before} +id=password-input +type +Assertion +Getter +PageContent +Returns the HTML ``attribute`` of the element found by ``selector``. + + + +${type_before} +password +Fails if objects are unequal after converting them to strings. + + + +Clicks the element 'id=password-toggle'. +id=password-toggle +PageContent +Setter +Simulates mouse click on the element found by ``selector``. + + + +Attribute is: 'text' +${type_after} = text +${type_after} +id=password-input +type +Assertion +Getter +PageContent +Returns the HTML ``attribute`` of the element found by ``selector``. + + + +${type_after} +text +Fails if objects are unequal after converting them to strings. + + + +Clicks the element 'id=password-toggle'. +id=password-toggle +PageContent +Setter +Simulates mouse click on the element found by ``selector``. + + + +Attribute is: 'password' +${type_final} = password +${type_final} +id=password-input +type +Assertion +Getter +PageContent +Returns the HTML ``attribute`` of the element found by ``selector``. + + + +${type_final} +password +Fails if objects are unequal after converting them to strings. + + + + +Closed all browsers +ALL +BrowserControl +Setter +Closes the current browser. + + +Close all browser contexts and the browser itself. + + +Click the eye icon and verify the password field type toggles. + + + + + + +Creating Session using : alias=anon, url=http://127.0.0.1:8765, headers={}, cookies={}, auth=None, timeout=None, proxies=None, verify=False, debug=0 +anon +${BASE_URL} +Create Session: create a HTTP session to a server + + +Create an unauthenticated HTTP session. + + + + +${payload} = {'master_password': 'TestPass123!', 'anime_directory': '/tmp/aniworld_test_anime', 'name': 'AniworldTest', 'data_dir': 'data', 'scheduler_enabled': 'False', 'logging_level': 'INFO', 'backup_enabled': ... +${payload} +master_password=${SETUP_PASSWORD} +anime_directory=/tmp/aniworld_test_anime +name=AniworldTest +data_dir=data +scheduler_enabled=False +logging_level=INFO +backup_enabled=False +Creates and returns a dictionary based on the given ``items``. + + + +POST Request : url=http://127.0.0.1:8765/api/auth/setup + path_url=/api/auth/setup + headers={'User-Agent': 'python-requests/2.33.1', 'Accept-Encoding': 'gzip, deflate, br', 'Accept': '*/*', 'Connection': 'keep-alive', 'Content-Length': '208', 'Content-Type': 'application/json'} + body=b'{"master_password": "TestPass123!", "anime_directory": "/tmp/aniworld_test_anime", "name": "AniworldTest", "data_dir": "data", "scheduler_enabled": "False", "logging_level": "INFO", "backup_enabled": "False"}' + +POST Response : url=http://127.0.0.1:8765/api/auth/setup + status=400, reason=Bad Request + headers={'date': 'Fri, 26 Jun 2026 19:03:00 GMT', 'server': 'uvicorn', 'content-length': '47', 'content-type': 'application/json'} + body={"detail":"Master password already configured"} + +${resp} = <Response [400]> +${resp} +anon +/api/auth/setup +json=${payload} +expected_status=any +Sends a POST request on a previously created HTTP Session. + + + + + +45s +Pauses the test executed for the given time. + + + +${resp} +anon +/api/auth/setup +json=${payload} +expected_status=any +Sends a POST request on a previously created HTTP Session. + + + + + + + + + +45s +Pauses the test executed for the given time. + + + +${resp} +anon +/api/auth/setup +json=${payload} +expected_status=any +Sends a POST request on a previously created HTTP Session. + + + + + + + + + +${json} = {'detail': 'Master password already configured'} +${json} +${resp.text} +Convert String to JSON object + + + +${detail} = ['Master password already configured'] +${detail} +${json} +$.detail +Get Value From JSON using JSONPath + + + + + + + + + + + + +Setup failed with 400: ${detail} +Fails the test with the given message and optionally alters its tags. + + + + + + + + + +${resp.status_code} +201 +Fails if objects are unequal after converting them to integers. + + + + + + + + + + +Unexpected status ${resp.status_code} from /api/auth/setup +Fails the test with the given message and optionally alters its tags. + + +Configure the master password via the setup endpoint. + + +Create Anonymous Session +AND +Setup Master Password +Executes all the given keywords in a sequence. + + + + +Successfully initialized new page object and opened url: http://127.0.0.1:8765/login +No browser and context was open. New browser and context was automatically opened when page is created. +${BASE_URL}${path} +BrowserControl +Setter +Open a new Page. + + +/login +Open a new page at the given path relative to BASE_URL. + + + + +Fills the text 'TestPass123!' in the given field. +id=password-input +${password} +PageContent +Setter +Clears and fills the given ``txt`` into the text field found by ``selector``. + + + +Clicks the element 'id=login-submit-btn'. +id=login-submit-btn +PageContent +Setter +Simulates mouse click on the element found by ``selector``. + + + +</td></tr><tr><td colspan="3"><a href="browser/screenshot/fail-screenshot-1.png" target="_blank"><img src="browser/screenshot/fail-screenshot-1.png" width="800px"/></a> +See also file:///home/lukas/Volume/repo/Aniworld/robot_output/playwright-log.txt for additional details. +TimeoutError: locator.waitFor: Timeout 5000ms exceeded. +Call log: + - waiting for locator('id=search-input') to be visible + +id=search-input +visible +timeout=5s +PageContent +Wait +Waits for the element found by ``selector`` to satisfy state option. +TimeoutError: locator.waitFor: Timeout 5000ms exceeded. +Call log: + - waiting for locator('id=search-input') to be visible + + +${SETUP_PASSWORD} +Fill the login form and submit it. +TimeoutError: locator.waitFor: Timeout 5000ms exceeded. +Call log: + - waiting for locator('id=search-input') to be visible + + + +Click the logout button and wait for redirect to login. + + + +Login +Assert that the page title contains the given text. + + + +id=password-input +Assert that the element matching the selector is visible. + + + + +Closed all browsers +ALL +BrowserControl +Setter +Closes the current browser. + + +Close all browser contexts and the browser itself. + + +Log in then log out and verify redirect to login page. +TimeoutError: locator.waitFor: Timeout 5000ms exceeded. +Call log: + - waiting for locator('id=search-input') to be visible + + +Login UI tests for Aniworld. +Covers login page load, valid/invalid login, rate limit UI, password visibility, and logout. + + + + +All Tests + + + + +Login + + + + + diff --git a/robot_output/playwright-log.txt b/robot_output/playwright-log.txt new file mode 100644 index 0000000..e413eb6 --- /dev/null +++ b/robot_output/playwright-log.txt @@ -0,0 +1,226 @@ +{"level":"info","time":"2026-06-26T19:01:26.202Z","seq":1,"component":"browser-library","msg":"Listening on 127.0.0.1:40597"} +{"level":"info","time":"2026-06-26T19:01:26.263Z","seq":2,"component":"browser-library","event_kind":"grpc","action":"newPage","status":"started"} +{"level":"info","time":"2026-06-26T19:01:26.264Z","seq":3,"component":"browser-library","msg":"No active browser, creating a new one"} +{"level":"info","time":"2026-06-26T19:01:26.318Z","seq":4,"component":"browser-library","msg":"Setting default timeout for context context=d175c6c3-4b1d-4b0b-a765-ea73098025fc to 10000"} +{"level":"info","time":"2026-06-26T19:01:26.319Z","seq":5,"component":"browser-library","msg":"Changed active context: context=d175c6c3-4b1d-4b0b-a765-ea73098025fc"} +{"level":"info","time":"2026-06-26T19:01:26.361Z","seq":6,"component":"browser-library","msg":"Video path: undefined"} +{"level":"info","time":"2026-06-26T19:01:26.361Z","seq":7,"component":"browser-library","msg":"Changed active page"} +{"level":"info","time":"2026-06-26T19:01:26.651Z","seq":8,"component":"browser-library","event_kind":"grpc","action":"newPage","status":"succeeded"} +{"level":"info","time":"2026-06-26T19:01:26.663Z","seq":9,"component":"browser-library","event_kind":"grpc","action":"waitForElementState","status":"started"} +{"level":"info","time":"2026-06-26T19:01:26.664Z","seq":10,"component":"browser-library","msg":"Strict mode is enabled, find Locator with id=password-input in page."} +{"level":"info","time":"2026-06-26T19:01:26.682Z","seq":11,"component":"browser-library","event_kind":"grpc","action":"waitForElementState","status":"succeeded"} +{"level":"info","time":"2026-06-26T19:01:26.685Z","seq":12,"component":"browser-library","event_kind":"grpc","action":"waitForElementState","status":"started"} +{"level":"info","time":"2026-06-26T19:01:26.686Z","seq":13,"component":"browser-library","msg":"Strict mode is enabled, find Locator with id=login-submit-btn in page."} +{"level":"info","time":"2026-06-26T19:01:26.689Z","seq":14,"component":"browser-library","event_kind":"grpc","action":"waitForElementState","status":"succeeded"} +{"level":"info","time":"2026-06-26T19:01:26.692Z","seq":15,"component":"browser-library","event_kind":"grpc","action":"waitForElementState","status":"started"} +{"level":"info","time":"2026-06-26T19:01:26.692Z","seq":16,"component":"browser-library","msg":"Strict mode is enabled, find Locator with id=password-toggle in page."} +{"level":"info","time":"2026-06-26T19:01:26.695Z","seq":17,"component":"browser-library","event_kind":"grpc","action":"waitForElementState","status":"succeeded"} +{"level":"info","time":"2026-06-26T19:01:26.698Z","seq":18,"component":"browser-library","event_kind":"grpc","action":"closeAllBrowsers","status":"started"} +{"level":"info","time":"2026-06-26T19:01:26.701Z","seq":19,"component":"browser-library","msg":"Removed page=09f7a0ce-587a-4766-8f97-62ed642a973b from context=d175c6c3-4b1d-4b0b-a765-ea73098025fc page stack"} +{"level":"info","time":"2026-06-26T19:01:26.717Z","seq":20,"component":"browser-library","event_kind":"grpc","action":"closeAllBrowsers","status":"succeeded"} +{"level":"error","time":"2026-06-26T19:01:26.719Z","seq":21,"component":"browser-library","event_kind":"grpc_error","status":"failed","error_type":"Error","msg":"Error: Browser has been closed.\n at PlaywrightState.getActiveBrowser (/home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/index.js:12594:13)\n at PlaywrightServer.getActiveBrowser (/home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/index.js:13516:52)\n at PlaywrightServer.setTimeout (/home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/index.js:13734:56)\n at Object.onReceiveHalfClose (/home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/node_modules/@grpc/grpc-js/build/src/server.js:1478:25)\n at BaseServerInterceptingCall.maybePushNextMessage (/home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/node_modules/@grpc/grpc-js/build/src/server-interceptors.js:594:31)\n at BaseServerInterceptingCall.handleEndEvent (/home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/node_modules/@grpc/grpc-js/build/src/server-interceptors.js:634:14)\n at ServerHttp2Stream. (/home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/node_modules/@grpc/grpc-js/build/src/server-interceptors.js:387:18)\n at ServerHttp2Stream.emit (node:events:531:35)\n at endReadableNT (node:internal/streams/readable:1698:12)\n at process.processTicksAndRejections (node:internal/process/task_queues:89:21)"} +{"level":"info","time":"2026-06-26T19:01:26.723Z","seq":22,"suite_id":"s1","suite_name":"Login","component":"browser-library","event_kind":"grpc","action":"getBrowserCatalog","status":"started"} +{"level":"info","time":"2026-06-26T19:01:26.723Z","seq":23,"suite_id":"s1","suite_name":"Login","component":"browser-library","event_kind":"grpc","action":"getBrowserCatalog","status":"succeeded"} +{"level":"info","time":"2026-06-26T19:01:26.726Z","seq":24,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Valid Password","test_id":"s1-t2","component":"browser-library","event_kind":"grpc","action":"getBrowserCatalog","status":"started"} +{"level":"info","time":"2026-06-26T19:01:26.726Z","seq":25,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Valid Password","test_id":"s1-t2","component":"browser-library","event_kind":"grpc","action":"getBrowserCatalog","status":"succeeded"} +{"level":"info","time":"2026-06-26T19:01:26.747Z","seq":26,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Valid Password","test_id":"s1-t2","component":"browser-library","event_kind":"grpc","action":"newPage","status":"started"} +{"level":"info","time":"2026-06-26T19:01:26.747Z","seq":27,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Valid Password","test_id":"s1-t2","component":"browser-library","msg":"No active browser, creating a new one"} +{"level":"info","time":"2026-06-26T19:01:26.789Z","seq":28,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Valid Password","test_id":"s1-t2","component":"browser-library","msg":"Setting default timeout for context context=39202e9e-49fd-43c8-ace8-aefc1f8fc86a to 10000"} +{"level":"info","time":"2026-06-26T19:01:26.789Z","seq":29,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Valid Password","test_id":"s1-t2","component":"browser-library","msg":"Changed active context: context=39202e9e-49fd-43c8-ace8-aefc1f8fc86a"} +{"level":"info","time":"2026-06-26T19:01:26.817Z","seq":30,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Valid Password","test_id":"s1-t2","component":"browser-library","msg":"Video path: undefined"} +{"level":"info","time":"2026-06-26T19:01:26.817Z","seq":31,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Valid Password","test_id":"s1-t2","component":"browser-library","msg":"Changed active page"} +{"level":"info","time":"2026-06-26T19:01:27.135Z","seq":32,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Valid Password","test_id":"s1-t2","component":"browser-library","event_kind":"grpc","action":"newPage","status":"succeeded"} +{"level":"info","time":"2026-06-26T19:01:27.138Z","seq":33,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Valid Password","test_id":"s1-t2","component":"browser-library","event_kind":"grpc","action":"fillText","status":"started"} +{"level":"info","time":"2026-06-26T19:01:27.138Z","seq":34,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Valid Password","test_id":"s1-t2","component":"browser-library","msg":"Strict mode is enabled, find Locator with id=password-input in page."} +{"level":"info","time":"2026-06-26T19:01:27.161Z","seq":35,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Valid Password","test_id":"s1-t2","component":"browser-library","event_kind":"grpc","action":"fillText","status":"succeeded"} +{"level":"info","time":"2026-06-26T19:01:27.163Z","seq":36,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Valid Password","test_id":"s1-t2","component":"browser-library","event_kind":"grpc","action":"click","status":"started"} +{"level":"info","time":"2026-06-26T19:01:27.163Z","seq":37,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Valid Password","test_id":"s1-t2","component":"browser-library","msg":"Strict mode is enabled, find Locator with id=login-submit-btn in page."} +{"level":"info","time":"2026-06-26T19:01:27.195Z","seq":38,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Valid Password","test_id":"s1-t2","component":"browser-library","event_kind":"grpc","action":"click","status":"succeeded"} +{"level":"info","time":"2026-06-26T19:01:27.198Z","seq":39,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Valid Password","test_id":"s1-t2","component":"browser-library","event_kind":"grpc","action":"waitForElementState","status":"started"} +{"level":"info","time":"2026-06-26T19:01:27.198Z","seq":40,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Valid Password","test_id":"s1-t2","component":"browser-library","msg":"Strict mode is enabled, find Locator with id=search-input in page."} +{"level":"info","time":"2026-06-26T19:01:28.490Z","seq":41,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Valid Password","test_id":"s1-t2","component":"browser-library","event_kind":"grpc","action":"waitForElementState","status":"succeeded"} +{"level":"info","time":"2026-06-26T19:01:28.498Z","seq":42,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Valid Password","test_id":"s1-t2","component":"browser-library","event_kind":"grpc","action":"waitForElementState","status":"started"} +{"level":"info","time":"2026-06-26T19:01:28.498Z","seq":43,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Valid Password","test_id":"s1-t2","component":"browser-library","msg":"Strict mode is enabled, find Locator with id=search-input in page."} +{"level":"info","time":"2026-06-26T19:01:28.500Z","seq":44,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Valid Password","test_id":"s1-t2","component":"browser-library","event_kind":"grpc","action":"waitForElementState","status":"succeeded"} +{"level":"info","time":"2026-06-26T19:01:28.503Z","seq":45,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Valid Password","test_id":"s1-t2","component":"browser-library","event_kind":"grpc","action":"closeAllBrowsers","status":"started"} +{"level":"info","time":"2026-06-26T19:01:28.505Z","seq":46,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Valid Password","test_id":"s1-t2","component":"browser-library","msg":"Removed page=239186fc-38e8-4837-8481-220447e809d4 from context=39202e9e-49fd-43c8-ace8-aefc1f8fc86a page stack"} +{"level":"info","time":"2026-06-26T19:01:28.521Z","seq":47,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Valid Password","test_id":"s1-t2","component":"browser-library","event_kind":"grpc","action":"closeAllBrowsers","status":"succeeded"} +{"level":"error","time":"2026-06-26T19:01:28.523Z","seq":48,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Valid Password","test_id":"s1-t2","component":"browser-library","event_kind":"grpc_error","status":"failed","error_type":"Error","msg":"Error: Browser has been closed.\n at PlaywrightState.getActiveBrowser (/home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/index.js:12594:13)\n at PlaywrightServer.getActiveBrowser (/home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/index.js:13516:52)\n at PlaywrightServer.setTimeout (/home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/index.js:13734:56)\n at Object.onReceiveHalfClose (/home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/node_modules/@grpc/grpc-js/build/src/server.js:1478:25)\n at BaseServerInterceptingCall.maybePushNextMessage (/home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/node_modules/@grpc/grpc-js/build/src/server-interceptors.js:594:31)\n at BaseServerInterceptingCall.handleEndEvent (/home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/node_modules/@grpc/grpc-js/build/src/server-interceptors.js:634:14)\n at ServerHttp2Stream. (/home/lukas/miniconda3/lib/python3.13/site-packages/Browser/wrapper/node_modules/@grpc/grpc-js/build/src/server-interceptors.js:387:18)\n at ServerHttp2Stream.emit (node:events:531:35)\n at endReadableNT (node:internal/streams/readable:1698:12)\n at process.processTicksAndRejections (node:internal/process/task_queues:89:21)"} +{"level":"info","time":"2026-06-26T19:01:28.525Z","seq":49,"suite_id":"s1","suite_name":"Login","component":"browser-library","event_kind":"grpc","action":"getBrowserCatalog","status":"started"} +{"level":"info","time":"2026-06-26T19:01:28.526Z","seq":50,"suite_id":"s1","suite_name":"Login","component":"browser-library","event_kind":"grpc","action":"getBrowserCatalog","status":"succeeded"} +{"level":"info","time":"2026-06-26T19:01:28.528Z","seq":51,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Invalid Password","test_id":"s1-t3","component":"browser-library","event_kind":"grpc","action":"getBrowserCatalog","status":"started"} +{"level":"info","time":"2026-06-26T19:01:28.528Z","seq":52,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Invalid Password","test_id":"s1-t3","component":"browser-library","event_kind":"grpc","action":"getBrowserCatalog","status":"succeeded"} +{"level":"info","time":"2026-06-26T19:01:28.548Z","seq":53,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Invalid Password","test_id":"s1-t3","component":"browser-library","event_kind":"grpc","action":"newPage","status":"started"} +{"level":"info","time":"2026-06-26T19:01:28.548Z","seq":54,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Invalid Password","test_id":"s1-t3","component":"browser-library","msg":"No active browser, creating a new one"} +{"level":"info","time":"2026-06-26T19:01:28.590Z","seq":55,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Invalid Password","test_id":"s1-t3","component":"browser-library","msg":"Setting default timeout for context context=94ca32a1-e9f6-4c9c-9638-3ded8a5ebe3f to 10000"} +{"level":"info","time":"2026-06-26T19:01:28.590Z","seq":56,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Invalid Password","test_id":"s1-t3","component":"browser-library","msg":"Changed active context: context=94ca32a1-e9f6-4c9c-9638-3ded8a5ebe3f"} +{"level":"info","time":"2026-06-26T19:01:28.620Z","seq":57,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Invalid Password","test_id":"s1-t3","component":"browser-library","msg":"Video path: undefined"} +{"level":"info","time":"2026-06-26T19:01:28.620Z","seq":58,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Invalid Password","test_id":"s1-t3","component":"browser-library","msg":"Changed active page"} +{"level":"info","time":"2026-06-26T19:01:29.051Z","seq":59,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Invalid Password","test_id":"s1-t3","component":"browser-library","event_kind":"grpc","action":"newPage","status":"succeeded"} +{"level":"info","time":"2026-06-26T19:01:29.054Z","seq":60,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Invalid Password","test_id":"s1-t3","component":"browser-library","event_kind":"grpc","action":"fillText","status":"started"} +{"level":"info","time":"2026-06-26T19:01:29.055Z","seq":61,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Invalid Password","test_id":"s1-t3","component":"browser-library","msg":"Strict mode is enabled, find Locator with id=password-input in page."} +{"level":"info","time":"2026-06-26T19:01:29.075Z","seq":62,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Invalid Password","test_id":"s1-t3","component":"browser-library","event_kind":"grpc","action":"fillText","status":"succeeded"} +{"level":"info","time":"2026-06-26T19:01:29.077Z","seq":63,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Invalid Password","test_id":"s1-t3","component":"browser-library","event_kind":"grpc","action":"click","status":"started"} +{"level":"info","time":"2026-06-26T19:01:29.077Z","seq":64,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Invalid Password","test_id":"s1-t3","component":"browser-library","msg":"Strict mode is enabled, find Locator with id=login-submit-btn in page."} +{"level":"info","time":"2026-06-26T19:01:29.111Z","seq":65,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Invalid Password","test_id":"s1-t3","component":"browser-library","event_kind":"grpc","action":"click","status":"succeeded"} +{"level":"info","time":"2026-06-26T19:01:29.114Z","seq":66,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Invalid Password","test_id":"s1-t3","component":"browser-library","event_kind":"grpc","action":"waitForElementState","status":"started"} +{"level":"info","time":"2026-06-26T19:01:29.114Z","seq":67,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Invalid Password","test_id":"s1-t3","component":"browser-library","msg":"Strict mode is enabled, find Locator with id=login-error in page."} +{"level":"info","time":"2026-06-26T19:01:29.139Z","seq":68,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Invalid Password","test_id":"s1-t3","component":"browser-library","event_kind":"grpc","action":"waitForElementState","status":"succeeded"} +{"level":"info","time":"2026-06-26T19:01:29.142Z","seq":69,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Invalid Password","test_id":"s1-t3","component":"browser-library","event_kind":"grpc","action":"getText","status":"started"} +{"level":"info","time":"2026-06-26T19:01:29.142Z","seq":70,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Invalid Password","test_id":"s1-t3","component":"browser-library","msg":"Strict mode is enabled, find Locator with id=login-error in page."} +{"level":"info","time":"2026-06-26T19:01:29.142Z","seq":71,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Invalid Password","test_id":"s1-t3","component":"browser-library","msg":"Getting text content with text type ROBOT_FRAMEWORK_BROWSER_NO_SET"} +{"level":"info","time":"2026-06-26T19:01:29.142Z","seq":72,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Invalid Password","test_id":"s1-t3","component":"browser-library","msg":"Getting text content without text type"} +{"level":"info","time":"2026-06-26T19:01:29.157Z","seq":73,"suite_id":"s1","suite_name":"Login","test_name":"Login.Login With Invalid Password","test_id":"s1-t3","component":"browser-library","msg":"Locator was not an , ",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0 + + + + + + + + + + + + + + + + + + + + + +
+

Opening Robot Framework report failed

+
    +
  • Verify that you have JavaScript enabled in your browser.
  • +
  • Make sure you are using a modern enough browser. If using Internet Explorer, version 11 is required.
  • +
  • Check are there messages in your browser's JavaScript error log. Please report the problem if you suspect you have encountered a bug.
  • +
+
+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/server/services/auth_service.py b/src/server/services/auth_service.py index 7078e94..e22812f 100644 --- a/src/server/services/auth_service.py +++ b/src/server/services/auth_service.py @@ -186,22 +186,12 @@ class AuthService: self._failed.pop(identifier, None) def _check_locked(self, identifier: str) -> None: - import os - if os.getenv("ANIWORLD_TESTING") == "1": - return rec = self._get_fail_record(identifier) lu = rec.get("locked_until") if lu and datetime.now(timezone.utc) < lu: raise LockedOutError( "Too many failed attempts - temporarily locked out" ) - if lu and datetime.now(timezone.utc) >= lu: - # lock expired, reset - self._failed[identifier] = { - "count": 0, - "last": None, - "locked_until": None, - } # --- authentication --- def validate_master_password( diff --git a/src/server/web/templates/login.html b/src/server/web/templates/login.html index 9db985a..77a72f6 100644 --- a/src/server/web/templates/login.html +++ b/src/server/web/templates/login.html @@ -302,9 +302,8 @@ }); // Form submission - const loginForm = document.getElementById('login-form'); + const loginForm = document.getElementById('login-form'); const loginButton = document.getElementById('login-submit-btn'); - const messageContainer = document.getElementById('message-container'); loginForm.addEventListener('submit', async (e) => { e.preventDefault(); diff --git a/tests/robot/resources/common.resource b/tests/robot/resources/common.resource index 5b94d7c..319c954 100644 --- a/tests/robot/resources/common.resource +++ b/tests/robot/resources/common.resource @@ -108,8 +108,13 @@ Setup Master Password ... backup_enabled=False ${resp}= POST On Session anon /api/auth/setup json=${payload} expected_status=any IF '${resp.status_code}' == '429' - # Rate limited - wait and retry once - Sleep 6s + # Rate limited - wait and retry + Sleep 45s + ${resp}= POST On Session anon /api/auth/setup json=${payload} expected_status=any + END + IF '${resp.status_code}' == '429' + # Still rate limited - wait longer and retry again + Sleep 45s ${resp}= POST On Session anon /api/auth/setup json=${payload} expected_status=any END IF '${resp.status_code}' == '400' diff --git a/tests/robot/resources/websocket_keywords.py b/tests/robot/resources/websocket_keywords.py new file mode 100644 index 0000000..22dd82a --- /dev/null +++ b/tests/robot/resources/websocket_keywords.py @@ -0,0 +1,66 @@ +"""WebSocket keyword library for Robot Framework tests.""" + +import asyncio +from typing import Optional + +import websockets +from robot.api import logger + + +class WebSocketKeywords: + """Keywords for WebSocket testing.""" + + def __init__(self): + self._connection: Optional[websockets.WebSocketClientProtocol] = None + + def connect_to_websocket(self, ws_url: str) -> str: + """Establish a WebSocket connection and return connection info. + + Args: + ws_url: Full WebSocket URL including token query param. + + Returns: + String representation of the connected WebSocket. + """ + asyncio.run(self._async_connect(ws_url)) + return f"Connected to {ws_url}" + + async def _async_connect(self, ws_url: str) -> None: + self._connection = await websockets.connect(ws_url) + logger.info(f"WebSocket connected: {self._connection}") + + def send_websocket_message(self, message: str) -> None: + """Send a text message over the active WebSocket connection. + + Args: + message: Text message to send. + """ + if self._connection is None: + raise RuntimeError("No active WebSocket connection. Use Connect To WebSocket first.") + asyncio.run(self._connection.send(message)) + logger.info(f"Sent message: {message}") + + def receive_websocket_message(self, timeout: float = 5.0) -> str: + """Receive a text message from the active WebSocket connection. + + Args: + timeout: Maximum seconds to wait for a message. + + Returns: + The received text message. + """ + if self._connection is None: + raise RuntimeError("No active WebSocket connection. Use Connect To WebSocket first.") + try: + message = asyncio.run(asyncio.wait_for(self._connection.recv(), timeout=timeout)) + logger.info(f"Received message: {message}") + return message + except asyncio.TimeoutError: + raise TimeoutError(f"No message received within {timeout} seconds") + + def close_websocket(self) -> None: + """Close the active WebSocket connection.""" + if self._connection is not None: + asyncio.run(self._connection.close()) + self._connection = None + logger.info("WebSocket closed")