【问题标题】:Python selenium how to wait until element is gone/becomes stale?Python selenium 如何等到元素消失/变得陈旧?
【发布时间】:2021-08-27 06:58:03
【问题描述】:

我正在尝试以编程方式解决此地址出现的问题:https://www.arealme.com/brain-memory-game/en/,简而言之,它将连续显示几个闪烁的数字,并要求您以相反的顺序输入数字。

现在这里是详细信息,当你进入网站时,会有一个开始按钮,它有一个从左侧填充它的蓝色条,我发现如果你让你的机器人在蓝色条之前点击那个按钮填满按钮,则按钮的功能不会被触发,脚本的其余部分也不会运行。

点击开始按钮后,页面会刷新,你会看到这个闪烁的文字:

数字即将到来。请注意...

然后,你会看到几个闪烁的数字,它们通过改变alpha来闪烁,它们从完全可见变为完全不可见,当一个数字消失时会出现另一个数字,即使它们具有相同的值,它们也是不同的数字。

然后会有这样的信息:

请点击你刚刚看到的倒序号码。

还有一个带有十个按钮的数字键盘。

按照它说的做,然后会出现一个按钮,告诉你是否正确,你需要点击它才能继续下一个问题。

总共有十个问题。

现在介绍技术细节。

我已经设法找到所涉及元素的类名、xpath 和 id。

开始按钮的id是'start',我发现time.sleep(3)足够填满了。

类的层次结构如下:

<div class="questionWrapper">
    <div class="question">
        <div class="gnumber_title">
            <div class="gend-tip">Please click on the number you just saw IN REVERSE ORDER.</div>
        </div>
        <div class="gnumber_btns" id="gbtn0" style=""><button data-n="9">9</button><button data-n="8">8</button><button data-n="7">7</button><button data-n="6">6</button><button data-n="5">5</button><button data-n="4">4</button><button data-n="3">3</button><button data-n="2">2</button><button data-n="1">1</button><button data-n="0">0</button></div>
    </div>
    <div class="answer" value="10">1</div>
</div>

问题封装在"questionWrapper" 类中,每个类中只有一个"gnumber_title" 实例,对象包含消息和数字。

类在任何时候都只包含一个元素,类所包含的内容会随着时间而变化,但它们都相对于所述类共享相同的 xpath:'./div'

在问题开始时,当提示是第一条消息时,所述位置内的对象属于"gstart-tip blink_me"类。

然后它会消失,取而代之的是一个'gflash-num'类的对象,可以使用.text属性访问其内容。

通过改变样式闪烁,样式会从"opacity: 1.000000;"变为"opacity: 0.000000;"(具体值我不知道,但它是一个从1到0的六位小数的浮点数),然后它会变成"display: none;",然后将其删除并出现"glash-num"的另一个实例。

"gflash-num" 的多个实例之后,出现第二条消息,它的类是"gend-tip",与数字和第一条消息位于相同的xpath。

并且数字键盘将可见,其类为"gnumber_btns"

点击相同数量的按钮后,出现类为"answer"的按钮,点击进入下一题。

这是我解决问题的尝试:

import time
from selenium import webdriver

Firefox = webdriver.Firefox()
Firefox.get('https://www.arealme.com/brain-memory-game/en/')

time.sleep(3)
Firefox.find_element_by_id('start').click()

questions = Firefox.find_elements_by_class_name("questionWrapper")
for q in questions:
    numbers = []
    title = q.find_element_by_class_name('gnumber_title')
    while True:
        if title.find_element_by_xpath('./div').get_attribute('class') != 'gstart-tip blink_me':
            break
    while True:
        if title.find_element_by_xpath('./div').get_attribute('class') == "gend-tip":
            break
        numbers.append(title.find_element_by_class_name('gflash-num').text)
        while True:
            if not title.find_element_by_class_name('gflash-num').get_attribute('style').startswith('opacity'):
                break
        while True:
            if not title.find_element_by_class_name('gflash-num').get_attribute('style').startswith('display'):
                break
    buttons = q.find_element_by_class_name("gnumber_btns")
    for n in reversed(numbers):
        buttons.find_element_by_xpath(f'.//*[text() = "{n}"]').click()
    time.sleep(0.5)
    q.find_element_by_class_name("answer").click()

还有错误:

---------------------------------------------------------------------------
StaleElementReferenceException            Traceback (most recent call last)
<ipython-input-1-9d34e88c4046> in <module>
     20         numbers.append(title.find_element_by_class_name('gflash-num').text)
     21         while True:
---> 22             if not title.find_element_by_class_name('gflash-num').get_attribute('style').startswith('opacity'):
     23                 break
     24         while True:

c:\program files\python39\lib\site-packages\selenium\webdriver\remote\webelement.py in get_attribute(self, name)
    137         attributeValue = ''
    138         if self._w3c:
--> 139             attributeValue = self.parent.execute_script(
    140                 "return (%s).apply(null, arguments);" % getAttribute_js,
    141                 self, name)

