diff --git a/packages/playground/tests/frontend_selenium/Config.ini b/packages/playground/tests/frontend_selenium/Config.ini index 3f63d2c549..2222b381f4 100644 --- a/packages/playground/tests/frontend_selenium/Config.ini +++ b/packages/playground/tests/frontend_selenium/Config.ini @@ -5,4 +5,4 @@ net = dev seed = node_seed = address = -email = +email = \ No newline at end of file diff --git a/packages/playground/tests/frontend_selenium/pages/statistics.py b/packages/playground/tests/frontend_selenium/pages/statistics.py new file mode 100644 index 0000000000..e537943b26 --- /dev/null +++ b/packages/playground/tests/frontend_selenium/pages/statistics.py @@ -0,0 +1,89 @@ +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + + + +class StatisticsPage: + + logout_button = (By.XPATH, "//button[.//span[text()=' Logout ']]") + tfgrid_button = (By.XPATH, "//span[text()='TFGrid']") + grid_status_button = (By.XPATH, "//span[text()='Grid Status']") + node_monitoring_button = (By.XPATH, "//span[text()='Node Monitoring']") + statistics_button = (By.XPATH, "//span[text()='Node Statistics']") + statistics_label = (By.XPATH, "//*[contains(text(), 'Statistics')]") + map = (By.XPATH,"//button[contains(@class, 'btn-main-container')]") + nodes_online = (By.XPATH, "//span[text()='Nodes Online']/ancestor::div/following-sibling::div[@class='v-card-text card-body']") + dedicated_machines = (By.XPATH, "//span[text()='Dedicated Machines']/ancestor::div/following-sibling::div[@class='v-card-text card-body']") + farms = (By.XPATH, "//span[text()='Farms']/ancestor::div/following-sibling::div[@class='v-card-text card-body']") + countries = (By.XPATH, "//span[text()='Countries']/ancestor::div/following-sibling::div[@class='v-card-text card-body']") + cpus = (By.XPATH, "//span[text()='CPUs']/ancestor::div/following-sibling::div[@class='v-card-text card-body']") + ssd_storage = (By.XPATH, "//span[text()='SSD Storage']/ancestor::div/following-sibling::div[@class='v-card-text card-body']") + hdd_storage = (By.XPATH, "//span[text()='HDD Storage']/ancestor::div/following-sibling::div[@class='v-card-text card-body']") + ram = (By.XPATH, "//span[text()='RAM']/ancestor::div/following-sibling::div[@class='v-card-text card-body']") + gpus = (By.XPATH, "//span[text()='GPUs']/ancestor::div/following-sibling::div[@class='v-card-text card-body']") + access_nodes = (By.XPATH, "//span[text()='Access Nodes']/ancestor::div/following-sibling::div[@class='v-card-text card-body']") + gateways = (By.XPATH, "//span[text()='Gateways']/ancestor::div/following-sibling::div[@class='v-card-text card-body']") + twins = (By.XPATH, "//span[text()='Twins']/ancestor::div/following-sibling::div[@class='v-card-text card-body']") + public_ips = (By.XPATH, "//span[text()='Public IPs']/ancestor::div/following-sibling::div[@class='v-card-text card-body']") + conracts = (By.XPATH, "//span[text()='Contracts']/ancestor::div/following-sibling::div[@class='v-card-text card-body']") + number_of_workloads = (By.XPATH, "//span[text()='Number of workloads']/ancestor::div/following-sibling::div[@class='v-card-text card-body']") + + + def __init__(self, browser): + self.browser = browser + + def navigate(self): + webdriver.ActionChains(self.browser).send_keys(Keys.ESCAPE).perform() + self.browser.find_element(*self.tfgrid_button).click() + self.browser.find_element(*self.statistics_button).click() + WebDriverWait(self.browser, 60).until(EC.visibility_of_element_located(self.statistics_label)) + + def statistics_detials(self): + details = {} + wait = WebDriverWait(self.browser, 60) # Increased wait time to 60 seconds + elements_to_fetch = { + "nodes": self.nodes_online, + "dedicatedNodes": self.dedicated_machines, + "farms": self.farms, + "countries": self.countries, + "totalCru": self.cpus, + "totalSru": self.ssd_storage, + "totalHru": self.hdd_storage, + "totalMru": self.ram, + "gpus": self.gpus, + "accessNodes": self.access_nodes, + "gateways": self.gateways, + "twins": self.twins, + "publicIps": self.public_ips, + "contracts": self.conracts, + "workloads_number": self.number_of_workloads + } + + for key, locator in elements_to_fetch.items(): + try: + element_text = wait.until(EC.visibility_of_element_located(locator)).text + details[key] = element_text + except TimeoutException: + details[key] = None # Add None or some default value to maintain dictionary consistency + + return details + + def get_link(self): + WebDriverWait(self.browser, 30).until(EC.number_of_windows_to_be(2)) + self.browser.switch_to.window(self.browser.window_handles[1]) + url = self.browser.current_url + self.browser.close() + self.browser.switch_to.window(self.browser.window_handles[0]) + return url + + def grid_status_link(self): + self.browser.find_element(*self.grid_status_button).click() + return self.get_link() + + def node_monitoring_link(self): + self.browser.find_element(*self.node_monitoring_button).click() + return self.get_link() \ No newline at end of file diff --git a/packages/playground/tests/frontend_selenium/tests/TFGrid/test_statistics.py b/packages/playground/tests/frontend_selenium/tests/TFGrid/test_statistics.py new file mode 100644 index 0000000000..5be61dab99 --- /dev/null +++ b/packages/playground/tests/frontend_selenium/tests/TFGrid/test_statistics.py @@ -0,0 +1,63 @@ +import math +from utils.utils import byte_converter,convert_to_scaled_float +from pages.statistics import StatisticsPage +from utils.grid_proxy import GridProxy +from pages.dashboard import DashboardPage + +def before_test_setup(browser): + statistics_page = StatisticsPage(browser) + dashboard_page = DashboardPage(browser) + dashboard_page.open_and_load() + statistics_page.navigate() + return statistics_page + + +def test_statistics_details(browser): + """ + TC1503 - Verify Statistics + Steps: + - Navigate to the dashboard. + - Click on TFGrid from side menu. + - Click on Stats. + Result: Assert that the displayed values should match the data from the grid proxy. + """ + statistics_page = before_test_setup(browser) + grid_proxy = GridProxy(browser) + statistics_details = statistics_page.statistics_detials() + grid_statistics_details = grid_proxy.get_stats() + # Convert necessary values from string to integer for comparison, but keeping the dictionary structure + statistics_details_converted = { + key: int(value.replace(',', '')) if value is not None and value.replace(',', '').isdigit() else value + for key, value in statistics_details.items() + } + # Full set of assertions, comparing UI stats with proxy stats + assert grid_statistics_details['nodes'] == statistics_details_converted['nodes'] + assert grid_statistics_details['dedicatedNodes'] == statistics_details_converted['dedicatedNodes'] + assert grid_statistics_details['farms'] == statistics_details_converted['farms'] + assert grid_statistics_details['countries'] == statistics_details_converted['countries'] + assert grid_statistics_details['totalCru'] == statistics_details_converted['totalCru'] + assert math.isclose(convert_to_scaled_float(grid_statistics_details['totalSru']), convert_to_scaled_float(byte_converter(statistics_details_converted['totalSru'])), abs_tol=0.002) + assert math.isclose(convert_to_scaled_float(grid_statistics_details['totalHru']), convert_to_scaled_float(byte_converter(statistics_details_converted['totalHru'])), abs_tol=0.002) + assert math.isclose(convert_to_scaled_float(grid_statistics_details['totalMru']), convert_to_scaled_float(byte_converter(statistics_details_converted['totalMru'])), abs_tol=0.002) + assert grid_statistics_details['gpus'] == statistics_details_converted['gpus'] + assert grid_statistics_details['accessNodes'] == statistics_details_converted['accessNodes'] + assert grid_statistics_details['gateways'] == statistics_details_converted['gateways'] + assert grid_statistics_details['twins'] == statistics_details_converted['twins'] + assert grid_statistics_details['publicIps'] == statistics_details_converted['publicIps'] + assert grid_statistics_details['contracts'] == statistics_details_converted['contracts'] + assert grid_statistics_details['workloads_number'] == statistics_details_converted['workloads_number'] + + +def test_tfgrid_links(browser): + """ + TC2867 - Verify TFGrid links + Steps: + - Navigate to the dashboard. + - Click on TFGrid from side menu. + - Click on Grid Status. + - Click on Node Monitoring. + Result: Assert that The links match the pages. + """ + statistics_page = before_test_setup(browser) + assert statistics_page.grid_status_link() == 'https://status.grid.tf/status/threefold/' + assert statistics_page.node_monitoring_link() == 'https://metrics.grid.tf/d/rYdddlPWkfqwf/zos-host-metrics?orgId=2&refresh=30s/' \ No newline at end of file diff --git a/packages/playground/tests/frontend_selenium/utils/grid_proxy.py b/packages/playground/tests/frontend_selenium/utils/grid_proxy.py index 6ead105842..55ee69ba7f 100644 --- a/packages/playground/tests/frontend_selenium/utils/grid_proxy.py +++ b/packages/playground/tests/frontend_selenium/utils/grid_proxy.py @@ -62,7 +62,36 @@ def get_twin_node(self, twin_id): details = r.json() return details + def get_stats(self): - r = requests.post('https://stats.' + Base.net + '.grid.tf/api/stats-summary') - stats_json = r.json() - return list(stats_json.values()) \ No newline at end of file + up = requests.get(Base.gridproxy_url + 'stats?status=up', timeout=10).json() + standby = requests.get(Base.gridproxy_url + 'stats?status=standby', timeout=10).json() + # Initialize a dictionary to store the merged data + merged_data = {} + # Merge simple values, summing if they differ + keys_to_sum = ['nodes', 'accessNodes', 'totalCru', 'totalSru', 'totalMru', 'totalHru', 'gpus', 'dedicatedNodes', 'workloads_number'] + for key in keys_to_sum: + merged_data[key] = up[key] + standby[key] + # Merge the "farms", "publicIps", "gateways", "twins", and "contracts" fields (they are the same) + keys_to_add_once = ['farms', 'publicIps', 'gateways', 'twins', 'contracts'] + for key in keys_to_add_once: + merged_data[key] = up[key] + # Merge nodesDistribution and calculate unique and common countries + up_distribution = up['nodesDistribution'] + standby_distribution = standby['nodesDistribution'] + merged_distribution = {} + common_countries = 0 + for country, up_count in up_distribution.items(): + standby_count = standby_distribution.get(country, 0) + merged_distribution[country] = up_count + standby_count + if standby_count > 0: + common_countries += 1 + for country, standby_count in standby_distribution.items(): + if country not in merged_distribution: + merged_distribution[country] = standby_count + merged_data['nodesDistribution'] = merged_distribution + # Calculate the total countries: all unique countries minus common countries + total_countries = len(merged_distribution) # Total unique countries + merged_data['countries'] = total_countries + # Return the dictionary directly + return merged_data \ No newline at end of file diff --git a/packages/playground/tests/frontend_selenium/utils/utils.py b/packages/playground/tests/frontend_selenium/utils/utils.py index d2f717831d..89c7a17ffb 100644 --- a/packages/playground/tests/frontend_selenium/utils/utils.py +++ b/packages/playground/tests/frontend_selenium/utils/utils.py @@ -176,16 +176,34 @@ def randomize_public_ipv4(): ip_subnet = ip + '/' + random.choice(['26', '27', '28', '29']) return ip_subnet, ip + +def convert_to_scaled_float(number): + str_number = str(number) + if '.' in str_number: + decimal_index = str_number.index('.') + else: + decimal_index = len(str_number) + divisor = 10 ** decimal_index + scaled_number = number / divisor + return scaled_number + + def byte_converter(value): + # Define the unit and the numeric value before checking conditions + unit = value[-2].upper() # Last character represents the unit (P, T, G) + number_str = value[:-3].strip() # Everything except the last two characters is the number + if value != '0': - if value[-2] == 'P': - return float(value[:-3])*(1024*2) - elif value[-2] == 'T': - return float(value[:-3])*1024 - else: - return float(value[:-3]) - else: - return float(value) + # Convert based on the unit + if unit == 'P': # Petabytes + return float(number_str) * (1024 ** 5) + elif unit == 'T': # Terabytes + return float(number_str) * (1024 ** 4) + elif unit == 'G': # Gigabytes + return float(number_str) * (1024 ** 3) + + return float(value) + def get_min(nodes, resource): min = nodes[0][resource]