Skip to content

Instantly share code, notes, and snippets.

@beatngu13
Last active May 4, 2024 17:22
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save beatngu13/8d5f60355d7fbef143198f020b1efca3 to your computer and use it in GitHub Desktop.
Save beatngu13/8d5f60355d7fbef143198f020b1efca3 to your computer and use it in GitHub Desktop.
Dead-simple cross-browser testing with Selenium and JUnit 5 parameterized tests
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.openqa.selenium.WebDriver;
class CrossBrowserTest {
@ParameterizedTest
@MethodSource( "my.package.WebDriverFactory#getAll" )
void cross_browser_test( final WebDriver driver ) {
System.out.println( "Test with " + driver.getClass().getSimpleName() );
}
}
import java.util.Arrays;
import java.util.stream.Stream;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
public class WebDriverFactory {
enum Driver {
CHROME,
FIREFOX
// add more drivers
}
public static WebDriver get( final Driver driver ) {
// add default options
switch ( driver ) {
case CHROME:
return new ChromeDriver();
case FIREFOX:
return new FirefoxDriver();
default:
throw new IllegalArgumentException( "No '" + driver + "' driver available." );
}
}
public static Stream<WebDriver> getAll() {
return Arrays.stream( Driver.values() ).map( WebDriverFactory::get );
}
}
@martin-v
Copy link

Or even simpler

public class WebDriverFactory {
  public static Stream<WebDriver> drivers() {
    return Stream.of( new ChromeDriver(), new FirefoxDriver() );
  }
}

@beatngu13
Copy link
Author

