diff --git a/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/.gitignore b/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/.gitignore new file mode 100644 index 0000000000..aa39ee7e72 --- /dev/null +++ b/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/.gitignore @@ -0,0 +1,2 @@ +### Root Path +artifacts_*/ \ No newline at end of file diff --git a/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/Dockerfile b/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/Dockerfile index 41dc55ed63..3b8dc83cad 100644 --- a/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/Dockerfile +++ b/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/Dockerfile @@ -1,5 +1,5 @@ # Use an official Python base image -FROM python:3.10-slim-bookworm +FROM python:3.13.0-slim-bookworm # ENV Variables ENV WORK_PATH=/app @@ -30,6 +30,7 @@ COPY . ${WORK_PATH}/${APP_PATH} WORKDIR ${WORK_PATH}/${APP_PATH} # Install Python Dependencies +RUN python3 -m pip install --upgrade pip RUN python3 -m pip install . # Default command diff --git a/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/Makefile b/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/Makefile index 52015ece7d..32fee5ae9e 100644 --- a/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/Makefile +++ b/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/Makefile @@ -1,12 +1,13 @@ # Variables -CONSOLE=/bin/bash -APP=dashboard_saturation_tests -DATA=data -WORK=/app -ARTIFACTS=artifacts -LOGS=logs -SCREENSHOTS=screenshots -CSV=csv +CONSOLE := /bin/bash +APP := dashboard_saturation_tests +DATA := data +WORK := /app +DATETIME := $(shell date +%Y%m%d_%H%M%S) +ARTIFACTS := artifacts_$(DATETIME) +LOGS := logs +SCREENSHOTS := screenshots +CSV := csv .PHONY: init init: rm purge build run ## Recreate Containers @@ -48,9 +49,6 @@ purge: ## Purge All Docker Resources docker system prune -a -f .PHONY: destroy destroy: rm purge ## Destroy All Docker Resources -.PHONY: style -style: ## Check Python Style - docker exec -it $(APP) $(CONSOLE) -c "pycodestyle data/dashboard_saturation_tests.py" || true .PHONY: help help: ## Display Help Message @cat $(MAKEFILE_LIST) | grep -e "^[a-zA-Z_\-]*: *.*## *" | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' \ No newline at end of file diff --git a/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/README.md b/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/README.md index 4f61bb066f..2ba9f8de5d 100644 --- a/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/README.md +++ b/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/README.md @@ -9,6 +9,7 @@ dashboard_saturation_tests/ ├── data/ │ ├── lib/ │ │ ├── CookieManager.js +│ │ ├── ItemManager.js │ │ ├── PathManager.js │ │ └── ScreenshotManager.js │ ├── tests/ @@ -20,8 +21,11 @@ dashboard_saturation_tests/ │ ├── artillery.xml │ ├── dashboard_saturation_tests.py │ └── processor.js -├── README.md -└── pyproject.toml +├── .gitignore +├── Dockerfile +├── Makefile +├── pyproject.toml +└── README.md ``` ## Prerequisites @@ -34,6 +38,18 @@ To run the script you need to have Python and Pip installed. ### Install Artillery + Playwright +The requirements for using Artillery + Playwright are indicative. The resources needed to run the tests depend greatly on the complexity of the tests. + +| Simulated Users | CPU Cores | Memory (RAM) | +| --------------- | --------- | ------------ | +| 1 | 1 | 2 GB | +| 3 | 2 | 4 GB | +| 5 | 2 | 4 GB | +| 7 | 3 | 6 GB | +| 10 | 4 | 8 GB | +| 15 | 6 | 12 GB | +| 20 | 8 | 16 GB | + Artillery, Playwright and all the dependencies required for them to run correctly must be installed. Some dependencies are libraries that can be used in tests. ```shell script @@ -56,19 +72,29 @@ artillery --version playwright --version ``` -## Initial setup +## Initial Setup To run the tests, it is necessary to install the dependencies and the package. This can be done by running the following command: -```shell script +1. Move to the `wazuh-qa/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests` directory + +2. Create the Python environment + +```bash python3 -m venv env +``` + +3. Activate the environment: +```bash source env/bin/activate -python3 -m pip install . ``` -Note: The use of the environment is optional, +4. Install the package +```bash +python3 -m pip install . +``` -## Artillery + Playwright +## Usage To run the tests, we will need to use the following command: @@ -99,14 +125,6 @@ dashboard-saturation-tests --password --ip - `--artillery` needs to receive a valid Artillery configuration file (for example, `artillery.yml`). - `--type` only accepts two values ​​(`aggregate` or `intermediate`). Either or both can be chosen. -### Check PEP 8 - -The Python script complies with the PEP 8 standard. To verify that it continues to comply with the standard (after making changes) you just have to execute the following commands: - -```shell script -pycodestyle dashboard_saturation_tests.py -``` - ## Using Docker It is possible to use the `Docker` image with the entire environment set up for running tests. To facilitate its use, there is a `makefile` file with the necessary instructions. An example of use would be: @@ -122,10 +140,50 @@ The `make exec` command (and, by extension, the `make test` command) requires th In the Docker container, everything is in the `/app` directory. In `/app`, Artillery, Playwright and everything else necessary for them to work are installed. In `/app/dashboard_saturation_tests` are all the scripts. In that directory, the packages are installed and all the commands are executed. +## Analysis of the Results + +### CSV + +Running tests generates two types of CSV files: + +- `Intermediate`: These represent intermediate statistics that are generated and printed to the console during the test run. By default, they’re generated every 10 seconds. These data points are useful for monitoring the progress of the test in real time. + - `Summaries`: These include partial summaries during the test execution. Useful for seeing application behavior in specific intervals. + - `Histograms`: Show the distribution of response times and other metrics in specific time intervals. Helps to identify performance peaks and dips. + - `Counters`: Show the number of requests, errors, and other events during specific intervals of the test. Ideal for monitoring progress and quickly spotting issues. + +- `Aggregate`: These represent the overall statistics for the entire duration of the test. They correspond to the final statistics printed after the test completes. These data provide a complete summary of the test's performance. + - `Summaries`: Provides an overall view of performance across the entire test once it’s completed. + - `Histograms`: Offers a general view of how performance metrics were distributed over time during the entire test. Useful for checking consistency and stability. + - `Counters`: Provides a total summary of completed requests, errors, and key events at the end of the test. Gives a clear picture of overall performance. + +### Logs + +The log file generated by Artillery contains detailed information about each HTTP request made during the test. + +This information is useful for analyzing the performance and efficiency of HTTP requests during load testing. + +### Screenshots + +Taking screenshots of the dashboard during load tests is a good idea for several reasons: + +- `Visual Documentation`: It allows you to document the performance and stability of the system visually during the tests. This is particularly useful for reports and presentations. + +- `Detailed Analysis`: You can compare screenshots from different moments to identify patterns or recurring issues that might not be evident from numerical data alone. + +- `Problem-Solving`: If something goes wrong, having a screenshot of the exact moment can help identify what was happening in the system at that specific point. + +- `Effective Communication`: It's much easier to explain problems and solutions to the team when you can show them exactly what was happening through screenshots. + +Screenshots provide an additional layer of information and context that can be crucial to understanding and improving system performance. + ## Example +This is an example of the most basic test execution and its corresponding result. + ```shell script dashboard-saturation-tests -p password -i ip ``` -- Result: [report.zip](https://github.com/user-attachments/files/16542340/report.zip) \ No newline at end of file +By default, the output is stored in 3 folders: `csv/`, `logs/` and `screenshots/`. This can be changed via the command parameters. + +- Result: [report.zip](https://github.com/user-attachments/files/16542340/report.zip) diff --git a/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/__init__.py b/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/artillery.yml b/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/artillery.yml index e4af2c64b5..6fa629a264 100644 --- a/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/artillery.yml +++ b/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/artillery.yml @@ -18,6 +18,7 @@ config: screenshots: '{{ screenshots }}' session: '{{ session }}' username: '{{ username }}' + timeout: '{{ timeout }}' scenarios: - engine: playwright name: test_01_login diff --git a/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/dashboard_saturation_tests.py b/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/dashboard_saturation_tests.py index 22c93ea41b..c0195907d9 100644 --- a/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/dashboard_saturation_tests.py +++ b/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/dashboard_saturation_tests.py @@ -10,18 +10,33 @@ Playwright must be installed for it to work properly. """ +import fnmatch import json import time from argparse import ArgumentParser, Namespace, RawTextHelpFormatter -from datetime import datetime -from os import getcwd, makedirs +from datetime import datetime, timezone +from os import getcwd, makedirs, scandir, listdir from os.path import isabs, join from subprocess import run +from typing import Any import pandas as pd import yaml +LOGS = "logs/" +SCREENSHOTS = "screenshots/" +CSV = 'csv/' +SESSION = '.auth/' +ARTILLERY = 'data/artillery.yml' +USER = 'admin' +ITERATIONS = 1 +TYPE = ['aggregate', 'intermediate'] +WAIT = 5 +TIMEOUT = 10000 +DEBUG = False + + # Global Configuration Variables artillery_result_type = [ # Final statistics printed to the console at the end of a test @@ -118,7 +133,8 @@ def gen_artillery_params(args: Namespace) -> dict: 'username': args.user, 'password': args.password, 'screenshots': args.screenshots, - 'session': args.session + 'session': args.session, + 'timeout': args.timeout } return artillery_params @@ -185,7 +201,7 @@ def gen_log_filename(log_path: str) -> str: Returns: str: File name of the log (include path). """ - return log_path + datetime.now().strftime("log-%Y%m%d%H%M%S.log") + return log_path + datetime.now().strftime("log-%Y%m%d%H%M%S.json") def gen_url(ip: str) -> str: @@ -202,17 +218,46 @@ def gen_url(ip: str) -> str: return f'{url_format}{ip}' -def gen_csv_filename(csv_path: str, type: str) -> str: +def gen_csv_filename(csv_path: str, type: str, entry: str = None) -> str: """Generate csv file name (per type). Args: csv_path (str): Path of the CSVs. type (str): CSV data type. + entry (str): Log Entry. Returns: str: File name of the csv (include type and path). """ - return csv_path + datetime.now().strftime(f"{type}-%Y%m%d%H%M%S.csv") + return csv_path + datetime.now().strftime(f"{type}_{entry}_%Y%m%d%H%M%S.csv") + + +def merge_dictionaries(list_of_dicts: list[dict[str, Any]]) -> dict[str, Any]: + """Merge a list of dictionaries into a single dictionary. + + Args: + list_of_dicts (str): list of dictionaries. + + Returns: + dict: Dictionary with merged data. + """ + combined_dict = { + 'counters': {}, + 'summaries': {}, + 'histograms': {}, + } + + keys_to_merge = ['counters', 'summaries', 'histograms'] + + for item in list_of_dicts: + for key in keys_to_merge: + if key in item: + if key in combined_dict: + combined_dict[key].update(item[key]) + else: + combined_dict[key] = item[key] + + return combined_dict def convert_json_to_csv(args: Namespace, json_output: str) -> None: @@ -222,14 +267,22 @@ def convert_json_to_csv(args: Namespace, json_output: str) -> None: args (Namespace): Script parameters. json_output (str): Path and file name of the log. """ + keys = ['counters', 'summaries', 'histograms'] + + with open(json_output) as f: + data = json.load(f) + for type in args.type: - with open(json_output) as f: - data = json.load(f) + filtered_data = data[type] - csv_filename = gen_csv_filename(args.csv, type) + if isinstance(filtered_data, list): + filtered_data = merge_dictionaries(filtered_data) - df = pd.json_normalize(data[type]) - df.to_csv(csv_filename, index=False) + for key in keys: + csv_filename = gen_csv_filename(args.csv, type, key) + filtered_data[key]['timestamp'] = datetime.now(timezone.utc).isoformat() + df = pd.json_normalize(filtered_data[key]) + df.to_csv(csv_filename, index=False) def run_artillery(args: Namespace) -> None: @@ -255,7 +308,34 @@ def run_artillery(args: Namespace) -> None: command = f'artillery run {params} {target} {quiet} {output} {script}' run(command, shell=True) - convert_json_to_csv(args, json_filename) + + if any(scandir(args.logs)): + convert_json_to_csv(args, json_filename) + + +def merge_csv(args: Namespace) -> None: + """Merge multiple CSV files into one. + + Args: + args (Namespace): Script parameters. + """ + for type in args.type: + keys = ['counters', 'summaries', 'histograms'] + + for key in keys: + pattern = f'{type}_{key}*' + files = fnmatch.filter(listdir(args.csv), pattern) + + if len(files) > 1: + data_frames = [] + + for file in files: + df = pd.read_csv(join(args.csv, file)) + data_frames.append(df) + + combined_df = pd.concat(data_frames) + filename = gen_csv_filename(args.csv, f'combined_{type}', key) + combined_df.to_csv(filename, index=False) def get_script_arguments() -> Namespace: @@ -274,48 +354,48 @@ def get_script_arguments() -> Namespace: '-l', '--log', dest='logs', type=str, - default='logs/', - help='Directory to store the logs. Default "logs".' + default=LOGS, + help=f'Directory to store the logs. Default {LOGS}.' ) parser.add_argument( '-s', '--screenshots', dest='screenshots', type=str, - default='screenshots/', - help='Directory to store the screenshots. Default "screenshots".' + default=SCREENSHOTS, + help=f'Directory to store the screenshots. Default {SCREENSHOTS}.' ) parser.add_argument( '-c', '--csv', dest='csv', type=str, - default='csv/', - help='Directory to store the CSVs. Default "csv".' + default=CSV, + help=f'Directory to store the CSVs. Default {CSV}.' ) parser.add_argument( '-o', '--session', dest='session', type=str, - default='.auth/', - help='Directory to store the Sessions. Default ".auth".' + default=SESSION, + help=f'Directory to store the Sessions. Default {SESSION}.' ) parser.add_argument( '-a', '--artillery', dest='artillery', type=str, - default="data/artillery.yml", - help='Path to the Artillery Script. Default "artillery.yml".' + default=ARTILLERY, + help=f'Path to the Artillery Script. Default {ARTILLERY}.' ) parser.add_argument( '-u', '--user', dest='user', type=str, - default='admin', - help='Wazuh User for the Dashboard. Default "admin".' + default=USER, + help=f'Wazuh User for the Dashboard. Default {USER}.' ) parser.add_argument( @@ -330,8 +410,8 @@ def get_script_arguments() -> Namespace: '-q', '--iterations', dest='iterations', type=int, - default=1, - help=f'Number of Tests to Run. Default 1.' + default=ITERATIONS, + help=f'Number of Tests to Run. Default {ITERATIONS}.' ) parser.add_argument( @@ -348,16 +428,24 @@ def get_script_arguments() -> Namespace: type=str, nargs='+', action='store', - default=['aggregate', 'intermediate'], - help='JSON data to create the CSV.' + default=TYPE, + help=f'JSON data to create the CSV. Default {TYPE}' ) parser.add_argument( '-w', '--wait', dest='wait', type=int, - default=5, - help='Waiting Time between Executions.' + default=WAIT, + help=f'Waiting Time between Executions. Default {WAIT}' + ) + + parser.add_argument( + '-m', '--timeout', + dest='timeout', + type=int, + default=TIMEOUT, + help=f'Timeout in milliseconds. Default {TIMEOUT}.' ) parser.add_argument( @@ -365,8 +453,8 @@ def get_script_arguments() -> Namespace: dest='debug', action='store_true', required=False, - default=False, - help='Enable Debug Mode.' + default=DEBUG, + help=f'Enable Debug Mode. Default {DEBUG}' ) return parser.parse_args() @@ -382,6 +470,8 @@ def main() -> None: run_artillery(script_args) time.sleep(script_args.wait) + merge_csv(script_args) + if __name__ == "__main__": main() diff --git a/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/lib/ItemManager.js b/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/lib/ItemManager.js new file mode 100644 index 0000000000..d0b011f6c1 --- /dev/null +++ b/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/lib/ItemManager.js @@ -0,0 +1,77 @@ +class ItemManager { + + // Page provides methods to interact with a single tab in a Browser + page = null; + + // Timeout defines the maximum waiting time in milliseconds + timeout = null; + + /** + * Class constructor + * @param {Object} page - Page provides methods to interact with a single tab in a Browser + */ + constructor(page, timeout) { + this.page = page; + this.timeout = timeout; + } + + /** + * Wait for the text to appear on the dashboard + * @param {String} dashboard_text + */ + async waitForText(dashboard_text) { + // Function to wait for a certain text to appear + await this.page.waitForSelector(`text=${dashboard_text}`, { state: 'visible', timeout: this.timeout }); + } + + /** + * Wait for Overview to load completely + */ + async waitForOverview() { + // Function to wait for a certain text to appear + await this.waitForText('Overview') + await this.waitForText('Agents summary') + await this.waitForText('Last 24 hours alerts') + await this.waitForText('Endpoint security'); + await this.waitForText('Threat intelligence'); + await this.waitForText('Security operations'); + await this.waitForText('Cloud security'); + } + + /** + * Wait for Endpoint Summary to load completely + */ + async waitForEndpointSummary() { + // Function to wait for a certain text to appear + await this.waitForText('Endpoints') + await this.waitForText('Agents by Status') + await this.waitForText('Top 5 OS') + await this.waitForText('Top 5 groups'); + await this.waitForText('Agents'); + } + + /** + * Wait for Endpoint Summary (Agent Status) to load completely + */ + async waitForAgentStatus() { + // Function to wait for a certain text to appear + await this.waitForText('Active') + await this.waitForText('Disconnected') + await this.waitForText('Pending'); + await this.waitForText('Never connected'); + } + + /** + * Wait for Endpoint Summary (Agent Info) to load completely + */ + async waitForAgentStatus() { + // Function to wait for a certain text to appear + await this.waitForText('ID') + await this.waitForText('Name') + await this.waitForText('Version'); + await this.waitForText('Status'); + } + +} + +module.exports = { ItemManager }; \ No newline at end of file diff --git a/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/lib/PathManager.js b/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/lib/PathManager.js index aa5901969b..f5159aabc4 100644 --- a/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/lib/PathManager.js +++ b/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/lib/PathManager.js @@ -17,16 +17,6 @@ class PathManager { 'threat-hunting-event': this.root + '/threat-hunting#/overview/?' }; - // Wazuh Dashboard Requests to Wait for to Ensure It is Loaded - requests = { - 'login': '/ui/logos/wazuh_dashboard_login_background.svg', - 'overview': '/bundles/plugin/data/data.chunk.5.js', - 'endpoint-summary': '/bundles/plugin/wazuh/0317d582b93c20f68e059e389aecab33.woff2', - 'agents': '/bundles/plugin/visTypeVislib/visTypeVislib.chunk.2.js', - 'threat-hunting': '/elastic/samplealerts', - 'threat-hunting-event': '/ui/logos/opensearch_mark_on_light.svg' - }; - /** * Check that Option Exist * @param {String} option @@ -36,15 +26,6 @@ class PathManager { return this.options.hasOwnProperty(option); } - /** - * Check that Request Exist - * @param {String} request - * @returns {Boolean} Returns if the request is valid - */ - check_request(request) { - return this.requests.hasOwnProperty(request); - } - /** * Throw an Error */ @@ -73,26 +54,6 @@ class PathManager { // Go to Selected Page await this.page.goto(this.options[option]); } - - /** - * Wait for a page to load - * @param {String} request - */ - async waitfor(request) { - // Check if the request is not correct - if (!this.check_request(request)) { - this.throw_error(); - } - - // If the Request is not Login, Wait for the Dashboard URL to Load - if ('login' != request) { - await this.page.waitForURL('**' + this.options[request] + '**'); - } - - // Wait for the Request to Load - await this.page.waitForResponse(response => response.url().includes(this.requests[request])); - } - } diff --git a/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/lib/ScreenshotManager.js b/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/lib/ScreenshotManager.js index 3a6bd0d783..7a54ce411f 100644 --- a/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/lib/ScreenshotManager.js +++ b/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/lib/ScreenshotManager.js @@ -6,6 +6,8 @@ class ScreenshotManager { extension = `.png`; // Page provides methods to interact with a single tab in a Browser page = null; + // User ID to group screenshots + user_id = null; /** * Class constructor @@ -14,7 +16,42 @@ class ScreenshotManager { */ constructor(page, filepath) { this.page = page; - this.filepath = filepath + this.filepath = filepath; + this.user_id = this.get_user_id(); + } + + /** + * Get the current date and time + * @returns {String} Returns the current date and time + */ + get_date_and_time() { + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + const hours = String(now.getHours()).padStart(2, '0'); + const minutes = String(now.getMinutes()).padStart(2, '0'); + const seconds = String(now.getSeconds()).padStart(2, '0'); + const milliseconds = String(now.getMilliseconds()).padStart(3, '0'); + + return `${year}${month}${day}_${hours}${minutes}${seconds}_${milliseconds}`; + } + + /** + * Generate the full name of the screenshot + * @param {String} name - Screenshot name + * @returns {String} Returns the full name of the screenshot + */ + get_complete_name(name) { + return this.user_id + '-' + name + '_' + this.get_date_and_time() + this.extension; + } + + /** + * Generate random user id + * @returns {String} Returns the random user id + */ + get_user_id() { + return Math.random().toString(36).substring(7); } /** @@ -23,7 +60,7 @@ class ScreenshotManager { */ async takeAnScreenshot(name) { // Generate the Complete Path to Store the Screenshots - let full_path = this.filepath + name + this.extension; + const full_path = this.filepath + this.get_complete_name(name); // Take a Browser Screenshot await this.page.screenshot({ path: full_path, fullPage: true }); diff --git a/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/tests/EndpointTest.js b/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/tests/EndpointTest.js index 75e8fb81d7..b94a9b9fbf 100644 --- a/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/tests/EndpointTest.js +++ b/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/tests/EndpointTest.js @@ -1,5 +1,6 @@ const { expect } = require("@playwright/test"); const { PathManager } = require("./../lib/PathManager.js"); +const { ItemManager } = require("./../lib/ItemManager.js"); const { CookieManager } = require("./../lib/CookieManager.js"); const { ScreenshotManager } = require("./../lib/ScreenshotManager.js"); @@ -11,6 +12,8 @@ class EndpointTest { vuContext = null; // PathManager Instance pathManager = null; + // ItemManager Instance + itemManager = null; // CookieManager Instance cookieManager = null; // ScreenshotManager Instance @@ -25,6 +28,7 @@ class EndpointTest { this.page = page; this.vuContext = vuContext; this.pathManager = new PathManager(page); + this.itemManager = new ItemManager(page, vuContext.vars.timeout); this.cookieManager = new CookieManager(page, vuContext.vars.username, vuContext.vars.session); this.screenshotManager = new ScreenshotManager(page, vuContext.vars.screenshots); } @@ -46,41 +50,34 @@ class EndpointTest { async accessEndpoint() { // Go to Endpoint Section await this.pathManager.goto('endpoint-summary'); - await this.pathManager.waitfor('endpoint-summary'); + + // Check that the Page has Loaded Correctly + await this.itemManager.waitForEndpointSummary(); // Take a Browser Screenshot await this.screenshotManager.takeAnScreenshot('test_03_endpoint_summary_is_loaded'); - - // Check that the Page has Loaded Correctly - await expect(this.page.getByText('Endpoints')).toBeVisible(); } /** - * Check Endpoints Information + * Check Endpoints Status */ - async checkEndpointsInfo() { - // Take a Browser Screenshot - await this.screenshotManager.takeAnScreenshot('test_03_endpoint_summary_has_data'); + async checkEndpointsStatus() { + // Check that the Status on the Endpoints Page Appears + await this.itemManager.waitForAgentStatus() - // Check that the Information on the Endpoints Page Appears - await expect(this.page.getByText('Status').first()).toBeVisible(); - await expect(this.page.getByText('Details')).toBeVisible(); - await expect(this.page.getByText('Evolution')).toBeVisible(); - await expect(this.page.getByText('Agents').last()).toBeVisible(); + // Take a Browser Screenshot + await this.screenshotManager.takeAnScreenshot('test_03_endpoint_summary_has_statuses'); } /** - * Check Endpoints Status + * Check Info about Agents */ - async checkEndpointsStatus() { + async checkAgentInfo() { // Take a Browser Screenshot await this.screenshotManager.takeAnScreenshot('test_03_endpoint_summary_has_agents'); // Check that the Status on the Endpoints Page Appears - await expect(this.page.getByText('Active').first()).toBeVisible(); - await expect(this.page.getByText('Disconnected').first()).toBeVisible(); - await expect(this.page.getByText('Pending').first()).toBeVisible(); - await expect(this.page.getByText('Never connected').first()).toBeVisible(); + await this.itemManager.waitForAgentInfo() } /** @@ -92,8 +89,8 @@ class EndpointTest { // Run the Tests await this.accessEndpoint(); - await this.checkEndpointsInfo(); await this.checkEndpointsStatus(); + await this.checkAgentInfo(); // Close the Browser await this.page.close(); diff --git a/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/tests/EventTest.js b/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/tests/EventTest.js index bdec4ea28e..6e04ac3985 100644 --- a/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/tests/EventTest.js +++ b/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/tests/EventTest.js @@ -1,5 +1,6 @@ const { expect } = require("@playwright/test"); const { PathManager } = require("./../lib/PathManager.js"); +const { ItemManager } = require("./../lib/ItemManager.js"); const { CookieManager } = require("./../lib/CookieManager.js"); const { ScreenshotManager } = require("./../lib/ScreenshotManager.js"); @@ -11,6 +12,8 @@ class EventTest { vuContext = null; // PathManager Instance pathManager = null; + // ItemManager Instance + itemManager = null; // CookieManager Instance cookieManager = null; // ScreenshotManager Instance @@ -25,6 +28,7 @@ class EventTest { this.page = page; this.vuContext = vuContext; this.pathManager = new PathManager(page); + this.itemManager = new ItemManager(page, vuContext.vars.timeout); this.cookieManager = new CookieManager(page, vuContext.vars.username, vuContext.vars.session); this.screenshotManager = new ScreenshotManager(page, vuContext.vars.screenshots); } @@ -46,13 +50,12 @@ class EventTest { async accessEventTab() { // Go to Endpoint Summary Page await this.pathManager.goto('endpoint-summary'); - await this.pathManager.waitfor('endpoint-summary'); + + // Check that the Page has Loaded Correctly + await this.itemManager.waitForEndpointSummary(); // Take a Browser Screenshot await this.screenshotManager.takeAnScreenshot('test_04_endpoint_summary_is_loaded'); - - // Check that the Page is Loaded - await expect(this.page.getByText('Endpoints')).toBeVisible(); } /** @@ -61,7 +64,9 @@ class EventTest { async accessAgent() { // Click on an Agent await this.page.getByText('001').click(); - await this.pathManager.waitfor('agents'); + + // Check that the Page has Loaded Correctly + await this.itemManager.waitForText('Compliance'); // Take a Browser Screenshot await this.screenshotManager.takeAnScreenshot('test_04_agent_section_is_loaded'); @@ -73,7 +78,9 @@ class EventTest { async accessAlerts() { // Check that the Threat Hunting Section has been Accessed await this.page.getByText('Threat Hunting ').click(); - await this.pathManager.waitfor('threat-hunting'); + + // Check that the Page has Loaded Correctly + await this.itemManager.waitForText('Threat Hunting'); // Take a Browser Screenshot await this.screenshotManager.takeAnScreenshot('test_04_threat_hunting_is_loaded'); @@ -85,13 +92,16 @@ class EventTest { async checkEvents() { // Click on Events Tab await this.page.getByText('Events').click(); - await this.pathManager.waitfor('threat-hunting-event'); - - // Take a Browser Screenshot - await this.screenshotManager.takeAnScreenshot('test_04_events_tab_is_loaded'); + + // Check that the Page has Loaded Correctly + await this.itemManager.waitForText('Threat Hunting'); + await this.itemManager.waitForText('Events'); // Check that the Event Table has been Loaded expect(await this.page.getByLabel('tr').count()).toBeGreaterThanOrEqual(1); + + // Take a Browser Screenshot + await this.screenshotManager.takeAnScreenshot('test_04_events_tab_is_loaded'); } /** diff --git a/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/tests/LoginTest.js b/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/tests/LoginTest.js index 5290d0820c..6c4ff5dd2d 100644 --- a/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/tests/LoginTest.js +++ b/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/tests/LoginTest.js @@ -1,5 +1,6 @@ const { expect } = require("@playwright/test"); const { PathManager } = require("./../lib/PathManager.js"); +const { ItemManager } = require("./../lib/ItemManager.js"); const { CookieManager } = require("./../lib/CookieManager.js"); const { ScreenshotManager } = require("./../lib/ScreenshotManager.js"); @@ -11,6 +12,8 @@ class LoginTest { vuContext = null; // PathManager Instance pathManager = null; + // ItemManager Instance + itemManager = null; // CookieManager Instance cookieManager = null; // ScreenshotManager Instance @@ -25,6 +28,7 @@ class LoginTest { this.page = page; this.vuContext = vuContext; this.pathManager = new PathManager(page); + this.itemManager = new ItemManager(page, vuContext.vars.timeout); this.cookieManager = new CookieManager(page, vuContext.vars.username, vuContext.vars.session); this.screenshotManager = new ScreenshotManager(page, vuContext.vars.screenshots); } @@ -35,13 +39,12 @@ class LoginTest { async accessLogin(){ // Go to Login Page await this.pathManager.goto('login'); - await this.pathManager.waitfor('login'); + + // Check that the Page is Loaded + await this.itemManager.waitForText('Log in'); // Take a Browser Screenshot await this.screenshotManager.takeAnScreenshot('test_01_login_is_loaded'); - - // Check that the Login Page is Loaded - await expect(this.page.getByText('Log In')).toBeVisible(); } /** @@ -69,13 +72,10 @@ class LoginTest { */ async checkLogin() { // Wait for the Overview Page to Load - await this.pathManager.waitfor('overview'); + await this.itemManager.waitForOverview(); // Take a Browser Screenshot await this.screenshotManager.takeAnScreenshot('test_01_overview_is_loaded'); - - // Check that the Page is Loaded - await expect(this.page.getByText('Overview')).toBeVisible(); } /** diff --git a/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/tests/OverviewTest.js b/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/tests/OverviewTest.js index 75d0486f3e..0695730933 100644 --- a/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/tests/OverviewTest.js +++ b/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/data/tests/OverviewTest.js @@ -1,5 +1,6 @@ const { expect } = require("@playwright/test"); const { PathManager } = require("./../lib/PathManager.js"); +const { ItemManager } = require("./../lib/ItemManager.js"); const { CookieManager } = require("./../lib/CookieManager.js"); const { ScreenshotManager } = require("./../lib/ScreenshotManager.js"); @@ -11,6 +12,8 @@ class OverviewTest { vuContext = null; // PathManager Instance pathManager = null; + // ItemManager Instance + itemManager = null; // CookieManager Instance cookieManager = null; // ScreenshotManager Instance @@ -25,6 +28,7 @@ class OverviewTest { this.page = page; this.vuContext = vuContext; this.pathManager = new PathManager(page); + this.itemManager = new ItemManager(page, vuContext.vars.timeout); this.cookieManager = new CookieManager(page, vuContext.vars.username, vuContext.vars.session); this.screenshotManager = new ScreenshotManager(page, vuContext.vars.screenshots); } @@ -41,38 +45,24 @@ class OverviewTest { } /** - * Access the Dashboard (Overview) + * Access the Dashboard (Overview) and check information */ async accessOverview() { // Go to Overview Section await this.pathManager.goto('overview'); - await this.pathManager.waitfor('overview'); // Take a Browser Screenshot await this.screenshotManager.takeAnScreenshot('test_02_overview_is_loaded'); - - // Check that the Overview Page is Loaded - await expect(this.page.getByText('Overview')).toBeVisible(); - } - /** - * Check Overview Information - */ - async checkOverviewInfo() { + // Check that the Page is Loaded + await this.itemManager.waitForOverview(); + // Take a Browser Screenshot await this.screenshotManager.takeAnScreenshot('test_02_overview_dashboard'); - - // Check that the Overview Information Appears - await expect(this.page.getByTitle('Agents Summary')).toBeVisible(); - await expect(this.page.getByTitle('Last 24 hours alerts')).toBeVisible(); - await expect(this.page.getByTitle('Endpoint security')).toBeVisible(); - await expect(this.page.getByTitle('Threat intelligence')).toBeVisible(); - await expect(this.page.getByTitle('Security operations')).toBeVisible(); - await expect(this.page.getByTitle('Cloud security')).toBeVisible(); } /** - * Run the Full Test + * Run the full test */ async executeTest() { // Restore Browser Session @@ -80,7 +70,6 @@ class OverviewTest { // Run the Tests await this.accessOverview(); - await this.checkOverviewInfo(); // Close the Browser await this.page.close(); diff --git a/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/pyproject.toml b/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/pyproject.toml index 54142cf113..8394d410b4 100644 --- a/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/pyproject.toml +++ b/deps/wazuh_testing/wazuh_testing/dashboard_saturation_tests/pyproject.toml @@ -3,7 +3,6 @@ name = "dashboard_saturation_tests" version = "0.0.1" dependencies = [ "pandas>=2.2.2", - "pycodestyle>=2.12.1", "pyyaml>=6.0.2" ] requires-python = ">=3.10" @@ -20,5 +19,8 @@ Homepage = "https://wazuh.com/" Documentation = "https://documentation.wazuh.com/" Repository = "https://github.com/wazuh/" +[tool.setuptools.packages.find] +include = ["data"] + [project.scripts] dashboard-saturation-tests = "data.dashboard_saturation_tests:main" \ No newline at end of file