当 Jmeter 中 运行 具有 Java 采样器和多线程时,Selenium 测试不起作用

Selenium tests doesn't work when run in Jmeter with Java Sampler and multiple threads

我正在 运行 使用 Jmeter 和 Java Sampler 对网站进行 Selenium 测试。 我导出了测试 class,它使用 "Eclipse ->Export to jar" 扩展了 AbstractJavaSamplerClient,并将其复制到 Jmeter/lib/ext。我在测试中使用来自另一个 java 项目的 classes。我把这个项目打包成.jar,然后复制到Jmeter/lib。 当我 运行 我在 Jmeter 中的 1 个线程测试时它工作得很好但是如果我 运行 2 个或更多线程 Selenium 网络驱动程序无法找到元素但我看到它们是可见的。我是新手,但看起来 Selenium 不能在多线程中工作。 我究竟做错了什么?请帮忙

根据 How to Use JUnit With JMeter 指南,某些限制适用于 JMeter 的 JUnit 采样器,即

  1. Only the “Elapsed Time” is recorded for the JUnit Request Sampler. You won’t be able to see Latency or Bytes metrics as JMeter knows nothing about your JUnit test. Therefore, JMeter is only capable of measuring your test method execution time

  2. There is currently (as per JMeter 2.11) no way to run the JUnit suite or fixture. Only individual test cases can be executed and only one test case per the JUnit Request Sampler instance

  3. There are certain limitations to the JUnit test design. Due to JMeter’s multithreaded nature, the test developer needs to use a thread-safe approach (i.e. avoid static fields and methods wherever possible and keep in mind that the resource might be busy with another thread).

  4. Limited JUnit Request Sampler configuration capabilities assume that all pre- and post-test logic should go in setUp and tearDown methods. If you need to feed external data to a sampler use property files or system properties

您最有可能遇到第 3 点的问题。

此外,根据 WebDriver Tutorial

Note: It is NOT the intention of this project to replace the HTTP Samplers included in JMeter. Rather it is meant to compliment them by measuring the end user load time.

此外,来自同一来源:

From experience, the number of browser (threads) that the reader creates should be limited by the following formula:

C = B + 1

where C = Number of Cores of the host running the test and N = Number of Browser (threads). eg, if the current reader's host has 4 cores, the formula would yield:

4 = 3 + 1

meaning that the script should have a MAXIMUM of 3 threads.

所以我建议使用 JMeter 来产生主要负载和 WebDriver Sampler 或通过 JUnit Sampler 在单独线程组中的 1 个线程中的现有代码来衡量现实生活中的用户体验。

我被这个问题困扰了一段时间,没什么,请在没有 "static" 关键字的情况下声明所有变量,它对我有用。

谢谢

正如 Dmitri T 在他的回答中指出的那样,当使用某些静态字段时,通过 Jmerter 进行的 JUnit-Selenium 测试的多线程 运行 中断了。 在我们的项目中,我们遇到了与问题状态相同的症状,比如测试找不到元素,或者一个线程在其他人正在使用它时关闭了 webdriver。

所以我们实现了以下方法,使我们的测试安全,并允许通过 JMeter 以多线程方式运行。

首先,让我们提一下 Junit-Selenium 测试中的静态对象通常与 @BeforeClass 方法一起出现,这些方法必须声明为静态并使用静态字段。此外,您可能可以使用 @ClassRule 注释,该注释也必须放在静态字段上。但是@ClassRule注解不被JMeter Junit Sampler处理,所以我就不多说了。

在我们的案例中使用 @BeforeClass 初始化,我们有 TestBase class 初始化 webdriver 和一些其他资源以用于所有测试。我只是简化了我们真正的 classes 以便更清楚地演示:

 public abstract class P01_SL_TestBase {
    public static WebDriverResource driver = new FirefoxDriverResource();
    public static PageProvider<ARMLoginPage> loginPageProvider;
    public static ARMLoginPage loginPage;
    public static ARMApplicationStartPage ARMPage;

    @BeforeClass
     public static void loadApplication () {
        initResourcesForJmeterRunner();
        loadARM (armUser); 
     }

     public static void initResourcesForJmeterRunner() throws Throwable {
         webDriverResource.before();
     }

     public static void loadARM(BPMUser armUser) {

        loginPageProvider =
            new PageProvider<ARMLoginPage>(ARMLoginPage.class,
                                           BASE_URL_ADF_ARM_LOGIN_PAGE,
                                           driver.getDriver()); //Here
                                           //we get thread-local value of 
                                           //Firefox driver for our current thread 
                                           //from the threadwide storage
        loginPage = loginPageProvider.goHome();

    }
}

