草庐IT

java - 如何解决,陈旧元素异常?如果元素不再附加到 DOM?

coder 2023-08-29 原文

我有一个关于“元素不再附加到 DOM”的问题。

我尝试了不同的解决方案,但它们间歇性地工作。请提出一个可能是永久性的解决方案。

WebElement getStaleElemById(String id, WebDriver driver) {
    try {
        return driver.findElement(By.id(id));
    } catch (StaleElementReferenceException e) {
        System.out.println("Attempting to recover from StaleElementReferenceException ...");
        return getStaleElemById(id, driver);
    }
}

WebElement getStaleElemByCss(String css, WebDriver driver) {
    try {
        return driver.findElement(By.cssSelector(css));
    } catch (StaleElementReferenceException e) {
        System.out.println("Attempting to recover from StaleElementReferenceException ...");
        return getStaleElemByCss(css, driver);
    } catch (NoSuchElementException ele) {
         System.out.println("Attempting to recover from NoSuchElementException ...");
         return getStaleElemByCss(css, driver);
    }
}

谢谢,
阿努

最佳答案

问题

您可能面临的问题是该方法返回正确(且有效!)的元素,但是当您稍后尝试访问它时,它已过时并抛出。

这通常发生在:

  • 您单击异步加载新页面或至少更改它的内容。
  • 您立即(在页面加载完成之前)搜索一个元素……然后您就找到了!
  • 页面最终卸载,新页面加载。
  • 您尝试访问以前找到的元素,但现在它已经过时了,即使新页面也包含它。


  • 解决方案

    我知道有四种方法可以解决它:
  • 使用适当的等待

    面对异步页面时,在每次预期页面加载后使用适当的等待。在初始单击后插入显式等待并等待新页面/新内容加载。只有在此之后,您才能尝试搜索所需的元素。这应该是你要做的第一件事。它将大大提高您的测试的稳健性。
  • 你这样做的方式

    我已经使用您的方法的变体两年了(连同解决方案 1 中的上述技术),它在大多数情况下绝对有效,并且仅在奇怪的 WebDriver 错误上失败。尝试通过 .isDisplayed() 在找到元素后立即访问它(在从方法返回之前)方法什么的。如果它抛出,你已经知道如何再次搜索。如果它通过了,您还有一个(错误的)保证。
  • 使用在陈旧时重新找到自己的 WebElement

    写个 WebElement记住它是如何被找到的装饰器,并在它被访问和抛出时重新找到它。这显然迫使您使用自定义 findElement()将返回装饰器实例的方法(或者更好的是,装饰 WebDriver 将从通常的 findElement()findElemens() 方法返回您的实例)。像这样做:
    public class NeverStaleWebElement implements WebElement {
        private WebElement element;
        private final WebDriver driver;
        private final By foundBy;
    
        public NeverStaleWebElement(WebElement element, WebDriver driver, By foundBy) {
            this.element = element;
            this.driver = driver;
            this.foundBy = foundBy;
        }
    
        @Override
        public void click() {
            try {
                element.click();
            } catch (StaleElementReferenceException e) {
                // log exception
    
                // assumes implicit wait, use custom findElement() methods for custom behaviour
                element = driver.findElement(foundBy);
    
                // recursion, consider a conditioned loop instead
                click();
            }
        }
    
        // ... similar for other methods, too
    
    }
    

    请注意,虽然我认为 foundBy应该可以从通用 WebElements 访问信息以使其更容易,Selenium 开发人员认为尝试这样的事情是错误的并选择了 not to make this information public .在过时的元素上重新查找可以说是一种不好的做法,因为您正在隐式地重新查找元素,而没有任何机制来检查它是否合理。重新查找机制可能会找到一个完全不同的元素,而不是再次相同的元素。此外,它以 findElements() 严重失败当找到很多元素时(您要么需要禁止重新查找由 findElements() 找到的元素,要么记住您的元素来自返回的 List 的数量)。

    我认为它有时会很有用,但确实没有人会使用选项 1 和选项 2,这对于测试的稳健性来说显然是更好的解决方案。使用它们,只有在您确定需要它之后,才可以使用。
  • 使用任务队列(可以重新运行过去的任务)

    以全新方式实现您的整个工作流程!
  • 制作要运行的中央作业队列。让这个队列记住过去的工作。
  • 通过命令模式的方式实现每一个需要的任务(“找到一个元素并点击它”、“找到一个元素并向它发送 key ”等)。调用时,将任务添加到中央队列,然后(同步或异步,无关紧要)运行它。
  • @LoadsNewPage 注释每个任务, @Reversible等根据需要。
  • 你的大部分任务都会自己处理它们的异常,它们应该是独立的。
  • 当队列遇到陈旧元素异常时,它会从任务历史记录中取出最后一个任务并重新运行它以重试。

  • 这显然需要很多努力,如果没有好好考虑,很快就会适得其反。在我手动修复它们所在的页面后,我使用了一个(更复杂和强大的)变体来恢复失败的测试。在某些情况下(例如,在 StaleElementException 上),失败不会立即结束测试,而是会等待(在 15 秒后最终超时之前),弹出一个信息窗口并让用户选择手动刷新页面/单击右键/修复表单/随便什么。然后它会重新运行失败的任务,甚至有可能在历史中倒退一些步骤(例如到最后的 @LoadsNewPage 作业)。

    最后的挑剔

    综上所述,您的原始解决方案可以使用一些改进。您可以将这两种方法合二为一,更通用(或者至少让它们委托(delegate)给这个方法以减少代码重复):
    WebElement getStaleElem(By by, WebDriver driver) {
        try {
            return driver.findElement(by);
        } catch (StaleElementReferenceException e) {
            System.out.println("Attempting to recover from StaleElementReferenceException ...");
            return getStaleElem(by, driver);
        } catch (NoSuchElementException ele) {
            System.out.println("Attempting to recover from NoSuchElementException ...");
            return getStaleElem(by, driver);
        }
    }
    

    使用 Java 7,即使是单个 multicatch 块也足够了:
    WebElement getStaleElem(By by, WebDriver driver) {
        try {
            return driver.findElement(by);
        } catch (StaleElementReferenceException | NoSuchElementException e) {
            System.out.println("Attempting to recover from " + e.getClass().getSimpleName() + "...");
            return getStaleElem(by, driver);
        }
    }
    

    这样,您可以大大减少需要维护的代码量。

    关于java - 如何解决,陈旧元素异常?如果元素不再附加到 DOM?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17174515/

    有关java - 如何解决,陈旧元素异常?如果元素不再附加到 DOM?的更多相关文章

    1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

      我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

    2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

      总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

    3. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

      关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

    4. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

      给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

    5. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

      我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

    6. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

      我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

    7. ruby - 如何指定 Rack 处理程序 - 2

      Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

    8. ruby - 如何每月在 Heroku 运行一次 Scheduler 插件? - 2

      在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/

    9. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

      我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

    10. ruby-on-rails - 如果为空或不验证数值,则使属性默认为 0 - 2

      我希望我的UserPrice模型的属性在它们为空或不验证数值时默认为0。这些属性是tax_rate、shipping_cost和price。classCreateUserPrices8,:scale=>2t.decimal:tax_rate,:precision=>8,:scale=>2t.decimal:shipping_cost,:precision=>8,:scale=>2endendend起初,我将所有3列的:default=>0放在表格中,但我不想要这样,因为它已经填充了字段,我想使用占位符。这是我的UserPrice模型:classUserPrice回答before_val

    随机推荐