Fix Chrome and Firefox in new test framework on GitHub Actions (#44804)

Closes #44776

Signed-off-by: stianst <stianst@gmail.com>
This commit is contained in:
Stian Thorgersen
2025-12-10 16:22:47 +01:00
committed by GitHub
parent d9aa424d51
commit d25a731ae5
13 changed files with 161 additions and 57 deletions

View File

@@ -914,7 +914,7 @@ jobs:
- name: Run new Forms IT
run: |
./mvnw package -f tests/pom.xml -Dkc.test.browser=${{ matrix.browser }} -Dtest=FormsTestSuite
./mvnw package -f tests/pom.xml -Dkc.test.browser=${{ matrix.browser }}-headless -Dtest=FormsTestSuite
- uses: ./.github/actions/upload-flaky-tests
name: Upload flaky tests

View File

@@ -174,6 +174,16 @@ Valid values:
| firefox | Firefox WebDriver |
| firefox-headless | Firefox WebDriver without UI |
Resolving the web driver is done either automatically by Selenium, or the binary can be specified directly either
through using `CHROMEWEBDRIVER` and `GECKOWEBDRIVER` environment variables (these environment variables are already
configured on GitHub Actions), or it can be configured using standard test framework configuration options.
Configuration:
| Value | Description |
|-----------------------------------------------------|--------------------|
| `kc.test.browser.driver` / `KC_TEST_BROWSER_DRIVER` | Path to the driver |
### Supplier configuration
#### Set the supplier

View File

@@ -32,8 +32,8 @@
<description>UI extension for Keycloak Test Framework</description>
<properties>
<selenium.version>4.25.0</selenium.version>
<selenium.html.unit.version>4.25.0</selenium.html.unit.version>
<selenium.version>4.39.0</selenium.version>
<selenium.html.unit.version>4.39.0</selenium.html.unit.version>
</properties>
<dependencies>

View File

@@ -1,7 +1,5 @@
package org.keycloak.testframework.ui.webdriver;
import java.time.Duration;
import java.util.Map;
import org.keycloak.testframework.injection.InstanceContext;
import org.keycloak.testframework.injection.LifeCycle;
@@ -9,10 +7,7 @@ import org.keycloak.testframework.injection.RequestedInstance;
import org.keycloak.testframework.injection.Supplier;
import org.keycloak.testframework.ui.annotations.InjectWebDriver;
import org.openqa.selenium.MutableCapabilities;
import org.openqa.selenium.PageLoadStrategy;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.CapabilityType;
public abstract class AbstractWebDriverSupplier implements Supplier<ManagedWebDriver, InjectWebDriver> {
@@ -38,9 +33,4 @@ public abstract class AbstractWebDriverSupplier implements Supplier<ManagedWebDr
public abstract WebDriver getWebDriver();
public void setCommonCapabilities(MutableCapabilities capabilities) {
capabilities.setCapability(CapabilityType.PAGE_LOAD_STRATEGY, PageLoadStrategy.NORMAL.toString());
capabilities.setCapability("timeouts", Map.of("implicit", Duration.ofSeconds(5).toMillis()));
}
}

View File

@@ -1,8 +1,6 @@
package org.keycloak.testframework.ui.webdriver;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
public class ChromeHeadlessWebDriverSupplier extends AbstractWebDriverSupplier {
@@ -13,15 +11,6 @@ public class ChromeHeadlessWebDriverSupplier extends AbstractWebDriverSupplier {
@Override
public WebDriver getWebDriver() {
ChromeOptions options = new ChromeOptions();
setCommonCapabilities(options);
options.addArguments(
"--headless",
"--disable-gpu",
"--window-size=1920,1200",
"--ignore-certificate-errors",
"--disable-dev-shm-usage"
);
return new ChromeDriver(options);
return DriverUtils.createChromeDriver(true);
}
}

View File

@@ -1,8 +1,6 @@
package org.keycloak.testframework.ui.webdriver;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
public class ChromeWebDriverSupplier extends AbstractWebDriverSupplier {
@@ -13,8 +11,6 @@ public class ChromeWebDriverSupplier extends AbstractWebDriverSupplier {
@Override
public WebDriver getWebDriver() {
ChromeOptions options = new ChromeOptions();
setCommonCapabilities(options);
return new ChromeDriver(options);
return DriverUtils.createChromeDriver(false);
}
}

View File

@@ -0,0 +1,66 @@
package org.keycloak.testframework.ui.webdriver;
import java.time.Duration;
import java.util.Map;
import org.openqa.selenium.MutableCapabilities;
import org.openqa.selenium.PageLoadStrategy;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
class DriverOptions {
static ChromeOptions createChromeOptions(boolean headless) {
ChromeOptions options = new ChromeOptions();
setCommonCapabilities(options);
if (headless) {
options.addArguments(
"--headless=new",
"--disable-gpu",
"--window-size=1920,1200",
"--ignore-certificate-errors",
"--disable-dev-shm-usage",
"--remote-allow-origins=*",
"--no-sandbox"
);
}
return options;
}
static FirefoxOptions createFirefoxOptions(boolean headless) {
FirefoxOptions options = new FirefoxOptions();
setCommonCapabilities(options);
if (headless) {
options.addArguments("-headless");
}
options.addPreference("extensions.update.enabled", "false");
options.addPreference("app.update.enabled", "false");
options.addPreference("app.update.auto", "false");
return options;
}
static DesiredCapabilities createHtmlUnitOptions() {
DesiredCapabilities capabilities = new DesiredCapabilities();
setCommonCapabilities(capabilities);
capabilities.setBrowserName("htmlunit");
capabilities.setCapability(HtmlUnitDriver.DOWNLOAD_IMAGES_CAPABILITY, false);
capabilities.setCapability(HtmlUnitDriver.JAVASCRIPT_ENABLED, true);
return capabilities;
}
private static void setCommonCapabilities(MutableCapabilities capabilities) {
capabilities.setCapability(CapabilityType.PAGE_LOAD_STRATEGY, PageLoadStrategy.NORMAL.toString());
capabilities.setCapability("timeouts", Map.of("implicit", Duration.ofSeconds(5).toMillis()));
}
}

View File

@@ -0,0 +1,70 @@
package org.keycloak.testframework.ui.webdriver;
import java.io.File;
import org.keycloak.testframework.config.Config;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeDriverService;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxDriverService;
import org.openqa.selenium.firefox.GeckoDriverService;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
class DriverUtils {
static ChromeDriver createChromeDriver(boolean headless) {
ChromeDriverService.Builder builder = new ChromeDriverService.Builder();
File driver = resolveDriver("CHROMEWEBDRIVER", "chromedriver");
if (driver != null) {
builder.usingDriverExecutable(driver);
}
ChromeDriverService driverService = builder.build();
return new ChromeDriver(driverService, DriverOptions.createChromeOptions(headless));
}
static FirefoxDriver createFirefoxDriver(boolean headless) {
GeckoDriverService.Builder builder = new GeckoDriverService.Builder();
File driver = resolveDriver("GECKOWEBDRIVER", "geckodriver");
if (driver != null) {
builder.usingDriverExecutable(driver);
}
FirefoxDriverService driverService = builder.build();
return new FirefoxDriver(driverService, DriverOptions.createFirefoxOptions(headless));
}
static HtmlUnitDriver createHtmlUnitDriver() {
HtmlUnitDriver driver = new HtmlUnitDriver(DriverOptions.createHtmlUnitOptions());
driver.getWebClient().getOptions().setCssEnabled(false);
return driver;
}
private static File resolveDriver(String envName, String driverName) {
File driver = Config.getValueTypeConfig(ManagedWebDriver.class, "driver", null, File.class);
if (driver != null) {
return driver;
}
// Environment variable can point to directory where the driver is located, or the driver directly
String driverPathFromEnv = System.getenv(envName);
if (driverPathFromEnv != null) {
driver = new File(driverPathFromEnv);
if (driver.isFile()) {
return driver;
} else {
return new File(driver, driverName + (isWindows() ? ".exe" : ""));
}
}
return null;
}
private static boolean isWindows() {
return System.getProperty("os.name").toLowerCase().contains("win");
}
}

View File

@@ -1,8 +1,6 @@
package org.keycloak.testframework.ui.webdriver;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
public class FirefoxHeadlessWebDriverSupplier extends AbstractWebDriverSupplier {
@@ -13,9 +11,6 @@ public class FirefoxHeadlessWebDriverSupplier extends AbstractWebDriverSupplier
@Override
public WebDriver getWebDriver() {
FirefoxOptions options = new FirefoxOptions();
setCommonCapabilities(options);
options.addArguments("-headless");
return new FirefoxDriver(options);
return DriverUtils.createFirefoxDriver(true);
}
}

View File

@@ -1,8 +1,6 @@
package org.keycloak.testframework.ui.webdriver;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
public class FirefoxWebDriverSupplier extends AbstractWebDriverSupplier {
@@ -13,8 +11,6 @@ public class FirefoxWebDriverSupplier extends AbstractWebDriverSupplier {
@Override
public WebDriver getWebDriver() {
FirefoxOptions options = new FirefoxOptions();
setCommonCapabilities(options);
return new FirefoxDriver(options);
return DriverUtils.createFirefoxDriver(false);
}
}

View File

@@ -1,8 +1,6 @@
package org.keycloak.testframework.ui.webdriver;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
public class HtmlUnitWebDriverSupplier extends AbstractWebDriverSupplier {
@@ -13,15 +11,6 @@ public class HtmlUnitWebDriverSupplier extends AbstractWebDriverSupplier {
@Override
public WebDriver getWebDriver() {
DesiredCapabilities capabilities = new DesiredCapabilities();
setCommonCapabilities(capabilities);
capabilities.setBrowserName("htmlunit");
capabilities.setCapability(HtmlUnitDriver.DOWNLOAD_IMAGES_CAPABILITY, false);
capabilities.setCapability(HtmlUnitDriver.JAVASCRIPT_ENABLED, true);
HtmlUnitDriver driver = new HtmlUnitDriver(capabilities);
driver.getWebClient().getOptions().setCssEnabled(false);
return driver;
return DriverUtils.createHtmlUnitDriver();
}
}

View File

@@ -6,6 +6,7 @@ import java.util.function.Function;
import org.keycloak.testframework.ui.page.AbstractPage;
import org.junit.jupiter.api.Assertions;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.WebDriverWait;
@@ -21,7 +22,7 @@ public class WaitUtils {
public WaitUtils waitForPage(AbstractPage page) {
String expectedPageId = page.getExpectedPageId();
try {
createDefaultWait().until(d -> expectedPageId.equals(managed.page().getCurrentPageId()));
createDefaultWait().ignoring(StaleElementReferenceException.class).until(d -> expectedPageId.equals(managed.page().getCurrentPageId()));
} catch (TimeoutException e) {
Assertions.fail("Expected page '" + expectedPageId + "' to be loaded, but currently on page '" + managed.page().getCurrentPageId() + "' after timeout");
}
@@ -47,7 +48,7 @@ public class WaitUtils {
}
private WebDriverWait createDefaultWait() {
return new WebDriverWait(managed.driver(), Duration.ofSeconds(5), Duration.ofMillis(50));
return new WebDriverWait(managed.driver(), Duration.ofSeconds(10), Duration.ofMillis(50));
}
}

View File

@@ -1,12 +1,14 @@
package org.keycloak.tests.suites;
import org.keycloak.tests.admin.AdminHeadersTest;
import org.keycloak.tests.i18n.LoginPageTest;
import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.Suite;
@Suite
// TODO: Select relevant test classes or packages once they have been migrated
@SelectClasses({AdminHeadersTest.class})
@SelectClasses({
LoginPageTest.class
})
public class FormsTestSuite {
}