WORKING_BROWSER_IMAGES_TRANSLATION = new ConcurrentHashMap<>();
public static final int SELENIUM_PORT = 4444;
public static final int VNC_PORT = 5900;
@@ -133,9 +129,9 @@ public BrowserWebDriverContainer(final DockerImageName dockerImageName)
protected WaitStrategy getDefaultWaitStrategy()
{
return new WaitAllStrategy()
+ .withStrategy(new HostPortWaitStrategy())
.withStrategy(new LogMessageWaitStrategy()
.withRegEx(LOG_MSG_WAIT_STRATEGY_REGEX))
- .withStrategy(new HostPortWaitStrategy())
.withStartupTimeout(Duration.of(60, ChronoUnit.SECONDS));
}
@@ -341,7 +337,6 @@ protected void configureVNC()
}
// region Validate image
- // If testcontainers could implement the same method better or made stuff protected we wouldn't need reflection
@SuppressWarnings("java:S3011")
protected void validateImage()
{
@@ -355,16 +350,8 @@ protected void validateImage()
// In this case try to look for alternative images
try
{
- final Field fImage = GenericContainer.class.getDeclaredField("image");
- fImage.setAccessible(true);
- final RemoteDockerImage remoteDockerImage = (RemoteDockerImage)fImage.get(this);
-
- final Method mGetImageName = RemoteDockerImage.class.getDeclaredMethod("getImageName");
- mGetImageName.setAccessible(true);
- final DockerImageName currentImage = (DockerImageName)mGetImageName.invoke(remoteDockerImage);
-
this.setDockerImageName(WORKING_BROWSER_IMAGES_TRANSLATION.computeIfAbsent(
- currentImage,
+ GenericContainerImageNameAccessor.getImageName(this),
this::validateImageOrPickAlternative));
}
catch(final Exception ex)
diff --git a/testcontainers-selenium/src/main/java/software/xdev/testcontainers/selenium/containers/browser/CapabilitiesBrowserWebDriverContainer.java b/testcontainers-selenium/src/main/java/software/xdev/testcontainers/selenium/containers/browser/CapabilitiesBrowserWebDriverContainer.java
index 4c925649..ce8249fe 100644
--- a/testcontainers-selenium/src/main/java/software/xdev/testcontainers/selenium/containers/browser/CapabilitiesBrowserWebDriverContainer.java
+++ b/testcontainers-selenium/src/main/java/software/xdev/testcontainers/selenium/containers/browser/CapabilitiesBrowserWebDriverContainer.java
@@ -46,7 +46,7 @@ public CapabilitiesBrowserWebDriverContainer(
{
this(getStandardImageForCapabilities(
capabilities,
- SeleniumUtils.getClasspathSeleniumVersion(),
+ SeleniumVersionDetector.getClasspathSeleniumVersion(),
browserDockerImages));
}
diff --git a/testcontainers-selenium/src/main/java/software/xdev/testcontainers/selenium/containers/browser/GenericContainerImageNameAccessor.java b/testcontainers-selenium/src/main/java/software/xdev/testcontainers/selenium/containers/browser/GenericContainerImageNameAccessor.java
new file mode 100644
index 00000000..08b7086a
--- /dev/null
+++ b/testcontainers-selenium/src/main/java/software/xdev/testcontainers/selenium/containers/browser/GenericContainerImageNameAccessor.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright © 2024 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.testcontainers.selenium.containers.browser;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Objects;
+
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.images.RemoteDockerImage;
+import org.testcontainers.utility.DockerImageName;
+
+
+/**
+ * Extracts the {@link DockerImageName} from {@link GenericContainer} without pulling it.
+ *
+ * Uses reflection because there is no official way to access it.
+ *
+ */
+public final class GenericContainerImageNameAccessor
+{
+ // Cache to improve performance
+ private static boolean initialized;
+ private static Field fImage;
+ private static Method mGetImageName;
+
+ public static DockerImageName getImageName(final GenericContainer> container) throws Exception
+ {
+ if(!initialized)
+ {
+ fImage = GenericContainer.class.getDeclaredField("image");
+ fImage.setAccessible(true);
+
+ mGetImageName = RemoteDockerImage.class.getDeclaredMethod("getImageName");
+ mGetImageName.setAccessible(true);
+
+ initialized = true;
+ }
+
+ final RemoteDockerImage remoteDockerImage = (RemoteDockerImage)fImage.get(container);
+
+ return (DockerImageName)Objects.requireNonNull(mGetImageName.invoke(remoteDockerImage));
+ }
+
+ private GenericContainerImageNameAccessor()
+ {
+ }
+}
diff --git a/testcontainers-selenium/src/main/java/software/xdev/testcontainers/selenium/containers/browser/SeleniumUtils.java b/testcontainers-selenium/src/main/java/software/xdev/testcontainers/selenium/containers/browser/SeleniumUtils.java
index 77d53591..0c42856a 100644
--- a/testcontainers-selenium/src/main/java/software/xdev/testcontainers/selenium/containers/browser/SeleniumUtils.java
+++ b/testcontainers-selenium/src/main/java/software/xdev/testcontainers/selenium/containers/browser/SeleniumUtils.java
@@ -15,131 +15,34 @@
*/
package software.xdev.testcontainers.selenium.containers.browser;
-import java.io.InputStream;
-import java.net.URL;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.jar.Attributes;
import java.util.jar.Manifest;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
/**
- * Utility methods for Selenium.
- *
- * Forked from Testcontainers and enhanced with caching.
- *
+ * @deprecated Renamed to {@link SeleniumVersionDetector}
*/
+@SuppressWarnings("checkstyle:IllegalIdentifierName")
+@Deprecated(forRemoval = true)
public final class SeleniumUtils
{
- private static final Logger LOG = LoggerFactory.getLogger(SeleniumUtils.class);
-
- // as of 2026-01
- public static final String DEFAULT_SELENIUM_VERSION = "4.45.0";
- private static String cachedVersion;
-
- private SeleniumUtils()
- {
- }
+ public static final String DEFAULT_SELENIUM_VERSION = SeleniumVersionDetector.DEFAULT_SELENIUM_VERSION;
- /**
- * Based on the JARs detected on the classpath, determine which version of selenium-api is available.
- *
- * @return the detected version of Selenium API, or DEFAULT_SELENIUM_VERSION if it could not be determined
- */
public static String getClasspathSeleniumVersion()
{
- if(cachedVersion != null)
- {
- return cachedVersion;
- }
- cachedVersion = determineClasspathSeleniumVersion();
- return cachedVersion;
+ return SeleniumVersionDetector.getClasspathSeleniumVersion();
}
public static synchronized String determineClasspathSeleniumVersion()
{
- if(cachedVersion != null)
- {
- return cachedVersion;
- }
-
- final Set seleniumVersions = new HashSet<>();
- try
- {
- final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
- final Enumeration manifests = classLoader.getResources("META-INF/MANIFEST.MF");
-
- while(manifests.hasMoreElements())
- {
- final URL manifestURL = manifests.nextElement();
- try(final InputStream is = manifestURL.openStream())
- {
- final Manifest manifest = new Manifest();
- manifest.read(is);
-
- final String seleniumVersion = getSeleniumVersionFromManifest(manifest);
- if(seleniumVersion != null)
- {
- seleniumVersions.add(seleniumVersion);
- LOG.info("Selenium API version {} detected on classpath", seleniumVersion);
- }
- }
- }
- }
- catch(final Exception e)
- {
- LOG.debug("Failed to determine Selenium-Version from selenium-api JAR Manifest", e);
- }
-
- if(seleniumVersions.isEmpty())
- {
- LOG.warn(
- "Failed to determine Selenium version from classpath - will use default version of {}",
- DEFAULT_SELENIUM_VERSION
- );
- return DEFAULT_SELENIUM_VERSION;
- }
-
- final String foundVersion = seleniumVersions.iterator().next();
- if(seleniumVersions.size() > 1)
- {
- LOG.warn(
- "Multiple versions of Selenium API found on classpath - will select {}, but this may not be reliable",
- foundVersion
- );
- }
-
- return foundVersion;
+ return SeleniumVersionDetector.determineClasspathSeleniumVersion();
}
- /**
- * Read Manifest to get Selenium Version.
- *
- * @param manifest manifest
- * @return Selenium Version detected
- */
public static String getSeleniumVersionFromManifest(final Manifest manifest)
{
- String seleniumVersion = null;
- final Attributes buildInfo = manifest.getAttributes("Build-Info");
- if(buildInfo != null)
- {
- seleniumVersion = buildInfo.getValue("Selenium-Version");
- }
-
- // Compatibility Selenium > 3.X
- if(seleniumVersion == null)
- {
- final Attributes seleniumInfo = manifest.getAttributes("Selenium");
- if(seleniumInfo != null)
- {
- seleniumVersion = seleniumInfo.getValue("Selenium-Version");
- }
- }
- return seleniumVersion;
+ return SeleniumVersionDetector.getSeleniumVersionFromManifest(manifest);
+ }
+
+ private SeleniumUtils()
+ {
}
}
diff --git a/testcontainers-selenium/src/main/java/software/xdev/testcontainers/selenium/containers/browser/SeleniumVersionDetector.java b/testcontainers-selenium/src/main/java/software/xdev/testcontainers/selenium/containers/browser/SeleniumVersionDetector.java
new file mode 100644
index 00000000..ff3e5f65
--- /dev/null
+++ b/testcontainers-selenium/src/main/java/software/xdev/testcontainers/selenium/containers/browser/SeleniumVersionDetector.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright © 2024 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.testcontainers.selenium.containers.browser;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Version detection methods for Selenium.
+ *
+ * Forked from Testcontainers SeleniumUtils and enhanced with caching.
+ *
+ */
+public final class SeleniumVersionDetector
+{
+ private static final Logger LOG = LoggerFactory.getLogger(SeleniumVersionDetector.class);
+
+ // as of 2026-08
+ public static final String DEFAULT_SELENIUM_VERSION = "4.47.0";
+ private static String cachedVersion;
+
+ /**
+ * Based on the JARs detected on the classpath, determine which version of selenium-api is available.
+ *
+ * @return the detected version of Selenium API, or DEFAULT_SELENIUM_VERSION if it could not be determined
+ */
+ public static String getClasspathSeleniumVersion()
+ {
+ if(cachedVersion != null)
+ {
+ return cachedVersion;
+ }
+ cachedVersion = determineClasspathSeleniumVersion();
+ return cachedVersion;
+ }
+
+ public static synchronized String determineClasspathSeleniumVersion()
+ {
+ if(cachedVersion != null)
+ {
+ return cachedVersion;
+ }
+
+ final Set seleniumVersions = new HashSet<>();
+ try
+ {
+ final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+ final Enumeration manifests = classLoader.getResources("META-INF/MANIFEST.MF");
+
+ while(manifests.hasMoreElements())
+ {
+ final URL manifestURL = manifests.nextElement();
+ try(final InputStream is = manifestURL.openStream())
+ {
+ final Manifest manifest = new Manifest();
+ manifest.read(is);
+
+ final String seleniumVersion = getSeleniumVersionFromManifest(manifest);
+ if(seleniumVersion != null)
+ {
+ seleniumVersions.add(seleniumVersion);
+ LOG.info("Selenium API version {} detected on classpath", seleniumVersion);
+ }
+ }
+ }
+ }
+ catch(final Exception e)
+ {
+ LOG.debug("Failed to determine Selenium-Version from selenium-api JAR Manifest", e);
+ }
+
+ if(seleniumVersions.isEmpty())
+ {
+ LOG.warn(
+ "Failed to determine Selenium version from classpath - will use default version of {}",
+ DEFAULT_SELENIUM_VERSION
+ );
+ return DEFAULT_SELENIUM_VERSION;
+ }
+
+ final String foundVersion = seleniumVersions.iterator().next();
+ if(seleniumVersions.size() > 1)
+ {
+ LOG.warn(
+ "Multiple versions of Selenium API found on classpath - will select {}, but this may not be reliable",
+ foundVersion
+ );
+ }
+
+ return foundVersion;
+ }
+
+ /**
+ * Read Manifest to get Selenium Version.
+ *
+ * @param manifest manifest
+ * @return Selenium Version detected
+ */
+ public static String getSeleniumVersionFromManifest(final Manifest manifest)
+ {
+ String seleniumVersion = null;
+ final Attributes buildInfo = manifest.getAttributes("Build-Info");
+ if(buildInfo != null)
+ {
+ seleniumVersion = buildInfo.getValue("Selenium-Version");
+ }
+
+ // Compatibility Selenium > 3.X
+ if(seleniumVersion == null)
+ {
+ final Attributes seleniumInfo = manifest.getAttributes("Selenium");
+ if(seleniumInfo != null)
+ {
+ seleniumVersion = seleniumInfo.getValue("Selenium-Version");
+ }
+ }
+ return seleniumVersion;
+ }
+
+ private SeleniumVersionDetector()
+ {
+ }
+}
diff --git a/testcontainers-selenium/src/test/java/software/xdev/testcontainers/selenium/containers/browser/GenericContainerImageNameAccessorTest.java b/testcontainers-selenium/src/test/java/software/xdev/testcontainers/selenium/containers/browser/GenericContainerImageNameAccessorTest.java
new file mode 100644
index 00000000..e7075fe7
--- /dev/null
+++ b/testcontainers-selenium/src/test/java/software/xdev/testcontainers/selenium/containers/browser/GenericContainerImageNameAccessorTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright © 2024 XDEV Software (https://xdev.software)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package software.xdev.testcontainers.selenium.containers.browser;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.utility.DockerImageName;
+
+
+class GenericContainerImageNameAccessorTest
+{
+ static final String IMAGE = "alpine:3";
+ static final DockerImageName DOCKER_IMAGE_NAME = DockerImageName.parse(IMAGE);
+
+ @ParameterizedTest
+ @MethodSource
+ void reflectionStillWorks(final GenericContainer> container)
+ {
+ assertEquals(
+ DOCKER_IMAGE_NAME,
+ assertDoesNotThrow(() -> GenericContainerImageNameAccessor.getImageName(container)));
+ }
+
+ static Stream reflectionStillWorks()
+ {
+ return Stream.of(
+ new GenericContainer<>(IMAGE),
+ new GenericContainer<>(CompletableFuture.completedFuture(IMAGE)))
+ .map(Arguments::of);
+ }
+}