mavenjunitcucumberplaywrightplaywright-java

When I run multile tests in Playwright, every test after first open the browser but it doesn't open the page I think


I work as solo QA in my company and recently I started working on Playwright for test automation. After I completing each test i would run it few times just to make sure everything is working and that it's not flanky. After I completed 30 of them I ran all the tests using Cucumber Test runner. First test pass, every test after that every test fails for the following reason:

Error navigating to URL: Playwright connection closed
Error clicking on element: Error {
  message='Target page, context or browser has been closed
  name='TargetClosedError
  stack='TargetClosedError:Target page, context or browser has been closed

I get what the error says but I am following the best practices where I am closing the page, browser, browser context and playwright instance after each test but I am doing something wrong obviously. This is how my Playwright Driver looks like:

public class PlaywrightDriver {

    private static Playwright playwright;
    private static Browser browser;
    private static BrowserContext browserContext;
    private static Page page;

    public PlaywrightDriver() {
        playwright = Playwright.create();
    }

    public Page initDriver() {

        //initializing browser type
        String browserType = ConfigurationReader.getProperty("browser");
        
        //setting arguments for the session
        List<String> arguments = new ArrayList<>();
        arguments.add("--start-maximized");

        switch (browserType.toLowerCase()) {

            case "chromium":
                browser = playwright.chromium().launch(new BrowserType.LaunchOptions().setHeadless(false).setArgs(arguments));
                break;
        }

        browserContext = browser.newContext();
        page = browserContext.newPage();
        return page;
    }


    public void closeDriver() {
        if(page != null){
            page.close();
        }
        if (browserContext != null) {
            browserContext.close();
        }
        if (browser != null) {
            browser.close();
        }
        if (playwright != null) {
            playwright.close();
        }
    }
}

This is how my Hook Class looks like

public class Hooks extends Base_StepDef{

    private PlaywrightDriver driver;
    private static Page page;

    @Before
    public void setup(Scenario scenario) {
        driver = new PlaywrightDriver();
        page = driver.initDriver();
    }

    @After
    public void tearDown(Scenario scenario) {
        softAssert.assertAll();
        driver.closeDriver();
   
    }


    public static Page getPage() {
        return page; // Provide access to the Page instance
    }
}

Added the navigate method that is in the Browser Utils

 public static void navigateTo(String url) {
      page.navigate(url);
       
    }

I am expecting for my tests to run one after another, the page and browser context should be new for each test, but playwright instance can be closed after all. Overall all I need is for multiple test run to work

Update: I removed the static navigate method I used from BrowserUtils and the page is able to load but as soon as the first method from BrowserUtils I hit the roadblock again

Solution: I use BrowserUtils static methods like click or getTextFromElement etc. All those methods require a object of Page class which I had in Hooks page. So what I did before was I created another object of Page class in BrowserUtils and assign that page object to the Page from Hooks class which was static, so this object had to be static as well. Which is no good, since it is static it remains the same, so after the first test when I tried to click on anything it was trying to click with page from previous test, thus it was not completing the action. This might not be perfect patch but it is working... Thanks everyone


Solution

  • I would recommend that you do not open and close the PlayWright and the Browser object for each single test, but just before and after the complete testrun.

    Only do this per test for the BrowserContext. You do that now in the @Before and @After annotated methods.

    Here an example how you can still use the Cucumber annotations for all that stuff. Please take care that this is only written for single-threaded use. If you want to run multiple tests in parallel, I would recommend that you introduce dependency injection, e.g. Google Guice for Cucumber, and have your elements "scenario scoped", that means they are instantiated per scenario, or "singleton scoped", that means they are instantiated per test run. You should mix those appropriatly.

    import com.microsoft.playwright.Browser;
    import com.microsoft.playwright.BrowserContext;
    import com.microsoft.playwright.BrowserType;
    import com.microsoft.playwright.Page;
    import com.microsoft.playwright.Playwright;
    
    public class PlaywrightDriver {
    
      private static Playwright playwright;
      private static Browser browser;
      private static BrowserContext browserContext;
      private static Page page;
    
      public PlaywrightDriver() {
        playwright = Playwright.create();
      }
    
      public void initContext() {
        browserContext = browser.newContext();
      }
    
      public Page getPage() {
        if (null == page) {
          if (null == browserContext) {
            initContext();
          }
          page = browserContext.newPage();
        }
        return page;
      }
    
      public void initDriver() {
        browser = playwright.chromium().launch(
            new BrowserType.LaunchOptions()
                           .setHeadless(false)
                           .setArgs(arguments));
      }
    
      public void closePage() {
        if (page != null) {
          page.close();
          page = null;
        }
      }
    
      public void closeContext() {
        closePage();
        if (browserContext != null) {
          browserContext.close();
          browserContext = null;
        }
      }
    
      public void closeDriver() {
        closeContext();
        if (browser != null) {
          browser.close();
          browser = null;
        }
        if (playwright != null) {
          playwright.close();
          playwright = null;
        }
      }
    }
    
    import io.cucumber.java.After;
    import io.cucumber.java.AfterAll;
    import io.cucumber.java.Before;
    import io.cucumber.java.BeforeAll;
    
    public class Hooks extends Base_StepDef {
    
      private PlaywrightDriver driver;
    
      @BeforeAll
      public void setupRun() {
        driver = new PlaywrightDriver();
        driver.initDriver();
      }
    
      @AfterAll
      public void tearDownRun(Scenario scenario) {
        softAssert.assertAll();
        driver.closeDriver();
      }
    
      @Before
      public void setup(Scenario scenario) {
        // get page here to move lazy initialization out of the test
        driver.getPage(); 
      }
    
      @After
      public void tearDown() {
        driver.closeContext();
      }
    
      public Page getPage() {
        return driver.getPage(); // Provide access to the Page instance
      }
    }
    

    I moved the getPage() method to the instance instead of the class to make the first step towards parallel test execution.