c:\program files\python39\lib\site-packages\selenium\webdriver\remote\webdriver.py in execute_script(self, script, *args)
    632             command = Command.EXECUTE_SCRIPT
    633
--> 634         return self.execute(command, {
    635             'script': script,
    636             'args': converted_args})['value']

c:\program files\python39\lib\site-packages\selenium\webdriver\remote\webdriver.py in execute(self, driver_command, params)
    319         response = self.command_executor.execute(driver_command, params)
    320         if response:
--> 321             self.error_handler.check_response(response)
    322             response['value'] = self._unwrap_value(
    323                 response.get('value', None))

c:\program files\python39\lib\site-packages\selenium\webdriver\remote\errorhandler.py in check_response(self, response)
    240                 alert_text = value['alert'].get('text')
    241             raise exception_class(message, screen, stacktrace, alert_text)
--> 242         raise exception_class(message, screen, stacktrace)
    243
    244     def _value_or_default(self, obj, key, default):

StaleElementReferenceException: Message: The element reference of <div class="gflash-num"> is stale; either the element is no longer attached to the DOM, it is not in the current frame context, or the document has been refreshed

动态元素的任何迭代都可能引发异常,我必须等到位于该 xpath 的元素不再是 "gstart-tip blink_me" 才能开始下一阶段的代码执行,然后添加 "gflash-num" 的值等到元素消失再添加下一个值,最后变成"gend-tip"时,按相反的顺序点击按钮。

我试图通过不分配变量并即时获取属性来避免异常,但仍然引发异常。

但它引发异常的时间正是它应该从睡眠中醒来并添加数字的时间。

那么我怎么能等到一个元素变得陈旧/不可见/删除/不存在/无论如何,所有的谷歌搜索都告诉了如何做相反的事情:等到元素不再陈旧,但我想等待直到元素消失,那么怎么做呢?


我想我发现了一些对解决问题非常重要的东西,使用 ffmpeg 从过程的屏幕录制中提取帧,我能够确定闪烁的确切时间。

第一个提示正好显示 3 秒,每个数字正好闪烁 1 秒。

【问题讨论】:

标签: python python-3.x selenium


【解决方案1】:

要等到任何元素变得不可见,我们有这个预期条件:

invisibility_of_element

在我可以看到的代码中:

class invisibility_of_element(invisibility_of_element_located):
    """ An Expectation for checking that an element is either invisible or not
    present on the DOM.

    element is either a locator (text) or an WebElement
    """
    def __init(self, element):
        self.target = element

如果你有一个正在运行的 webdriverwait 对象,那么你可以试试这个:

WebDriverWait(driver, 10).until(EC.invisibility_of_element((By.XPATH, "xpath here")))

这将等到元素不可见,由 xpath 定义。

【讨论】:

  • 我已经对此进行了调查,但是我不知道它是否会有所帮助,因为所有元素共享相同的 xpath,那么当元素被删除并且另一个元素出现在同一个 xpath 中时会发生什么?
  • 同一元素的立即放置可见性,也考虑一下是否可以应用xpath索引。
【解决方案2】:

我已经解决了我的问题,没有使用invisibility_of_element,也许我绕过了这个问题,但是代码现在没有抛出异常。

由于我知道元素的确切“寿命”,我可以使用 time.sleep() 而不是依赖行为可能无法预测的 EC.invisibility_of_element()

代码:

import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

Firefox = webdriver.Firefox()
Firefox.get('https://www.arealme.com/brain-memory-game/en/')

time.sleep(3)
Firefox.find_element_by_id('start').click()

questions = Firefox.find_elements_by_class_name("questionWrapper")
wait = WebDriverWait(Firefox, 3)

for i, q in enumerate(questions):
    wait.until(EC.visibility_of_element_located((By.ID, f'q{i + 1}')))
    numbers = []
    ids = set()
    title = q.find_element_by_class_name('gnumber_title')
    if title.find_element_by_xpath('./div').get_attribute('class') == 'gstart-tip blink_me':
        time.sleep(3)
    while True:
        if title.find_element_by_xpath('./div').get_attribute('class') == "gend-tip":
            break
        number = title.find_element_by_class_name('gflash-num')
        if number.id not in ids:
            numbers.append(number.text)
            ids.add(number.id)
            time.sleep(1)
    wait.until(EC.visibility_of_element_located((By.ID, f'gbtn{i}')))
    buttons = q.find_element_by_class_name("gnumber_btns")
    for n in reversed(numbers):
        buttons.find_element_by_xpath(f'.//*[@data-n = "{n}"]').click()
        time.sleep(0.1)
    time.sleep(0.5)
    q.find_element_by_class_name("answer").click()

【讨论】:

    猜你喜欢
    • 2018-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-10
    • 2017-05-28
    • 2017-06-26
    • 2018-12-05
    • 1970-01-01
    相关资源
    最近更新 更多