284 lines
7.9 KiB
Python
284 lines
7.9 KiB
Python
# 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 refresh(self):
|
|
self.driver.refresh()
|
|
|
|
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", "abcd1234")
|
|
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_select(self, fieldname, text):
|
|
elem = self.find(xpath='//select[@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, for_invisible=False):
|
|
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:
|
|
if not for_invisible:
|
|
elem = self.get_wait(timeout).until(
|
|
EC.presence_of_element_located((_by, selector)))
|
|
else:
|
|
elem = self.get_wait(timeout).until(
|
|
EC.invisibility_of_element_located((_by, selector)))
|
|
return elem
|
|
except Exception as e:
|
|
# body = self.driver.find_element_by_id('body_div')
|
|
# print(body.get_attribute('innerHTML'))
|
|
self.print_console()
|
|
raise e
|
|
|
|
def wait_for_invisible(self, selector=None, everywhere=False, timeout=20, xpath=None):
|
|
self.wait_for(selector, everywhere, timeout, xpath, True)
|
|
|
|
def get_console(self):
|
|
out = []
|
|
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)
|
|
|
|
out.append(source + ' ' + line_no)
|
|
out.append(message)
|
|
out.append('-'*40)
|
|
|
|
return out
|
|
|
|
def print_console(self):
|
|
for line in self.get_console():
|
|
print(line)
|
|
|
|
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, freeze = False):
|
|
self.wait_for('body[data-ajax-state="complete"]', True)
|
|
if freeze:
|
|
self.wait_for_invisible(".freeze-message-container")
|
|
|
|
|
|
# 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)
|
|
#
|
|
#
|