def page_visual_is_valid(selenium, selenium_generics: SeleniumGenerics, data_table): custom_wait = CustomWait(selenium) custom_wait.static_wait(2) selenium.execute_script( 'window.scrollTo({ left: 0, top: 0});') custom_wait.static_wait(4) base_screenshot_dir = f"{pytest.globalDict['base_screenshot_dir']}/full_page/{data_table['page_name'][0]}_{selenium_generics._file_extension()}" actual_screenshot_dir = f"{pytest.globalDict['actual_screenshot_dir']}/full_page/{data_table['page_name'][0]}_{selenium_generics._file_extension()}" diff_screenshot_dir = f"{pytest.globalDict['diff_screenshot_dir']}/full_page/{data_table['page_name'][0]}_{selenium_generics._file_extension()}" device_offset = selenium.capabilities['deviceOffset'] \ if 'deviceOffset' in selenium.capabilities else 0 elements_to_hide = { "start": data_table['start'] if data_table['start'][0] != 'None' else None, "all": data_table['all'] if data_table['all'][0] != 'None' else None, "end": data_table['end'] if data_table['end'][0] != 'None' else None, } image_cv2 = selenium.get_full_page_screenshot_as_base64(elements_to_hide, device_offset) from utils.utils import compare_images base_score = 0.999 score = compare_images(image_cv2, base_screenshot_dir, actual_screenshot_dir, diff_screenshot_dir, base_score) assert score >= base_score, f"Actual component screenshot is different from Base with {score}. Diff saved here: {diff_screenshot_dir}"
def take_element_screenshot_with_actions(selenium, selenium_generics, base_url, data_table): pytest.scenarioDict['screenshots'] = pytest.scenarioDict['screenshots'] \ if 'screenshots' in pytest.scenarioDict else [] custom_wait = CustomWait(selenium) actions = json.loads(data_table['actions']) if 'menu_button' in actions: selenium_generics.click(selector=actions['menu_button']) custom_wait.static_wait(4) actual_screenshot_dir = selenium_generics. \ get_actual_screenshot_dir(base_url, base_url, 'megamenu_0', data_table) selenium.find_element(By.XPATH, actions['menu']).screenshot(actual_screenshot_dir) pytest.scenarioDict['screenshots'].append(actual_screenshot_dir) elements = selenium.find_elements(By.XPATH, actions['hover']) if 'hover' in actions \ else selenium.find_elements(By.XPATH, actions['click']) if 'click' in actions \ else [] i = 1 for element in elements: if 'hover' in actions: selenium_generics.hover(element=element) element = element.find_element(By.XPATH, actions['expanded']) custom_wait.static_wait(2) image_b = element.screenshot_as_base64 element = selenium.find_element(By.XPATH, '//div[contains(@class, "bg-image")]') custom_wait.static_wait(2) image_a = element.screenshot_as_base64 import base64 from io import BytesIO from PIL import Image image_a = Image.open(BytesIO(base64.b64decode(image_a))) image_b = Image.open(BytesIO(base64.b64decode(image_b))) stitched_image = Image.new('RGB', (image_a.width, image_a.height + image_b.height)) stitched_image.paste(im=image_a, box=(0, 0)) stitched_image.paste(im=image_b, box=(0, image_a.height)) i = i + 1 actual_screenshot_dir = selenium_generics. \ get_actual_screenshot_dir(base_url, base_url, f'megamenu_{i}', data_table) stitched_image.save(actual_screenshot_dir) pytest.scenarioDict['screenshots'].append(actual_screenshot_dir) if 'click' in actions: selenium_generics.click(element=element) custom_wait.static_wait(2) element = element.find_element(By.XPATH, actions['expanded']) actual_screenshot_dir = selenium_generics. \ get_actual_screenshot_dir(base_url, base_url, f'megamenu_{i}', data_table) element.screenshot(actual_screenshot_dir) i = i + 1 pytest.scenarioDict['screenshots'].append(actual_screenshot_dir) if 'click' in actions: selenium_generics.click(element=selenium.find_elements(By.XPATH, actions['click'])[0])
def take_full_page_screenshot_from_sitemap_with_actions(selenium, selenium_generics, base_url, data_table): pytest.scenarioDict['screenshots'] = pytest.scenarioDict['screenshots'] \ if 'screenshots' in pytest.scenarioDict else [] pytest.scenarioDict['pdf_file_name'] = \ f"{pytest.globalDict['actual_screenshot_dir']}/full_page/{data_table['site']}_{selenium.get_window_size()['width']}_{selenium.get_window_size()['height']}.pdf" urls = pytest.scenarioDict['urls'] custom_wait = CustomWait(selenium) for url in urls: response = requests.get(url, allow_redirects=False) if response.status_code >= 300: continue selenium.get(url) custom_wait.static_wait(10) page_size = selenium_generics.page_size() if page_size['height'] < 100 or page_size['width'] < 100: continue actual_screenshot_dir = selenium_generics.get_actual_screenshot_dir(url, base_url, '01', data_table) elements_to_hide = selenium_generics.get_elements_to_hide(data_table) device_offset = selenium.capabilities['deviceOffset'] if 'deviceOffset' in selenium.capabilities else 0 if data_table['pre_action_screenshot'] == 'true': selenium.get_full_page_screenshot_as_png(actual_screenshot_dir, elements_to_hide, device_offset) pytest.scenarioDict['screenshots'].append(actual_screenshot_dir) base_screenshot_dir = actual_screenshot_dir for action in json.loads(data_table['actions']): selenium_generics.execute_action(action) custom_wait.static_wait(2) selenium.execute_script('window.scrollTo({ left: 0, top: 0});') custom_wait.static_wait(2) actual_screenshot_dir = selenium_generics.get_actual_screenshot_dir(url, base_url, '02', data_table) selenium.get_full_page_screenshot_as_png(actual_screenshot_dir, elements_to_hide, device_offset) if data_table['pre_action_screenshot'] == 'true': from utils.utils import compare_images base_score = 0.999 score = compare_images(None, base_screenshot_dir, actual_screenshot_dir, None, base_score) if score >= base_score: import os os.remove(actual_screenshot_dir) else: pytest.scenarioDict['screenshots'].append(actual_screenshot_dir)
class SeleniumGenerics: def __init__(self, selenium): self._os = re.sub(r"[\s]*", "", selenium.desired_capabilities['platformName'].lower()) \ if 'platformName' in selenium.desired_capabilities else \ re.sub(r"[\s]*", "", selenium.desired_capabilities['platform'].lower()) self._device = 'mobile' if self._os in ['android', 'ios' ] else 'desktop' self._selenium = selenium self._custom_wait = CustomWait(selenium) def click(self, selector=None, element=None): if selector is not None: element = self.__find_element(selector) self._selenium.execute_script('arguments[0].click();', element) def execute_action(self, action): if 'execute_script_on_elements' in action.keys(): elements = self.__find_elements( action['execute_script_on_elements']['elements']) for element in elements: if element.is_displayed() is True: self._custom_wait.static_wait(2) self._selenium.execute_script( action['execute_script_on_elements']['script'], element) if 'click' in action.keys(): elements = self.__find_elements(action['click']) for element in elements: if element.is_displayed() is True: self._custom_wait.static_wait(2) self.scroll_into_view(element=element) self.click(element=element) def shadow_click(self, parent_selector, shadow_path): elem = self.__find_shadow_element(parent_selector, shadow_path) self._selenium.execute_script('arguments[0].click();', elem) def get_actual_screenshot_dir(self, url, base_url, count, data_table): page_name = f"{data_table['site']}_{url.replace(base_url, '').replace('/', '_')}_{count}.png" resolution = f"{self._selenium.get_window_size()['width']}_{self._selenium.get_window_size()['height']}" return f"{pytest.globalDict['actual_screenshot_dir']}/full_page/{data_table['site']}/{resolution}/{page_name}" def get_elements_to_hide(self, data_table): start_elements = [] start_elements_locators = data_table['start'].split( ' ~ ') if data_table['start'] != 'None' else [] for start_element_locator in start_elements_locators: elements = self._selenium.find_elements(By.XPATH, start_element_locator) if elements.__len__(): start_elements.append(elements[0]) all_elements = [] all_elements_locators = data_table['all'].split( ' ~ ') if data_table['all'] != 'None' else [] for all_element_locator in all_elements_locators: elements = self._selenium.find_elements(By.XPATH, all_element_locator) if elements.__len__(): all_elements.append(elements[0]) end_elements = [] end_elements_locators = data_table['end'].split( ' ~ ') if data_table['end'] != 'None' else [] for end_element_locator in end_elements_locators: elements = self._selenium.find_elements(By.XPATH, end_element_locator) if elements.__len__(): end_elements.append(elements[0]) return { "start": start_elements, "all": all_elements, "end": end_elements, } def hover(self, selector=None, element=None): if selector is not None: element = self.__find_element(selector) actions = ActionChains(self._selenium) actions.move_to_element(element).perform() def shadow_hover(self, parent_selector, shadow_path): elem = self.__find_shadow_element(parent_selector, shadow_path) actions = ActionChains(self._selenium) actions.move_to_element(elem).perform() def navigate_to_url(self, url): self._selenium.get(url) def page_size(self): return { 'height': self._selenium.execute_script('return document.body.offsetHeight'), 'width': self._selenium.execute_script('return document.body.offsetWidth') } def set_component_style(self, component_locator, data_table): script = f'document.body.insertAdjacentHTML("beforeend", ' \ f' "<style>{component_locator} {{ ' for key in data_table: script += f' --{key}: {data_table[key]}; ' if key in data_table else '' script += f' }}</style>");' self._selenium.execute_script(script) def scroll_into_view(self, selector=None, element=None): if selector is not None: element = self.__find_element(selector) script = 'arguments[0].scrollIntoView(true)' self._selenium.execute_script(script, element) def is_element_visible(self, selector) -> bool: return self._custom_wait.wait_for_element_visible(selector) is not None def is_element_invisible(self, selector) -> bool: return self._custom_wait.wait_for_element_not_visible( selector) is not None def validate_element_style(self, selector, data_table): elem = self.__find_element(selector) self.__validate_style(elem, data_table) def validate_shadow_element_style(self, data_table): elem = self.__find_shadow_element( data_table['parent_xpath'], *data_table['shadow_path'].split(' ~ ')) data_table.pop('parent_xpath', None) data_table.pop('shadow_path', None) self.__validate_style(elem, data_table) def validate_element_text(self, selector, expected_text): elem = self.__find_element(selector) inner_text = elem.get_attribute('innerText') inner_text = re.sub(r"[\n\t]*", "", inner_text).strip() text_content = elem.get_attribute('textContent') text_content = re.sub(r"[\n\t]*", "", text_content).strip() expected_text = re.sub(r"[\n\t]*", "", str(expected_text)).strip() self._custom_wait.wait_until( lambda: inner_text == expected_text or text_content == expected_text, description= f'Element text is not correct: \nExpected text: {expected_text} ' f'\nActual inner_text: {inner_text} \nActual text_content: #{text_content}' ) def validate_page_loaded(self, page_url): self._custom_wait.wait_until( lambda: self._selenium.current_url == page_url, description= f'Page not loaded. \nExpected: {page_url} \nActual: {self._selenium.current_url}' ) self._custom_wait \ .wait_for_the_attribute_value(self._selenium.find_element(By.XPATH, '//html'), 'class', 'hydrated') self._custom_wait.static_wait(5) def validate_scroll_position(self, scroll_position): self._custom_wait.wait_until( lambda: self._selenium.execute_script("return window.pageYOffset;" ) == scroll_position, description="Scroll to position %s failed" % scroll_position) def _file_extension(self): if self._device == 'mobile': return '_%s.png' % self._os elif 'browserName' in self._selenium.desired_capabilities: if self._selenium.desired_capabilities['browserName'] in [ 'Chrome', 'chrome' ]: return '_%s_chrome.png' % self._os if self._selenium.desired_capabilities[ 'browserName'] == 'internet explorer': return '_%s_ie.png' % self._os if self._selenium.desired_capabilities[ 'browserName'] == 'MicrosoftEdge': return '_%s_edge.png' % self._os if self._selenium.desired_capabilities['browserName'] in [ 'Safari', 'safari' ]: return '_%s_safari.png' % self._os if self._selenium.desired_capabilities['browserName'] in [ 'Firefox', 'firefox' ]: return '_%s_firefox.png' % self._os return '.png' def get_text_from_element(self, xpath) -> str: element = self.__find_element(xpath) if element.get_attribute('value') in [None, ""]: actual_text = element.text else: actual_text = element.get_attribute('value') return actual_text def get_attribute_of_element(self, xpath, attribute) -> str: element = self.__find_element(xpath) return element.get_attribute(attribute) def get_css_attribute_of_element(self, xpath, attribute) -> str: element = self.__find_element(xpath) return element.value_of_css_property(attribute) def __find_element(self, xpath): return self._selenium.find_element(By.XPATH, xpath) def __find_elements(self, xpath): return self._selenium.find_elements(By.XPATH, xpath) def __find_shadow_element(self, parent_xpath, shadow_path): elem = self._selenium.find_element(By.XPATH, parent_xpath) return elem.shadow_cascade_find_element(*shadow_path.split(' ~ ')) def find_shadow_element_by_xpath_within_host(self, shadow_host: WebElement, debug: bool = False, *args) -> WebElement: """Returns an element identified by the given list of XPATH selectors within shadow DOM(s) of the provided host element Returns: WebElement: returns the element as WebElement """ script: str = 'var result = arguments[0];' js_shadow_xpath_template: str = '\n result = document.evaluate("{}", result.shadowRoot.firstChild, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;' for xpath_locator in args: script += js_shadow_xpath_template.format(xpath_locator) script += '\n return result;' if debug: print('recursive js shadow xpath evaluator script - ' + script) return self._selenium.execute_script(script, shadow_host) def find_shadow_element_by_xpath(self, debug: bool = False, *args) -> WebElement: """Returns an element identified by the given list of XPATH selectors. The first element in the list should be part of the main DOM Every other element in the list except the last one should be a shadow DOM host The last element in the list is treated as a normal element Returns: WebElement: returns the element as WebElement """ script: str = 'var result = document.evaluate("{}", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;'.format( args[0]) js_shadow_xpath_template: str = '\n result = document.evaluate("{}", result.shadowRoot.firstChild, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;' for xpath_locator in args[1:]: script += js_shadow_xpath_template.format(xpath_locator) script += '\n return result;' if debug: print('recursive js shadow xpath evaluator script - ' + script) return self._selenium.execute_script(script) @staticmethod def __validate_style(elem, data_table): error_messages = [] for key, value in data_table.items(): if key == 'font-family': if value.lower() not in elem.value_of_css_property( key).lower(): error_messages.append( f'\n{key} not correct. \nActual: {elem.value_of_css_property(key)} \nExpected: {value}' ) else: if elem.value_of_css_property( key).lower() not in value.lower(): error_messages.append( f'\n{key} not correct. \nActual: {elem.value_of_css_property(key)} \nExpected: {value}' ) assert error_messages.__len__( ) == 0, f'Element style failed with errors: {"".join(error_messages)}'