diff --git a/.config/checkstyle/checkstyle.xml b/.config/checkstyle/checkstyle.xml index 94a22c6c..b5858800 100644 --- a/.config/checkstyle/checkstyle.xml +++ b/.config/checkstyle/checkstyle.xml @@ -81,15 +81,13 @@ - + - - - + @@ -146,7 +144,8 @@ - + + diff --git a/.github/.lycheeignore b/.github/.lycheeignore index dc88a070..217b0ae4 100644 --- a/.github/.lycheeignore +++ b/.github/.lycheeignore @@ -1,3 +1,4 @@ # Ignorefile for broken link check localhost mvnrepository.com +stackoverflow.com diff --git a/.github/workflows/check-build.yml b/.github/workflows/check-build.yml index f7bd723f..c4f0b8dc 100644 --- a/.github/workflows/check-build.yml +++ b/.github/workflows/check-build.yml @@ -33,22 +33,34 @@ jobs: steps: - uses: actions/checkout@v7 - - name: Set up JDK - uses: actions/setup-java@v5 - with: - distribution: ${{ matrix.distribution }} - java-version: ${{ matrix.java }} - - - name: Cache Maven - uses: actions/cache@v6 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-mvn-build-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-mvn-build- + - parallel: + - name: Set up JDK + uses: actions/setup-java@v5 + with: + distribution: ${{ matrix.distribution }} + java-version: ${{ matrix.java }} + + - name: Cache Maven + uses: actions/cache@v6 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-mvn-build-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-mvn-build- + + - name: Docker Image Cache Restore + id: docker-image-cache-restore + uses: xdev-software/tci/.github/actions/docker-image-cache/restore@f686d444930db567aae0cbc8076694f8c886f479 # v4.1.0 + with: + # This cache could always change however because docker images can be changed remotely if the version is not fixated + # As a compromise it utilizes the hash of the pom files because when the dependencies stay the same + # the images should also be roughly the same + key: ${{ runner.os }}-check-build-docker-images-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-check-build-docker-images- - name: Build with Maven - run: ./mvnw -B clean package -P run-integration-tests + run: ./mvnw -B clean package -P run-integration-tests -D checks.disable=true - name: Check for uncommited changes run: | @@ -68,6 +80,12 @@ jobs: exit 1 fi + - name: Docker Image Cache Save + uses: xdev-software/tci/.github/actions/docker-image-cache/save@f686d444930db567aae0cbc8076694f8c886f479 # v4.1.0 + with: + key: ${{ steps.docker-image-cache-restore.outputs.key }} + cache-hit: ${{ steps.docker-image-cache-restore.outputs.cache-hit }} + checkstyle: runs-on: ubuntu-latest if: ${{ github.event_name != 'pull_request' || !startsWith(github.head_ref, 'renovate/') }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6133d1a6..dc0033ea 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -91,7 +91,7 @@ jobs: - name: Create Release id: create-release - uses: shogo82148/actions-create-release@6a396031bc74c57403da1018fec74d24c6aa03cd # v1 + uses: shogo82148/actions-create-release@8674c0ecee617c8dfb006526bcc22a362c70b88a # v1 with: tag_name: v${{ steps.version.outputs.release }} release_name: v${{ steps.version.outputs.release }} diff --git a/.idea/PMDPlugin.xml b/.idea/PMDPlugin.xml deleted file mode 100644 index 0936e518..00000000 --- a/.idea/PMDPlugin.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/pmd-x.xml b/.idea/pmd-x.xml index 260e454b..7b3b48fa 100644 --- a/.idea/pmd-x.xml +++ b/.idea/pmd-x.xml @@ -10,6 +10,7 @@ diff --git a/CHANGELOG.md b/CHANGELOG.md index ac359948..8fc4ef90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 2.1.0 +* Rename `SeleniumUtils` to `SeleniumVersionDetector` +* Extract image name extraction logic +* Update default Selenium version to `4.47.0` +* Updated dependencies + # 2.0.2 * Extract wait strategy values into constants * Updated dependencies diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7b31bb1f..ef64ad2f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ We would absolutely love to get the community involved, and we welcome any form ### Communication channels * Communication is primarily done using issues. -* If you need support as soon as possible and you can't wait for any pull request, feel free to use [our support](https://xdev.software/en/services/support). +* If you need support as soon as possible and you can't wait for any pull request, feel free to use [our support](https://xdev.software/en/services-products/support). * As a last resort measure or on otherwise important matter you may also [contact us directly](https://xdev.software/en/about-us/contact). ### Ways to help @@ -12,7 +12,7 @@ We would absolutely love to get the community involved, and we welcome any form * **Send pull requests**
If you want to contribute code, check out the development instructions below. * However when contributing larger new features, please first discuss the change you wish to make via issue with the owners of this repository before making it.
Otherwise your work might be rejected and your effort was pointless. -We also encourage you to read the [contribution instructions by GitHub](https://docs.github.com/en/get-started/quickstart/contributing-to-projects). +We also encourage you to read the [contribution instructions by GitHub](https://docs.github.com/en/get-started/exploring-projects-on-github/contributing-to-a-project). ## Developing @@ -20,17 +20,17 @@ We also encourage you to read the [contribution instructions by GitHub](https:// You should have the following things installed: * Git * Java 25 - should be as unmodified as possible (Recommended: [Eclipse Adoptium](https://adoptium.net/temurin/releases/)) -* Maven (Note that the [Maven Wrapper](https://maven.apache.org/wrapper/) is shipped with the repo) +* Maven (Note that the [Maven Wrapper](https://maven.apache.org/tools/wrapper/) is shipped with the repo) ### Recommended setup -* Install ``IntelliJ`` (Community Edition is sufficient) - * Install the following plugins: - * [Save Actions](https://plugins.jetbrains.com/plugin/22113) - Provides save actions, like running the formatter or adding ``final`` to fields - * [SonarLint](https://plugins.jetbrains.com/plugin/7973-sonarlint) - CodeStyle/CodeAnalysis - * You may consider disabling telemetry in the settings under ``Tools > Sonarlint -> About`` - * [Checkstyle-IDEA](https://plugins.jetbrains.com/plugin/1065-checkstyle-idea) - CodeStyle/CodeAnalysis +* Install `IntelliJ` + * Recommended setup actions + * Disable not needed plugins + * Disable [telemetry](https://www.jetbrains.com/help/idea/settings-usage-statistics.html) + * Configure the available memory * Import the project - * Ensure that everything is encoded in ``UTF-8`` + * You will get prompted to install the required plugins + * Ensure that everything is encoded in `UTF-8` * Ensure that the JDK/Java-Version is correct diff --git a/README.md b/README.md index 900c6f7d..f50622f5 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,9 @@ A re-implementation of [Testcontainer Selenium/WebDriver](https://java.testconta * No VNC Server started in the browser container (unless explicitly stated) → Saves memory * Uses [Selenium's implementation](https://github.com/SeleniumHQ/docker-selenium/tree/trunk/Video) and isn't [based](https://github.com/testcontainers/vnc-recorder) on [some python code from 2010](https://pypi.org/project/vnc2flv/#history) * Way more customization options for e.g. ``framerate``, ``codec``, ``preset`` ... - * Uses ``mkv`` as default recording format (wider support in comparison to ``flv``) + * Uses `mkv` as default recording format + * wider support in comparison to `flv` + * `mkv` allows incomplete recordings without file corruption * [Renders while saving the video](https://github.com/SeleniumHQ/docker-selenium/blob/4c572afd1173b5bd49fa2def3b54ea552fccee85/Video/video.sh#L126) (not when finished which takes additional time) * Stops the recorder before saving the file so that there is no way that [it runs forever](https://github.com/testcontainers/testcontainers-java/discussions/6229). * Automatically tries to select a alternative Selenium version for the docker image if it [doesn't exist](https://github.com/SeleniumHQ/docker-selenium/issues/1979). @@ -36,7 +38,7 @@ This was done because `.mp4` requires finalization as otherwise the file/contain [Installation guide for the latest release](https://github.com/xdev-software/testcontainers-selenium/releases/latest#Installation) ## Support -If you need support as soon as possible and you can't wait for any pull request, feel free to use [our support](https://xdev.software/en/services/support). +If you need support as soon as possible and you can't wait for any pull request, feel free to use [our support](https://xdev.software/en/services-products/support). ## Contributing See the [contributing guide](./CONTRIBUTING.md) for detailed instructions on how to get started with our project. diff --git a/pom.xml b/pom.xml index 85a33a63..8cf8d4e8 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ software.xdev testcontainers-selenium-root - 2.0.3-SNAPSHOT + 2.1.0-SNAPSHOT pom @@ -45,7 +45,7 @@ com.puppycrawl.tools checkstyle - 13.8.0 + 13.10.0 diff --git a/testcontainers-selenium-demo/pom.xml b/testcontainers-selenium-demo/pom.xml index 06423b60..624d4964 100644 --- a/testcontainers-selenium-demo/pom.xml +++ b/testcontainers-selenium-demo/pom.xml @@ -7,11 +7,11 @@ software.xdev testcontainers-selenium-root - 2.0.3-SNAPSHOT + 2.1.0-SNAPSHOT testcontainers-selenium-demo - 2.0.3-SNAPSHOT + 2.1.0-SNAPSHOT jar @@ -34,7 +34,7 @@ org.seleniumhq.selenium selenium-dependencies-bom - 4.46.0 + 4.47.0 pom import diff --git a/testcontainers-selenium/pom.xml b/testcontainers-selenium/pom.xml index a719d121..e41a4ddb 100644 --- a/testcontainers-selenium/pom.xml +++ b/testcontainers-selenium/pom.xml @@ -6,7 +6,7 @@ software.xdev testcontainers-selenium - 2.0.3-SNAPSHOT + 2.1.0-SNAPSHOT jar testcontainers-selenium @@ -70,7 +70,7 @@ org.seleniumhq.selenium selenium-api - 4.46.0 + 4.47.0 provided @@ -78,7 +78,7 @@ org.junit.jupiter junit-jupiter - 6.1.2 + 6.1.3 test @@ -90,7 +90,7 @@ org.seleniumhq.selenium selenium-remote-driver - 4.46.0 + 4.47.0 test @@ -108,13 +108,13 @@ org.seleniumhq.selenium selenium-support - 4.46.0 + 4.47.0 test org.seleniumhq.selenium selenium-firefox-driver - 4.46.0 + 4.47.0 test @@ -127,7 +127,7 @@ org.seleniumhq.selenium selenium-chrome-driver - 4.46.0 + 4.47.0 test @@ -158,7 +158,7 @@ com.mycila license-maven-plugin - 5.0.0 + 5.1.1 ${project.organization.url} @@ -252,7 +252,7 @@ org.codehaus.mojo flatten-maven-plugin - 1.7.3 + 1.8.0 ossrh @@ -320,7 +320,7 @@ com.puppycrawl.tools checkstyle - 13.8.0 + 13.10.0 diff --git a/testcontainers-selenium/src/main/java/software/xdev/testcontainers/selenium/containers/browser/BrowserWebDriverContainer.java b/testcontainers-selenium/src/main/java/software/xdev/testcontainers/selenium/containers/browser/BrowserWebDriverContainer.java index ab87da76..d6c961a7 100644 --- a/testcontainers-selenium/src/main/java/software/xdev/testcontainers/selenium/containers/browser/BrowserWebDriverContainer.java +++ b/testcontainers-selenium/src/main/java/software/xdev/testcontainers/selenium/containers/browser/BrowserWebDriverContainer.java @@ -16,8 +16,6 @@ package software.xdev.testcontainers.selenium.containers.browser; import java.io.IOException; -import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; @@ -27,12 +25,11 @@ import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.function.Supplier; @@ -66,7 +63,7 @@ /** * A chrome/firefox/custom container based on SeleniumHQ's standalone container sets. */ -@SuppressWarnings({"java:S119", "java:S2160", "PMD.GodClass"}) +@SuppressWarnings({"java:S119", "java:S2160"}) public class BrowserWebDriverContainer> extends GenericContainer implements TestLifecycleAware @@ -82,8 +79,7 @@ public class BrowserWebDriverContainer WORKING_BROWSER_IMAGES_TRANSLATION = - Collections.synchronizedMap(new HashMap<>()); + protected static final Map 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); + } +}