# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt from __future__ import unicode_literals, print_function from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait #from selenium.webdriver.support.select import Select from selenium.webdriver.support import expected_conditions as EC #from selenium.common.exceptions import TimeoutException from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.desired_capabilities import DesiredCapabilities import time import signal import os, sys import frappe from ast import literal_eval class TestDriver(object): def __init__(self, port='8000'): self.port = port chrome_options = Options() capabilities = DesiredCapabilities.CHROME if os.environ.get('CI'): self.host = 'localhost' else: self.host = frappe.local.site # enable browser logging capabilities['loggingPrefs'] = {'browser':'ALL'} chrome_options.add_argument('--no-sandbox') chrome_options.add_argument('--start-maximized') self.driver = webdriver.Chrome(chrome_options=chrome_options, desired_capabilities=capabilities, port=9515) self.driver.set_window_size(1080,800) self.cur_route = None self.logged_in = False @property def localhost(self): return "http://{host}:{port}".format(host=self.host, port=self.port) def get(self, url): return self.driver.get(os.path.join(self.localhost, url)) def start(self): def signal_handler(signal, frame): self.close() sys.exit(0) signal.signal(signal.SIGINT, signal_handler) def close(self): if self.driver: self.driver.quit() self.driver = None def login(self, wait_for_id="#page-desktop"): if self.logged_in: return self.get('login') self.wait_for("#login_email") self.set_input("#login_email", "Administrator") self.set_input("#login_password", "admin") self.click('.btn-login') self.wait_for(wait_for_id) self.logged_in = True def set_input(self, selector, text, key=None, xpath=None): elem = self.find(selector, xpath=xpath)[0] elem.clear() elem.send_keys(text) if key: time.sleep(0.5) elem.send_keys(key) time.sleep(0.2) def set_field(self, fieldname, text): elem = self.find(xpath='//input[@data-fieldname="{0}"]'.format(fieldname)) elem[0].send_keys(text) def set_text_editor(self, fieldname, text): elem = self.find(xpath='//div[@data-fieldname="{0}"]//div[@contenteditable="true"]'.format(fieldname)) elem[0].send_keys(text) def find(self, selector=None, everywhere=False, xpath=None): if xpath: return self.driver.find_elements_by_xpath(xpath) else: if self.cur_route and not everywhere: selector = self.cur_route + " " + selector return self.driver.find_elements_by_css_selector(selector) def wait_for(self, selector=None, everywhere=False, timeout=20, xpath=None): if self.cur_route and not everywhere: selector = self.cur_route + " " + selector time.sleep(0.5) if selector: _by = By.CSS_SELECTOR if xpath: _by = By.XPATH selector = xpath try: elem = self.get_wait(timeout).until( EC.presence_of_element_located((_by, selector))) return elem except Exception, e: # body = self.driver.find_element_by_id('body_div') # print(body.get_attribute('innerHTML')) self.print_console() raise e def print_console(self): for entry in self.driver.get_log('browser'): source, line_no, message = entry.get('message').split(' ', 2) if message[0] in ('"', "'"): # message is a quoted/escaped string message = literal_eval(message) print(source + ' ' + line_no) print(message) print('-'*40) def get_wait(self, timeout=20): return WebDriverWait(self.driver, timeout) def scroll_to(self, selector): self.execute_script("frappe.ui.scroll('{0}')".format(selector)) def set_route(self, *args): self.execute_script('frappe.set_route({0})'\ .format(', '.join(['"{0}"'.format(r) for r in args]))) self.wait_for(xpath='//div[@data-page-route="{0}"]'.format('/'.join(args)), timeout=4) def click(self, css_selector, xpath=None): self.wait_till_clickable(css_selector, xpath).click() def click_primary_action(self): selector = ".page-actions .primary-action" #self.scroll_to(selector) self.wait_till_clickable(selector).click() self.wait_for_ajax() def click_secondary_action(self): selector = ".page-actions .btn-secondary" #self.scroll_to(selector) self.wait_till_clickable(selector).click() self.wait_for_ajax() def click_modal_primary_action(self): self.get_visible_modal().find_element_by_css_selector('.btn-primary').click() def get_visible_modal(self): return self.get_visible_element('.modal-content') def get_visible_element(self, selector=None, xpath=None): for elem in self.find(selector=selector, xpath=xpath): if elem.is_displayed(): return elem def wait_till_clickable(self, selector=None, xpath=None): if self.cur_route: selector = self.cur_route + " " + selector by = By.CSS_SELECTOR if xpath: by = By.XPATH selector = xpath return self.get_wait().until(EC.element_to_be_clickable( (by, selector))) def execute_script(self, js): self.driver.execute_script(js) def wait_for_ajax(self): self.wait_for('body[data-ajax-state="complete"]', True) # def go_to_module(module_name, item=None): # global cur_route # # # desktop # find(".navbar-home", True)[0].click() # cur_route = None # wait("#page-desktop") # # page = "Module/" + module_name # m = find('#page-desktop [data-link="{0}"] .app-icon'.format(page)) # if not m: # page = "List/" + module_name # m = find('#page-desktop [data-link="{0}"] .app-icon'.format(page)) # if not m: # raise Exception("Module {0} not found".format(module_name)) # # m[0].click() # wait_for_page(page) # # if item: # elem = find('[data-label="{0}"]'.format(item))[0] # elem.click() # page = elem.get_attribute("data-route") # wait_for_page(page) # # def new_doc(module, doctype): # go_to_module(module, doctype) # primary_action() # wait_for_page("Form/" + doctype) # # def add_child(fieldname): # find('[data-fieldname="{0}"] .grid-add-row'.format(fieldname))[0].click() # wait('[data-fieldname="{0}"] .form-grid'.format(fieldname)) # # def done_add_child(fieldname): # selector = '[data-fieldname="{0}"] .grid-row-open .btn-success'.format(fieldname) # scroll_to(selector) # wait_till_clickable(selector).click() # # def set_field(fieldname, value, fieldtype="input"): # _driver.switch_to.window(_driver.current_window_handle) # selector = '{0}[data-fieldname="{1}"]'.format(fieldtype, fieldname) # set_input(selector, value, key=Keys.TAB) # wait_for_ajax() # # def set_select(fieldname, value): # select = Select(find('select[data-fieldname="{0}"]'.format(fieldname))[0]) # select.select_by_value(value) # wait_for_ajax() # # # def wait_for_page(name): # global cur_route # cur_route = None # route = '[data-page-route="{0}"]'.format(name) # wait_for_ajax() # elem = wait(route) # wait_for_ajax() # cur_route = route # return elem # # # def wait_till_visible(selector): # if cur_route: # selector = cur_route + " " + selector # return get_wait().until(EC.visibility_of_element_located((By.CSS_SELECTOR, selector))) # # # def wait_for_state(state): # return wait(cur_route + '[data-state="{0}"]'.format(state), True) # #