I have a Cucumber TestNG test written that runs several tests in parallel. Currently I create the driver per scenario and my Hooks class looks like the following.
public class Hooks {
private TestContext testContext;
#Inject
public Hooks(TestContext testContext) {
this.testContext = testContext;
}
#Before
public void initializeTestContext(final Scenario scenario) {
this.testContext.initializeContext();
}
#After
public void after(final Scenario scenario) {
LOG.debug("Executing After Hook");
if (shouldScreenshot(scenario)) {
embedScreenshotToReport(scenario);
}
this.testContext.destroyContext();
}
#Before("#skip_scenario")
public void skipScenario(Scenario scenario) {
LOG.info("Skipping scenario: {}", scenario.getName());
Assume.assumeTrue(false);
}
private boolean shouldScreenshot(Scenario scenario) {
return scenario.isFailed() && Screenshot.isRequired();
}
private void embedScreenshotToReport(Scenario scenario) {
final byte[] screenshot = ((TakesScreenshot) testContext.getWebDriver()).getScreenshotAs(OutputType.BYTES);
scenario.embed(screenshot, "image/png");
}
}
My TestsContext class looks like the following.
#ScenarioScoped
public class TestContext extends WebUITest {
private static final String DEFAULT_DRIVER_NAME = "default";
private WebDriver webDriver;
public WebDriver getWebDriver() {
return this.webDriver;
}
public void initializeContext() {
System.out.println("DEBUGGING Test Context: driver created");
this.webDriver = initializeDriver(URL.getTestHostURL(), DEFAULT_DRIVER_NAME);
}
public void destroyContext() {
System.out.println("destory method called");
}
}
My Runner class with TestNG looks like the following.
glue = {"com.cucumber.test.glue.hook",
"com.cucumber.test.glue.stepdef" },
features = "features/MyFeature.feature",
plugin = {"pretty", "html:build/brazil-cucumber-tests/cucumber-pretty"},
strict = true)
public class ParallelRunner extends AbstractTestNGCucumberTests {
#Override
#DataProvider(parallel = true)
public Object[][] scenarios() {
return super.scenarios();
}
}
I was thinking of reusing the same driver instance across parallel tests but is that possible? If so, how can I instantiate the driver? Cucumber doesn't seem to have a beforeAll kind of method and when I use beforeAll of testNG in Hooks class, it doesn't seem to get called. Any advice would be much appreciated.
Reusing driver session across multiple thread doesn't makes sense. Instead reusing driver session between test/scenarios within same thread is possible. For that you need to update your code to use thread local driver instance.
Simplest way is to use qaf-cucumber with property selenium.singletone = 1. This stetting will reuse driver session for tests methods/scenarios running in same thread. If you change your mind and want to use new session for each scenario set selenium.singletone=Methods
Anywhere you want driver object, you can get from testbase:
WebDriver webDriver = new WebDriverTestBase().getDriver();
your code may look like below:
public class Hooks {
private TestContext testContext;
#Inject
public Hooks(TestContext testContext) {
this.testContext = testContext;
}
#After
public void after(final Scenario scenario) {
LOG.debug("Executing After Hook");
if (shouldScreenshot(scenario)) {
embedScreenshotToReport(scenario);
}
this.testContext.destroyContext();
}
#Before("#skip_scenario")
public void skipScenario(Scenario scenario) {
LOG.info("Skipping scenario: {}", scenario.getName());
Assume.assumeTrue(false);
}
private boolean shouldScreenshot(Scenario scenario) {
return scenario.isFailed() && Screenshot.isRequired();
}
private void embedScreenshotToReport(Scenario scenario) {
final byte[] screenshot = ((TakesScreenshot) new WebDriverTestBase().getDriver().getScreenshotAs(OutputType.BYTES);
scenario.embed(screenshot, "image/png");
}
}
#ScenarioScoped
public class TestContext extends WebUITest {
//nothing to do for driver management!....
//just set driver.name property
//code below is only for compatibility
public WebDriver getWebDriver() {
return new WebDriverTestBase().getDriver();
}
public void destroyContext() {
System.out.println("destory method called");
}
}
You can take benefit of other features of QAF like
listeners
External data-providers
Locator repository
Wait/Assertion/verification with auto screenshot
pure TestNG BDD implementation
Related
I am trying to execute tests cases in parallel with TestNG. My test cases will use the same page and sometimes the same page objects. I close the webdriver after every scenario, however, I am getting an invalid session ID on random test cases. I checked the session IDs and it seems like the method is trying to use the most recently closed session to try and locate the web element. Does this mean that my drivers are not threadsafe? My DriverFactory class is as below
DriverFactory Class
public class DriverFactory{
private DriverFactory() {
}
private static DriverFactory instance = new DriverFactory();
public static DriverFactory getInstance() {
return instance;
}
ThreadLocal<WebDriver> driver = new ThreadLocal<WebDriver>();
public WebDriver getDriver() {
return driver.get();
}
public void setDriver(WebDriver driverParm) {
driver.set(driverParm);
}
public void closeBrowser() {
driver.get().close();
driver.remove();
}
}
In my Page Object classes, I created a constructor with the WebDriver. An example of my page object classes is as follows
Page Object Class
public class ExamplePageOne{
private WebDriver driver;
public ExmaplePageOne(WebDriver driver){
this.driver=driver;
PageFactory.initElements(driver,this);
}
#FindBy(how=How.XPATH, using = "//xpath here")
WebElement ButtonOne;
#FindBy(how=How.XPATH, using = "//xpath here")
WebElement ButtonTwo;
public void clickOnButtonOne(){
ButtonOne.click()
}
public void clickOnButtonTwo(){
ButtonTwo.click()
}
public void validate(){
//validate code here
}
}
In my step definition files, I create the instance of the page object class whenever I want to use it. I have also tried to create the instance out of the step methods but that did not work too.
Step Definition Class
public class StepDef{
#Given("Given step")
public void given_step(){
//block of code here
}
#When("When step")
public void when_step(){
ExamplePageOne pageOne = new ExamplePageOne(DriverFactory.getInstance.getDriver());
pageOne.clickOnButtonOne;
}
#Then("Then step")
public void then_step(){
ExamplePageOne pageOne = new ExamplePageOne(DriverFactory.getInstance.getDriver());
pageOne.validate;
}
}
In my hooks class, I set up and tear down the browser.
Hooks class
public class Hooks{
#Before
public void setup(){
DriverFactory.getInstance.setDriver(myOpenBrowserMethod);
WebDriver driver = DriverFactory.getInstance.getDriver();
driver.get("url here")
}
#After
public void teardown(){
DriverFactory.getInstance.closeBrowser();
}
}
Sorry if the code looks kinda weird, mostly typing this with memory. Is there something that I am doing wrong or missing? I thought my logic made sense but unfortunately not. Any help is appreciated, thank you!
I have a small vertx application with an AppLauncher class that extend of VertxCommandLauncher and I set a appConfig.json with the typical config parameters :
public class AppLauncher extends VertxCommandLauncher implements VertxLifecycleHooks {
public static void main(String[] args) {
new AppLauncher().dispatch(args);
}
#Override
public void afterConfigParsed(JsonObject config) {
AppConfig.INSTANCE.setConfig(config);
}
To run my application in my IDE I put in edit configuration my main class (Applauncher.java) and the arguments :
run io.vertx.covid.verticle.MainVerticle -conf../vertx-application/src/main/resources/appConfig.json
This is my test class:
#BeforeAll
static void deployVerticles(Vertx vertx, VertxTestContext testContext) {
vertx.deployVerticle(BaseVerticle.class.getName(),testContext
.succeeding(id->testContext.completeNow()));
}
This is my BaseVerticle class that all my verticles extends from:
public abstract class BaseVerticle extends AbstractVerticle {
public static String CONTEXT_PATH = AppConfig.INSTANCE.getConfig().getString(Constants.CONTEXT_PATH);
}
And this is my AppConfig class :
public enum AppConfig {
INSTANCE;
private JsonObject config;
public JsonObject getConfig() {
return config;
}
public void setConfig(JsonObject config) {
this.config = config;
}
}
Everything works, but if I would like to test it in a separete way then I deploy my verticles but I have a Nullpointer in the CONTEXT_PATH (BaseVerticle class) because the config (suppose to be taken from appConfig.json) is null.
I haven't found a way to pass the arguments with my appConfig.json or should I call to the main method passing the arguments?
I like to do something that is similar to profiles in my vertx application.
If you set an environment variable with the key vertx-config-path before the vertx instance is initialized, you can control where vertx's config retriever (you might need to add vert-config to your gradle/maven dependencies) gets the configuration from.
In your launcher, you can do something like the following, which will give you the ability to add profile based config files to your resources folder conf/config-%s.json where %s is the profile name:
public class CustomLauncher extends Launcher {
public static final String ACTIVE_PROFILE_PROPERTY = "APP_ACTIVE_PROFILE";
private static final CLI cli = CLI.create("main")
.addOption(new Option()
.setShortName("p")
.setLongName("profile")
);
public static void main(String[] args) {
initDefaults(Arrays.asList(args));
new CustomLauncher().dispatch(args);
}
public static void executeCommand(String cmd, String... args) {
initDefaults(Arrays.asList(args));
new CustomLauncher().execute(cmd, args);
}
public static void initDefaults(List<String> args) {
System.setProperty(LoggerFactory.LOGGER_DELEGATE_FACTORY_CLASS_NAME, SLF4JLogDelegateFactory.class.getName());
CommandLine parse = cli.parse(args);
String profile = parse.getOptionValue("p");
if (profile != null && !profile.isEmpty()) {
System.setProperty(ACTIVE_PROFILE_PROPERTY, profile);
System.setProperty("vertx-config-path", String.format("conf/config-%s.json", profile));
}
}
}
Then in your test, instead of relaying on vertx test extension to inject vertx for you, you can initialize it by yourself and control the profile (aka which config file to load) like the following:
private static Vertx vertx;
#BeforeAll
public static void deployVerticles(VertxTestContext testContext) {
CustomLauncher.initDefaults(Arrays.asList("--profile", "test"))
vertx = Vertx.vertx();
ConfigRetriever.create(vertx).getConfig(asyncResult -> {
if (asyncResult.succeeded()) {
JsonObject config = asyncResult.result();
DeploymentOptions deploymentOptions = new DeploymentOptions()
.setConfig(config);
vertx.deployVerticle(BaseVerticle.class.getName(), deploymentOptions);
} else {
// handle failure
}
});
}
Then when you run your application, instead of providing -conf, you can use -p or --profile
I also highly recommend to get familiar with vertx-config as you can also get env variables, k8s config maps, and much more.
EDIT: I also highly recommend to move to Kotlin if possible, makes the async-code much easier to handle in an imperative way (with Coroutines). It's very hard to deal with libraries like Vert.x in Java compared to languages like Kotlin.
I solved my problem creating a verticle with the config stuffs (vertx-config documentation), here is my verticle config class:
public class ConfigVerticle extends AbstractVerticle {
protected static Logger logger = LoggerFactory.getLogger(ConfigVerticle.class);
public static JsonObject config;
#Override
public void start() throws Exception {
ConfigStoreOptions fileStore = new ConfigStoreOptions()
.setType("file")
.setOptional(true)
.setConfig(new JsonObject().put("path", "conf/appConfig.json"));
ConfigStoreOptions sysPropsStore = new ConfigStoreOptions().setType("sys");
ConfigRetrieverOptions options = new ConfigRetrieverOptions().addStore(fileStore).addStore(sysPropsStore);
ConfigRetriever retriever = ConfigRetriever.create(vertx, options);
retriever.getConfig(ar -> {
if (ar.failed()) {
logger.info("Failed to retrieve config from appConfig.json");
} else {
config = ar.result();
vertx.deployVerticle(MainVerticle.class.getName(), new DeploymentOptions().setConfig(config));
}
});
}
}
And my MainVerticle.class I pass the new configuration like this:
public class MainVerticle extends AbstractVerticle {
#Override
public void start(){
vertx.deployVerticle(BackendVerticle.class.getName(), new DeploymentOptions().setConfig(config()));
}
}
Then, my simpleTests :
#ExtendWith(VertxExtension.class)
public class BaseCovidTest {
protected WebClient webClient;
#BeforeEach
void initWebClient(Vertx vertx){
webClient = WebClient.create(vertx);
}
#BeforeAll
static void deployVerticles(Vertx vertx, VertxTestContext vertxTestContext) {
vertx.deployVerticle(ConfigVerticle.class.getName() ,vertxTestContext
.succeeding(id-> {
try {
vertxTestContext.awaitCompletion(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
vertxTestContext.completeNow();
}));
}
}
And everything works, thanks #Tom that inspired me to fix it!
Selenium - I have defined my driver in a singleton class and used that driver in multiple classes. Now if i run the classes in series it's working fine, But if i try to run the classes in parallel it's not working.
For eg : if i give the thread count as 2 only one browser is opened.
How to run the testcases in parallel using singleton class?
Singleton class :
public class SingletonDesignPattern {
private static Utilities obj;
private SingletonDesignPattern() {
}
public static Utilities getInstance() {
if(obj == null) {
obj = new Utilities();
}
return obj;
}
Driver class:
public class Driver {
public static WebDriver driver;
public static Utilities util = SingletonDesignPattern.getInstance();
public String currentdate = null;
Test_action test = new Test_action();
#BeforeSuite
public void execute_test() throws Exception {
try {
driver = util.initializeDriver();
}
catch (Exception e) {
e.printStackTrace();
}
}
#BeforeTest
public void beforebacklog() throws Exception
{
util.getlogin();
}
#AfterSuite
public void quit() throws Exception {
driver.quit();
}
}
I am running parallel tests using selenium (selenium-server-standalone-2.47.1.jar) grid with TestNg, kicked off by ant, and using a TestListenerAdapter. Screenshots are taken in the listener's 'onTestFailure' method. The problem is that the listener seems to get crossed up about which driver it should be using, and sometimes takes a screenshot of the wrong browser window, or fails altogether if the driver that it thinks it should be using has already quit.
When the tests start, TestNg's #BeforeTest and the TestListenerAdapter's 'onTestStart' methods are running on the same thread, but when the test fails, the TestListenerAdapter's 'onTestFailure' method appears to be running on a separate thread. It seems like the threads are getting crossed/shared somehow, but I can't figure out why.
Here is some skeleton code, any assistance greatly appreciated.
Base Test Class:
public class baseClassTests{
protected AutomationUtils au;
protected DriverUtils du;
#BeforeTest(alwaysRun = true)
#Parameters({ "selenium.OS", "selenium.browser" })
public void beforeTest(String OS, String browser) {
//these call simple private methods to know where to set up the driver
String port = getPort(OS, browser);
String host = getHost(OS);
//make a driver utility object here, this makes a driver
du = new DriverUtils(browser, host, port);
//pass this driver utility object to another class of utilities
//this 'AutomationUtils' class gets a RemoteWebDriver ('driver') by calling driver=du.getDriver();
//the 'AutomationUtils' class is then the one that does all of the 'driver.findBy...' etc
au = new AutomationUtils(du);
}
#BeforeMethod(alwaysRun = true)
public void beforeMethod(Method m, ITestResult tr) {
du.deleteCookies();
testNgTestName = m.getName();
print("Method: "+testNgTestName + " Thread: "+Thread.currentThread().hashCode());
//set the attribute of the ITestResult object so we can use the same object in the listener
tr.setAttribute("du", du);
tr.setAttribute("au", au);
}
}
Listener class
public class AmSimpleTestListener extends TestListenerAdapter {
private DriverUtils driveU;
private AutomationUtils AutoU;
private RemoteWebDriver driver;
private RemoteWebDriver augmentedDriver;
private String methodName;
private String browser;
private String browserVersion;
String testClass;
#Override
public void onTestStart(ITestResult tr) {
//pick up the correct driver utility object from the test class/method we are in
driveU = (DriverUtils) tr.getAttribute("du");
AutoU = (AutomationUtils) tr.getAttribute("au");
driver = du.getDriver();
augmentedDriver = (RemoteWebDriver) new Augmenter().augment(driver);
methodName = tr.getName();
testClass=tr.getTestClass(); //sort of, I actually parse it up a bit
browser = driveU.getBrowser();
browserVersion = driveU.getBrowserVersion();
print("Method: "+methodName + " Thread: "+Thread.currentThread().hashCode());
}
#Override
public void onTestFailure(ITestResult tr) {
print("Method: "+tr.getName() + " Thread: "+Thread.currentThread().hashCode());
try{
writeScreenshotFile();
}
catch (Exception e){
Out.error("Unable to take screen shot");
e.printStackTrace();
}
}
private String writeScreenshotFile() {
if (driver != null && driver.getSessionId() != null) {
File scrShot = ((TakesScreenshot) augmentedDriver).getScreenshotAs(OutputType.FILE);
File localPathToScreenShot = new File("/path/to/base/directory/"+testClass+"/"+methodName+".png");
try {
FileUtils.copyFile(scrShot, localPathToScreenShot);
} catch (Exception e) {
Out.error("Couldn't write screenshot to file");
}
return localPathToScreenShot.getAbsolutePath();
}
return "Could not get path.";
}
}
DriverUtils class makes/supplies the driver
public class DriverUtils {
private RemoteWebDriver driver;
private int timeout;
private String browserVersion;
private String browser
private DesiredCapabilities caps;
public DriverUtils(String browser, String host, String port) {
String hostUrl = "http://" + host + ":" + port + "/wd/hub";
this.browser=browser;
//do some stuff here to set capabilties
driver = new RemoteWebDriver(new URL(hostUrl), caps);
browserVersion = driver.getCapabilities().getVersion();
}
public RemoteWebDriver getDriver() {
return driver;
}
public AmBrowser getBrowser() {
return browser;
}
public String getBrowserVersion() {
return browserVersion;
}
public void quitDriver() {
driver.quit();
}
public void deleteCookies(){
driver.manage().deleteAllCookies();
}
}
public class AutomationUtils extends BaseClassUtils {
public AutomationUtils(DriverUtils driverUtils) {
//pass it up to the base class utils (this is different than base class tests, above)
//do this so the driver can be accessed by other utility classes as well
super(driverUtils);
}
//All sorts of methods here to find elements, login, blah blah everything that is done with a driver object
}
public class BaseClassUtils { //this is a different class than BaseClassTests
//make the driver a protected object so all utility classes can access as nec.
protected final RemoteWebDriver driver;
public BaseClassUtils(DriverUtils driverUtils) {
driver = driverUtils.getDriver();
}
}
Tests are run via ant.
<suite name="Dev2 for debugging" parallel="tests" thread-count="10">-- tests here </suite>
After tinkering this for a while, I came to the conclusion that there were two things that seemed to help tremendously.
eliminate the listener, and take all screenshots in the #AfterMethod
Move the #Before/After Method/Test methods into the child classes, but simply call methods in the parent to do all the work.
Another thing I noticed is that for #2, TestNG is supposed to run the parent #Before methods then the child #Before methods; and then at the end run the child '#After' methods and then the parent #After methods.
I ran a series of simple tests, I found that all before/after methods were not being run, so for the few cases where I was using #Before and #After methods in both parent and child, I consolidated.
Things seem to run much better now, the driver does not get confused, and screenshots are being attached to the correct browser/test.
Try using a ThreadLocal for RemoteWebDriver so it can handle parallel runs:
public class DriverUtils {
private static ThreadLocal<RemoteWebDriver> driver = new ThreadLocal<~>();
private int timeout;
private String browserVersion;
private String browser
private DesiredCapabilities caps;
public DriverUtils(String browser, String host, String port) {
String hostUrl = "http://" + host + ":" + port + "/wd/hub";
this.browser=browser;
//do some stuff here to set capabilties
driver.set(new RemoteWebDriver(new URL(hostUrl), caps));
browserVersion = getDriver().getCapabilities().getVersion();
}
public RemoteWebDriver getDriver() {
return driver.get();
}
public AmBrowser getBrowser() {
return browser;
}
public String getBrowserVersion() {
return browserVersion;
}
public void quitDriver() {
getDriver().quit();
}
public void deleteCookies(){
getDriver().manage().deleteAllCookies();
}
}
I have been writing Selenium test for web application and there seem to be multiple instances of Internal Server Error in application in case of Internal Server Error, application displays custom error page and and error id is displayed to user to pursue matter with technical team, in case user encounter it.
This makes it a little laborious to debug the test failures during Selenium execution.
I was thinking to use some mechanism to keep polling a page with each step executed in test to find if there was any instance of Internal Server error, And this is when I came across Junit Rule and thought of writing a custom annotation for it, some thing like -
public class SelTestCase {
protected WebDriver driver;
#Before
public void startDriver() {
driver = new FirefoxDriver();
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
driver.get("http://www.google.com/");
}
#After
public void closeDriver() {
driver.quit();
}
}
public class GoogleSearchTest extends SelTestCase {
#Rule
PageChecker pageChecker = new PageChecker();
#Test
#CheckPage
public void testGoogleSearch() {
GoogleHomePage googleHomePage = PageFactory.initElements(driver,
GoogleHomePage.class);
googleHomePage.searchGoogle("Selenium HQ");
assert driver.getPageSource().contains("seleniumhq") : "Selenium headquarter search failed";
}
}
SelTestCase class creates instance of WebDriver to execute test, And here is the PageChecker class -
public class PageChecker extends SelTestCase {
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD})
public #interface CheckPage {
// page check should take place here (Though not sure if it is right place)
// like if(driver.getPageSource.contains("Internal Server Error") {throw Exception ("Err")}
}
}
This is what I am stuck with, how do I proceed with CheckPage annonations?
IMHO there are two solutions to your problems. If the feature is only needed by a small part of your tests, then I would not use a rule. Instead add a single line errorChecker.checkPage(driver) to each tests and implement the check in this method.
If you need it for almost all your tests:
Convert SelTestCase to a rule by extending ExternalResource.
public class WebDriverRule extends ExternalResource {
public WebDriver driver;
#Override
protected void before() {
driver = new FirefoxDriver();
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
driver.get("http://www.google.com/");
}
#Override
protected void after() {
driver.quit();
}
}
Add the page check code to the rule by extending Verifier.
public class PageChecker extends Verifier {
private WebDriverRule webDriverRule;
private enabled = true;
public PageChecker(WebDriverRule webDriverRule) {
this.webDriverRule = webDriverRule;
}
public void disable() {
this.enabled = false;
}
#Override
public void verify() {
if(enabled && notValid())
throw new AssertionError("foo");
}
private boolean notValid() {
WebDriver driver = webDriverRule.driver;
//do something with driver
}
}
Use org.junit.rules.RuleChain to control the execution order of the two rules.
public class GoogleSearchTest {
private WebDriverRule webDriverRule = new WebDriverRule();
private PageChecker pageChecker = new PageChecker(webDriverRule);
#Rule
public RuleChain driverAroundPageChecker
= RuleChain.outerRule(webDriverRule).around(pageChecker);
#Test
public void testGoogleSearch() {
GoogleHomePage googleHomePage = PageFactory.initElements(driver,
GoogleHomePage.class);
googleHomePage.searchGoogle("Selenium HQ");
assert driver.getPageSource().contains("seleniumhq") : "Selenium headquarter search failed";
}
#Test
public void testWithouPageCheck() {
pageChecker.disable();
//here is your real test
}
}