简体   繁体   English

如何在第三方Maven依赖项中使用执行切入点?

[英]How to use execution pointcut with third-party maven dependency?

I am trying to develop an AspectJ aspect that will automatically swallow any ElementNotVisibleException or StaleElementReferenceException s (subclasses of RuntimeException ) thrown by Selenium-Java's WebDriver instances (including multiple subclasses of WebDriver -- ChromeDriver , FirefoxDriver , etc.) 我正在尝试开发一个AspectJ方面,该方面将自动吞噬Selenium-Java的WebDriver实例(包括WebDriver多个子类ChromeDriverFirefoxDriver等)引发的任何ElementNotVisibleExceptionStaleElementReferenceException s( RuntimeException子类)。

Basically, the standard suggested solution in a non-AOP context for handling ENVE and SERE exceptions with Selenium is to simply try again. 基本上,在非AOP上下文中使用Selenium处理ENVESERE异常的标准建议解决方案是简单地重试。 And again. 然后再次。 And again, if necessary. 再有必要的话。

Something like this would work in a functional paradigm: 这样的事情将在功能范例中起作用:

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");
}

Then, when using WebDriver later: 然后,在以后使用WebDriver时:

tryWhileStale(() -> driver.findElement(By.xpath(...)).click());

However, this adds quite a bit of extra typing (as well as the very real possibility of accidentally forgetting the tryWhileStale() wrapper) that I'd like to avoid. 但是,这会增加很多我想避免的额外类型(以及很可能会真正忘记tryWhileStale()包装器的真正可能性)。

I don't want to download a copy of selenium-java, edit the source and rebuild, because I'm pulling Selenium directly from the public Maven repositories. 我不想下载selenium-java的副本,编辑源代码并重建,因为我是直接从公共Maven存储库中提取Selenium的。

I hoped that AspectJ would be able to figure out how to do this, so I did some research and realized I need an around advice with an execution pointcut. 我希望AspectJ能够弄清楚如何做到这一点,所以我做了一些研究,意识到我需要一个带有execution切入点的around建议。 If I use call instead of execution , it will trigger successfully, but it won't swallow the exception. 如果我使用call而不是execution ,它将成功触发,但是不会吞下异常。 The reason for this is mysterious to me, since it seems from my code flow in the aspect I've written would catch anything thrown in the proceed() call. 这对我来说是个神秘的原因,因为从我编写的方面来看,从我的代码流看来,它会捕获在proceed()调用中抛出的所有内容。

But an execution() pointcut won't work, either! 但是, execution()切入点也不起作用! This is because AspectJ is weaving my classes, but is not weaving Selenium-Java, even though I have a weaveDependency in my pom.xml! 这是因为即使我的pom.xml中具有weaveDependencyweaveDependency编织我的类,但并未编织Selenium-Java! The kicker is that call() works only if your classes are woven, while execution() works only if the class you're calling is woven. 踢球是call()只有您的类梭织,同时execution()如果你调用类编织才起作用。 Obviously, either one works if you have both your classes and the third-party classes woven. 显然,如果同时编织了您的课程和第三方课程,那么任何一个都可以使用。

Is there any way to do this without abandoning AOP or Maven entirely? 有什么方法可以完全放弃AOP或Maven吗? Below is my code that should purportedly work if I were able to weave selenium-java: 如果能够编织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;
    }
}

I was a bit curious and set up a sample project with an aspect intercepting call(WebElement WebDriver+.findElement(*)) (similar to your approach) and also call(void WebElement+.click()) . 我有点好奇,并设置了一个示例项目,该项目具有方面拦截call(WebElement WebDriver+.findElement(*)) (类似于您的方法)并且还call(void WebElement+.click()) I used a sample page with an inline frame (iframe) from W3schools in order to emulate a few WebDriver exceptions such as NoSuchElementException and StaleElementReferenceException . 为了模拟一些WebDriver异常(例如NoSuchElementExceptionStaleElementReferenceException ),我使用了W3schools的带有内联框架(iframe)示例页面 This is easy if you just switch the focus from the main frame to an iframe and try to access an element from the former (or vice versa). 如果仅将焦点从主框架切换到iframe并尝试从前一个框架访问元素(反之亦然),则这很容易。

My sample aspect does not wait for n seconds, but iterates over the main and all iframes in order to re-issue the original call in that context. 我的示例方面不等待n秒,而是遍历main和所有iframe,以便在该情况下重新发出原始调用。 It should be very easy to adapt the sample code to your needs. 使示例代码适应您的需求应该非常容易。

Oh BTW, I used native AspectJ syntax, not the annotation-based one. 哦,顺便说一句,我使用的是本机AspectJ语法,而不是基于注释的语法。 I hope you do not mind, I find the native syntax more expressive and elegant. 希望您不要介意,我发现本机语法更富表现力和优雅。

Maven POM to build the project: Maven POM构建项目:

The pom.xml contains a few extra plugins for pom.xml包含一些额外的插件,用于

  • building a single, executeable JAR ( one-jar ) containing the AspectJ runtime and all other dependencies. 构建一个包含AspectJ运行时和所有其他依赖项的单个可执行JAR( 一个jar )。 Very convenient for distributing and running your compiled program. 分发和运行已编译程序非常方便。
  • I also included exec-maven so as to enable you to easily run your program via mvn clean compile exec:java . 我还包括exec-maven ,以便使您能够通过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 driver application: Java驱动程序应用程序:

As you can see, the application holds a WebDriver reference needed by the second pointcut only (the first one does not need it, it can find it via target() binding). 如您所见,该应用程序仅包含第二个切入点所需的WebDriver引用(第一个切入点不需要它,可以通过target()绑定找到它)。 The Application class also implements Closeable which enables us to use try with resources in the main method, making sure that the driver will be closed outomatically as the Application instance goes out of scope. 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();
        }
    }
}

Aspect: 方面:

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;
        }
    }
}

Console log: 控制台日志:

Here you can see which of the two pointcuts fires when, how after failed tries they are restarted etc. 在这里,您可以查看何时触发两个切入点中的哪个切入点,尝试失败后如何重启等。

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

I hope this helps. 我希望这有帮助。 Enjoy! 请享用!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM