diff --git a/java/src/org/openqa/selenium/internal/Debug.java b/java/src/org/openqa/selenium/internal/Debug.java index 0b012f180f59e..8af26e6b16716 100644 --- a/java/src/org/openqa/selenium/internal/Debug.java +++ b/java/src/org/openqa/selenium/internal/Debug.java @@ -17,37 +17,50 @@ package org.openqa.selenium.internal; +import java.util.Arrays; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.ConsoleHandler; +import java.util.logging.Filter; +import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.logging.SimpleFormatter; -import java.util.logging.StreamHandler; +import org.jspecify.annotations.Nullable; /** Used to provide information about whether Selenium is running under debug mode. */ public class Debug { - private static final boolean IS_DEBUG; private static final AtomicBoolean DEBUG_WARNING_LOGGED = new AtomicBoolean(false); private static final Logger SELENIUM_LOGGER = Logger.getLogger("org.openqa.selenium"); - private static boolean loggerConfigured = false; - static { - IS_DEBUG = - Boolean.getBoolean("selenium.debug") || Boolean.getBoolean("selenium.webdriver.verbose"); - } + private static boolean loggerConfigured = false; + private static @Nullable Handler installedHandler; + private static @Nullable Level previousLevel; + private static @Nullable Level levelSetByDebug; private Debug() { // Utility class } + /** Returns whether either Selenium debug system property is currently enabled. */ public static boolean isDebugging() { - return IS_DEBUG; + return Boolean.getBoolean("selenium.debug") || Boolean.getBoolean("selenium.webdriver.verbose"); } + /** + * Returns the legacy level selected by the current debug system properties. + * + * @deprecated Log at a fixed severity and use {@link #configureLogger()} to expose debug output. + */ + @Deprecated(forRemoval = true) public static Level getDebugLogLevel() { return isDebugging() ? Level.INFO : Level.FINE; } + static synchronized boolean isHandlerCurrentlyInstalled() { + return installedHandler != null + && Arrays.asList(SELENIUM_LOGGER.getHandlers()).contains(installedHandler); + } + public static boolean isDebugAll() { boolean everything = Boolean.parseBoolean(System.getenv("SE_DEBUG")); if (everything && DEBUG_WARNING_LOGGED.compareAndSet(false, true)) { @@ -59,16 +72,76 @@ public static boolean isDebugAll() { return everything; } - public static void configureLogger() { - if (!isDebugAll() || loggerConfigured) { + private static Level effectiveLevel(Logger logger) { + for (Logger current = logger; current != null; current = current.getParent()) { + Level level = current.getLevel(); + if (level != null) { + return level; + } + } + return Level.INFO; + } + + /** + * Applies the current Selenium debug switches to the {@code org.openqa.selenium} logger. Selenium + * owns the added handler and restores only logger state that it changed. Repeated calls also + * repair an externally removed handler or a less-verbose logger level. + */ + public static synchronized void configureLogger() { + boolean shouldDebug = isDebugAll() || isDebugging(); + Handler currentHandler = installedHandler; + boolean handlerInstalled = + currentHandler != null + && Arrays.asList(SELENIUM_LOGGER.getHandlers()).contains(currentHandler); + Level currentEffectiveLevel = effectiveLevel(SELENIUM_LOGGER); + if (shouldDebug == loggerConfigured + && (!shouldDebug + || (handlerInstalled + && currentHandler != null + && currentEffectiveLevel.intValue() <= Level.FINE.intValue() + && currentHandler.getLevel().intValue() <= currentEffectiveLevel.intValue()))) { return; } - SELENIUM_LOGGER.setLevel(Level.FINE); + if (shouldDebug) { + if (!loggerConfigured) { + previousLevel = SELENIUM_LOGGER.getLevel(); + levelSetByDebug = null; + } + + if (currentEffectiveLevel.intValue() > Level.FINE.intValue()) { + SELENIUM_LOGGER.setLevel(Level.FINE); + levelSetByDebug = Level.FINE; + currentEffectiveLevel = Level.FINE; + } + + if (handlerInstalled && currentHandler != null) { + currentHandler.setLevel(currentEffectiveLevel); + } else { + if (currentHandler != null) { + SELENIUM_LOGGER.removeHandler(currentHandler); + currentHandler.close(); + } + Handler handler = new ConsoleHandler(); + handler.setLevel(currentEffectiveLevel); + Filter belowInfo = record -> record.getLevel().intValue() < Level.INFO.intValue(); + handler.setFilter(belowInfo); + SELENIUM_LOGGER.addHandler(handler); + installedHandler = handler; + } + } else { + if (currentHandler != null) { + SELENIUM_LOGGER.removeHandler(currentHandler); + currentHandler.close(); + installedHandler = null; + } + if (levelSetByDebug != null && levelSetByDebug.equals(SELENIUM_LOGGER.getLevel())) { + SELENIUM_LOGGER.setLevel(previousLevel); + } + previousLevel = null; + levelSetByDebug = null; + } - StreamHandler handler = new StreamHandler(System.err, new SimpleFormatter()); - handler.setLevel(Level.FINE); - SELENIUM_LOGGER.addHandler(handler); - loggerConfigured = true; + loggerConfigured = shouldDebug; } } diff --git a/java/src/org/openqa/selenium/remote/RemoteWebDriver.java b/java/src/org/openqa/selenium/remote/RemoteWebDriver.java index b80dfd0eb655d..7639fe3fa26ce 100644 --- a/java/src/org/openqa/selenium/remote/RemoteWebDriver.java +++ b/java/src/org/openqa/selenium/remote/RemoteWebDriver.java @@ -122,8 +122,9 @@ public class RemoteWebDriver PrintsPage, TakesScreenshot { + // Configure before subclass constructor arguments can trigger driver discovery logs. static { - org.openqa.selenium.internal.Debug.configureLogger(); + Debug.configureLogger(); } private static final Logger LOG = Logger.getLogger(RemoteWebDriver.class.getName()); @@ -207,6 +208,7 @@ public RemoteWebDriver( CommandExecutor executor, Capabilities capabilities, ClientConfig clientConfig) { this.clientConfig = Require.nonNull("Client config", clientConfig); this.executor = Require.nonNull("Command executor", executor); + Debug.configureLogger(); this.capabilities = requireNonNullElseGet(capabilities, () -> new ImmutableCapabilities()); try { diff --git a/java/test/org/openqa/selenium/internal/BUILD.bazel b/java/test/org/openqa/selenium/internal/BUILD.bazel index e0e9427d6cb5d..4a70406ee6f39 100644 --- a/java/test/org/openqa/selenium/internal/BUILD.bazel +++ b/java/test/org/openqa/selenium/internal/BUILD.bazel @@ -9,5 +9,7 @@ java_test_suite( "//java/src/org/openqa/selenium:core", artifact("org.assertj:assertj-core"), artifact("org.junit.jupiter:junit-jupiter-api"), + artifact("uk.org.webcompere:system-stubs-core"), + artifact("uk.org.webcompere:system-stubs-jupiter"), ] + JUNIT5_DEPS, ) diff --git a/java/test/org/openqa/selenium/internal/DebugTest.java b/java/test/org/openqa/selenium/internal/DebugTest.java new file mode 100644 index 0000000000000..30bca4a8eaee2 --- /dev/null +++ b/java/test/org/openqa/selenium/internal/DebugTest.java @@ -0,0 +1,463 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you 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 org.openqa.selenium.internal; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.logging.ConsoleHandler; +import java.util.logging.ErrorManager; +import java.util.logging.Filter; +import java.util.logging.Formatter; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; +import uk.org.webcompere.systemstubs.jupiter.SystemStub; +import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; + +@Tag("UnitTests") +@ExtendWith(SystemStubsExtension.class) +class DebugTest { + + private static Logger seleniumLogger() { + return Logger.getLogger("org.openqa.selenium"); + } + + private String oldDebugProperty; + private String oldVerboseProperty; + private Level oldLoggerLevel; + + @SystemStub private EnvironmentVariables environment; + + @BeforeEach + void storeSystemProperties() { + oldDebugProperty = System.getProperty("selenium.debug"); + oldVerboseProperty = System.getProperty("selenium.webdriver.verbose"); + oldLoggerLevel = seleniumLogger().getLevel(); + System.clearProperty("selenium.debug"); + System.clearProperty("selenium.webdriver.verbose"); + } + + @AfterEach + void restoreSystemProperties() { + if (oldDebugProperty != null) { + System.setProperty("selenium.debug", oldDebugProperty); + } else { + System.clearProperty("selenium.debug"); + } + if (oldVerboseProperty != null) { + System.setProperty("selenium.webdriver.verbose", oldVerboseProperty); + } else { + System.clearProperty("selenium.webdriver.verbose"); + } + Debug.configureLogger(); + seleniumLogger().setLevel(oldLoggerLevel); + } + + @Test + void isDebuggingReflectsPropertySetAfterClassLoad() { + assertThat(Debug.isDebugging()).isFalse(); + + System.setProperty("selenium.debug", "true"); + + assertThat(Debug.isDebugging()).isTrue(); + } + + @Test + @SuppressWarnings({"deprecation", "removal"}) + void getDebugLogLevelHonoursTheLegacyVerboseProperty() { + System.setProperty("selenium.webdriver.verbose", "true"); + + assertThat(Debug.getDebugLogLevel()).isEqualTo(Level.INFO); + } + + @Test + void configureLoggerRaisesSeleniumLoggerToFine() { + System.setProperty("selenium.debug", "true"); + + Debug.configureLogger(); + + assertThat(seleniumLogger().getLevel()).isEqualTo(Level.FINE); + } + + @Test + void configureLoggerDoesNotClobberALevelChangedWhileDebuggingWasOn() { + System.setProperty("selenium.debug", "true"); + Debug.configureLogger(); + assertThat(seleniumLogger().getLevel()).isEqualTo(Level.FINE); + + // Something other than Debug changes the level while debugging is still on -- e.g. the user's + // own logging config. + seleniumLogger().setLevel(Level.WARNING); + + System.clearProperty("selenium.debug"); + Debug.configureLogger(); + + // The externally-set WARNING must survive. Debug must not clobber it with the level that was + // ambient before IT turned debugging on -- that snapshot is stale the moment anything else + // changes the level in between. + assertThat(seleniumLogger().getLevel()).isEqualTo(Level.WARNING); + } + + @Test + void configureLoggerRestoresPreDebugLevelAndRemovesHandlerWhenTurnedOff() { + Level preDebugLevel = seleniumLogger().getLevel(); + List handlersBeforeDebug = new ArrayList<>(List.of(seleniumLogger().getHandlers())); + + System.setProperty("selenium.debug", "true"); + Debug.configureLogger(); + + List handlersWhileDebugging = new ArrayList<>(List.of(seleniumLogger().getHandlers())); + handlersWhileDebugging.removeAll(handlersBeforeDebug); + assertThat(handlersWhileDebugging).hasSize(1); + Handler installedHandler = handlersWhileDebugging.get(0); + + // No external override happens in between -- this is the plain turn-on/turn-off round trip. + System.clearProperty("selenium.debug"); + Debug.configureLogger(); + + assertThat(seleniumLogger().getLevel()).isEqualTo(preDebugLevel); + assertThat(seleniumLogger().getHandlers()).doesNotContain(installedHandler); + } + + @Test + void configureLoggerIsIdempotent() { + int before = seleniumLogger().getHandlers().length; + System.setProperty("selenium.debug", "true"); + + for (int i = 0; i < 5; i++) { + Debug.configureLogger(); + } + + assertThat(seleniumLogger().getHandlers().length - before).isEqualTo(1); + } + + @Test + void configureLoggerLeavesUserHandlerConfigurationAlone() throws UnsupportedEncodingException { + Handler userHandler = new ConsoleHandler(); + Filter userFilter = record -> false; + Formatter userFormatter = new SimpleFormatter(); + ErrorManager userErrorManager = new ErrorManager(); + userHandler.setLevel(Level.WARNING); + userHandler.setFilter(userFilter); + userHandler.setFormatter(userFormatter); + userHandler.setEncoding("UTF-8"); + userHandler.setErrorManager(userErrorManager); + seleniumLogger().addHandler(userHandler); + try { + System.setProperty("selenium.debug", "true"); + Debug.configureLogger(); + assertThat(seleniumLogger().getHandlers()).contains(userHandler); + assertThat(userHandler.getLevel()).isEqualTo(Level.WARNING); + assertThat(userHandler.getFilter()).isSameAs(userFilter); + assertThat(userHandler.getFormatter()).isSameAs(userFormatter); + assertThat(userHandler.getEncoding()).isEqualTo("UTF-8"); + assertThat(userHandler.getErrorManager()).isSameAs(userErrorManager); + + System.clearProperty("selenium.debug"); + Debug.configureLogger(); + assertThat(seleniumLogger().getHandlers()).contains(userHandler); + assertThat(userHandler.getLevel()).isEqualTo(Level.WARNING); + assertThat(userHandler.getFilter()).isSameAs(userFilter); + assertThat(userHandler.getFormatter()).isSameAs(userFormatter); + assertThat(userHandler.getEncoding()).isEqualTo("UTF-8"); + assertThat(userHandler.getErrorManager()).isSameAs(userErrorManager); + } finally { + seleniumLogger().removeHandler(userHandler); + } + } + + @Test + void infoRecordsAreNotDuplicatedWhenDebuggingIsEnabled() { + List userHandlerRecords = new ArrayList<>(); + Handler userHandler = + new Handler() { + @Override + public void publish(LogRecord record) { + userHandlerRecords.add(record); + } + + @Override + public void flush() {} + + @Override + public void close() {} + }; + // Simulates a handler the caller already has attached directly to this logger (e.g. their + // own handler at INFO) that already prints INFO-and-above records on its own. + userHandler.setLevel(Level.INFO); + seleniumLogger().addHandler(userHandler); + + boolean oldUseParentHandlers = seleniumLogger().getUseParentHandlers(); + // Isolate this check to handlers attached directly to org.openqa.selenium. Propagation to the + // JVM's own root logger handler is a separate, legitimate print channel this test isn't + // about, and it would otherwise be indistinguishable from a real duplicate here. + seleniumLogger().setUseParentHandlers(false); + + PrintStream originalErr = System.err; + ByteArrayOutputStream capturedErr = new ByteArrayOutputStream(); + String marker = "duplicate-check-" + UUID.randomUUID(); + try { + System.setErr(new PrintStream(capturedErr)); + System.setProperty("selenium.debug", "true"); + Debug.configureLogger(); + + seleniumLogger().log(Level.INFO, marker); + for (Handler handler : seleniumLogger().getHandlers()) { + handler.flush(); + } + } finally { + System.setErr(originalErr); + seleniumLogger().setUseParentHandlers(oldUseParentHandlers); + seleniumLogger().removeHandler(userHandler); + } + + // The caller's own handler must still see the record: Selenium never suppresses records for + // handlers it doesn't own. + assertThat(userHandlerRecords).extracting(LogRecord::getMessage).containsExactly(marker); + // Selenium's own handler must not ALSO print it to stderr -- otherwise the exact same line + // the caller's handler just printed would appear a second time, straight from Selenium's own + // console handler. + assertThat(capturedErr.toString()).doesNotContain(marker); + } + + @Test + void fineRecordsReachTheSeleniumOwnedHandler() { + boolean oldUseParentHandlers = seleniumLogger().getUseParentHandlers(); + PrintStream originalErr = System.err; + ByteArrayOutputStream capturedErr = new ByteArrayOutputStream(); + String marker = "fine-check-" + UUID.randomUUID(); + try { + seleniumLogger().setUseParentHandlers(false); + System.setErr(new PrintStream(capturedErr)); + System.setProperty("selenium.debug", "true"); + Debug.configureLogger(); + + seleniumLogger().log(Level.FINE, marker); + for (Handler handler : seleniumLogger().getHandlers()) { + handler.flush(); + } + } finally { + System.setErr(originalErr); + seleniumLogger().setUseParentHandlers(oldUseParentHandlers); + } + + assertThat(capturedErr.toString()).containsOnlyOnce(marker); + } + + @Test + void seleniumOwnedHandlerRepairsToAMoreVerboseLevel() { + environment.set("SE_DEBUG", "false"); + System.clearProperty("selenium.debug"); + Debug.configureLogger(); + List oldHandlers = List.of(seleniumLogger().getHandlers()); + oldHandlers.forEach(seleniumLogger()::removeHandler); + boolean oldUseParentHandlers = seleniumLogger().getUseParentHandlers(); + PrintStream originalErr = System.err; + ByteArrayOutputStream capturedErr = new ByteArrayOutputStream(); + String marker = "finer-check-" + UUID.randomUUID(); + try { + seleniumLogger().setUseParentHandlers(false); + System.setErr(new PrintStream(capturedErr)); + System.setProperty("selenium.debug", "true"); + Debug.configureLogger(); + + seleniumLogger().setLevel(Level.FINER); + Debug.configureLogger(); + + seleniumLogger().log(Level.FINER, marker); + for (Handler handler : seleniumLogger().getHandlers()) { + handler.flush(); + } + } finally { + System.clearProperty("selenium.debug"); + Debug.configureLogger(); + oldHandlers.forEach(seleniumLogger()::addHandler); + System.setErr(originalErr); + seleniumLogger().setUseParentHandlers(oldUseParentHandlers); + } + + assertThat(capturedErr.toString()).containsOnlyOnce(marker); + } + + @Test + void configureLoggerDoesNotRestoreALevelItNeverChanged() { + seleniumLogger().setLevel(Level.FINER); + System.setProperty("selenium.debug", "true"); + Debug.configureLogger(); // debug on, level untouched (already more verbose than FINE) + + // Something else deliberately drops verbosity to FINE while debugging is on. + seleniumLogger().setLevel(Level.FINE); + + System.clearProperty("selenium.debug"); + Debug.configureLogger(); + + // Debug never changed the level (it was already more verbose when debug turned on), so + // turning debug off must not "restore" a pre-debug snapshot it never took either. + assertThat(seleniumLogger().getLevel()).isEqualTo(Level.FINE); + } + + @Test + @SuppressWarnings({"deprecation", "removal"}) + void getDebugLogLevelStillReportsInfoWhileDeprecated() { + System.setProperty("selenium.debug", "true"); + assertThat(Debug.getDebugLogLevel()).isEqualTo(Level.INFO); + + System.clearProperty("selenium.debug"); + assertThat(Debug.getDebugLogLevel()).isEqualTo(Level.FINE); + } + + @Test + void configureLoggerRepairRestoresHandlerAndFineLoggabilityWithoutReplacingSnapshot() { + Level preDebugLevel = seleniumLogger().getLevel(); + List handlersBeforeDebug = new ArrayList<>(List.of(seleniumLogger().getHandlers())); + + System.setProperty("selenium.debug", "true"); + Debug.configureLogger(); + + List handlersWhileDebugging = new ArrayList<>(List.of(seleniumLogger().getHandlers())); + handlersWhileDebugging.removeAll(handlersBeforeDebug); + seleniumLogger().removeHandler(handlersWhileDebugging.get(0)); + seleniumLogger().setLevel(Level.INFO); + + Debug.configureLogger(); + + assertThat(Debug.isHandlerCurrentlyInstalled()).isTrue(); + assertThat(seleniumLogger().isLoggable(Level.FINE)).isTrue(); + + System.clearProperty("selenium.debug"); + Debug.configureLogger(); + + assertThat(seleniumLogger().getLevel()).isEqualTo(preDebugLevel); + } + + @Test + void configureLoggerDoesNotChangeRootLoggerForSystemPropertyDebugging() { + Logger rootLogger = Logger.getLogger(""); + Level rootLevel = rootLogger.getLevel(); + List rootHandlers = List.of(rootLogger.getHandlers()); + + System.setProperty("selenium.debug", "true"); + Debug.configureLogger(); + + assertThat(rootLogger.getLevel()).isEqualTo(rootLevel); + assertThat(rootLogger.getHandlers()).containsExactlyElementsOf(rootHandlers); + } + + @Test + void seDebugConfiguresFineHandlerAndRestoresLoggerLevel() { + environment.set("SE_DEBUG", "false"); + Debug.configureLogger(); + Level preDebugLevel = seleniumLogger().getLevel(); + List handlersBeforeDebug = new ArrayList<>(List.of(seleniumLogger().getHandlers())); + + environment.set("SE_DEBUG", "true"); + Debug.configureLogger(); + + List handlersWhileDebugging = new ArrayList<>(List.of(seleniumLogger().getHandlers())); + handlersWhileDebugging.removeAll(handlersBeforeDebug); + assertThat(handlersWhileDebugging) + .singleElement() + .extracting(Handler::getLevel) + .isEqualTo(Level.FINE); + + environment.set("SE_DEBUG", "false"); + Debug.configureLogger(); + + assertThat(seleniumLogger().getLevel()).isEqualTo(preDebugLevel); + } + + @Test + void configureLoggerLeavesExplicitMoreVerboseLevelsEffective() { + for (Level level : List.of(Level.FINER, Level.FINEST, Level.ALL)) { + seleniumLogger().setLevel(level); + System.setProperty("selenium.debug", "true"); + + Debug.configureLogger(); + + assertThat(seleniumLogger().getLevel()).isEqualTo(level); + assertThat(seleniumLogger().isLoggable(level)).isTrue(); + + System.clearProperty("selenium.debug"); + Debug.configureLogger(); + } + } + + @Test + void configureLoggerLeavesInheritedMoreVerboseLevelsEffective() { + Logger parentLogger = Logger.getLogger("org.openqa"); + Level oldParentLevel = parentLogger.getLevel(); + try { + for (Level level : List.of(Level.FINER, Level.FINEST, Level.ALL)) { + parentLogger.setLevel(level); + seleniumLogger().setLevel(null); + System.setProperty("selenium.debug", "true"); + + Debug.configureLogger(); + + assertThat(seleniumLogger().getLevel()).isNull(); + assertThat(seleniumLogger().isLoggable(level)).isTrue(); + + System.clearProperty("selenium.debug"); + Debug.configureLogger(); + } + } finally { + parentLogger.setLevel(oldParentLevel); + } + } + + @Test + void configureLoggerRestoresMoreVerboseLevelAfterLateRepair() { + Logger parentLogger = Logger.getLogger("org.openqa"); + Level oldParentLevel = parentLogger.getLevel(); + parentLogger.setLevel(Level.FINER); + try { + for (boolean inherited : List.of(false, true)) { + seleniumLogger().setLevel(inherited ? null : Level.FINER); + System.setProperty("selenium.debug", "true"); + Debug.configureLogger(); + + seleniumLogger().setLevel(Level.INFO); + Debug.configureLogger(); + + assertThat(seleniumLogger().isLoggable(Level.FINE)).isTrue(); + + System.clearProperty("selenium.debug"); + Debug.configureLogger(); + + assertThat(seleniumLogger().getLevel()).isEqualTo(inherited ? null : Level.FINER); + assertThat(seleniumLogger().isLoggable(Level.FINER)).isTrue(); + } + } finally { + parentLogger.setLevel(oldParentLevel); + } + } +} diff --git a/java/test/org/openqa/selenium/remote/RemoteWebDriverInitializationTest.java b/java/test/org/openqa/selenium/remote/RemoteWebDriverInitializationTest.java index 5759b4f253ea8..549ce28bd69c2 100644 --- a/java/test/org/openqa/selenium/remote/RemoteWebDriverInitializationTest.java +++ b/java/test/org/openqa/selenium/remote/RemoteWebDriverInitializationTest.java @@ -41,7 +41,11 @@ import java.time.Duration; import java.util.Map; import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; import org.jspecify.annotations.NullMarked; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -49,6 +53,7 @@ import org.openqa.selenium.ImmutableCapabilities; import org.openqa.selenium.Platform; import org.openqa.selenium.SessionNotCreatedException; +import org.openqa.selenium.internal.Debug; import org.openqa.selenium.remote.http.ClientConfig; import org.openqa.selenium.remote.http.Contents; import org.openqa.selenium.remote.http.HttpClient; @@ -58,7 +63,52 @@ @Tag("UnitTests") class RemoteWebDriverInitializationTest { + private static Logger seleniumLogger() { + return Logger.getLogger("org.openqa.selenium"); + } + private boolean quitCalled = false; + private String oldDebugProperty; + private String oldVerboseProperty; + private Level oldLoggerLevel; + + @BeforeEach + void storeDebugState() { + oldDebugProperty = System.getProperty("selenium.debug"); + oldVerboseProperty = System.getProperty("selenium.webdriver.verbose"); + oldLoggerLevel = seleniumLogger().getLevel(); + System.clearProperty("selenium.debug"); + System.clearProperty("selenium.webdriver.verbose"); + } + + @AfterEach + void restoreDebugState() { + if (oldDebugProperty != null) { + System.setProperty("selenium.debug", oldDebugProperty); + } else { + System.clearProperty("selenium.debug"); + } + if (oldVerboseProperty != null) { + System.setProperty("selenium.webdriver.verbose", oldVerboseProperty); + } else { + System.clearProperty("selenium.webdriver.verbose"); + } + Debug.configureLogger(); + seleniumLogger().setLevel(oldLoggerLevel); + } + + @Test + void constructingASecondDriverPicksUpADebugPropertyChangedAfterTheFirst() { + CommandExecutor inMemoryExecutor = command -> echoCapabilities.apply(command); + + new RemoteWebDriver(inMemoryExecutor, new ImmutableCapabilities()); + + System.setProperty("selenium.debug", "true"); + + new RemoteWebDriver(inMemoryExecutor, new ImmutableCapabilities()); + + assertThat(seleniumLogger().getLevel()).isEqualTo(Level.FINE); + } @Test void testQuitsIfStartSessionFails() {