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
}
}
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 am new to Selenium, so basically whenever I ran my test, it would open up the URL which I have mentioned in my test. In my same Test I have also mentioned to fill the username and Password.
But somehow once the browser gets launched and redirect to the URL, it opens up another instance of blank browser failing my script that element not found.
Please help me here.
///////////////////////////////////////////////////////////////////////////
public class TruefillTest extends BaseClass {
public Truefill truefill()
{
WebDriver driver=InitializeDriver();
return new Truefill(driver);
}
#Test
public void userLoginIntoTheSystem()
{
truefill().dashBoard().Url();
truefill().dashBoard().EnterUsername("bjdgfe#swcwr.com");
truefill().dashBoard().EnterPassword("Test1234");
}
///////////////////////////////////////////////
public class Truefill {
private WebDriver driver;
public Truefill(WebDriver driver) {
this.driver=driver;
}
public DashBoardPage dashBoard()
{
return new DashBoardPage(driver);
}
////////////////////////////////////////////////////////////
public class DashBoardPage {
private final WebDriver driver;
By Username= By.xpath("//input[#name='name']");
By Password= By.xpath("//input[contains(#id,'exampleInputPassword1')]");
public DashBoardPage(WebDriver driver) {
this.driver=driver;
}
public void Url()
{
driver.get("https://rahulshettyacademy.com/angularpractice/");
}
public void EnterUsername(String username)
{
driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);
driver.findElement(Username).sendKeys(username);
}
public void EnterPassword(String password)
{
driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);
driver.findElement(Password).sendKeys(password);
}
////////////////////////////////////////////////////////////
public class BaseClass {
WebDriver driver;
public WebDriver InitializeDriver()
{
WebDriverManager.chromedriver().setup();
driver = new ChromeDriver();
return driver;
}
}
Each call to the truefill() method initializes a new instance of WebDriver. Since your test is calling it multiple times, it will start a new browser instance on each line. Instead, store the DashboardPage in a local variable:
#Test
public void userLoginIntoTheSystem() {
DashBoardPage dashBoardPage = truefill().dashBoard();
dashBoardPage.Url();
dashBoardPage.EnterUsername("bjdgfe#swcwr.com");
dashBoardPage.EnterPassword("Test1234");
}
You might also want to use a setup method to initialize the Truefill instance, rather than creating it on demand:
private Truefill truefill;
#BeforeEach
public void initializeTruefill() {
WebDriver driver = InitializeDriver();
truefill = new Truefill(driver);
}
#Test
public void userLoginIntoTheSystem() {
DashBoardPage dashBoardPage = truefill.dashBoard();
dashBoardPage.Url();
dashBoardPage.EnterUsername("bjdgfe#swcwr.com");
dashBoardPage.EnterPassword("Test1234");
}
This assumes JUnit 5. If you're using JUnit 4, the annotation is #Before instead of #BeforeEach.
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
My test project has #BeforeClass and #AfterClass in the BaseTest.class:
public class BaseTest {
#BeforeClass(groups = {"first"})
public void beforeTest() {
getDriver().get("URL");
}
#AfterClass(alwaysRun = true)
public void afterTest() {
getDriver().close();
removeDriver();
}
}
and its FirstTest.Class:
public class SmokeTest extends BaseTest {
private PageOne pageOne = PageFactory.initElements(getDriver(), PageOne.class);
#Test(testName = "test1", groups = { "first" })
public void firstTest() {
pageOne.goSomething();
}
#Test(testName = "test2", groups = { "first" })
public void secondTest() {
pageOne.goSomethingElse();
}
}
WebDriver singleton:
public class WebDriverSingleton {
private static WebDriver driver;
public static WebDriver getDriver() {
if (driver == null) {
driver = new ChromeDriver();
}
return driver;
}
public static void removeDriver() {
driver.quit();
driver = null;
}
}
And it works just fine. Until I have added new cases which I'd like to start in a new browser instances.
What I've added:
public class BaseModuleTest {
#BeforeClass(groups = {"second"})
public void beforeClass() {
getDriver().get("another URL");
}
}
public class ModuleTest extends BaseModuleTest {
private PageTwo pageTwo = PageFactory.initElements(getDriver(), PageTwo.class);
private PageOne pageOne = PageFactory.initElements(getDriver(), PageOne.class);
#Test(testName = "test1", groups = {"second"})
public void testUp() {
pageTwo.goToPageOne();
pageOne.doSomething(); // getting fail here
}
}
When I start test using testgnconfiguration.xml
'first' group passes
browser window closes
new instance of WebDriver opens new browser window
goes to URL
when starts interacting with the first WebElement on the page that was instantiated during 'first' group run returns an error:
org.openqa.selenium.NoSuchSessionException: Session ID is null. Using WebDriver after calling quit()?
So far as I'm using the Page Object Model and I'm using in the 'second' group the same Pages which has been initialised in the 'first' group, I guess they somehow 'remember' the first Session Id which became 'null'.
Although I reinitialise them in ModuleTest.test1()...
Anyway if my suggestions are correct, how to pass a new sessionId to instantiated page objects? Or what is proper way to reinitialise them?
Looks like your problem is:
#BeforeClass(groups = {"first"})
and
#AfterClass(alwaysRun = true)
You are always destroying the driver object after finishing with a class, but you are only creating a driver object for a class in a specific group. I would suggest tweaking your #BeforeClass to:
#BeforeClass(alwaysRun = true)
or
#BeforeClass(groups = {"first, "second"})
public class BTest implements WebDriverProvider
{
#Test
public void launch()
{
System.setProperty("browser", "firefox");
Configuration.browser=BTest.class.getName();
Configuration.screenshots = false;
Configuration.browserSize = "1920x1200";
}
#Override
public WebDriver createDriver(DesiredCapabilities arg0) {
FirefoxDriverManager.getInstance().setup();
return new FirefoxDriver();
}
}
In your test you just setting configuration but doesn't do anything to actually start the browser.
try to open some web page. e.g.:
open("http://google.com");