erplibre/script/selenium/selenium_lib.py
Mathieu Benoit e353d10f23 [IMP] script selenium: refactor to support different project
- add scrolling for button and text
- improve navigation with generated word
- support video recording of Selenium's actions
2024-08-25 00:23:57 -04:00

450 lines
16 KiB
Python

#!/usr/bin/env python3
from randomwordfr import RandomWordFr
import re
import os
import sys
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.firefox.service import Service
from subprocess import getoutput
from selenium.webdriver import ActionChains
from selenium.webdriver.common.actions.wheel_input import ScrollOrigin
class SeleniumLib(object):
def __init__(self, config):
self.config = config
self.video_recorder = None
self.filename = "video_" + time.strftime("%Y_%m_%d-%H_%M_%S") + ".webm"
self.driver = None
def configure(self):
# Configuration pour lancer Firefox en mode de navigation privée
firefox_options = webdriver.FirefoxOptions()
if not self.config.not_private_mode:
firefox_options.add_argument("--private")
# firefox_options.set_preference("browser.link.open_newwindow", 3)
# firefox_options.set_preference("browser.link.open_newwindow.restriction", 2)
# Définissez la préférence pour les notifications de bureau
# 1 signifie "autoriser", 2 signifie "bloquer"
firefox_options.set_preference(
"permissions.default.desktop-notification", 1
)
firefox_services = None
if self.config.firefox_binary_path:
firefox_services = Service(
executable_path=self.config.firefox_binary_path
)
if self.config.gecko_binary_path:
firefox_options.binary_location = self.config.gecko_binary_path
# Créez une instance du navigateur Firefox avec les options de navigation privée
try:
self.driver = webdriver.Firefox(
options=firefox_options, service=firefox_services
)
except Exception:
print(
"Cannot open Firefox profile, so will force firefox snap for"
" Ubuntu users."
)
firefox_services = Service(
executable_path=getoutput(
"find /snap/firefox -name geckodriver"
).split("\n")[-1]
)
firefox_options.binary_location = getoutput(
"find /snap/firefox -name firefox"
).split("\n")[-1]
self.driver = webdriver.Firefox(
options=firefox_options, service=firefox_services
)
# Ajout de l'enregistrement
if self.config.record_mode:
new_path = os.path.normpath(
os.path.join(os.path.dirname(__file__), "..", "..")
)
sys.path.append(new_path)
from script.selenium.selenium_video import VideoRecorder
self.video_recorder = VideoRecorder(
self.driver, filename=self.filename
)
# import vlc
# Install DarkReader to help my eyes
# TODO do a script to check if it's the last version
if not self.config.no_dark_mode:
self.driver.install_addon(
"./script/selenium/darkreader-firefox.xpi", temporary=True
)
self.driver.install_addon(
"./script/selenium/odoo_debug-4.0.xpi", temporary=True
)
if not self.config.not_private_mode and not self.config.no_dark_mode:
self.driver.get("about:addons")
# Enable Dark Reader into incognito
self.click("/html/body/div/div[1]/categories-box/button[2]")
self.click(
"/html/body/div/div[2]/div/addon-list/section[1]/addon-card/div/div/div/div/button",
)
self.click(
"/html/body/div/div[2]/div/addon-list/section[1]/addon-card/div/addon-options/panel-list/panel-item[5]",
)
self.click(
"/html/body/div/div[2]/div/addon-card/div/addon-details/named-deck/section/div[5]/div/label[1]/input",
)
# Enable Odoo_debug into incognito
# Back
self.click(
"/html/body/div/div[2]/addon-page-header/div/div[2]/button"
)
self.click(
"/html/body/div/div[2]/div/addon-list/section[1]/addon-card[2]/div/div/div/div/button",
)
self.click(
"/html/body/div/div[2]/div/addon-list/section[1]/addon-card[2]/div/addon-options/panel-list/panel-item[5]",
)
self.click(
"/html/body/div/div[2]/div/addon-card/div/addon-details/named-deck/section/div[5]/div/label[1]/input",
)
# Ouvrez la page web
self.driver.get(f"{self.config.url}/web")
# Close tab opening by DarkReader
if self.config.not_private_mode and not self.config.no_dark_mode:
self.driver.switch_to.window(self.driver.window_handles[-1])
self.driver.close()
self.driver.switch_to.window(self.driver.window_handles[0])
@staticmethod
def get_french_word_no_space_no_accent():
word = ""
rw = RandomWordFr()
while not word or " " in word:
word = rw.get().get("word")
if not re.match("^[a-zA-Z_]+$", word):
word = ""
return word
def click(self, xpath, timeout=5):
wait = WebDriverWait(self.driver, timeout)
button = wait.until(
EC.element_to_be_clickable(
(
By.XPATH,
xpath,
)
)
)
button.click()
def get_element(self, by: str = By.ID, value: str = None, timeout=5):
wait = WebDriverWait(self.driver, timeout)
ele = wait.until(
EC.visibility_of_any_elements_located(
(
by,
value,
)
)
)
return ele[0]
def click_with_mouse_move(
self,
by: str = By.ID,
value: str = None,
timeout: int = 5,
no_scroll: bool = False,
viewport_ele_by: str = By.ID,
viewport_ele_value: str = None,
):
# ele = self.driver.find_element(by, value)
ele = self.get_element(by, value, timeout)
if not no_scroll:
viewport_ele = None
if viewport_ele_value:
viewport_ele = self.get_element(
viewport_ele_by, viewport_ele_value, timeout
)
self.scrollto_element(ele, viewport_ele=viewport_ele)
ActionChains(self.driver).move_to_element(ele).perform()
time.sleep(self.config.selenium_default_delay)
wait = WebDriverWait(self.driver, timeout)
button = wait.until(
EC.element_to_be_clickable(
(
by,
value,
)
)
)
button.click()
def input_text_with_mouse_move(
self,
by: str = By.ID,
value: str = None,
text_value: str = "",
delay_after: int = -1,
timeout: int = 5,
no_scroll: bool = False,
viewport_ele_by: str = By.ID,
viewport_ele_value: str = None,
):
# ele = self.driver.find_element(by, value)
ele = self.get_element(by, value)
if not no_scroll:
viewport_ele = None
if viewport_ele_value:
viewport_ele = self.get_element(
viewport_ele_by, viewport_ele_value, timeout
)
self.scrollto_element(ele, viewport_ele=viewport_ele)
ActionChains(self.driver).move_to_element(ele).perform()
ele.send_keys(text_value)
if delay_after > 0:
time.sleep(delay_after)
elif self.config.selenium_default_delay > 0:
time.sleep(self.config.selenium_default_delay)
def scrollto_element1(self, element_name):
# ele = self.driver.find_element(By.TAG_NAME, element_name)
ele = self.get_element(By.TAG_NAME, element_name)
# delta_y = ele.rect["y"]
ActionChains(self.driver).scroll_to_element(ele).perform()
# ele = self.get_element(By.TAG_NAME, element_name)
# delta_y = ele.rect["y"]
# ActionChains(self.driver).scroll_by_amount(0, delta_y).perform()
time.sleep(0.5)
def scrollto_xpath5(self, xpath):
footer = self.driver.find_element(By.XPATH, xpath)
# footer = self.get_element(By.XPATH, xpath)
delta_y = footer.rect["y"]
ActionChains(self.driver).scroll_by_amount(0, int(delta_y)).perform()
time.sleep(0.5)
def scrollto_xpath2(self, xpath):
footer = self.driver.find_element(By.XPATH, xpath)
# footer = self.get_element(By.XPATH, xpath)
scroll_origin = ScrollOrigin.from_element(footer)
ActionChains(self.driver).scroll_from_origin(
scroll_origin, 0, 100
).perform()
time.sleep(0.5)
def scrollto_xpath3(self, xpath):
footer = self.driver.find_element(By.XPATH, xpath)
# footer = self.get_element(By.XPATH, xpath)
scroll_origin = ScrollOrigin.from_element(footer)
ActionChains(self.driver).scroll_to_element(scroll_origin).perform()
time.sleep(0.5)
def scrollto_xpath(
self,
xpath,
offset_middle=True,
smooth_scroll=True,
smooth_speed=50,
smooth_fps=1 / 25,
):
footer = self.driver.find_element(By.XPATH, xpath)
# footer = self.get_element(By.XPATH, xpath)
scroll_origin = ScrollOrigin.from_viewport(10, 10)
delta_y = footer.rect["y"]
if offset_middle:
# TODO check size windows and divide by 2
delta_y -= 400
print(f"scroll to xpath at y:{delta_y} position")
if smooth_scroll:
actual_pos = 0
while delta_y > actual_pos:
actual_pos += smooth_speed
print(actual_pos)
ActionChains(self.driver).scroll_from_origin(
scroll_origin, 0, int(smooth_speed)
).perform()
print(f"Wait {smooth_fps} seconds")
time.sleep(smooth_fps)
print("end")
else:
ActionChains(self.driver).scroll_from_origin(
scroll_origin, 0, int(delta_y)
).perform()
time.sleep(0.5)
def scrollto_element(
self,
element,
offset_middle=True,
smooth_scroll=True,
smooth_speed=50,
smooth_fps=1 / 25,
viewport_ele=None,
):
if viewport_ele:
scroll_origin = ScrollOrigin.from_element(viewport_ele, 10, 10)
else:
scroll_origin = ScrollOrigin.from_viewport(10, 10)
delta_y = element.rect["y"]
if offset_middle:
# TODO check size windows and divide by 2
delta_y -= 400
print(f"scroll to xpath at y:{delta_y} position")
if smooth_scroll:
actual_pos = 0
while delta_y > actual_pos:
actual_pos += smooth_speed
print(actual_pos)
ActionChains(self.driver).scroll_from_origin(
scroll_origin, 0, int(smooth_speed)
).perform()
print(f"Wait {smooth_fps} seconds")
time.sleep(smooth_fps)
print("end")
else:
ActionChains(self.driver).scroll_from_origin(
scroll_origin, 0, int(delta_y)
).perform()
# action = ActionChains(self.driver)
# action.click_and_hold(element).move_by_offset(0, 1).perform()
# # Don't forget to release the click_and_hold, for example with:
# action.reset_actions()
time.sleep(0.5)
def inject_cursor(self):
cursor_script = """
var cursor = document.createElement('div');
cursor.style.position = 'absolute';
cursor.style.zIndex = '9999';
cursor.style.width = '30px';
cursor.style.height = '30px';
cursor.style.borderRadius = '50%';
cursor.style.backgroundColor = 'red';
cursor.style.pointerEvents = 'none';
document.body.appendChild(cursor);
document.addEventListener('mousemove', function(e) {
cursor.style.left = e.pageX - 5 + 'px';
cursor.style.top = e.pageY - 5 + 'px';
});
"""
self.driver.execute_script(cursor_script)
def open_tab(self, url):
# Open a new window
self.driver.execute_script("window.open('');")
# Switch to the new window and open new URL
self.driver.switch_to.window(self.driver.window_handles[1])
self.driver.get(url)
def check_bot_chat_and_close(self):
try:
xpath_button = "/html/body/div[3]/div[1]/span[2]/a[2]"
button = self.driver.find_element(By.XPATH, xpath_button)
# button = self.get_element(By.XPATH, xpath_button)
self.click(self.driver, xpath_button)
except Exception:
print("Chatbot cannot be found, stop searching it.")
def start_record(self):
# Démarrer l'enregistrement
if self.config.record_mode:
self.video_recorder.start()
def stop_record(self):
time.sleep(self.config.selenium_default_delay_presentation)
# Arrêter l'enregistrement
if self.config.record_mode:
time.sleep(self.config.selenium_video_time_waiting_end)
self.video_recorder.stop()
print("End of recording video")
if self.config.selenium_video_auto_play_video:
print(f"Play video {self.filename}")
# By python-vlc
# media_player = vlc.MediaPlayer(filename)
# media = vlc.Media(filename)
# media_player.set_media(media)
# media_player.play()
# time.sleep(15)
# By external process
os.popen(f"vlc {self.filename}")
def fill_parser(parser):
parser.add_argument(
"--url",
default="http://127.0.0.1:8069",
help="First URL to open.",
)
group_animation = parser.add_argument_group(title="Animation")
group_animation.add_argument(
"--selenium_default_delay",
default=0.4,
type=float,
help="Pause at the end before close video.",
)
group_browser = parser.add_argument_group(title="Browser")
group_browser.add_argument(
"--not_private_mode",
action="store_true",
help="Default is private mode.",
)
group_browser.add_argument(
"--no_dark_mode",
action="store_true",
help=(
"By default, will be in dark mode, because the main developer"
" like it!"
),
)
group_browser.add_argument(
"--firefox_binary_path",
help="Can specify firefox path to open selenium.",
)
group_browser.add_argument(
"--gecko_binary_path",
help="Can specify firefox path to open selenium.",
)
group_record = parser.add_argument_group(title="Recording")
group_record.add_argument(
"--record_mode",
action="store_true",
help="Start recording (not finish to be implemented).",
)
group_record.add_argument(
"--selenium_video_time_waiting_end",
default=5,
type=int,
help="Pause at the end before close video.",
)
group_record.add_argument(
"--selenium_default_delay_presentation",
default=2,
type=float,
help="Pause at the end before close video.",
)
group_record.add_argument(
"--selenium_video_auto_play_video",
action="store_true",
help="Autoplay the video when done.",
)