diff --git a/Dockerfile b/Dockerfile index f2d546b..996adfc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,22 @@ # https://github.com/joyzoursky/docker-python-chromedriver # https://github.com/TomRoush/python-selenium-firefox-docker -FROM tomroush/python-selenium-firefox-docker:latest +FROM ubuntu:22.04 AS selenium + +ARG DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y \ + python3 python3-pip \ + # firefox needs the following installed on 22.04 + libgtk-3-common libasound2 libdbus-glib-1-2 \ + firefox \ + xvfb \ + && rm -rf /var/lib/apt/lists/* + +RUN pip3 install --no-cache-dir selenium requests + +FROM selenium ENV SELENIUM_HEADLESS=true +ARG DEBIAN_FRONTEND=noninteractive ADD page_perf_timer.py /opt/page_timer/ diff --git a/page_perf_timer.py b/page_perf_timer.py index c839ff1..262e0c3 100644 --- a/page_perf_timer.py +++ b/page_perf_timer.py @@ -1,10 +1,13 @@ import argparse import functools +import hashlib import os import sys import time import uuid +import requests + # Generated by Selenium IDE from selenium import webdriver from selenium.common.exceptions import NoSuchElementException @@ -38,6 +41,7 @@ class EndStepReached(Exception): """ Raised when a specific action step has been reached """ + pass @@ -127,7 +131,7 @@ def wait_for_history_panel_to_load(self): @clock_action("login_page_load") def load_galaxy_login(self): # Open Galaxy window - self.driver.get(f"{self.server}/login") + self.driver.get(f"{self.server}login") # Wait for username entry to appear self.wait.until(self.is_able_to_login) @@ -154,10 +158,51 @@ def login_to_galaxy_homepage(self): # Wait for tool panel to load self.wait.until( expected_conditions.presence_of_element_located( - (By.XPATH, "//div[@class='tool-panel-section']//a[@class='title-link' and contains(., 'Get Data')]") + ( + By.XPATH, + "//div[@class='tool-panel-section']//a[contains(@class, 'title-link') and contains(., 'Get Data')]", + ) + ) + ) + + @clock_action("dummy_file_upload") + def upload_dummy_file(self): + upload_activity = self.driver.find_element(By.ID, "activity-upload") + upload_activity.click() + # paste/fetch data + paste_button = self.driver.find_element(By.ID, "btn-new") + paste_button.click() + # paste/fetch data + upload_row = self.driver.find_element(By.XPATH, "//div[@id='upload-row-0']//textarea") + upload_row.send_keys("https://zenodo.org/records/13711466/files/(Galaxy%20History)%20Selenium_test_1_Input_data.rocrate.zip?download=1") + # start + start_button = self.driver.find_element(By.ID, "btn-start") + start_button.click() + # close + close_button = self.driver.find_element(By.ID, "btn-close") + close_button.click() + # wait for history item to appear + self.wait.until( + expected_conditions.presence_of_element_located( + ( + By.XPATH, + "//div[@data-index='0']//div[@data-state='running' and contains(., 'Selenium_test_1_Input_data')]", + ) ) ) + @clock_action("dummy_file_download") + def download_dummy_file(self): + open_download_link = self.driver.find_element(By.XPATH, "//div[@data-index='0']//div[@data-state='running' and contains(., 'Selenium_test_1_Input_data')]") + open_download_link.click() + with SeleniumCustomWait(self.driver, 1200): + download_link = self.driver.find_element(By.XPATH, "//div[@data-index='0']//div[@data-state='ok' and contains(., 'Selenium_test_1_Input_data')]//a[@title='Download']") + r = requests.get(download_link.get_attribute("href"), stream=True) + sig = hashlib.md5() + for line in r.iter_lines(): + sig.update(line) + assert sig.hexdigest() == "a62aaaefa04bdc7acd3b29a127bba3e6" + @clock_action("tool_search_load") def search_for_tool(self): # Select tool search box @@ -190,38 +235,52 @@ def load_tool_form(self): expected_conditions.presence_of_element_located((By.ID, "execute")) ) - @clock_action("shared_histories_page_load") - def load_shared_histories(self): + @clock_action("published_histories_page_load") + def load_published_histories(self): # Request history page - self.driver.get(f"{self.server}/histories/list_shared") + self.driver.get(f"{self.server}histories/list_published") # Wait for history page to load self.wait.until( expected_conditions.presence_of_element_located( - (By.XPATH, "//h2[contains(., 'Histories shared with you by others')]") + ( + By.XPATH, + "//li[@id='histories-published-tab' and contains(., 'Public Histories')]", + ) ) ) self.wait_for_history_panel_to_load() - @clock_action("import_shared_history") - def import_shared_history(self): + @clock_action("import_published_history") + def import_published_history(self): + # Search for the relevant history + search_history_input = self.driver.find_element( + By.XPATH, + f"//div[@id='histories-published-grid']//input[@placeholder='search histories']", + ) + search_history_input.click() + search_history_input.send_keys(f"{self.workflow_name.lower()}_input_data") + # Select relevant history import_history_btn = self.driver.find_element( By.XPATH, - f"//tbody[@id='grid-table-body']//button[contains(translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), '{self.workflow_name.lower()}_input_data')]", + f"//table[@class='grid-table']//button[contains(translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), '{self.workflow_name.lower()}_input_data')]", ) - import_history_btn.click() - self.wait_for_history_panel_to_load() + # Workaround for ElementClickInterceptedException + self.driver.execute_script("arguments[0].click();", import_history_btn) # View history details - view_history_menu_item = self.driver.find_element( + view_history_menu_item = import_history_btn.find_element( By.XPATH, - f"//div[@id='{import_history_btn.get_attribute('id')}-menu']//a[contains(., 'View')]", + f"./following-sibling::div//button[contains(@data-description, 'grid operation view')]", ) view_history_menu_item.click() self.wait.until( expected_conditions.presence_of_element_located( - (By.XPATH, f"//h3[contains(., '{self.workflow_name.lower()}_input_data')]") + ( + By.XPATH, + f"//h3[contains(translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), '{self.workflow_name.lower()}_input_data')]", + ) ) ) # Invoke copy history dialogue @@ -239,6 +298,20 @@ def import_shared_history(self): By.XPATH, f"//button[contains(., 'Copy History')]", ).click() + + # activate the history + self.wait.until( + expected_conditions.presence_of_element_located( + ( + By.XPATH, + f"//div[@class='alert alert-info' and contains(., 'History imported and is now your active history')]", + ) + ) + ) + + # Request history page + self.driver.get(f"{self.server}histories/list") + # Wait for history panel to load with new history self.wait.until( expected_conditions.presence_of_element_located( @@ -252,20 +325,37 @@ def import_shared_history(self): @clock_action("workflow_list_page_load") def load_workflow_list(self): # Request workflows list page - self.driver.get(f"{self.server}/workflows/list") + self.driver.get(f"{self.server}workflows/list_published") # Wait for workflow page to load and import button to appear self.wait.until( - expected_conditions.presence_of_element_located((By.ID, "workflow-import")) + expected_conditions.presence_of_element_located( + (By.XPATH, "//li[@id='published' and contains(., 'Public workflows')]") + ) ) @clock_action("workflow_run_page_load") def load_workflow_run_form(self): - # Select relevant workflow - run_workflow_btn = self.driver.find_element( + # Search for the relevant history + search_workflow_input = self.driver.find_element( By.XPATH, - f"//table[@id='workflow-table']//tr[contains(., '{self.workflow_name}')]//button[@title='Run workflow']", + f"//div[@id='workflow-list-filter']//input", ) - run_workflow_btn.click() + search_workflow_input.click() + search_workflow_input.send_keys(f"{self.workflow_name.lower()}") + + # wait for list to be filtered + self.wait.until( + lambda d: len( + d.find_elements(By.CSS_SELECTOR, "#workflow-cards .workflow-card") + ) + == 1 + ) + + # Select relevant workflow + run_workflow_btn = self.driver.find_element(By.ID, "workflow-run-button") + # Workaround for ElementClickInterceptedException + self.driver.execute_script("arguments[0].click();", run_workflow_btn) + # Wait for workflow form to load and run button to appear self.wait.until( expected_conditions.presence_of_element_located((By.ID, "run-workflow")) @@ -277,13 +367,13 @@ def run_workflow(self): # Select relevant choice input_1_select = self.driver.find_element( By.XPATH, - "//a[@class='select2-choice' and contains(., 'Subsample of reads from')]", + "//div[@class='ui-form-composite']//input/following-sibling::span[contains(., 'Subsample of reads from Human exome R2')][1]", ) input_1_select.click() # Select relevant choice input_1_select = self.driver.find_element( By.XPATH, - "//div[@id='select2-drop']//ul/li/div[contains(., 'Subsample of reads from human exome R1')]", + "//div[@class='ui-form-composite']//ul[@role='listbox']//li[@role='option']//span[contains(., 'Subsample of reads from human exome R1')]", ) input_1_select.click() workflow_wait = 14400 @@ -293,55 +383,55 @@ def run_workflow(self): # Select relevant choice input_1_select = self.driver.find_element( By.XPATH, - "//div[@data-label='Forward Reads']//a[@class='select2-choice']", + "//div[@class='ui-form-composite']//input/following-sibling::span[contains(., 'ERR019289_2.fastq.gz')][1]", ) input_1_select.click() # Select relevant choice input_1_select = self.driver.find_element( By.XPATH, - "//div[@id='select2-drop']//ul/li/div[contains(., 'ERR019289_1.fastq.gz')]", + "//div[@class='ui-form-composite']//ul[@role='listbox']//li[@role='option']//span[contains(., 'ERR019289_1.fastq.gz')]", ) input_1_select.click() workflow_wait = 14400 elif self.workflow_name == "Selenium_test_4": - input_select = self.driver.find_element( + input_1_select = self.driver.find_element( By.XPATH, - "//div[@data-label='ARTIC primers to amplicon assignments']//a[@class='select2-choice']", + "//div[@class='ui-form-composite']//div[@data-label='ARTIC primers to amplicon assignments']//input/following-sibling::span[contains(., 'NC_045512.2.fna.fasta')][1]", ) - input_select.click() + input_1_select.click() # Select relevant choice - input_select = self.driver.find_element( + input_1_select = self.driver.find_element( By.XPATH, - "//div[@id='select2-drop']//ul/li/div[contains(., 'ARTIC_SARS_CoV-2_amplicon_info_v3.tsv')]", + "//div[@class='ui-form-composite']//div[@data-label='ARTIC primers to amplicon assignments']//ul[@role='listbox']//li[@role='option']//span[contains(., 'ARTIC_SARS_CoV-2_amplicon_info_v3.tsv')]", ) - input_select.click() + input_1_select.click() workflow_wait = 14400 else: raise Exception(f"Workflow name not in known list: {self.workflow_name}") # Run the workflow self.driver.find_element(By.ID, "run-workflow").click() - # Wait for view reports button to appear (workflow to finish) - with SeleniumCustomWait(self.driver, workflow_wait): - self.wait.until( - expected_conditions.presence_of_element_located( - ( - By.XPATH, - "//div[@id='center']//a[contains(@class, 'invocation-report-link') and contains(., 'View Report')]", - ) - ) - ) + + # wait for the running message to appear + loading_xpath = "//div[@id='center']//div[@role='tabpanel']//div[@role='alert']//span[@data-description='loading message' and contains(., 'Waiting to complete invocation')]" + self.wait.until(expected_conditions.presence_of_element_located((By.XPATH, loading_xpath))) + + # Wait for running message to disappear + custom_wait = WebDriverWait(self.driver, workflow_wait) + custom_wait.until(expected_conditions.invisibility_of_element_located((By.XPATH, loading_xpath))) def run_test_sequence(self): self.load_galaxy_login() self.login_to_galaxy_homepage() self.search_for_tool() self.load_tool_form() - self.load_shared_histories() - self.import_shared_history() + self.load_published_histories() + self.import_published_history() self.load_workflow_list() self.load_workflow_run_form() self.run_workflow() + self.upload_dummy_file() + self.download_dummy_file() def measure_timings(self): self.timings = {} diff --git a/registration_email/registration_email_perf_timer.py b/registration_email/registration_email_perf_timer.py index 46cc7e2..954ecf9 100644 --- a/registration_email/registration_email_perf_timer.py +++ b/registration_email/registration_email_perf_timer.py @@ -146,14 +146,16 @@ def register_new_account(self): def delete_test_account(self): gi = galaxy.GalaxyInstance(url=self.server, key=self.api_key) user = gi.users.get_users(f_name=self.username)[0] - if user['email'] == self.email: - gi.users.delete_user(user['id']) - gi.users.delete_user(user['id'], purge=True) + if user["email"] == self.email: + gi.users.delete_user(user["id"]) + gi.users.delete_user(user["id"], purge=True) @tenacity.retry( retry=tenacity.retry_if_result(lambda result: not result), wait=tenacity.wait_fixed(int(os.environ.get("IMAP_POLL_SECONDS", 10))), - stop=tenacity.stop_after_attempt(int(os.environ.get("IMAP_MAX_POLL_ATTEMPTS", 12))), + stop=tenacity.stop_after_attempt( + int(os.environ.get("IMAP_MAX_POLL_ATTEMPTS", 12)) + ), retry_error_callback=(lambda _: False), ) def verify_email_received(self):