弹簧导致WebDriver降低()'每次测试之后

发布于 2025-02-08 05:28:12 字数 3614 浏览 6 评论 0原文

我正在尝试使Spring Boot Selenium应用程序工作。我可以成功进行一项测试,但是当我进行多个测试时,第二个测试会抱怨:

会话ID为null。调用Quit()?

后使用WebDriver

查看源代码并放入断点后,春季似乎正在调用quit()方法,用于webdriver在第一个测试之后。

每次测试后如何让它不退出?我考虑过只是不使用页面类的依赖注入。

这是我的测试类:

@SpringBootTest
public class LoginTest {

  @Autowired LoginPage loginPage;

  @Test
  public void shouldLogin() {
    loginPage.login();
  }

  @Test
  public void shouldLoginToAdminPage() {
    loginPage.adminLogin();
  }
}

我的登录页

@Component
public class LoginPage extends BasePage {

  @FindBy(how = How.XPATH, using = "//gat-input[@formcontrolname = 'userName']//input")
  private WebElement txtUserName;

  @FindBy(how = How.XPATH, using = "//gat-input[@formcontrolname='password']//input")
  private WebElement txtPassword;

  @FindBy(how = How.XPATH, using = "//gat-button[@label='Login']/button")
  private WebElement btnLogin;

  protected void login(String username, String password) {
    loadPage();
    sendKeysWhenReady(txtUserName, username);
    sendKeysWhenReady(txtPassword, password);
    clickWhenReady(btnLogin);
  }

  public void adminLogin() {
    login(properties.getAdminUsername(), properties.getAdminPassword());
  }

  public void login() {
    login(properties.getRegularUsername(), properties.getRegularPassword());
  }

  @Override
  public void verifyPageLoaded() {
    waitTillElementIsReady(btnLogin, 5);
  }

  @Override
  protected String getPath() {
    return "/login";
  }
}

basepage

public abstract class BasePage {

  @Autowired private WebDriver driver;
  @Autowired protected Properties properties;

  @PostConstruct
  public void initDriver() {
    driver.manage().timeouts().pageLoadTimeout(5, TimeUnit.SECONDS);
    PageFactory.initElements(driver, this);
  }

  public void loadPage() {
    getDriver().get(getUrl());
    verifyPageLoaded();
  }

  public String getUrl() {
    return properties.getBaseUrl().concat(getPath());
  }

  // example "/login"
  protected abstract String getPath();

  public abstract void verifyPageLoaded();

  public WebDriver getDriver() {
    return driver;
  }

  protected void waitTillElementIsReady(WebElement webElement, int seconds) {
    WebDriverWait wait = new WebDriverWait(driver, seconds);
    wait.until(ExpectedConditions.visibilityOf(webElement));
  }

  protected void sendKeysWhenReady(WebElement webElement, String keys) {
    waitTillElementIsReady(webElement, 5);
    webElement.sendKeys(keys);
  }

  protected void clickWhenReady(WebElement webElement) {
    waitTillElementIsReady(webElement, 3);
    webElement.click();
  }
}

配置类:

public class WebDriverLibrary {
  @Bean
  public WebDriver getChromeDriver() {
    WebDriverManager.chromedriver().setup();
    return new ChromeDriver();
  }
}

和我的依赖项:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.seleniumhq.selenium:selenium-java:4.2.1'
    implementation 'io.github.bonigarcia:webdrivermanager:5.2.0'
    implementation 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.testng:testng:7.6.0'
    testImplementation 'org.testcontainers:junit-jupiter:1.17.2'
    testImplementation 'org.testcontainers:selenium:1.17.2'
}

I'm trying to get a Spring Boot Selenium app to work. I can get one test to run successfully, but when I run more than one test, the second test complains with:

Session ID is null. Using WebDriver after calling quit()?

After looking through the source code and putting in breakpoints, it looks like Spring is calling the quit() method for WebDriver after the first test.

How do I get it to not quit after each test? I've thought about just not using dependency injection for the page classes.

Here is my test class:

@SpringBootTest
public class LoginTest {

  @Autowired LoginPage loginPage;

  @Test
  public void shouldLogin() {
    loginPage.login();
  }

  @Test
  public void shouldLoginToAdminPage() {
    loginPage.adminLogin();
  }
}

My LoginPage:

@Component
public class LoginPage extends BasePage {

  @FindBy(how = How.XPATH, using = "//gat-input[@formcontrolname = 'userName']//input")
  private WebElement txtUserName;

  @FindBy(how = How.XPATH, using = "//gat-input[@formcontrolname='password']//input")
  private WebElement txtPassword;

  @FindBy(how = How.XPATH, using = "//gat-button[@label='Login']/button")
  private WebElement btnLogin;

