|
|
@@ -0,0 +1,471 @@
|
|
|
+from selenium.webdriver.common.by import By
|
|
|
+from selenium.webdriver.support.ui import WebDriverWait
|
|
|
+from selenium.webdriver.support import expected_conditions as EC
|
|
|
+from selenium.webdriver.common.action_chains import ActionChains
|
|
|
+from selenium.webdriver.common.keys import Keys
|
|
|
+from selenium.common.exceptions import TimeoutException, NoSuchElementException, ElementNotVisibleException
|
|
|
+import time
|
|
|
+
|
|
|
+
|
|
|
+class SeleniumBase:
|
|
|
+ def __init__(self, driver):
|
|
|
+ """
|
|
|
+ 初始化Selenium基础操作类
|
|
|
+ :param driver: WebDriver实例
|
|
|
+ """
|
|
|
+ self.driver = driver
|
|
|
+ self.wait = WebDriverWait(self.driver, 10) # 默认等待10秒
|
|
|
+
|
|
|
+ def set_implicit_wait(self, seconds):
|
|
|
+ """设置隐式等待时间"""
|
|
|
+ self.driver.implicitly_wait(seconds)
|
|
|
+
|
|
|
+ def set_explicit_wait(self, seconds):
|
|
|
+ """设置显式等待时间"""
|
|
|
+ self.wait = WebDriverWait(self.driver, seconds)
|
|
|
+
|
|
|
+ # ==================== 浏览器操作 ====================
|
|
|
+ def open_url(self, url):
|
|
|
+ """打开指定URL"""
|
|
|
+ self.driver.get(url)
|
|
|
+
|
|
|
+ def get_current_url(self):
|
|
|
+ """获取当前URL"""
|
|
|
+ return self.driver.current_url
|
|
|
+
|
|
|
+ def get_page_title(self):
|
|
|
+ """获取页面标题"""
|
|
|
+ return self.driver.title
|
|
|
+
|
|
|
+ def refresh_page(self):
|
|
|
+ """刷新页面"""
|
|
|
+ self.driver.refresh()
|
|
|
+
|
|
|
+ def navigate_forward(self):
|
|
|
+ """浏览器前进"""
|
|
|
+ self.driver.forward()
|
|
|
+
|
|
|
+ def navigate_back(self):
|
|
|
+ """浏览器后退"""
|
|
|
+ self.driver.back()
|
|
|
+
|
|
|
+ def maximize_window(self):
|
|
|
+ """最大化窗口"""
|
|
|
+ self.driver.maximize_window()
|
|
|
+
|
|
|
+ def set_window_size(self, width, height):
|
|
|
+ """设置窗口大小"""
|
|
|
+ self.driver.set_window_size(width, height)
|
|
|
+
|
|
|
+ def get_window_size(self):
|
|
|
+ """获取窗口大小"""
|
|
|
+ return self.driver.get_window_size()
|
|
|
+
|
|
|
+ def switch_to_frame(self, frame_reference):
|
|
|
+ """切换到指定frame"""
|
|
|
+ self.driver.switch_to.frame(frame_reference)
|
|
|
+
|
|
|
+ def switch_to_default_content(self):
|
|
|
+ """切换回默认内容"""
|
|
|
+ self.driver.switch_to.default_content()
|
|
|
+
|
|
|
+ def switch_to_window(self, window_handle):
|
|
|
+ """切换到指定窗口"""
|
|
|
+ self.driver.switch_to.window(window_handle)
|
|
|
+
|
|
|
+ def switch_to_newest_window(self):
|
|
|
+ """切换到最新打开的窗口"""
|
|
|
+ handles = self.driver.window_handles
|
|
|
+ self.driver.switch_to.window(handles[-1])
|
|
|
+ return handles[-1]
|
|
|
+
|
|
|
+ def close_current_window(self):
|
|
|
+ """关闭当前窗口"""
|
|
|
+ self.driver.close()
|
|
|
+
|
|
|
+ def get_all_window_handles(self):
|
|
|
+ """获取所有窗口句柄"""
|
|
|
+ return self.driver.window_handles
|
|
|
+
|
|
|
+ def get_current_window_handle(self):
|
|
|
+ """获取当前窗口句柄"""
|
|
|
+ return self.driver.current_window_handle
|
|
|
+
|
|
|
+ def execute_script(self, script, *args):
|
|
|
+ """执行JavaScript脚本"""
|
|
|
+ return self.driver.execute_script(script, *args)
|
|
|
+
|
|
|
+ def scroll_to_element(self, element):
|
|
|
+ """滚动到指定元素"""
|
|
|
+ self.execute_script("arguments[0].scrollIntoView();", element)
|
|
|
+
|
|
|
+ def scroll_to_bottom(self):
|
|
|
+ """滚动到页面底部"""
|
|
|
+ self.execute_script("window.scrollTo(0, document.body.scrollHeight);")
|
|
|
+
|
|
|
+ def scroll_to_top(self):
|
|
|
+ """滚动到页面顶部"""
|
|
|
+ self.execute_script("window.scrollTo(0, 0);")
|
|
|
+
|
|
|
+ def take_screenshot(self, filename):
|
|
|
+ """截取屏幕截图"""
|
|
|
+ self.driver.save_screenshot(filename)
|
|
|
+
|
|
|
+ # ==================== 元素定位 ====================
|
|
|
+ def find_element(self, by, value, timeout=None):
|
|
|
+ """
|
|
|
+ 查找元素(带等待)
|
|
|
+ :param by: 定位方式 (By.ID, By.XPATH, By.CSS_SELECTOR, etc.)
|
|
|
+ :param value: 定位值
|
|
|
+ :param timeout: 超时时间,默认使用类初始化时的等待时间
|
|
|
+ :return: WebElement
|
|
|
+ """
|
|
|
+ wait = self.wait if timeout is None else WebDriverWait(self.driver, timeout)
|
|
|
+ try:
|
|
|
+ return wait.until(EC.presence_of_element_located((by, value)))
|
|
|
+ except TimeoutException:
|
|
|
+ raise NoSuchElementException(f"元素未找到: {by}={value}")
|
|
|
+
|
|
|
+ def find_elements(self, by, value, timeout=None):
|
|
|
+ """
|
|
|
+ 查找多个元素(带等待)
|
|
|
+ :param by: 定位方式
|
|
|
+ :param value: 定位值
|
|
|
+ :param timeout: 超时时间
|
|
|
+ :return: 元素列表
|
|
|
+ """
|
|
|
+ wait = self.wait if timeout is None else WebDriverWait(self.driver, timeout)
|
|
|
+ try:
|
|
|
+ return wait.until(EC.presence_of_all_elements_located((by, value)))
|
|
|
+ except TimeoutException:
|
|
|
+ return []
|
|
|
+
|
|
|
+ def find_visible_element(self, by, value, timeout=None):
|
|
|
+ """
|
|
|
+ 查找可见元素
|
|
|
+ :param by: 定位方式
|
|
|
+ :param value: 定位值
|
|
|
+ :param timeout: 超时时间
|
|
|
+ :return: WebElement
|
|
|
+ """
|
|
|
+ wait = self.wait if timeout is None else WebDriverWait(self.driver, timeout)
|
|
|
+ try:
|
|
|
+ return wait.until(EC.visibility_of_element_located((by, value)))
|
|
|
+ except TimeoutException:
|
|
|
+ raise ElementNotVisibleException(f"元素不可见: {by}={value}")
|
|
|
+
|
|
|
+ def find_clickable_element(self, by, value, timeout=None):
|
|
|
+ """
|
|
|
+ 查找可点击元素
|
|
|
+ :param by: 定位方式
|
|
|
+ :param value: 定位值
|
|
|
+ :param timeout: 超时时间
|
|
|
+ :return: WebElement
|
|
|
+ """
|
|
|
+ wait = self.wait if timeout is None else WebDriverWait(self.driver, timeout)
|
|
|
+ try:
|
|
|
+ return wait.until(EC.element_to_be_clickable((by, value)))
|
|
|
+ except TimeoutException:
|
|
|
+ raise ElementNotVisibleException(f"元素不可点击: {by}={value}")
|
|
|
+
|
|
|
+ # 快捷定位方法
|
|
|
+ def by_id(self, id_value, timeout=None):
|
|
|
+ """通过ID定位元素"""
|
|
|
+ return self.find_element(By.ID, id_value, timeout)
|
|
|
+
|
|
|
+ def by_name(self, name_value, timeout=None):
|
|
|
+ """通过Name定位元素"""
|
|
|
+ return self.find_element(By.NAME, name_value, timeout)
|
|
|
+
|
|
|
+ def by_xpath(self, xpath_value, timeout=None):
|
|
|
+ """通过XPath定位元素"""
|
|
|
+ return self.find_element(By.XPATH, xpath_value, timeout)
|
|
|
+
|
|
|
+ def by_css(self, css_value, timeout=None):
|
|
|
+ """通过CSS选择器定位元素"""
|
|
|
+ return self.find_element(By.CSS_SELECTOR, css_value, timeout)
|
|
|
+
|
|
|
+ def by_class(self, class_value, timeout=None):
|
|
|
+ """通过Class定位元素"""
|
|
|
+ return self.find_element(By.CLASS_NAME, class_value, timeout)
|
|
|
+
|
|
|
+ def by_link_text(self, link_text, timeout=None):
|
|
|
+ """通过链接文本定位元素"""
|
|
|
+ return self.find_element(By.LINK_TEXT, link_text, timeout)
|
|
|
+
|
|
|
+ def by_partial_link_text(self, partial_link_text, timeout=None):
|
|
|
+ """通过部分链接文本定位元素"""
|
|
|
+ return self.find_element(By.PARTIAL_LINK_TEXT, partial_link_text, timeout)
|
|
|
+
|
|
|
+ def by_tag(self, tag_name, timeout=None):
|
|
|
+ """通过标签名定位元素"""
|
|
|
+ return self.find_element(By.TAG_NAME, tag_name, timeout)
|
|
|
+
|
|
|
+ # ==================== 元素操作 ====================
|
|
|
+ def click(self, by, value, timeout=None):
|
|
|
+ """点击元素"""
|
|
|
+ element = self.find_clickable_element(by, value, timeout)
|
|
|
+ element.click()
|
|
|
+
|
|
|
+ def input_text(self, by, value, text, timeout=None):
|
|
|
+ """输入文本"""
|
|
|
+ element = self.find_element(by, value, timeout)
|
|
|
+ element.clear()
|
|
|
+ element.send_keys(text)
|
|
|
+
|
|
|
+ def clear_text(self, by, value, timeout=None):
|
|
|
+ """清除文本"""
|
|
|
+ element = self.find_element(by, value, timeout)
|
|
|
+ element.clear()
|
|
|
+
|
|
|
+ def get_text(self, by, value, timeout=None):
|
|
|
+ """获取元素文本"""
|
|
|
+ element = self.find_element(by, value, timeout)
|
|
|
+ return element.text
|
|
|
+
|
|
|
+ def get_attribute(self, by, value, attribute_name, timeout=None):
|
|
|
+ """获取元素属性"""
|
|
|
+ element = self.find_element(by, value, timeout)
|
|
|
+ return element.get_attribute(attribute_name)
|
|
|
+
|
|
|
+ def is_displayed(self, by, value, timeout=None):
|
|
|
+ """检查元素是否显示"""
|
|
|
+ try:
|
|
|
+ element = self.find_visible_element(by, value, timeout)
|
|
|
+ return element.is_displayed()
|
|
|
+ except (NoSuchElementException, ElementNotVisibleException):
|
|
|
+ return False
|
|
|
+
|
|
|
+ def is_enabled(self, by, value, timeout=None):
|
|
|
+ """检查元素是否启用"""
|
|
|
+ try:
|
|
|
+ element = self.find_element(by, value, timeout)
|
|
|
+ return element.is_enabled()
|
|
|
+ except NoSuchElementException:
|
|
|
+ return False
|
|
|
+
|
|
|
+ def is_selected(self, by, value, timeout=None):
|
|
|
+ """检查元素是否被选中"""
|
|
|
+ try:
|
|
|
+ element = self.find_element(by, value, timeout)
|
|
|
+ return element.is_selected()
|
|
|
+ except NoSuchElementException:
|
|
|
+ return False
|
|
|
+
|
|
|
+ def select_dropdown_by_value(self, by, value, option_value, timeout=None):
|
|
|
+ """通过值选择下拉选项"""
|
|
|
+ from selenium.webdriver.support.ui import Select
|
|
|
+ element = self.find_element(by, value, timeout)
|
|
|
+ select = Select(element)
|
|
|
+ select.select_by_value(option_value)
|
|
|
+
|
|
|
+ def select_dropdown_by_text(self, by, value, option_text, timeout=None):
|
|
|
+ """通过文本选择下拉选项"""
|
|
|
+ from selenium.webdriver.support.ui import Select
|
|
|
+ element = self.find_element(by, value, timeout)
|
|
|
+ select = Select(element)
|
|
|
+ select.select_by_visible_text(option_text)
|
|
|
+
|
|
|
+ def select_dropdown_by_index(self, by, value, index, timeout=None):
|
|
|
+ """通过索引选择下拉选项"""
|
|
|
+ from selenium.webdriver.support.ui import Select
|
|
|
+ element = self.find_element(by, value, timeout)
|
|
|
+ select = Select(element)
|
|
|
+ select.select_by_index(index)
|
|
|
+
|
|
|
+ def get_dropdown_options(self, by, value, timeout=None):
|
|
|
+ """获取下拉选项"""
|
|
|
+ from selenium.webdriver.support.ui import Select
|
|
|
+ element = self.find_element(by, value, timeout)
|
|
|
+ select = Select(element)
|
|
|
+ return [option.text for option in select.options]
|
|
|
+
|
|
|
+ def hover(self, by, value, timeout=None):
|
|
|
+ """鼠标悬停"""
|
|
|
+ element = self.find_element(by, value, timeout)
|
|
|
+ ActionChains(self.driver).move_to_element(element).perform()
|
|
|
+
|
|
|
+ def double_click(self, by, value, timeout=None):
|
|
|
+ """双击元素"""
|
|
|
+ element = self.find_element(by, value, timeout)
|
|
|
+ ActionChains(self.driver).double_click(element).perform()
|
|
|
+
|
|
|
+ def right_click(self, by, value, timeout=None):
|
|
|
+ """右键点击元素"""
|
|
|
+ element = self.find_element(by, value, timeout)
|
|
|
+ ActionChains(self.driver).context_click(element).perform()
|
|
|
+
|
|
|
+ def drag_and_drop(self, source_by, source_value, target_by, target_value, timeout=None):
|
|
|
+ """拖放元素"""
|
|
|
+ source_element = self.find_element(source_by, source_value, timeout)
|
|
|
+ target_element = self.find_element(target_by, target_value, timeout)
|
|
|
+ ActionChains(self.driver).drag_and_drop(source_element, target_element).perform()
|
|
|
+
|
|
|
+ def press_key(self, by, value, key, timeout=None):
|
|
|
+ """按下键盘按键"""
|
|
|
+ element = self.find_element(by, value, timeout)
|
|
|
+ element.send_keys(key)
|
|
|
+
|
|
|
+ def press_enter(self, by, value, timeout=None):
|
|
|
+ """按下回车键"""
|
|
|
+ self.press_key(by, value, Keys.ENTER, timeout)
|
|
|
+
|
|
|
+ def press_tab(self, by, value, timeout=None):
|
|
|
+ """按下Tab键"""
|
|
|
+ self.press_key(by, value, Keys.TAB, timeout)
|
|
|
+
|
|
|
+ def press_escape(self, by, value, timeout=None):
|
|
|
+ """按下Esc键"""
|
|
|
+ self.press_key(by, value, Keys.ESCAPE, timeout)
|
|
|
+
|
|
|
+ # ==================== 等待方法 ====================
|
|
|
+ def wait_for_element_present(self, by, value, timeout=None):
|
|
|
+ """等待元素出现"""
|
|
|
+ wait = self.wait if timeout is None else WebDriverWait(self.driver, timeout)
|
|
|
+ return wait.until(EC.presence_of_element_located((by, value)))
|
|
|
+
|
|
|
+ def wait_for_element_visible(self, by, value, timeout=None):
|
|
|
+ """等待元素可见"""
|
|
|
+ wait = self.wait if timeout is None else WebDriverWait(self.driver, timeout)
|
|
|
+ return wait.until(EC.visibility_of_element_located((by, value)))
|
|
|
+
|
|
|
+ def wait_for_element_invisible(self, by, value, timeout=None):
|
|
|
+ """等待元素不可见"""
|
|
|
+ wait = self.wait if timeout is None else WebDriverWait(self.driver, timeout)
|
|
|
+ return wait.until(EC.invisibility_of_element_located((by, value)))
|
|
|
+
|
|
|
+ def wait_for_element_clickable(self, by, value, timeout=None):
|
|
|
+ """等待元素可点击"""
|
|
|
+ wait = self.wait if timeout is None else WebDriverWait(self.driver, timeout)
|
|
|
+ return wait.until(EC.element_to_be_clickable((by, value)))
|
|
|
+
|
|
|
+ def wait_for_text_present(self, by, value, text, timeout=None):
|
|
|
+ """等待元素包含特定文本"""
|
|
|
+ wait = self.wait if timeout is None else WebDriverWait(self.driver, timeout)
|
|
|
+ return wait.until(EC.text_to_be_present_in_element((by, value), text))
|
|
|
+
|
|
|
+ def wait_for_title_contains(self, text, timeout=None):
|
|
|
+ """等待标题包含特定文本"""
|
|
|
+ wait = self.wait if timeout is None else WebDriverWait(self.driver, timeout)
|
|
|
+ return wait.until(EC.title_contains(text))
|
|
|
+
|
|
|
+ def wait_for_title_is(self, text, timeout=None):
|
|
|
+ """等待标题等于特定文本"""
|
|
|
+ wait = self.wait if timeout is None else WebDriverWait(self.driver, timeout)
|
|
|
+ return wait.until(EC.title_is(text))
|
|
|
+
|
|
|
+ def wait_for_alert_present(self, timeout=None):
|
|
|
+ """等待警告框出现"""
|
|
|
+ wait = self.wait if timeout is None else WebDriverWait(self.driver, timeout)
|
|
|
+ return wait.until(EC.alert_is_present())
|
|
|
+
|
|
|
+ # ==================== 弹窗处理 ====================
|
|
|
+ def accept_alert(self, timeout=None):
|
|
|
+ """接受警告框"""
|
|
|
+ alert = self.wait_for_alert_present(timeout)
|
|
|
+ alert.accept()
|
|
|
+
|
|
|
+ def dismiss_alert(self, timeout=None):
|
|
|
+ """取消警告框"""
|
|
|
+ alert = self.wait_for_alert_present(timeout)
|
|
|
+ alert.dismiss()
|
|
|
+
|
|
|
+ def get_alert_text(self, timeout=None):
|
|
|
+ """获取警告框文本"""
|
|
|
+ alert = self.wait_for_alert_present(timeout)
|
|
|
+ return alert.text
|
|
|
+
|
|
|
+ def input_alert_text(self, text, timeout=None):
|
|
|
+ """向警告框输入文本"""
|
|
|
+ alert = self.wait_for_alert_present(timeout)
|
|
|
+ alert.send_keys(text)
|
|
|
+ alert.accept()
|
|
|
+
|
|
|
+ # ==================== Cookie操作 ====================
|
|
|
+ def get_all_cookies(self):
|
|
|
+ """获取所有Cookie"""
|
|
|
+ return self.driver.get_cookies()
|
|
|
+
|
|
|
+ def get_cookie_by_name(self, name):
|
|
|
+ """根据名称获取Cookie"""
|
|
|
+ return self.driver.get_cookie(name)
|
|
|
+
|
|
|
+ def add_cookie(self, cookie_dict):
|
|
|
+ """添加Cookie"""
|
|
|
+ self.driver.add_cookie(cookie_dict)
|
|
|
+
|
|
|
+ def delete_cookie(self, name):
|
|
|
+ """删除指定Cookie"""
|
|
|
+ self.driver.delete_cookie(name)
|
|
|
+
|
|
|
+ def delete_all_cookies(self):
|
|
|
+ """删除所有Cookie"""
|
|
|
+ self.driver.delete_all_cookies()
|
|
|
+
|
|
|
+ # ==================== JS执行 ====================
|
|
|
+ def js_click(self, by, value, timeout=None):
|
|
|
+ """通过JS点击元素"""
|
|
|
+ element = self.find_element(by, value, timeout)
|
|
|
+ self.execute_script("arguments[0].click();", element)
|
|
|
+
|
|
|
+ def js_input_text(self, by, value, text, timeout=None):
|
|
|
+ """通过JS输入文本"""
|
|
|
+ element = self.find_element(by, value, timeout)
|
|
|
+ self.execute_script("arguments[0].value = arguments[1];", element, text)
|
|
|
+
|
|
|
+ def js_set_attribute(self, by, value, attr_name, attr_value, timeout=None):
|
|
|
+ """通过JS设置元素属性"""
|
|
|
+ element = self.find_element(by, value, timeout)
|
|
|
+ self.execute_script(f"arguments[0].setAttribute('{attr_name}', '{attr_value}');", element)
|
|
|
+
|
|
|
+ def js_remove_attribute(self, by, value, attr_name, timeout=None):
|
|
|
+ """通过JS移除元素属性"""
|
|
|
+ element = self.find_element(by, value, timeout)
|
|
|
+ self.execute_script(f"arguments[0].removeAttribute('{attr_name}');", element)
|
|
|
+
|
|
|
+ def js_scroll_to_element(self, by, value, timeout=None):
|
|
|
+ """通过JS滚动到元素"""
|
|
|
+ element = self.find_element(by, value, timeout)
|
|
|
+ self.execute_script("arguments[0].scrollIntoView(true);", element)
|
|
|
+
|
|
|
+ # ==================== 其他实用方法 ====================
|
|
|
+ def switch_to_active_element(self):
|
|
|
+ """切换到当前活动元素"""
|
|
|
+ return self.driver.switch_to.active_element
|
|
|
+
|
|
|
+ def get_page_source(self):
|
|
|
+ """获取页面源码"""
|
|
|
+ return self.driver.page_source
|
|
|
+
|
|
|
+ def wait_for_page_load(self, timeout=30):
|
|
|
+ """等待页面完全加载"""
|
|
|
+ old_page = self.driver.find_element(By.TAG_NAME, 'html')
|
|
|
+ yield
|
|
|
+ WebDriverWait(self.driver, timeout).until(
|
|
|
+ EC.staleness_of(old_page)
|
|
|
+ )
|
|
|
+
|
|
|
+ def is_alert_present(self, timeout=5):
|
|
|
+ """检查警告框是否存在"""
|
|
|
+ try:
|
|
|
+ WebDriverWait(self.driver, timeout).until(EC.alert_is_present())
|
|
|
+ return True
|
|
|
+ except TimeoutException:
|
|
|
+ return False
|
|
|
+
|
|
|
+ def wait_for_ajax_complete(self, timeout=30):
|
|
|
+ """等待AJAX请求完成"""
|
|
|
+ js_condition = "return jQuery.active == 0"
|
|
|
+ WebDriverWait(self.driver, timeout).until(
|
|
|
+ lambda driver: self.execute_script(js_condition)
|
|
|
+ )
|
|
|
+
|
|
|
+ def highlight_element(self, by, value, timeout=None, duration=3):
|
|
|
+ """高亮显示元素(用于调试)"""
|
|
|
+ element = self.find_element(by, value, timeout)
|
|
|
+ original_style = element.get_attribute("style")
|
|
|
+ self.execute_script("arguments[0].setAttribute('style', arguments[1]);",
|
|
|
+ element, "border: 2px solid red; border-style: dashed;")
|
|
|
+ time.sleep(duration)
|
|
|
+ self.execute_script("arguments[0].setAttribute('style', arguments[1]);",
|
|
|
+ element, original_style)
|