Repository URL to install this package:
|
Version:
2.4.3 ▾
|
#-----------------------------------------------------------------------------
# Copyright (c) 2012 - 2022, Anaconda, Inc., and Bokeh Contributors.
# All rights reserved.
#
# The full license is in the file LICENSE.txt, distributed with this software.
#-----------------------------------------------------------------------------
''' Provide tools for executing Selenium tests.
'''
#-----------------------------------------------------------------------------
# Boilerplate
#-----------------------------------------------------------------------------
from __future__ import annotations
import logging # isort:skip
log = logging.getLogger(__name__)
#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
# Standard library imports
from typing import (
TYPE_CHECKING,
Any,
List,
Sequence,
Set,
)
# External imports
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.wait import WebDriverWait
if TYPE_CHECKING:
from selenium.webdriver.common.keys import _KeySeq
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.remote.webelement import WebElement
# Bokeh imports
from bokeh.models import Button
from bokeh.util.serialization import make_id
if TYPE_CHECKING:
from bokeh.models.callbacks import Callback
#-----------------------------------------------------------------------------
# Globals and constants
#-----------------------------------------------------------------------------
__all__ = (
'alt_click',
'ButtonWrapper',
'copy_table_rows',
'COUNT',
'drag_range_slider',
'drag_slider',
'element_to_finish_resizing',
'element_to_start_resizing',
'enter_text_in_cell',
'enter_text_in_cell_with_click_enter',
'enter_text_in_element',
'get_page_element',
'get_slider_bar_color',
'get_slider_title_text',
'get_slider_title_value',
'get_table_cell',
'get_table_column_cells',
'get_table_header',
'get_table_row',
'get_table_selected_rows',
'hover_element',
'INIT',
'paste_values',
'RECORD',
'RESULTS',
'SCROLL',
'select_element_and_press_key',
'shift_click',
'sort_table_column',
'wait_for_canvas_resize',
)
#-----------------------------------------------------------------------------
# General API
#-----------------------------------------------------------------------------
def COUNT(key: str) -> str:
return 'Bokeh._testing.count(%r);' % key
INIT = 'Bokeh._testing.init();'
def RECORD(key: str, value: Any, *, final: bool = True) -> str:
if final:
return 'Bokeh._testing.record(%r, %s);' % (key, value)
else:
return 'Bokeh._testing.record0(%r, %s);' % (key, value)
RESULTS = 'return Bokeh._testing.results'
def SCROLL(amt: float) -> str:
return """
const elt = document.getElementsByClassName("bk-canvas-events")[0];
const event = new WheelEvent('wheel', { deltaY: %f, clientX: 100, clientY: 100} );
elt.dispatchEvent(event);
""" % amt
def alt_click(driver: WebDriver, element: WebElement) -> None:
actions = ActionChains(driver)
actions.key_down(Keys.META)
actions.click(element)
actions.key_up(Keys.META)
actions.perform()
class ButtonWrapper:
def __init__(self, label: str, callback: Callback) -> None:
self.ref = "button-" + make_id()
self.obj = Button(label=label, css_classes=[self.ref])
self.obj.js_on_event('button_click', callback)
def click(self, driver: WebDriver) -> None:
button = driver.find_element_by_css_selector(".%s .bk-btn" % self.ref)
button.click()
class element_to_start_resizing:
''' An expectation for checking if an element has started resizing
'''
def __init__(self, element: WebElement) -> None:
self.element = element
self.previous_width = self.element.size['width']
def __call__(self, driver: WebDriver) -> bool:
current_width = self.element.size['width']
if self.previous_width != current_width:
return True
else:
self.previous_width = current_width
return False
class element_to_finish_resizing:
''' An expectation for checking if an element has finished resizing
'''
def __init__(self, element: WebElement) -> None:
self.element = element
self.previous_width = self.element.size['width']
def __call__(self, driver: WebDriver) -> bool:
current_width = self.element.size['width']
if self.previous_width == current_width:
return True
else:
self.previous_width = current_width
return False
def select_element_and_press_key(driver: WebDriver, element: WebElement, key: _KeySeq, press_number: int = 1) -> None:
actions = ActionChains(driver)
actions.move_to_element(element)
actions.click()
for _ in range(press_number):
actions = ActionChains(driver)
actions.send_keys_to_element(element, key)
actions.perform()
def hover_element(driver: WebDriver, element: WebElement) -> None:
hover = ActionChains(driver).move_to_element(element)
hover.perform()
def enter_text_in_element(driver: WebDriver, element: WebElement, text: str,
click: int = 1, enter: bool = True, mod: _KeySeq | None = None) -> None:
actions = ActionChains(driver)
actions.move_to_element(element)
if click == 1: actions.click()
elif click == 2: actions.double_click()
if enter:
text += Keys.ENTER
if mod:
actions.key_down(mod)
actions.send_keys(text)
if mod:
actions.key_up(mod)
actions.perform()
def enter_text_in_cell(driver: WebDriver, cell: WebElement, text: str) -> None:
actions = ActionChains(driver)
actions.move_to_element(cell)
actions.double_click() # start editing a cell
actions.click() # XXX: perhaps sleep() would also work; not required when interacting manually
actions.double_click() # select all text and overwrite it in the next step
actions.send_keys(text + Keys.ENTER)
actions.perform()
def enter_text_in_cell_with_click_enter(driver: WebDriver, cell: WebElement, text: str) -> None:
actions = ActionChains(driver)
actions.move_to_element(cell)
actions.click()
actions.send_keys(Keys.ENTER + text + Keys.ENTER)
actions.perform()
def copy_table_rows(driver: WebDriver, rows: Sequence[int]) -> None:
actions = ActionChains(driver)
row = get_table_row(driver, rows[0])
actions.move_to_element(row)
actions.click()
actions.key_down(Keys.SHIFT)
for r in rows[1:]:
row = get_table_row(driver, r)
actions.move_to_element(row)
actions.click()
actions.key_up(Keys.SHIFT)
actions.key_down(Keys.CONTROL)
actions.send_keys(Keys.INSERT)
actions.key_up(Keys.CONTROL)
# actions.send_keys(Keys.CONTROL, 'c')
actions.perform()
def paste_values(driver: WebDriver, el: WebElement | None = None) -> None:
actions = ActionChains(driver)
if el:
actions.move_to_element(el)
actions.key_down(Keys.SHIFT)
actions.send_keys(Keys.INSERT)
actions.key_up(Keys.SHIFT)
# actions.send_keys(Keys.CONTROL, 'v')
actions.perform()
def get_table_column_cells(driver: WebDriver, col: int) -> List[str]:
result = []
grid = driver.find_element_by_css_selector('.grid-canvas')
rows = grid.find_elements_by_css_selector(".slick-row")
for row in rows:
elt = row.find_element_by_css_selector('.slick-cell.l%d.r%d' % (col, col))
result.append(elt.text)
return result
def get_table_row(driver: WebDriver, row: int) -> WebElement:
return driver.find_element_by_css_selector('.grid-canvas .slick-row:nth-child(%d)' % row)
def get_table_selected_rows(driver: WebDriver) -> Set[int]:
result = set()
grid = driver.find_element_by_css_selector('.grid-canvas')
rows = grid.find_elements_by_css_selector(".slick-row")
for i, row in enumerate(rows):
elt = row.find_element_by_css_selector('.slick-cell.l1.r1')
if 'selected' in elt.get_attribute('class'):
result.add(i)
return result
def get_table_cell(driver: WebDriver, row: int, col: int) -> WebElement:
return driver.find_element_by_css_selector('.grid-canvas .slick-row:nth-child(%d) .r%d' % (row, col))
def get_table_header(driver: WebDriver, col: int) -> WebElement:
return driver.find_element_by_css_selector('.slick-header-columns .slick-header-column:nth-child(%d)' % col)
def get_page_element(driver: WebDriver, element_selector: str) -> WebElement:
return driver.find_element_by_css_selector(element_selector)
def shift_click(driver: WebDriver, element: WebElement) -> None:
actions = ActionChains(driver)
actions.key_down(Keys.SHIFT)
actions.click(element)
actions.key_up(Keys.SHIFT)
actions.perform()
def sort_table_column(driver: WebDriver, col: int, double: bool = False) -> None:
elt = driver.find_element_by_css_selector('.slick-header-columns .slick-header-column:nth-child(%d)' % col)
elt.click()
if double: elt.click()
def wait_for_canvas_resize(canvas: WebElement, test_driver: WebDriver) -> None:
'''
'''
try:
wait = WebDriverWait(test_driver, 1)
wait.until(element_to_start_resizing(canvas))
wait.until(element_to_finish_resizing(canvas))
except TimeoutException:
# Resize may or may not happen instantaneously,
# Put the waits in to give some time, but allow test to
# try and process.
pass
def drag_slider(driver: WebDriver, css_class: str, distance: float, release: bool = True) -> None:
el = driver.find_element_by_css_selector(css_class)
handle = el.find_element_by_css_selector('.noUi-handle')
actions = ActionChains(driver)
actions.move_to_element(handle)
actions.click_and_hold()
actions.move_by_offset(distance, 0)
if release:
actions.release()
actions.perform()
def drag_range_slider(driver: WebDriver, css_class: str, location: str, distance: float) -> None:
el = driver.find_element_by_css_selector(css_class)
handle = el.find_element_by_css_selector('.noUi-handle-' + location)
actions = ActionChains(driver)
actions.move_to_element(handle)
actions.click_and_hold()
actions.move_by_offset(distance, 0)
actions.release()
actions.perform()
def get_slider_title_text(driver: WebDriver, css_class: str) -> str:
el = driver.find_element_by_css_selector(css_class)
return el.find_element_by_css_selector('div.bk-input-group > div.bk-slider-title').text
def get_slider_title_value(driver: WebDriver, css_class: str) -> str:
el = driver.find_element_by_css_selector(css_class)
return el.find_element_by_css_selector('div.bk-input-group > div > span.bk-slider-value').text
def get_slider_bar_color(driver: WebDriver, css_class: str) -> str:
el = driver.find_element_by_css_selector(css_class)
bar = el.find_element_by_css_selector('.noUi-connect')
return bar.value_of_css_property('background-color')
#-----------------------------------------------------------------------------
# Dev API
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Private API
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Code
#-----------------------------------------------------------------------------