  protected void login(String username, String password) {
    loadPage();
    sendKeysWhenReady(txtUserName, username);
    sendKeysWhenReady(txtPassword, password);
    clickWhenReady(btnLogin);
  }

  public void adminLogin() {
    login(properties.getAdminUsername(), properties.getAdminPassword());
  }

  public void login() {
    login(properties.getRegularUsername(), properties.getRegularPassword());
  }

  @Override
  public void verifyPageLoaded() {
    waitTillElementIsReady(btnLogin, 5);
  }

  @Override
  protected String getPath() {
    return "/login";
  }
}

The BasePage:

public abstract class BasePage {

  @Autowired private WebDriver driver;
  @Autowired protected Properties properties;

  @PostConstruct
  public void initDriver() {
    driver.manage().timeouts().pageLoadTimeout(5, TimeUnit.SECONDS);
    PageFactory.initElements(driver, this);
  }

  public void loadPage() {
    getDriver().get(getUrl());
    verifyPageLoaded();
  }

  public String getUrl() {
    return properties.getBaseUrl().concat(getPath());
  }

  // example "/login"
  protected abstract String getPath();

  public abstract void verifyPageLoaded();

  public WebDriver getDriver() {
    return driver;
  }

  protected void waitTillElementIsReady(WebElement webElement, int seconds) {
    WebDriverWait wait = new WebDriverWait(driver, seconds);
    wait.until(ExpectedConditions.visibilityOf(webElement));
  }

  protected void sendKeysWhenReady(WebElement webElement, String keys) {
    waitTillElementIsReady(webElement, 5);
    webElement.sendKeys(keys);
  }

  protected void clickWhenReady(WebElement webElement) {
    waitTillElementIsReady(webElement, 3);
    webElement.click();
  }
}

The Config class:

public class WebDriverLibrary {
  @Bean
  public WebDriver getChromeDriver() {
    WebDriverManager.chromedriver().setup();
    return new ChromeDriver();
  }
}

And my dependencies:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.seleniumhq.selenium:selenium-java:4.2.1'
    implementation 'io.github.bonigarcia:webdrivermanager:5.2.0'
    implementation 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.testng:testng:7.6.0'
    testImplementation 'org.testcontainers:junit-jupiter:1.17.2'
    testImplementation 'org.testcontainers:selenium:1.17.2'
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

愛上了 2025-02-15 05:28:12

我发现有两种解决这个问题的方法。

但是,这一切都取决于Selenium Webdriver Java实施并不是真正的“春季”友好。您需要一个新的WebDriver,每个测试都需要在页面对象之间共享。没有很好的方法可以使用@Autowired页面对象。

因此,您可以使用依赖项注入,但是您必须在每个测试之前使用@dirtiesContext注释,每次测试都会创建并破坏webdriver 注射对象)。但是,这也可能非常昂贵,具体取决于每次测试的强度以及每次都将重新加载的上下文的大小。

另一种方法是不要注入页面对象,而是在每个测试之前重新创建它们(junit:@beforeach,testng:@beforemethod),WebDriver对象作为每个构造函数参数,以便每个页面对象都可以共享WebDriver。您还需要执行webdriver.quit()在每个测试之后关闭过时的驱动程序(junit:@aftereach,testng:@aftermethod )。

I found that there are two ways to address this.

However, it all comes down to the Selenium webdriver java implementation not really being "Spring" friendly. You need a new WebDriver for each test and it needs to be shared between page objects. There is no great way to do this with @Autowired page objects.

So you can use dependency injection, but you would have to use the @DirtiesContext annotation before each test, which would create and destroy the WebDriver each time (along with all the other injected objects). However, it would also be potentially very expensive depending on how intense each test is and how big the context that is being reloaded every time would be.

The other way is to not have the page objects injected, but to create them anew before each test (jUnit: @BeforeEach, TestNG: @BeforeMethod), with the WebDriver object as a constructor argument for each so that each page object can share the WebDriver. You would also need to perform the WebDriver.quit() after each test to shut down the stale driver (jUnit: @AfterEach, TestNG: @AfterMethod).

一笔一画续写前缘 2025-02-15 05:28:12

我最近遇到了类似的问题。在方法中添加@scope(configurableBeanfactory.scope_singleton) spring注释,其中注入webdriver bean,对我有帮助。
https://github.com/spring-projects/spring-projects/spring-boot/issues/spring-boot/issues/ 7454

I faced a similar problem recently. Adding the @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) Spring annotation in method, which inject webDriver bean, helped me.
https://github.com/spring-projects/spring-boot/issues/7454

不忘初心 2025-02-15 05:28:12

Spring automatically calls quit() on your injected driver

public class MyChromeDriver extends ChromeDriver
{
    private static final String SCREENSHOT_DIR = "/path/for/screenshots/";
   