Yep, that's sufficient if you don't need a factory method for a single driver (WebDriverFactory#driver( Driver )).

@jackeymason
Copy link

Thank you for putting this together.
I never would have considered the use of a Stream for this. This lead to me to exploring lambda expressions.
Regarding: "WebDriverFactory::driver"
This doesn't compile for me. Shouldn't the double colon be followed by method? Maybe you meant "get" ?
Tested with WebDriverFactory:get and it worked great.
Many thanks.

@beatngu13
Copy link
Author

Hey @jackeymason, glad you like the approach. And you are right, ::driver was a typo, which was the old method name. Thanks for pointing this out!

@Vivek-Malhotra
Copy link

do we have complete code. for .e.g I just need to run a simple login test using webdriver on two browsers. where do I pass the browser option to @test Method?

@beatngu13
Copy link
Author

@Vivek-Malhotra your @Test becomes a @ParameterizedTest (see https://gist.github.com/beatngu13/8d5f60355d7fbef143198f020b1efca3#file-crossbrowsertest-java-L7), then you can specify the WebDriver parameter (see https://gist.github.com/beatngu13/8d5f60355d7fbef143198f020b1efca3#file-crossbrowsertest-java-L9).

Or what exactly are you struggling with?

@Vivek-Malhotra
Copy link

Thanks, @beatngu13
I will try, but is there a way to do it with Junit-4 also. I have an existing framework with a large set of test suite which on Junit-4. I am not sure right now, if switching to Junit-5 will break anything, but will try this option also.

@standbyoneself
Copy link

Could it work with BeforeEach/BeforeAll annotations? I have a class with fixtures about running Playwright, creating browser, closing browser after all tests etc.

@beatngu13
Copy link
Author

@standbyoneself I'm not familiar with the Playwright API, do you have an example / some code to look at?

@standbyoneself
Copy link

Yes. Here is a class with tests:

import java.util.Date;

import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

import io.qameta.allure.Issue;
import io.qameta.allure.TmsLink;
import ru.sbrf.rmc.annotations.Certificate;
import ru.sbrf.rmc.models.AdminPagePath;
import ru.sbrf.rmc.pages.AdminPage;

@DisplayName("Админка")
public class AdminPageTests extends PageTestFixtures {
    private AdminPage adminPage;

    @BeforeEach
    public void createPage() {
        adminPage = new AdminPage(page);
    }

    @Nested
    @DisplayName("Объекты")
    class Subjects {
        private void shouldCreateAndDeleteSubject(String prefix, String searchQuery) {
            long timestamp = new Date().getTime();
            String name = prefix + "-" + timestamp;
            adminPage.goTo(AdminPagePath.SUBJECTS);
            adminPage.goToSubjectForm();
            adminPage.fillSubjectName(name);
            adminPage.fillSubjectSearchQuery(searchQuery);
            adminPage.submitSubjectForm();
            adminPage.waitFor(AdminPagePath.SUBJECTS);
            adminPage.checkIfSubjectIsVisible(name);
            adminPage.deleteSubject(name);
        }

        @ParameterizedTest(name = "{displayName}")
        @Certificate("curuser-6")
        @Issue("RMC-6335")
        @TmsLink("RMC-T6354")
        @CsvSource({"Subject,playwright OR selenium"})
        @DisplayName("Создание и удаление объекта с ролью \"Бизнес-администратор\"")
        public void shouldCreateAndDeleteSubjectByFeedAdmin(String prefix, String searchQuery) {
            shouldCreateAndDeleteSubject(prefix, searchQuery);
        }
    
        @ParameterizedTest(name = "{displayName}")
        @Certificate("curuser-10")
        @Issue("RMC-6335")
        @TmsLink("RMC-T6355")
        @CsvSource({"Subject,playwright OR selenium"})
        @DisplayName("Создание и удаление объекта с ролью \"Куратор процесса\"")
        public void shouldCreateAndDeleteSubjectByProcessManager(String prefix, String searchQuery) {
            shouldCreateAndDeleteSubject(prefix, searchQuery);
        }
    }
}

And a fixture class:

import java.lang.reflect.Method;
import java.util.List;
import java.util.Optional;

import org.junit.jupiter.api.*;

import com.microsoft.playwright.*;
import com.microsoft.playwright.options.Cookie;

import ru.sbrf.rmc.AuthClient;
import ru.sbrf.rmc.AuthClientImpl;
import ru.sbrf.rmc.annotations.Certificate;
import ru.sbrf.rmc.mappers.CookieMapperImpl;

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class PageTestFixtures {
    private Playwright playwright;
    private Browser browser;
    private BrowserContext context;
    protected Page page;
    private AuthClient authClient = new AuthClientImpl(new CookieMapperImpl());

    private String getCertificateName(TestInfo testInfo) throws NoSuchMethodException {
        Optional<Method> optionalMethod = testInfo.getTestMethod();

        if (optionalMethod.isEmpty()) {
            throw new NoSuchMethodException();
        }

        Method method = optionalMethod.get();
        Certificate annotation = method.getAnnotation(Certificate.class);
        return annotation.value();
    }

    @BeforeAll
    public void launchBrowser() {
        playwright = Playwright.create();
        browser = playwright.chromium().launch();
    }

    @AfterAll
    public void closeBrowser() {
        playwright.close();
    }

    @BeforeEach
    public void createContextAndPage(TestInfo testInfo) {
        context = browser.newContext(new Browser.NewContextOptions()
            .setBaseURL(System.getenv().getOrDefault("BASE_URL", "https://rmp-ift.sberbank.ru/lenta/"))
            .setIgnoreHTTPSErrors(true));
        page = context.newPage();

        try {
            String certificateName = getCertificateName(testInfo);
            List<Cookie> cookies = authClient.getSessionCookies(certificateName);
            context.addCookies(cookies);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @AfterEach
    public void closeContext() {
        context.close();
    }
}

@standbyoneself
Copy link

standbyoneself commented May 3, 2024

I have used @ParameterizedTest in couple with @​ValueSource(strings = {"chromium","firefox"}), but I have to explicitly call setupBrowser() in each test, which is really annoying. Like that:

Test

        @ParameterizedTest(name = "{displayName}")
        @Certificate("curuser-6")
        @Issue("RMC-6335")
        @TmsLink("RMC-T6354")
        @ValueSource(strings = {"chromium","firefox"})
        @DisplayName("Создание и удаление объекта с ролью \"Бизнес-администратор\"")
        public void shouldCreateAndDeleteSubjectByFeedAdmin(String browser) {
            setupBrowser(browser);
            shouldCreateAndDeleteSubject();
        }

Fixture

    protected void setupBrowser(String browserType) {
        playwright = Playwright.create();
        if (browserType.equals("chromium")) {
            browser = playwright.chromium().launch();
        } else if (browserType.equals("firefox")) {
            browser = playwright.firefox().launch();
        } else {
            throw new IllegalArgumentException();
        }
        createContextAndPage();
    }

Really want to have ability to use BeforeEach/AfterAll annotations for better readability and non-verbose reusability

@beatngu13
Copy link
Author

@standbyoneself I just had a look at the Playwright docs. Are you aware that there is (experimental) Junit 5 support? Apparently, it does all the lifecycle management for you. And for cross-browser testing, maybe OptionsFactory can help?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment