diff --git a/DrissionPage/_elements/chromium_element.py b/DrissionPage/_elements/chromium_element.py index c335efd..db43aec 100644 --- a/DrissionPage/_elements/chromium_element.py +++ b/DrissionPage/_elements/chromium_element.py @@ -9,6 +9,7 @@ from json import loads from os.path import basename from pathlib import Path from re import search +from threading import Thread from time import perf_counter, sleep from DataRecorder.tools import get_usable_path, make_valid_name @@ -594,7 +595,27 @@ class ChromiumElement(DrissionElement): self._run_js('this.focus();') return self - def hover(self, offset_x=None, offset_y=None): + def keep_pseudo_state(self, forcedPseudoClasses): + while self.__dict__.get('keep_count', 0): + self.owner.run_cdp('CSS.enable') + self.owner.run_cdp('CSS.forcePseudoState', nodeId=self._node_id, forcedPseudoClasses=forcedPseudoClasses) + + def force_pseudo_state(self, keep_state=True, forcedPseudoClasses=None): + if not forcedPseudoClasses: + forcedPseudoClasses = ['hover'] + if keep_state and self.__dict__.get('keep_count', 0) == 0: + from random import randint + x, y = self.owner.rect.size + x, y = (x, y) if x <= y else (y, x) + self.owner.actions.move_to(self, offset_x=randint(x, y), offset_y=randint(x, y), duration=.1) + self.__dict__['keep_count'] = 1 + Thread(target=self.keep_pseudo_state, args=(forcedPseudoClasses,)).start() + if self.__dict__.get('keep_count', 0) > 0 and not keep_state: + del self.__dict__['keep_count'] + return self + + def hover(self, offset_x=None, offset_y=None, keep_hover=False): + self.force_pseudo_state(keep_hover, forcedPseudoClasses=['hover']) self.owner.actions.move_to(self, offset_x=offset_x, offset_y=offset_y, duration=.1) return self @@ -641,8 +662,21 @@ class ChromiumElement(DrissionElement): txt1 = 'let tag = el.nodeName.toLowerCase();' txt3 = ''' && sib.nodeName.toLowerCase()==tag''' txt4 = ''' - if(nth>1){path = '/' + tag + '[' + nth + ']' + path;} - else{path = '/' + tag + path;}''' + let sib1 = el, chi = 0; + while (sib1) { + if (sib1.nodeType === Node.ELEMENT_NODE && sib1.nodeName.toLowerCase() === tag) { + chi += 1; + } + sib1 = sib1.nextSibling; + } + if (nth > 1) { + path = '/' + tag + '[' + nth + ']' + path; + } else if (chi > 1) { + path = '/' + tag + '[1]' + path; + } else { + path = '/' + tag + path; + } + ''' txt5 = '''return path;''' elif mode == 'css': @@ -652,7 +686,18 @@ class ChromiumElement(DrissionElement): break;} ''' txt3 = '' - txt4 = '''path = '>' + el.tagName.toLowerCase() + ":nth-child(" + nth + ")" + path;''' + txt4 = '''let tag = el.nodeName.toLowerCase(), sib1 = el, chi = 0; + while (sib1) { + if (sib1.nodeType === Node.ELEMENT_NODE && sib1.nodeName.toLowerCase() === tag) { + chi += 1; + } + sib1 = sib1.nextSibling; + } + if (nth > 1 || chi > 1) { + path = '>' + el.tagName.toLowerCase() + ":nth-child(" + nth + ")" + path; + } else { + path = '>' + el.tagName.toLowerCase() + path; + }''' txt5 = '''return path.substr(1);''' else: diff --git a/DrissionPage/_elements/chromium_element.pyi b/DrissionPage/_elements/chromium_element.pyi index 09ab107..225c30f 100644 --- a/DrissionPage/_elements/chromium_element.pyi +++ b/DrissionPage/_elements/chromium_element.pyi @@ -550,10 +550,21 @@ class ChromiumElement(DrissionElement): """使元素获取焦点""" ... - def hover(self, offset_x: int = None, offset_y: int = None) -> ChromiumElement: - """鼠标悬停,可接受偏移量,偏移量相对于元素左上角坐标。不传入offset_x和offset_y值时悬停在元素中点 + def force_pseudo_state(self, keep_state: bool = True, + forcedPseudoClasses: list[str] = ['hover']) -> ChromiumElement: + """ + 强制元素保持状态,如鼠标悬停,keep_state为True时需再次调用取消强制元素保持状态 + :param keep_state: 是否强制元素保持某个状态,默认为True + :param forcedPseudoClasses: 默认为['hover'],可用参数:active,hover,focus,focus-within,focus-visible,target + :return: None + """ + ... + + def hover(self, offset_x: int = None, offset_y: int = None, keep_hover: bool = False) -> ChromiumElement: + """鼠标悬停,可接受偏移量,偏移量相对于元素左上角坐标。不传入offset_x和offset_y值时悬停在元素中点,keep_hover为True时需再次调用取消强制hover :param offset_x: 相对元素左上角坐标的x轴偏移量 :param offset_y: 相对元素左上角坐标的y轴偏移量 + :param keep_hover: 为True时保持元素hover时的样式,为False时取消强制hover :return: None """ ... diff --git a/DrissionPage/_elements/session_element.py b/DrissionPage/_elements/session_element.py index d0cba7a..4a9aa86 100644 --- a/DrissionPage/_elements/session_element.py +++ b/DrissionPage/_elements/session_element.py @@ -155,11 +155,14 @@ class SessionElement(DrissionElement): if id_: path_str = f'>{ele.tag}#{id_}{path_str}' break - brothers = len(ele.eles(f'xpath:./preceding-sibling::*')) - path_str = f'>{ele.tag}:nth-child({brothers + 1}){path_str}' + before_brothers = len(ele.eles(f'xpath:./preceding-sibling::*')) + after_brothers = len(ele.eles(f'xpath:./following-sibling::*')) + path_str = f'>{ele.tag}:nth-child({before_brothers + 1}){path_str}' if ( + before_brothers or after_brothers) else f'>{ele.tag}{path_str}' else: - brothers = len(ele.eles(f'xpath:./preceding-sibling::{ele.tag}')) - path_str = f'/{ele.tag}[{brothers + 1}]{path_str}' if brothers > 0 else f'/{ele.tag}{path_str}' + before_brothers = len(ele.eles(f'xpath:./preceding-sibling::{ele.tag}')) + after_brothers = len(ele.eles(f'xpath:./following-sibling::{ele.tag}')) + path_str = f'/{ele.tag}[{before_brothers + 1}]{path_str}' if before_brothers > 0 else f'/{ele.tag}[1]{path_str}' if after_brothers > 0 else f'/{ele.tag}{path_str}' ele = ele.parent()