正如您在 TestBase 中看到的,我们有静态场驱动程序和其他一些常见情况。还有 @BeforeClass 方法可以做两件事。首先,实例化 webdriver。其次,初始化待测应用。因此 TestBase class 中的所有内容与单线程或多线程中的 运行 测试无关。

多线程的实现运行我们放入WebDriverResourceclass:

public abstract class WebDriverResource extends ExternalResource {

    //  private final RemoteWebDriver driver; //this is implementation for 
                                              //one thread running, 
                                              //it is not thread-safety

    /**
     * {@code threadLocalDriverStorage} field stores thread-local instances
     * of {@code RemoteWebDriver}.
     * Note that now {@code threadLocalDriverStorage} is not static. We made
     * this for backward compatibility of some 
     * existent tests using non-static createDriver() method.
     * TODO refactor createDriver() declaration and implementation to static
     * variant.
     */
    private ThreadLocal<RemoteWebDriver> threadLocalDriverStorage = 
                 new ThreadLocal<RemoteWebDriver>() {
        @Override
        protected RemoteWebDriver initialValue() {
            return createDriver(locale.toLanguageTag());
        }
    };

    public WebDriverResource() {
    }

    protected abstract RemoteWebDriver createDriver(String language);

    @Override
    public void before() throws Throwable {
        //here we call threadLocalDriverStorage.get() first time,
        //and so the get() will perform initialValue() 
        //and save to storage thread-local value for the current thread
        threadLocalDriverStorage.get()  
                         .manage()     
                         .window()
                         .maximize();    
    }

    @Override
    protected void after() {
        logger.fine("quit browser...");
        //driver.quit();
        threadLocalDriverStorage.get().close();
    }

    /**
     * @return thread-local value of {@code RemoteWebDriver}
     */
    public RemoteWebDriver getDriver() {
        return threadLocalDriverStorage.get();
    }
}

这里我们使用TreadLocalclass来实现threadLocalDriverStorage字段。我们将 initialValue() 方法重写为 RemoteWebDriver 对象的 return 实例。 然后在 before() 方法中,当我们第一次调用 TreadLocal.get() 时,它会导致调用 initialValue() 方法并为当前线程实例化 RemoteWebDriver 对象。 还有 getDriver() 其中 return 胎面局部值 RemoteWebDriver。应在整个测试过程中使用此方法 classes 以获取驱动程序对象。

我们也在我们的页面对象模型 classes 中存储网络驱动程序字段。因此,我们还将受保护的静态 ThreadLocal<RemoteWebDriver> threadLocalDriverStorage 字段添加到我们的基本页面 class 中。但是这里我们没有覆盖 initialValue() 方法。相反,我们使用 TreadLocal.set() 方法来保存当前线程的值。

public abstract class Page {

    //  private final RemoteWebDriver driver; //this is implementation for
                                              //one thread running,
                                              //is not thread-safety

    /**
     * {@threadLocalDriverStorage} field stores thread-local instances of
     * {@RemoteWebDriver}.
     * 
     * Note that {@threadLocalDriverStorage} must be static. Without that
     * static classes extended Page will 
     * work with unpredictable instance of {@threadLocalDriverStorage} and
     * as result would not set and get {@RemoteWebDriver} instance properly 
     * 
     * Note that {@ThreadLocal.initialValue()} method is not overrided.
     * Therefore thread-local value must be stored by
     * {@ThreadLocal.set()} method.
     * We do that in class consractor.
     */
    protected static ThreadLocal<RemoteWebDriver> threadLocalDriverStorage = 
                           new ThreadLocal<RemoteWebDriver>();

    public Page(WebDriver driver) {
        super();
        threadLocalDriverStorage.set((RemoteWebDriver)driver);
    }
}