简体   繁体   中英

TestNG Selenium with parallel threads

I've seen many questions regarding this topic but none seem to pertain to what I am looking for. I am trying to run parallel methods under a single class using the @BeforeMethod to start up the browser, and @AfterMethod to tear down the browser. For this example, let's just say it is a single class file that had several test methods inside of it. I wish to run 4 of them at the same time in different browsers. We are using Groovy but a Java answer would suffice.

XML:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >

<suite name="sampleSuite" thread-count="4" preserve-order="true" parallel="methods">
    <test name="sample">
        <classes>
            <class name="tests.some.class.MyClassFile"/>
        </classes>
    </test>
</suite>

DriverFactory class that is in charge of simply creating and returning a browser:

private static WebDriver createWebDriver() {
    String property = System.getProperty("driver", "chrome")
    def capabilities

    if (property == "chrome") {
        capabilities = DesiredCapabilities.chrome()


    } else if (property == "firefox") {
        System.setProperty("webdriver.gecko.driver", "/usr/local/Cellar/geckodriver/0.16.1/bin/geckodriver")
        capabilities = DesiredCapabilities.firefox()
    }

    def url = System.getProperty("seleniumServerUrl", "http://localhost:4444/wd/hub")

    if (!url) {
        throw new IllegalStateException("No 'seleniumServerUrl' system property set")
    }

    def webDriver = new RemoteWebDriver(new URL(url), capabilities)
    webDriver.manage().timeouts().implicitlyWait(20, TimeUnit.SECONDS)
    webDriver.manage().window().setSize(new Dimension(1920,1080))

    Runtime.addShutdownHook {
        try {
            webDriver.quit()
        } catch (ignore) {}
    }
    webDriver
}

For threading, also in DriverFactory class:

static final WebDriver createStackWebDriver(){
    ThreadLocal<WebDriver> webDriver = new ThreadLocal<WebDriver>(){
        protected WebDriver initialValue(){
            createWebDriver()
        }
    }
    webDriver.get()
}

Now I have a TestPlatform class that is the central "set up and tear down" piece of the architecture. All of my class files that contain the @Test methods extends this TestPlatform class:

class TestPlatform {

    WebDriver webDriver

    @BeforeMethod
    void startUpWebBrowser(){
        webDriver = DriverFactory.createStackWebDriver()
    }

    @AfterMethod
    void quitWebDriver(){
        webDriver.quit()
    }
}

When we run the XML file, what we see is 4 instances of startUpWebBrowser() , one out of the 4 would "pass" and continue the test, and it will promptly open up 4 methods out of one instance of startUpWebBrowser() , while the other 3 just stay "hanging". We need it to open up one browser in each startUpWebBrowser() instance, instead of 4 browsers in one instance.

Our current workaround is to wrap EACH @Test method inside its own class, and change the @BeforeMethod into @BeforeClass as well as the afters. This somehow works but is extremely ugly on the reporting side of it, and there are other few drawbacks to this way.

Is there any solution to this issue without doing an overhaul of the current architecture? Am I just missing something very simple?

The problem lies in your Test class TestPlatform wherein you are saving a webdriver instance variable into a class level data member WebDriver webDriver .

So when two or more @Test methods run in parallel, all of them are literally trying to assign the webdriver instance to the same webDriver instance.

Please fix it as below.

class TestPlatform {

    @BeforeMethod
    void startUpWebBrowser(){
        DriverFactory.createStackWebDriver()
        //Access webdriver instances only via DriverFactory.createStackWebDriver()
    }

    @AfterMethod
    void quitWebDriver(){
        DriverFactory.createStackWebDriver().quit()
    }
}

Seems like a ThreadLocal misusing. I'd use the following snippet:

private static final ThreadLocal<WebDriver> DRIVER_CONTAINER = new ThreadLocal<>();

@BeforeMethod
void startUpWebBrowser(){
    DRIVER_CONTAINER.set(createWebDriver());
}

@AfterMethod
void quitWebDriver(){
    ofNullable(getDriver()).ifPresent(WebDriver::quit);
    DRIVER_CONTAINER.remove();
}

public static WebDriver getDriver() {
    return DRIVER_CONTAINER.get();
}

Or just move driver management piece into IInvokedMethodListener .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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