diff --git a/packages/playground/tests/frontend_selenium/Config.ini b/packages/playground/tests/frontend_selenium/Config.ini deleted file mode 100644 index 2222b381f4..0000000000 --- a/packages/playground/tests/frontend_selenium/Config.ini +++ /dev/null @@ -1,8 +0,0 @@ -[Base] -port = 5173 -net = dev -[Utils] -seed = -node_seed = -address = -email = \ No newline at end of file diff --git a/packages/playground/tests/frontend_selenium/pages/farm.py b/packages/playground/tests/frontend_selenium/pages/farm.py index 780df58fbd..4469dd2f8c 100644 --- a/packages/playground/tests/frontend_selenium/pages/farm.py +++ b/packages/playground/tests/frontend_selenium/pages/farm.py @@ -98,14 +98,21 @@ def search_functionality(self, farm_name): return table def search_functionality_invalid_name(self, farm_name): - sleep(2) - self.browser.find_element(*self.search_bar).send_keys(Keys.CONTROL + "a") - self.browser.find_element(*self.search_bar).send_keys(Keys.DELETE) - for char in farm_name: - self.browser.find_element(*self.search_bar).send_keys(char) - table = self.browser.find_element(*self.table).text - sleep(3) - return table + # sleep(2) + # self.browser.find_element(*self.search_bar).send_keys(Keys.CONTROL + "a") + # self.browser.find_element(*self.search_bar).send_keys(Keys.DELETE) + # for char in farm_name: + # self.browser.find_element(*self.search_bar).send_keys(char) + # table = self.browser.find_element(*self.table).text + # sleep(3) + # return table + self.wait_until_visible(self.search_bar) + self.browser.find_element(*self.search_bar).clear() + self.browser.find_element(*self.search_bar).send_keys(farm_name) + WebDriverWait(self.browser, 30).until( + lambda driver: "No data available" in driver.find_element(*self.table).text or farm_name in driver.find_element(*self.table).text + ) + return self.browser.find_element(*self.table).text def display_all_farms(self): self.browser.execute_script("window.scrollTo(0,document.body.scrollHeight)") diff --git a/packages/playground/tests/frontend_selenium/selenium.md b/packages/playground/tests/frontend_selenium/selenium.md deleted file mode 100644 index 92ab10f270..0000000000 --- a/packages/playground/tests/frontend_selenium/selenium.md +++ /dev/null @@ -1,44 +0,0 @@ -# Selenium - -| Prerequisites | version | -| ---------------------------------------------------------------- | -------- | -| [Python](https://www.python.org/downloads/) | `3.10.4` | -| [pytest](https://pypi.org/project/pytest/) | `7.4.0` | -| [requests](https://pypi.org/project/requests/) | `2.31.0` | -| [selenium](https://pypi.org/project/selenium/) | `4.10.0` | -| [PyVirtualDisplay](https://pypi.org/project/PyVirtualDisplay/) | `3.0` | -| [webdriver-manager](https://pypi.org/project/webdriver-manager/) | `3.9.1` | - -## Running selenium - -### First - -- In the root directory, run `yarn install`, then `yarn lerna run build`, and finally `make run project=playground`. -- Change directory to frontend selenium by running `cd packages/playground/tests/frontend_selenium/` in the command line. -- Install the recommended version of the pip package listed above for a stable run, or you can just install Python 3 and use the command: - - `pip install -r requirements.txt --break-system-packages` (Use this if you don't use any of the listed packages). - - Or use Virtual Environments: First, create an environment using `python -m venv myenv`, then activate it using `source myenv/bin/activate`, and finally, install packages using `pip install -r requirements.txt`. -- Add your configuration either in [config.ini](../frontend_selenium/Config.ini) or by exporting `TFCHAIN_MNEMONICS`, `TFCHAIN_NODE_MNEMONICS`, `STELLAR_ADDRESS`, and `EMAIL`. -- Description of config under `Base` section: - - `port`: the port that the localhost is running on. - - `net`: the network that the tests will be run against. -- Description of config under `Utils` section: - - `seed`: twin mnemonic that will be used for all the automated tests. - - `node_seed`: twin mnemonic for the node that will be used in node-related tests. - - `address`: a stellar address with a TFT trustline that will be used in TFT bridge-related tests. - - `email`: a valid email that will be used for all the automated tests. -- If the port in serve changes from `5173` for any reason, you should update the `port` under the `Base` section in [config.ini](../frontend_selenium/Config.ini) to reflect the new value. -- You'll also need to install `Xvfb`, Run `sudo apt install xvfb`. - -### Second - -- You need to leave the localhost running and open a new terminal. -- You can run selenium tests with pytest through the command line using `python3 -m pytest -v`. - -### More options to run tests - -- If you want to run the tests visually to see how they are running, you need to comment out the lines `16` and `33` in the [conftest.py](../frontend_selenium/tests/conftest.py). -- You can also run single test file through the command line using `python3 -m pytest -v tests/file/test_file.py`. -- You can also run specific test cases through the command line using `python3 -m pytest -v tests/file/test_file.py::test_func`. -- You can also run collection of test cases through the command line using `python3 -m pytest -v -k 'test_func or test_func'`. -- You can also run all the tests and get an HTML report using [pytest-html](https://pypi.org/project/pytest-html/) package through the command line using `python3 -m pytest -v --html=report.html`. diff --git a/packages/playground/tests/frontend_selenium/tests/Farms/test_farm.py b/packages/playground/tests/frontend_selenium/tests/Farms/test_farm.py index 9d28ff27f8..6c754facc5 100644 --- a/packages/playground/tests/frontend_selenium/tests/Farms/test_farm.py +++ b/packages/playground/tests/frontend_selenium/tests/Farms/test_farm.py @@ -156,7 +156,9 @@ def test_farmpayout_address(browser): assert farm_page.wait_for_farm_name(farm_name) farm_page.search_functionality("") farm_page.setup_farmpayout_address(farm_name) - farm_page.wait_for_button(browser.find_element(*farm_page.add_v2_button)).click() + self.browser.execute_script("arguments[0].scrollIntoView();", self.browser.find_element(*farm_page.add_v2_button)) + self.wait_for_no_overlay() + self.wait_until_clickable(farm_page.add_v2_button).click() cases = [' ', 'dgdd', generate_string(), 'gdhjP6TF3UXYXTNEZ2P36J5FH7W4BJJQ4AYYAXC66I2Q2AH5B6O6Bcfg'] for case in cases: assert farm_page.add_farmpayout_address(case).is_enabled()==False @@ -166,7 +168,9 @@ def test_farmpayout_address(browser): assert farm_page.wait_for('Address Added successfully!') assert farm_page.farmpayout_address_value()[:-3] in case case = "GAK2AN6ZC4REV2GXZPTMJG2JKLRJQX746JNG7ACKNC4RSJE7ETAZSE7D" - farm_page.wait_for_button(browser.find_element(*farm_page.add_v2_button)).click() + self.browser.execute_script("arguments[0].scrollIntoView();", self.browser.find_element(*farm_page.add_v2_button)) + self.wait_for_no_overlay() + self.wait_until_clickable(farm_page.add_v2_button).click() farm_page.wait_for_button(farm_page.add_farmpayout_address(case)).click() assert farm_page.wait_for('This action will be reflected in a bit') assert farm_page.wait_for('Address Added successfully!') @@ -215,8 +219,8 @@ def test_ip(browser): cases = [gateway, '2.0.0.1', '3.0.0.0', '139.255.255.255', '59.15.35.78'] for case in cases: farm_page.setup_gateway('125.25.25.25/16', case, farm_name, False) - assert farm_page.wait_for('Gateway IP not in the provided IP range.') - assert browser.find_element(*farm_page.save_button).is_enabled()==False + assert farm_page.wait_for('Gateway IP not in the provided IP range.'), f"Expected error message for {case}" + assert not browser.find_element(*farm_page.save_button).is_enabled(), "Save button should be disabled" farm_page.close_ip() farm_page.setup_gateway(gateway, gateway, farm_name, False) assert farm_page.wait_for('IPs cannot be the same.') @@ -300,6 +304,8 @@ def test_range_ips(browser): assert farm_page.wait_for('Not a valid IP') assert browser.find_element(*farm_page.save_button).is_enabled()==False farm_page.add_range_ips('255.0.0.1/32', 0, 0).is_enabled() + assert farm_page.wait_for('IP is not public'), "Expected 'IP is not public' error" + assert not browser.find_element(*farm_page.save_button).is_enabled(), "Save button should be disabled for invalid IP" assert farm_page.wait_for('IP is not public') assert browser.find_element(*farm_page.save_button).is_enabled()==False assert farm_page.wait_for_button(farm_page.add_range_ips('1.1.1.254/16', '1.1.1.255/16', '1.1.1.1')).is_enabled()==True diff --git a/packages/playground/tests/frontend_selenium/tests/TFChain/test_homepage.py b/packages/playground/tests/frontend_selenium/tests/TFChain/test_homepage.py index 4433076b39..217620918e 100644 --- a/packages/playground/tests/frontend_selenium/tests/TFChain/test_homepage.py +++ b/packages/playground/tests/frontend_selenium/tests/TFChain/test_homepage.py @@ -24,7 +24,11 @@ def test_validate_homepage_links(browser): """ dashboard_page = before_test_setup(browser) assert dashboard_page.navigate_to_find_more() == ('https://threefold.io/') - assert dashboard_page.navigate_to_explore_capacity() == ( 'https://stats.' + Base.net + '.grid.tf/') + if Base.net == 'main': + stats_url = 'https://stats.grid.tf/' + else: + stats_url = 'https://stats.' + Base.net + '.grid.tf/' + assert dashboard_page.navigate_to_explore_capacity() == ( stats_url ) assert dashboard_page.navigate_to_learn_about_grid() == ('https://www.manual.grid.tf/') diff --git a/packages/playground/tests/frontend_selenium/tests/conftest.py b/packages/playground/tests/frontend_selenium/tests/conftest.py deleted file mode 100644 index 50e29c43d8..0000000000 --- a/packages/playground/tests/frontend_selenium/tests/conftest.py +++ /dev/null @@ -1,33 +0,0 @@ -import pytest -from selenium import webdriver -from webdriver_manager.chrome import ChromeDriverManager -from selenium.webdriver.chrome.service import Service -from pyvirtualdisplay import Display - -""" -This module contains shared browser fixtures. -""" - -@pytest.fixture -def browser(): - - # Virtual display for the browser, allowing it to run in headless mode - display = Display(visible=0, size=(1920, 1080)) - display.start() - - # Initialize the ChromeDriver instance with options - options = webdriver.ChromeOptions() - #options.add_extension('extension.crx') # For Adding Extension - driver = webdriver.Chrome(options=options) - driver.set_window_size(1920, 1080) - - # Make its calls wait up to 60 seconds for elements to appear - driver.implicitly_wait(60) - - # Return the WebDriver instance for the setup - yield driver - - # Quit the WebDriver instance for the cleanup - driver.quit() - # Ending virtual display for the browser - display.stop() \ No newline at end of file diff --git a/packages/playground/tests/frontend_selenium/utils/base.py b/packages/playground/tests/frontend_selenium/utils/base.py deleted file mode 100644 index b9f632e1de..0000000000 --- a/packages/playground/tests/frontend_selenium/utils/base.py +++ /dev/null @@ -1,9 +0,0 @@ -import configparser - -class Base: - config = configparser.ConfigParser() - config.read('Config.ini') - port = config['Base']['port'] - net = config['Base']['net'] - base_url = 'http://localhost:' + str(port) + '/' - gridproxy_url = 'https://gridproxy.' + str(net) + '.grid.tf/' \ 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 deleted file mode 100644 index b7f3a9b1af..0000000000 --- a/packages/playground/tests/frontend_selenium/utils/grid_proxy.py +++ /dev/null @@ -1,101 +0,0 @@ -import requests -from utils.base import Base - -""" -This module contains Grid Proxy getters. -""" - -class GridProxy: - - def __init__(self, browser): - self.browser = browser - - def get_rentable_node(self): - r = requests.post(Base.gridproxy_url + 'nodes?rentable=true&status=up') - node_list = r.json() - r = requests.post(Base.gridproxy_url + 'nodes?rented=true&status=up') - node_list.extend(r.json()) - return node_list - - def get_farm_details(self, farm_name): - r = requests.post(Base.gridproxy_url + 'farms?name=' + farm_name) - details = r.json() - return details - - def get_dedicate_status(self, node_id): - r = requests.post(Base.gridproxy_url + 'nodes/'+ str(node_id)) - dedicate_status = r.json() - return (dedicate_status['rentedByTwinId']) - - def get_node_ipv4(self, node_id): - r = requests.post(Base.gridproxy_url + 'nodes/'+ str(node_id)) - farm_node = r.json() - return (farm_node['publicConfig']['ipv4']) - - def get_node_fee(self, node_id): - r = requests.post(Base.gridproxy_url + 'nodes/'+ str(node_id)) - farm_node = r.json() - return (farm_node['extraFee'])/1000 - - def get_twin_address(self, twin_id): - r = requests.post(Base.gridproxy_url + 'twins?twin_id='+ twin_id) - details = r.json() - return details[0]['accountId'] - - def get_twin_relay(self, twin_id): - r = requests.post(Base.gridproxy_url + 'twins?twin_id='+ twin_id) - details = r.json() - return details[0]['relay'] - - def get_farm_ips(self, farm_id): - r = requests.post(Base.gridproxy_url + 'farms?farm_id='+ farm_id) - farm_list = r.json() - return len(farm_list[0]['publicIps']) - - def get_twin_node(self, twin_id): - r = requests.post(Base.gridproxy_url + 'farms?twin_id=' + twin_id) - details = r.json() - farms = '' - for detail in details: - farms += str(detail['farmId']) + ',' - r = requests.post(Base.gridproxy_url + 'nodes?farm_ids=' + farms[:-1]) - details = r.json() - return details - - def get_stats_capicity(self): - r = requests.post('https://stats.' + Base.net + '.grid.tf/api/stats-summary', timeout=10) - stats_json = r.json() - return list(stats_json.values()) - - def get_stats(self): - 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