    public void takeScreenshot(String fileName) throws IOException
    {
        File srcfile = getScreenshotAs(OutputType.FILE);
        File dstFile = new File(SCREENSHOT_DIR + fileName + ".png");

        dstFile.getParentFile().mkdirs();
        FileCopyUtils.copy(srcfile, dstFile);
    }
  
    /**
     * No-op quit which Spring can call after each test w/o doing any damage
     * @deprecated use {@link #realQuit()}
     */
    @Override
    @Deprecated
    public void quit() {}

    /**
     * Call this to actually invoke {@link ChromeDriver#quit()}
     */
    public void realQuit()
    {
        super.quit();
    }
}

这使我能够使我的基础测试类能够在错误时拍摄屏幕截图。

@ExtendWith(SeleniumMvcIntegrationTest.class)
public class SeleniumMvcIntegrationTest implements TestWatcher
{
    @Inject
    protected MyChromeDriver driver;

    @Override
    public void testFailed(ExtensionContext context, Throwable throwable)
    {
        getTestInstanceDriver(context).takeScreenshot(throwable.getClass().getSimpleName() + "-screenshot");

        // Can't call quit() in @AfterEach, or else it closes before we get the chance to take a screenshot
        getTestInstanceDriver(context).realQuit();
    }

    @Override
    public void testSuccessful(ExtensionContext context)
    {
        getTestInstanceDriver(context).realQuit();
    }

    @Override
    public void testDisabled(ExtensionContext context, Optional<String> reason)
    {
        getTestInstanceDriver(context).realQuit();
    }

    @Override
    public void testAborted(ExtensionContext context, Throwable cause)
    {
        getTestInstanceDriver(context).realQuit();
    }

    /**
     * This class is used to extend itself, which creates a new instance of SeleniumMvcIntegrationTest as a TestWatcher.
     * In the TestWatcher context, we want to access the driver belonging to the decorated instance; this convenience
     * method provides the ability to do that.
     */
    private MyChromeDriver getTestInstanceDriver(ExtensionContext context)
    {
        return ((SeleniumMvcIntegrationTest) context.getRequiredTestInstance()).driver;
    }
}

我在调用realquit()的地方,您可以轻松地使驱动程序打开。

Spring automatically calls quit() on your injected driver here. I was attempting to take screenshots whenever my tests failed, and finding the driver was already closed. I already had the need for a custom driver, so I fixed this with a no-op quit() override.

public class MyChromeDriver extends ChromeDriver
{
    private static final String SCREENSHOT_DIR = "/path/for/screenshots/";
   
    public void takeScreenshot(String fileName) throws IOException
    {
        File srcfile = getScreenshotAs(OutputType.FILE);
        File dstFile = new File(SCREENSHOT_DIR + fileName + ".png");

        dstFile.getParentFile().mkdirs();
        FileCopyUtils.copy(srcfile, dstFile);
    }
  
    /**
     * No-op quit which Spring can call after each test w/o doing any damage
     * @deprecated use {@link #realQuit()}
     */
    @Override
    @Deprecated
    public void quit() {}

    /**
     * Call this to actually invoke {@link ChromeDriver#quit()}
     */
    public void realQuit()
    {
        super.quit();
    }
}

This allowed me to give my base test class the ability to snap screenshots on error.

@ExtendWith(SeleniumMvcIntegrationTest.class)
public class SeleniumMvcIntegrationTest implements TestWatcher
{
    @Inject
    protected MyChromeDriver driver;

    @Override
    public void testFailed(ExtensionContext context, Throwable throwable)
    {
        getTestInstanceDriver(context).takeScreenshot(throwable.getClass().getSimpleName() + "-screenshot");

        // Can't call quit() in @AfterEach, or else it closes before we get the chance to take a screenshot
        getTestInstanceDriver(context).realQuit();
    }

    @Override
    public void testSuccessful(ExtensionContext context)
    {
        getTestInstanceDriver(context).realQuit();
    }

    @Override
    public void testDisabled(ExtensionContext context, Optional<String> reason)
    {
        getTestInstanceDriver(context).realQuit();
    }

    @Override
    public void testAborted(ExtensionContext context, Throwable cause)
    {
        getTestInstanceDriver(context).realQuit();
    }

    /**
     * This class is used to extend itself, which creates a new instance of SeleniumMvcIntegrationTest as a TestWatcher.
     * In the TestWatcher context, we want to access the driver belonging to the decorated instance; this convenience
     * method provides the ability to do that.
     */
    private MyChromeDriver getTestInstanceDriver(ExtensionContext context)
    {
        return ((SeleniumMvcIntegrationTest) context.getRequiredTestInstance()).driver;
    }
}

Where I'm calling realQuit(), you could just as easily leave the driver open.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文