我正在尝试开发一个AspectJ方面,该方面将自动吞下Selenium-Java的ElementNotVisibleException
实例抛出的任何StaleElementReferenceException
或RuntimeException
(WebDriver
的子类)(包括WebDriver
- ChromeDriver
,FirefoxDriver
等多个子类。
基本上,在非AOP环境中使用Selenium处理ENVE
和SERE
例外的标准建议解决方案就是再试一次。然后再次。如有必要,再次。
这样的东西可以在功能范例中起作用:
public void tryWhileStale(Runnable r)
{
int n = 0;
while(n < 5)
{
try
{
r.run();
break;
}
catch(StaleElementReferenceException | ElementNotVisibleException e){}
n++;
Thread.sleep(2000);
}
throw new RuntimeException("Timed out retrying");
}
然后,稍后使用WebDriver时:
tryWhileStale(() -> driver.findElement(By.xpath(...)).click());
然而,这增加了相当多的额外输入(以及偶然忘记tryWhileStale()
包装器的非常可能的可能性)。
我不想下载selenium-java的副本,编辑源代码并重建,因为我直接从公共Maven资源库中提取Selenium。
我希望AspectJ能够弄清楚如何做到这一点,所以我做了一些研究,并意识到我需要一个around
切入点execution
的建议。如果我使用call
而不是execution
,它将成功触发,但不会吞下异常。这样做的原因对我来说很神秘,因为从我编写的方面来看,我的代码流似乎会捕获proceed()调用中抛出的任何内容。
但execution()
切入点也行不通!这是因为AspectJ正在编织我的类,但是没有编织Selenium-Java,即使我的pom.xml中有weaveDependency
!问题是call()
仅在您的类被编织时起作用,而execution()
仅在您正在调用调用的类时才有效。显然,如果您同时编写了类和第三方类,则任何一个都可以工作。
如果不完全放弃AOP或Maven,有没有办法做到这一点?下面是我的代码,如果我能够编织selenium-java,那么该代码应该起作用:
@Aspect
class MyAspect {
@Around("execution (WebElement *.findElement(By))")
public Object around(ProceedingJoinPoint pjp)
{
Object f = null;
int n = 0;
do
{
try
{
System.err.println("Before " + this.toString());
f = pjp.proceed();
System.err.println("After " + this.toString());
return f;
}
catch(Throwable t)
{
try { Thread.sleep(5000); } catch(InterruptedException ie) { break; }
System.err.println("Waiting 5 seconds because of " + t.getClass().getSimpleName());
}
n++;
} while(n < 5);
System.err.println("Gave up waiting");
return null;
}
}
答案 0 :(得分:1)
我有点好奇并设置了一个示例项目,其中一个方面拦截call(WebElement WebDriver+.findElement(*))
(类似于您的方法)以及call(void WebElement+.click())
。我使用sample page with an inline frame (iframe) from W3schools来模拟一些WebDriver例外,例如NoSuchElementException
和StaleElementReferenceException
。如果您只是将焦点从主框架切换到iframe并尝试从前者访问元素(反之亦然),这很容易。
我的示例方面不会等待n秒,而是迭代主要和所有iframe,以便在该上下文中重新发出原始调用。根据您的需要调整示例代码应该非常容易。
哦,顺便说一句,我使用了原生的AspectJ语法,而不是基于注释的语法。我希望你不介意,我发现原生语法更具表现力和优雅。
Maven POM构建项目:
pom.xml 包含一些额外的
插件mvn clean compile exec:java
轻松运行程序。<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.scrum-master.stackoverflow</groupId>
<artifactId>selenium-aspectj-retry</artifactId>
<version>1.0-SNAPSHOT</version>
<name>Selenium auto-retry via AspectJ</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.source-target.version>1.8</java.source-target.version>
<aspectj.version>1.8.7</aspectj.version>
<main-class>de.scrum_master.app.Application</main-class>
</properties>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>${java.source-target.version}</source>
<target>${java.source-target.version}</target>
<!-- IMPORTANT -->
<useIncrementalCompilation>false</useIncrementalCompilation>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.8</version>
<configuration>
<!--<showWeaveInfo>true</showWeaveInfo> -->
<source>${java.source-target.version}</source>
<target>${java.source-target.version}</target>
<Xlint>ignore</Xlint>
<complianceLevel>${java.source-target.version}</complianceLevel>
<encoding>${project.build.sourceEncoding}</encoding>
<!--<verbose>true</verbose> -->
<!--<warn>constructorName,packageDefaultMethod,deprecation,maskedCatchBlocks,unusedLocals,unusedArguments,unusedImport</warn> -->
</configuration>
<executions>
<execution>
<!-- IMPORTANT -->
<phase>process-sources</phase>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.4.0</version>
<configuration>
<mainClass>${main-class}</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.dstovall</groupId>
<artifactId>onejar-maven-plugin</artifactId>
<version>1.4.4</version>
<executions>
<execution>
<goals>
<goal>one-jar</goal>
</goals>
</execution>
</executions>
<configuration>
<onejarVersion>0.96</onejarVersion>
<mainClass>${main-class}</mainClass>
<attachToBuild>true</attachToBuild>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
</plugin>
<!--
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
-->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<configuration>
<mainClass>${main-class}</mainClass>
<cleanupDaemonThreads>false</cleanupDaemonThreads>
</configuration>
</plugin>
<plugin>
<groupId>org.dstovall</groupId>
<artifactId>onejar-maven-plugin</artifactId>
<configuration>
<mainClass>${main-class}</mainClass>
</configuration>
</plugin>
</plugins>
</build>
<pluginRepositories>
<pluginRepository>
<id>OneJAR googlecode.com</id>
<url>http://onejar-maven-plugin.googlecode.com/svn/mavenrepo</url>
</pluginRepository>
</pluginRepositories>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>2.48.2</version>
</dependency>
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>1.3.0</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
</dependency>
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
</dependency>
</dependencies>
<organization>
<name>Scrum-Master.de - Agile Project Management</name>
<url>http://scrum-master.de</url>
</organization>
</project>
Java驱动程序应用程序:
如您所见,应用程序仅保留第二个切入点所需的WebDriver
引用(第一个切入点不需要它,它可以通过target()
绑定找到它)。 Application
类还实现了Closeable
,这使我们能够在main
方法中使用尝试使用资源,确保驱动程序将作为{自动关闭} {1}}实例超出范围。
Application
<强>方面:强>
package de.scrum_master.app;
import io.github.bonigarcia.wdm.ChromeDriverManager;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import java.io.Closeable;
import java.io.IOException;
public class Application implements Closeable {
private final WebDriver driver;
public Application() {
ChromeDriverManager.getInstance().setup();
driver = new ChromeDriver();
}
@Override
public void close() {
driver.quit();
}
public WebDriver getDriver() {
return driver;
}
public void doSomething() {
driver.get("http://www.w3schools.com/tags/tryit.asp?filename=tryhtml5_input_type_hidden");
// Button in main frame
WebElement button = driver.findElement(By.className("seeResult"));
// Text field in iframe
driver.findElement(By.name("fname"));
// Text area in main frame
driver.findElement(By.id("textareaCode"));
// Hidden input field in main frame
driver.findElement(By.name("bt"));
// Hidden input field in iframe
WebElement hiddenCountryField = driver.findElement(By.name("country"));
// Click button in main frame. This *refreshes* the iframe, making all existing
// references to elements therein (e.g. 'hiddenCountryField') stale
button.click();
// Get value of hidden input field after iframe refresh
System.out.println(driver.findElement(By.name("country")).getAttribute("value"));
// This alternative would *not* work because the aspect cannot repair a reference
// to an element which is gone forever because the iframe was refreshed
// System.out.println(hiddenCountryField.getAttribute("value"));
// Click submit button in iframe (triggers both advices)
driver.findElement(By.cssSelector("input[type=submit]")).click();
}
public static void main(String[] args) {
try (Application application = new Application()) {
application.doSomething();
}
}
}
控制台日志:
在这里,您可以看到两个切入点中的哪一个会在何时触发,尝试失败后如何重启等等。
package de.scrum_master.aspect;
import de.scrum_master.app.Application;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
public aspect IFrameSwitcher {
WebElement around(WebDriver driver, By by) :
!within(IFrameSwitcher) &&
call(WebElement WebDriver+.findElement(*)) &&
target(driver) &&
args(by)
{
System.out.println(thisJoinPoint + " -> " + by);
WebElement webElement;
try {
System.out.print(" Trying main frame -> ");
driver.switchTo().defaultContent();
webElement = proceed(driver, by);
System.out.println("OK");
return webElement;
}
catch (RuntimeException e) {
System.out.println(e.getClass().getSimpleName());
for (WebElement iframe : driver.findElements(By.tagName("iframe"))) {
try {
System.out.print(" Trying iframe " + iframe.getAttribute("id") + " -> ");
driver.switchTo().frame(driver.findElement(By.id("iframeResult")));
webElement = proceed(driver, by);
System.out.println("OK");
return webElement;
}
catch (RuntimeException e2) {
System.out.println(e2.getClass().getSimpleName());
e = e2;
}
}
throw e;
}
}
void around(Application application, WebElement webElement) :
within(Application) &&
call(void WebElement+.click()) &&
this(application) &&
target(webElement)
{
System.out.println(thisJoinPoint + " -> " + webElement);
WebDriver driver = application.getDriver();
try {
System.out.print(" Trying main frame -> ");
driver.switchTo().defaultContent();
proceed(application, webElement);
System.out.println("OK");
}
catch (RuntimeException e) {
System.out.println(e.getClass().getSimpleName());
for (WebElement iframe : driver.findElements(By.tagName("iframe"))) {
try {
System.out.print(" Trying iframe " + iframe.getAttribute("id") + " -> ");
driver.switchTo().frame(driver.findElement(By.id("iframeResult")));
proceed(application, webElement);
System.out.println("OK");
return;
}
catch (RuntimeException e2) {
System.out.println(e2.getClass().getSimpleName());
e = e2;
}
}
throw e;
}
}
}
我希望这会有所帮助。享受!