From 47f29113b767a4112850e81fc493e612fa55f6c7 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Fri, 14 Feb 2025 15:15:51 -0800 Subject: [PATCH 1/3] Allow individual tests to opt out of time zone shifting --- src/org/labkey/test/ExtraSiteWrapper.java | 6 + src/org/labkey/test/TestProperties.java | 63 ++++++- src/org/labkey/test/WebDriverWrapper.java | 176 ++++++++++--------- src/org/labkey/test/tests/DataViewsTest.java | 9 + 4 files changed, 167 insertions(+), 87 deletions(-) diff --git a/src/org/labkey/test/ExtraSiteWrapper.java b/src/org/labkey/test/ExtraSiteWrapper.java index 39e2c7da18..6fc4840e3d 100644 --- a/src/org/labkey/test/ExtraSiteWrapper.java +++ b/src/org/labkey/test/ExtraSiteWrapper.java @@ -54,6 +54,12 @@ public WebDriver getWrappedDriver() return extraDriver.getLeft(); } + @Override + protected boolean allowTimeZoneShifting() + { + return false; + } + @Override public void pauseJsErrorChecker(){} @Override diff --git a/src/org/labkey/test/TestProperties.java b/src/org/labkey/test/TestProperties.java index 456ab1da63..ce36941f87 100644 --- a/src/org/labkey/test/TestProperties.java +++ b/src/org/labkey/test/TestProperties.java @@ -72,7 +72,7 @@ public abstract class TestProperties } } - private static ZoneId browserTimeZone = null; + private static ZoneId browserZoneId = null; public static void load() { @@ -166,9 +166,9 @@ public static boolean isDumpBrowserConsole() return getBooleanProperty("webtest.dump.browser.console", false); } - public static ZoneId getBrowserTimeZone() + public static ZoneId getBrowserZoneId() { - if (browserTimeZone == null) + if (browserZoneId == null) { String tz = StringUtils.trimToNull(System.getProperty("webtest.browser.tz")); if (tz != null) @@ -176,14 +176,14 @@ public static ZoneId getBrowserTimeZone() String[] split = tz.split("[,\\s]+"); tz = split[LocalDateTime.now().getDayOfMonth() % split.length]; // Verify that time zone is valid - browserTimeZone = ZoneId.of(tz); + browserZoneId = ZoneId.of(tz); } else { - browserTimeZone = ZoneId.systemDefault(); + browserZoneId = ZoneId.systemDefault(); } } - return browserTimeZone; + return browserZoneId; } public static double getTimeoutMultiplier() @@ -410,4 +410,55 @@ private static double getDoubleProperty(String key, double def) } return def; } +// +// +// public static class DateFormatTestCase extends Assert +// { +// int gmtHour(long ms) +// { +// return (int) ((ms / (60 * 60 * 1000L)) % 24); +// } +// +// long calendarNoon(String tzName, int month) +// { +// var g = new GregorianCalendar(TimeZone.getTimeZone(tzName)); +// g.set(Calendar.YEAR, 2020); +// g.set(Calendar.MONTH, month); +// g.set(Calendar.DAY_OF_MONTH, 1); +// g.set(Calendar.HOUR_OF_DAY, 12); +// return g.getTimeInMillis(); +// } +// +// @Test +// public void testSimpleDateParse() throws Exception +// { +// // given the above, we expect hours field to change based on date for EET, CET, WET +// assertEquals(20, gmtHour(calendarNoon("America/Los_Angeles", Calendar.JANUARY))); // winter +// assertEquals(19, gmtHour(calendarNoon("America/Los_Angeles", Calendar.JULY))); // summer +// +// assertEquals(10, gmtHour(calendarNoon("EET", Calendar.JANUARY))); // winter +// assertEquals( 9, gmtHour(calendarNoon("EET", Calendar.JULY))); // summer +// +// assertEquals(11, gmtHour(calendarNoon("CET", Calendar.JANUARY))); // winter +// assertEquals(10, gmtHour(calendarNoon("CET", Calendar.JULY))); // summer +// +// assertEquals(12, gmtHour(calendarNoon("WET", Calendar.JANUARY))); // winter +// assertEquals(11, gmtHour(calendarNoon("WET", Calendar.JULY))); // summer +// +// // If we use FastDateFormat the times _do_not_change_ as we export (or at least hope) +// var fastFormat = FastDateFormat.getInstance("yyyy-MM-dd HH:mm (zzz)"); +// +// assertEquals(20, gmtHour(fastFormat.parse("2020-01-01 12:00 (Pacific Time)").getTime())); // winter +// assertEquals(20, gmtHour(fastFormat.parse("2020-07-01 12:00 (Pacific Time)").getTime())); // summer +// +// assertEquals(10, gmtHour(fastFormat.parse("2020-01-01 12:00 (EET)").getTime())); // winter +// assertEquals(10, gmtHour(fastFormat.parse("2020-07-01 12:00 (EET)").getTime())); // summer +// +// assertEquals(11, gmtHour(fastFormat.parse("2020-01-01 12:00 (CET)").getTime())); // winter +// assertEquals(11, gmtHour(fastFormat.parse("2020-07-01 12:00 (CET)").getTime())); // summer +// +// assertEquals(12, gmtHour(fastFormat.parse("2020-01-01 12:00 (WET)").getTime())); // winter +// assertEquals(12, gmtHour(fastFormat.parse("2020-07-01 12:00 (WET)").getTime())); // summer +// } +// } } diff --git a/src/org/labkey/test/WebDriverWrapper.java b/src/org/labkey/test/WebDriverWrapper.java index 4005c31201..b7349cd27d 100644 --- a/src/org/labkey/test/WebDriverWrapper.java +++ b/src/org/labkey/test/WebDriverWrapper.java @@ -88,7 +88,6 @@ import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeDriverService; import org.openqa.selenium.chrome.ChromeOptions; -import org.openqa.selenium.firefox.FirefoxBinary; import org.openqa.selenium.firefox.FirefoxDriver; import org.openqa.selenium.firefox.FirefoxDriverLogLevel; import org.openqa.selenium.firefox.FirefoxDriverService; @@ -120,6 +119,7 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.Duration; +import java.time.Instant; import java.time.ZoneId; import java.util.ArrayList; import java.util.Arrays; @@ -206,64 +206,78 @@ protected Pair createNewWebDriver(BrowserType browserT return createNewWebDriver(new ImmutablePair<>(null, null), browserType, downloadDir); } + /** + * @return Actual browser time zone offset in minutes + */ + public long getWebDriverTimeZoneOffset() + { + return executeScript("return new Date().getTimezoneOffset();", Long.class); + } + + /** + * Allow tests to disable time zone shifting. Warning: {@link TestProperties#getBrowserZoneId()} might not match actual + * browser time zone if this is false. + */ + protected boolean allowTimeZoneShifting() + { + return true; + } + protected Pair createNewWebDriver(@NotNull Pair oldDriverAndService, BrowserType browserType, File downloadDir) { WebDriver oldWebDriver = oldDriverAndService.getLeft(); - WebDriver newWebDriver = null; + WebDriver newWebDriver; DriverService oldDriverService = oldDriverAndService.getRight(); - DriverService newDriverService = null; + DriverService newDriverService; + Map browserEnv = new HashMap<>(); - ZoneId browserTimeZone = TestProperties.getBrowserTimeZone(); - if (browserTimeZone != ZoneId.systemDefault()) + ZoneId targetBrowserTimeZone = allowTimeZoneShifting() ? TestProperties.getBrowserZoneId() : ZoneId.systemDefault(); + int targetOffsetInSeconds = targetBrowserTimeZone.getRules().getOffset(Instant.now()).getTotalSeconds(); + + if (targetBrowserTimeZone != ZoneId.systemDefault()) { - TestLogger.info("Starting browser with TZ = " + browserTimeZone); - browserEnv.put("TZ", browserTimeZone.toString()); + browserEnv.put("TZ", targetBrowserTimeZone.toString()); } - else + + if (oldWebDriver != null && + (!browserType.matchesDriver(oldWebDriver) || + new WebDriverWrapperImpl(oldWebDriver).getWebDriverTimeZoneOffset() * -60 != targetOffsetInSeconds)) { - TestLogger.info("Starting browser with TZ = " + browserTimeZone + " (system default)"); + TestLogger.info("Quitting existing driver and service"); + oldWebDriver.quit(); + oldWebDriver = null; + if (oldDriverService != null && oldDriverService.isRunning()) + oldDriverService.stop(); } - switch (browserType) + final Pair result; + + if (oldWebDriver == null) { - case REMOTE: //experimental - { - try - { - newWebDriver = new RemoteWebDriver(new URL("http://localhost:4444/wd/hub"), new ChromeOptions()); - } - catch (MalformedURLException e) - { - throw new RuntimeException(e); - } - break; - } - case IE: //experimental + log("Starting new browser: " + browserType); + + switch (browserType) { - if (oldWebDriver != null && !(oldWebDriver instanceof InternetExplorerDriver)) + case REMOTE: //experimental { - oldWebDriver.quit(); - oldWebDriver = null; - if (oldDriverService != null && oldDriverService.isRunning()) - oldDriverService.stop(); + try + { + newDriverService = null; // Remote service runs independently of tests + newWebDriver = new RemoteWebDriver(new URL("http://localhost:4444/wd/hub"), new ChromeOptions()); + } + catch (MalformedURLException e) + { + throw new RuntimeException(e); + } + break; } - if (oldWebDriver == null) + case IE: //experimental { newDriverService = new InternetExplorerDriverService.Builder().withEnvironment(browserEnv).build(); newWebDriver = new InternetExplorerDriver((InternetExplorerDriverService) newDriverService); + break; } - break; - } - case CHROME: - { - if (oldWebDriver != null && !(oldWebDriver instanceof ChromeDriver)) - { - oldWebDriver.quit(); - oldWebDriver = null; - if (oldDriverService != null && oldDriverService.isRunning()) - oldDriverService.stop(); - } - if (oldWebDriver == null) + case CHROME: { configureChromeDriverLogging(downloadDir); ChromeOptions options = new ChromeOptions(); @@ -292,19 +306,9 @@ protected Pair createNewWebDriver(@NotNull Pair createNewWebDriver(@NotNull Pair 0) + if (!browserPath.isEmpty()) { - binary = new FirefoxBinary(new File(browserPath)); - } - else - { - binary = new FirefoxBinary(); + capabilities.setBinary(browserPath); } if (TestProperties.isRunWebDriverHeadless()) { TestLogger.warn("Launching Firefox in headless mode. This is still experimental"); - binary.addCommandLineOptions("--headless"); + capabilities.addArguments("--headless"); } - capabilities.setBinary(binary); + // Firefox 128: UnhandledAlertException doesn't include alert text. Need to leave alerts to get text manually. capabilities.setUnhandledPromptBehaviour(UnexpectedAlertBehaviour.IGNORE); FirefoxOptions firefoxOptions = new FirefoxOptions(capabilities); @@ -413,15 +412,13 @@ protected Pair createNewWebDriver(@NotNull Pair windowSize = TestProperties.getWindowSize(); if (windowSize.isPresent()) @@ -430,16 +427,21 @@ protected Pair createNewWebDriver(@NotNull Pair(newWebDriver, newDriverService); + result = Pair.of(newWebDriver, newDriverService); } else { - return oldDriverAndService; + log("Reusing existing browser"); + result = oldDriverAndService; } + + Capabilities caps = ((HasCapabilities) result.getLeft()).getCapabilities(); + String browserName = caps.getBrowserName(); + String browserVersion = caps.getBrowserVersion(); + log("Browser: " + browserName + " " + browserVersion); + log("Started browser with TZ = " + targetBrowserTimeZone + (targetBrowserTimeZone != ZoneId.systemDefault() ? "" : " (system default)")); + + return result; } private void configureChromeDriverLogging(File downloadDir) @@ -483,7 +485,7 @@ private void configureGeckoDriverLogging(File downloadDir) public boolean isFirefox() { - return getDriver().getClass().isAssignableFrom(FirefoxDriver.class); + return BrowserType.FIREFOX.matchesDriver(getDriver()); } public Object executeScript(@Language("JavaScript") String script, Object... arguments) @@ -553,11 +555,23 @@ private void setJsErrorLogging(boolean b) public enum BrowserType { - REMOTE, - FIREFOX, - IE, - CHROME, - HTML + REMOTE(null), + FIREFOX(FirefoxDriver.class), + IE(InternetExplorerDriver.class), + CHROME(ChromeDriver.class), + ; + + private final Class _driverClass; + + BrowserType(Class driverClass) + { + _driverClass = driverClass; + } + + boolean matchesDriver(@NotNull WebDriver oldDriver) + { + return _driverClass != null && oldDriver.getClass().isAssignableFrom(_driverClass); + } } public static void sleep(long ms) diff --git a/src/org/labkey/test/tests/DataViewsTest.java b/src/org/labkey/test/tests/DataViewsTest.java index 9da83a9fb9..0209cc78cc 100644 --- a/src/org/labkey/test/tests/DataViewsTest.java +++ b/src/org/labkey/test/tests/DataViewsTest.java @@ -58,6 +58,15 @@ public class DataViewsTest extends ParticipantListTest private final PortalHelper _portalHelper = new PortalHelper(this); + /** + * 52268: Data Views Webpart behaves badly when the server is in a different time zone + */ + @Override + protected boolean allowTimeZoneShifting() + { + return false; + } + @Override @LogMethod protected void doCreateSteps() { From c8da8183505a6dae17a6b2b6136450c073bb3854 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Mon, 17 Feb 2025 15:07:54 -0800 Subject: [PATCH 2/3] Fixes for time zone shifting --- src/org/labkey/test/WebDriverWrapper.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/org/labkey/test/WebDriverWrapper.java b/src/org/labkey/test/WebDriverWrapper.java index b7349cd27d..e21cb5675c 100644 --- a/src/org/labkey/test/WebDriverWrapper.java +++ b/src/org/labkey/test/WebDriverWrapper.java @@ -207,11 +207,26 @@ protected Pair createNewWebDriver(BrowserType browserT } /** + * Number of minutes to add to convert browser Date to UTC.
+ * Note: The sign of this is the opposite of Java's concept of time zone offset * @return Actual browser time zone offset in minutes */ - public long getWebDriverTimeZoneOffset() + public int getWebDriverTimeZoneOffset() { - return executeScript("return new Date().getTimezoneOffset();", Long.class); + return Math.toIntExact(executeScript("return new Date().getTimezoneOffset();", Long.class)); + } + + /** + * Test process might be running in a different time zone from the browser. This allows shifting test date to + * browser dates. + *
{@code
+     * TestDateUtils.diffFromTodaysDate(Calendar.MINUTE, getRelativeTimeZoneOffset())
+     * }
+ * @return The number of minutes to add to a system date to convert to the equivalent browser date + */ + public int getRelativeTimeZoneOffset() + { + return ZoneId.systemDefault().getRules().getOffset(Instant.now()).getTotalSeconds() / 60 - getWebDriverTimeZoneOffset(); } /** From c6a7f151233ead5e74e72ad5a9132066d3b1c5af Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Mon, 17 Feb 2025 15:23:52 -0800 Subject: [PATCH 3/3] Remove accidentally included change --- src/org/labkey/test/TestProperties.java | 51 ------------------------- 1 file changed, 51 deletions(-) diff --git a/src/org/labkey/test/TestProperties.java b/src/org/labkey/test/TestProperties.java index ce36941f87..c05ca8529d 100644 --- a/src/org/labkey/test/TestProperties.java +++ b/src/org/labkey/test/TestProperties.java @@ -410,55 +410,4 @@ private static double getDoubleProperty(String key, double def) } return def; } -// -// -// public static class DateFormatTestCase extends Assert -// { -// int gmtHour(long ms) -// { -// return (int) ((ms / (60 * 60 * 1000L)) % 24); -// } -// -// long calendarNoon(String tzName, int month) -// { -// var g = new GregorianCalendar(TimeZone.getTimeZone(tzName)); -// g.set(Calendar.YEAR, 2020); -// g.set(Calendar.MONTH, month); -// g.set(Calendar.DAY_OF_MONTH, 1); -// g.set(Calendar.HOUR_OF_DAY, 12); -// return g.getTimeInMillis(); -// } -// -// @Test -// public void testSimpleDateParse() throws Exception -// { -// // given the above, we expect hours field to change based on date for EET, CET, WET -// assertEquals(20, gmtHour(calendarNoon("America/Los_Angeles", Calendar.JANUARY))); // winter -// assertEquals(19, gmtHour(calendarNoon("America/Los_Angeles", Calendar.JULY))); // summer -// -// assertEquals(10, gmtHour(calendarNoon("EET", Calendar.JANUARY))); // winter -// assertEquals( 9, gmtHour(calendarNoon("EET", Calendar.JULY))); // summer -// -// assertEquals(11, gmtHour(calendarNoon("CET", Calendar.JANUARY))); // winter -// assertEquals(10, gmtHour(calendarNoon("CET", Calendar.JULY))); // summer -// -// assertEquals(12, gmtHour(calendarNoon("WET", Calendar.JANUARY))); // winter -// assertEquals(11, gmtHour(calendarNoon("WET", Calendar.JULY))); // summer -// -// // If we use FastDateFormat the times _do_not_change_ as we export (or at least hope) -// var fastFormat = FastDateFormat.getInstance("yyyy-MM-dd HH:mm (zzz)"); -// -// assertEquals(20, gmtHour(fastFormat.parse("2020-01-01 12:00 (Pacific Time)").getTime())); // winter -// assertEquals(20, gmtHour(fastFormat.parse("2020-07-01 12:00 (Pacific Time)").getTime())); // summer -// -// assertEquals(10, gmtHour(fastFormat.parse("2020-01-01 12:00 (EET)").getTime())); // winter -// assertEquals(10, gmtHour(fastFormat.parse("2020-07-01 12:00 (EET)").getTime())); // summer -// -// assertEquals(11, gmtHour(fastFormat.parse("2020-01-01 12:00 (CET)").getTime())); // winter -// assertEquals(11, gmtHour(fastFormat.parse("2020-07-01 12:00 (CET)").getTime())); // summer -// -// assertEquals(12, gmtHour(fastFormat.parse("2020-01-01 12:00 (WET)").getTime())); // winter -// assertEquals(12, gmtHour(fastFormat.parse("2020-07-01 12:00 (WET)").getTime())); // summer -// } -// } }