[英]How to use execution pointcut with third-party maven dependency?
我正在尝试开发一个AspectJ方面,该方面将自动吞噬Selenium-Java的WebDriver
实例(包括WebDriver
多个子类ChromeDriver
, FirefoxDriver
等)引发的任何ElementNotVisibleException
或StaleElementReferenceException
s( RuntimeException
子类)。
基本上,在非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能够弄清楚如何做到这一点,所以我做了一些研究,意识到我需要一个带有execution
切入点的around
建议。 如果我使用call
而不是execution
,它将成功触发,但是不会吞下异常。 这对我来说是个神秘的原因,因为从我编写的方面来看,从我的代码流看来,它会捕获在proceed()调用中抛出的所有内容。
但是, execution()
切入点也不起作用! 这是因为即使我的pom.xml中具有weaveDependency
, weaveDependency
编织我的类,但并未编织Selenium-Java! 踢球是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;
}
}
我有点好奇,并设置了一个示例项目,该项目具有方面拦截call(WebElement WebDriver+.findElement(*))
(类似于您的方法)并且还call(void WebElement+.click())
。 为了模拟一些WebDriver异常(例如NoSuchElementException
和StaleElementReferenceException
),我使用了W3schools的带有内联框架(iframe)的示例页面 。 如果仅将焦点从主框架切换到iframe并尝试从前一个框架访问元素(反之亦然),则这很容易。
我的示例方面不等待n秒,而是遍历main和所有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
方法中尝试使用资源 ,并确保在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;
}
}
}
控制台日志:
在这里,您可以查看何时触发两个切入点中的哪个切入点,尝试失败后如何重启等。
call(WebElement org.openqa.selenium.WebDriver.findElement(By)) -> By.className: seeResult
Trying main frame -> OK
call(WebElement org.openqa.selenium.WebDriver.findElement(By)) -> By.name: fname
Trying main frame -> NoSuchElementException
Trying iframe google_ads_iframe_/16833175/TryitLeaderboard_0 -> OK
call(WebElement org.openqa.selenium.WebDriver.findElement(By)) -> By.id: textareaCode
Trying main frame -> OK
call(WebElement org.openqa.selenium.WebDriver.findElement(By)) -> By.name: bt
Trying main frame -> OK
call(WebElement org.openqa.selenium.WebDriver.findElement(By)) -> By.name: country
Trying main frame -> NoSuchElementException
Trying iframe google_ads_iframe_/16833175/TryitLeaderboard_0 -> OK
call(void org.openqa.selenium.WebElement.click()) -> [[ChromeDriver: chrome on XP (5ab9e5f25d169bbc941ab1b08b346c50)] -> class name: seeResult]
Trying main frame -> OK
call(WebElement org.openqa.selenium.WebDriver.findElement(By)) -> By.name: country
Trying main frame -> NoSuchElementException
Trying iframe google_ads_iframe_/16833175/TryitLeaderboard_0 -> OK
Norway
call(WebElement org.openqa.selenium.WebDriver.findElement(By)) -> By.cssSelector: input[type=submit]
Trying main frame -> NoSuchElementException
Trying iframe google_ads_iframe_/16833175/TryitLeaderboard_0 -> OK
call(void org.openqa.selenium.WebElement.click()) -> [[ChromeDriver: chrome on XP (5ab9e5f25d169bbc941ab1b08b346c50)] -> css selector: input[type=submit]]
Trying main frame -> StaleElementReferenceException
Trying iframe google_ads_iframe_/16833175/TryitLeaderboard_0 -> OK
我希望这有帮助。 请享用!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.