From ae84fecad6c8720ff21b821329f71b54fbee0d3e Mon Sep 17 00:00:00 2001 From: ChrisJoosse Date: Sun, 13 Jul 2025 00:52:03 -0700 Subject: [PATCH 1/5] Optional features ui coverage --- .../core/admin/OptionalFeaturesPage.java | 106 ++++++++++++++++++ .../labkey/test/tests/AdminConsoleTest.java | 86 ++++++++++++++ 2 files changed, 192 insertions(+) create mode 100644 src/org/labkey/test/pages/core/admin/OptionalFeaturesPage.java diff --git a/src/org/labkey/test/pages/core/admin/OptionalFeaturesPage.java b/src/org/labkey/test/pages/core/admin/OptionalFeaturesPage.java new file mode 100644 index 0000000000..cea03f8c09 --- /dev/null +++ b/src/org/labkey/test/pages/core/admin/OptionalFeaturesPage.java @@ -0,0 +1,106 @@ +package org.labkey.test.pages.core.admin; + +import org.labkey.test.Locator; +import org.labkey.test.WebDriverWrapper; +import org.labkey.test.WebTestHelper; +import org.labkey.test.components.html.Checkbox; +import org.labkey.test.pages.LabKeyPage; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/* + Wraps Optional Features, Experimental Featueres, Deprecated Features pages linked + from Admin Console + */ +public class OptionalFeaturesPage extends LabKeyPage +{ + public OptionalFeaturesPage(WebDriver driver) + { + super(driver); + } + + public static OptionalFeaturesPage beginAt(WebDriverWrapper webDriverWrapper, OptionalFeatureType featureType) + { + webDriverWrapper.beginAt(WebTestHelper.buildURL("admin", "/", "optionalFeatures", Map.of("Type", featureType.toString()))); + return new OptionalFeaturesPage(webDriverWrapper.getDriver()); + } + + @Override + protected void waitForPage() + { + waitFor(()-> elementCache().listGroupLoc.findWhenNeeded(getDriver()).isDisplayed(), + "The page did not render in time", WAIT_FOR_JAVASCRIPT); + } + + public ShowAdminPage goToAdminConsole() + { + clickAndWait(Locator.linkWithText("Admin Console")); + return new ShowAdminPage(getDriver()); + } + + public Map getFeatureMap() + { + return elementCache().getListItems(); + } + + public Set getFeatureIds() + { + return getFeatureMap().keySet(); + } + + public boolean getFeatureStatus(String id) + { + return elementCache().getCheckboxById(id).get(); + } + + public OptionalFeaturesPage setFeatureStatus(String id, boolean status) + { + elementCache().getCheckboxById(id).set(status); + return this; + } + + @Override + protected ElementCache newElementCache() + { + return new ElementCache(); + } + + protected class ElementCache extends LabKeyPage.ElementCache + { + public final Locator.XPathLocator listGroupLoc = Locator.tagWithClass("div", "list-group"); + public final WebElement listGroupElement = listGroupLoc.waitForElement(getDriver(), 1500); + public final Locator listItemLabelLoc = Locator.tagWithClass("div", "list-group-item") + .child(Locator.tag("Label")); + private Map _listItems; + + public Map getListItems() + { + if (_listItems == null) { + _listItems = new HashMap(); + for (WebElement el : listItemLabelLoc.findElements(listGroupElement)) { + WebElement idEl = Locator.tagWithAttribute("type", "checkbox") + .findElement(el); + WebElement labelEl = Locator.tagWithClass("span", "toggle-label-text") + .findElement(el); + _listItems.put(idEl.getText(), labelEl.getText()); + } + } + return _listItems; + } + + public Checkbox getCheckboxById(String id) + { + return Checkbox.Checkbox(Locator.id(id)).waitFor(listGroupElement); + } + } + + public enum OptionalFeatureType{ + Experimental, + Optional, + Deprecated + } +} diff --git a/src/org/labkey/test/tests/AdminConsoleTest.java b/src/org/labkey/test/tests/AdminConsoleTest.java index 756af7106d..50a09dbcf3 100644 --- a/src/org/labkey/test/tests/AdminConsoleTest.java +++ b/src/org/labkey/test/tests/AdminConsoleTest.java @@ -15,6 +15,7 @@ */ package org.labkey.test.tests; +import org.assertj.core.api.Assertions; import org.junit.Test; import org.junit.experimental.categories.Category; import org.labkey.remoteapi.CommandException; @@ -25,13 +26,16 @@ import org.labkey.test.Locator; import org.labkey.test.WebDriverWrapper; import org.labkey.test.WebTestHelper; +import org.labkey.test.util.OptionalFeatureHelper; import org.labkey.test.categories.Daily; import org.labkey.test.pages.core.admin.CustomizeSitePage; +import org.labkey.test.pages.core.admin.OptionalFeaturesPage; import org.labkey.test.pages.core.login.LoginConfigRow; import org.labkey.test.pages.core.login.LoginConfigurePage; import org.labkey.test.util.LogMethod; import java.io.IOException; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -226,6 +230,88 @@ public void testSiteBannerAPIConfiguration() throws Exception .verifyFalse("expect banner not to be shown", bannerLoc.isDisplayed(getDriver())); } + @Test + public void testUIOptionalFeatures() + { + goToAdminConsole(); + waitAndClickAndWait(Locator.linkWithText("optional features")); + var optionalFeaturesPage = new OptionalFeaturesPage(getDriver()); + var featureIds = List.of("extendedMetrics", "StageFileUploads"); + var cn = createDefaultConnection(); + + for (String testId : featureIds) { + // capture initial state + boolean initialState = OptionalFeatureHelper.isOptionalFeatureEnabled(cn, testId); + + // ensure the UI reflects the same state + boolean initialUIState = optionalFeaturesPage.getFeatureStatus(testId); + checker().withScreenshot("initial state not as expected") + .wrapAssertion(()-> Assertions.assertThat(initialUIState) + .as("expect ui to align with API initial state") + .isEqualTo(initialState)); + + // toggle it the other way + optionalFeaturesPage.setFeatureStatus(testId, !initialState); + checker().withScreenshot("toggled state not as expected") + .awaiting(Duration.ofMillis(500), ()-> Assertions.assertThat(OptionalFeatureHelper.isOptionalFeatureEnabled(cn, testId)) + .as("expect toggling the UI to update the server status for the feature") + .isEqualTo(!initialState)); + + // use the API to restore the initial state + OptionalFeatureHelper.setOptionalFeature(cn, testId, initialState); + optionalFeaturesPage.goToAdminConsole(); + + optionalFeaturesPage = OptionalFeaturesPage.beginAt(this, + OptionalFeaturesPage.OptionalFeatureType.Optional); + + // verify the page state reflects the API change after a reload + checker().withScreenshot("state not as expected after api set and refresh") + .verifyEquals("expect page to reflect state after api config", + initialState, optionalFeaturesPage.getFeatureStatus(testId)); + } + } + + @Test + public void testUIExperimentalFeatures() + { + goToAdminConsole(); + waitAndClickAndWait(Locator.linkWithText("experimental features")); + var experimentalFeaturesPage = new OptionalFeaturesPage(getDriver()); + var featureIds = List.of("queryBasedDatasets", "LinkedDatasetCheck", "blockMaliciousClients"); + var cn = createDefaultConnection(); + + for (String testId : featureIds) { + // capture initial state + boolean initialState = OptionalFeatureHelper.isOptionalFeatureEnabled(cn, testId); + + // ensure the UI reflects the same state + boolean initialUIState = experimentalFeaturesPage.getFeatureStatus(testId); + checker().withScreenshot("initial state not as expected") + .wrapAssertion(()-> Assertions.assertThat(initialUIState) + .as("expect ui to align with API initial state") + .isEqualTo(initialState)); + + // toggle it the other way + experimentalFeaturesPage.setFeatureStatus(testId, !initialState); + checker().withScreenshot("toggled state not as expected") + .awaiting(Duration.ofMillis(500), ()-> Assertions.assertThat(OptionalFeatureHelper.isOptionalFeatureEnabled(cn, testId)) + .as("expect toggling the UI to update the server status for the feature") + .isEqualTo(!initialState)); + + // use the API to restore the initial state + OptionalFeatureHelper.setOptionalFeature(cn, testId, initialState); + experimentalFeaturesPage.goToAdminConsole(); + + experimentalFeaturesPage = OptionalFeaturesPage.beginAt(this, + OptionalFeaturesPage.OptionalFeatureType.Experimental); + + // verify the page state reflects the API change after a reload + checker().withScreenshot("state not as expected after api set and refresh") + .verifyEquals("expect page to reflect state after api config", + initialState, experimentalFeaturesPage.getFeatureStatus(testId)); + } + } + @Test public void testAppAdminRole() { From 8a9d6bbce0c383edcac4b78ba0df0866a6a94729 Mon Sep 17 00:00:00 2001 From: ChrisJoosse Date: Mon, 14 Jul 2025 11:03:12 -0700 Subject: [PATCH 2/5] cr feedback --- .../labkey/test/tests/AdminConsoleTest.java | 102 +++++++----------- 1 file changed, 38 insertions(+), 64 deletions(-) diff --git a/src/org/labkey/test/tests/AdminConsoleTest.java b/src/org/labkey/test/tests/AdminConsoleTest.java index 50a09dbcf3..b1134f2794 100644 --- a/src/org/labkey/test/tests/AdminConsoleTest.java +++ b/src/org/labkey/test/tests/AdminConsoleTest.java @@ -235,40 +235,9 @@ public void testUIOptionalFeatures() { goToAdminConsole(); waitAndClickAndWait(Locator.linkWithText("optional features")); - var optionalFeaturesPage = new OptionalFeaturesPage(getDriver()); var featureIds = List.of("extendedMetrics", "StageFileUploads"); - var cn = createDefaultConnection(); - - for (String testId : featureIds) { - // capture initial state - boolean initialState = OptionalFeatureHelper.isOptionalFeatureEnabled(cn, testId); - // ensure the UI reflects the same state - boolean initialUIState = optionalFeaturesPage.getFeatureStatus(testId); - checker().withScreenshot("initial state not as expected") - .wrapAssertion(()-> Assertions.assertThat(initialUIState) - .as("expect ui to align with API initial state") - .isEqualTo(initialState)); - - // toggle it the other way - optionalFeaturesPage.setFeatureStatus(testId, !initialState); - checker().withScreenshot("toggled state not as expected") - .awaiting(Duration.ofMillis(500), ()-> Assertions.assertThat(OptionalFeatureHelper.isOptionalFeatureEnabled(cn, testId)) - .as("expect toggling the UI to update the server status for the feature") - .isEqualTo(!initialState)); - - // use the API to restore the initial state - OptionalFeatureHelper.setOptionalFeature(cn, testId, initialState); - optionalFeaturesPage.goToAdminConsole(); - - optionalFeaturesPage = OptionalFeaturesPage.beginAt(this, - OptionalFeaturesPage.OptionalFeatureType.Optional); - - // verify the page state reflects the API change after a reload - checker().withScreenshot("state not as expected after api set and refresh") - .verifyEquals("expect page to reflect state after api config", - initialState, optionalFeaturesPage.getFeatureStatus(testId)); - } + verifyOptionalFeatures(featureIds, OptionalFeaturesPage.OptionalFeatureType.Optional); } @Test @@ -276,40 +245,9 @@ public void testUIExperimentalFeatures() { goToAdminConsole(); waitAndClickAndWait(Locator.linkWithText("experimental features")); - var experimentalFeaturesPage = new OptionalFeaturesPage(getDriver()); var featureIds = List.of("queryBasedDatasets", "LinkedDatasetCheck", "blockMaliciousClients"); - var cn = createDefaultConnection(); - - for (String testId : featureIds) { - // capture initial state - boolean initialState = OptionalFeatureHelper.isOptionalFeatureEnabled(cn, testId); - // ensure the UI reflects the same state - boolean initialUIState = experimentalFeaturesPage.getFeatureStatus(testId); - checker().withScreenshot("initial state not as expected") - .wrapAssertion(()-> Assertions.assertThat(initialUIState) - .as("expect ui to align with API initial state") - .isEqualTo(initialState)); - - // toggle it the other way - experimentalFeaturesPage.setFeatureStatus(testId, !initialState); - checker().withScreenshot("toggled state not as expected") - .awaiting(Duration.ofMillis(500), ()-> Assertions.assertThat(OptionalFeatureHelper.isOptionalFeatureEnabled(cn, testId)) - .as("expect toggling the UI to update the server status for the feature") - .isEqualTo(!initialState)); - - // use the API to restore the initial state - OptionalFeatureHelper.setOptionalFeature(cn, testId, initialState); - experimentalFeaturesPage.goToAdminConsole(); - - experimentalFeaturesPage = OptionalFeaturesPage.beginAt(this, - OptionalFeaturesPage.OptionalFeatureType.Experimental); - - // verify the page state reflects the API change after a reload - checker().withScreenshot("state not as expected after api set and refresh") - .verifyEquals("expect page to reflect state after api config", - initialState, experimentalFeaturesPage.getFeatureStatus(testId)); - } + verifyOptionalFeatures(featureIds, OptionalFeaturesPage.OptionalFeatureType.Experimental); } @Test @@ -443,4 +381,40 @@ public void testAdminConsoleCredits() log("Verifying the page is properly loaded"); assertTextPresent("JAR Files Distributed with the API Module"); } + + private void verifyOptionalFeatures(List featureIds, OptionalFeaturesPage.OptionalFeatureType optionalFeatureType) + { + var optionalFeaturesPage = new OptionalFeaturesPage(getDriver()); + var cn = createDefaultConnection(); + + for (String testId : featureIds) { + // capture initial state + boolean initialState = OptionalFeatureHelper.isOptionalFeatureEnabled(cn, testId); + + // ensure the UI reflects the same state + boolean initialUIState = optionalFeaturesPage.getFeatureStatus(testId); + checker().withScreenshot("initial state not as expected") + .wrapAssertion(()-> Assertions.assertThat(initialUIState) + .as("expect ui to align with API initial state") + .isEqualTo(initialState)); + + // toggle it the other way + optionalFeaturesPage.setFeatureStatus(testId, !initialState); + checker().withScreenshot("toggled state not as expected") + .awaiting(Duration.ofMillis(500), ()-> Assertions.assertThat(OptionalFeatureHelper.isOptionalFeatureEnabled(cn, testId)) + .as("expect toggling the UI to update the server status for the feature") + .isEqualTo(!initialState)); + + // use the API to restore the initial state + OptionalFeatureHelper.setOptionalFeature(cn, testId, initialState); + optionalFeaturesPage.goToAdminConsole(); + + optionalFeaturesPage = OptionalFeaturesPage.beginAt(this, optionalFeatureType); + + // verify the page state reflects the API change after a reload + checker().withScreenshot("state not as expected after api set and refresh") + .verifyEquals("expect page to reflect state after api config", + initialState, optionalFeaturesPage.getFeatureStatus(testId)); + } + } } From f6f40e755f0741c0f86f0be8f52a008eadbc48d1 Mon Sep 17 00:00:00 2001 From: ChrisJoosse Date: Mon, 14 Jul 2025 13:07:40 -0700 Subject: [PATCH 3/5] cr feedback --- .../test/pages/core/admin/OptionalFeaturesPage.java | 2 +- src/org/labkey/test/tests/AdminConsoleTest.java | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/org/labkey/test/pages/core/admin/OptionalFeaturesPage.java b/src/org/labkey/test/pages/core/admin/OptionalFeaturesPage.java index cea03f8c09..15749bcb91 100644 --- a/src/org/labkey/test/pages/core/admin/OptionalFeaturesPage.java +++ b/src/org/labkey/test/pages/core/admin/OptionalFeaturesPage.java @@ -13,7 +13,7 @@ import java.util.Set; /* - Wraps Optional Features, Experimental Featueres, Deprecated Features pages linked + Wraps Optional Features, Experimental Features, Deprecated Features pages linked from Admin Console */ public class OptionalFeaturesPage extends LabKeyPage diff --git a/src/org/labkey/test/tests/AdminConsoleTest.java b/src/org/labkey/test/tests/AdminConsoleTest.java index b1134f2794..4078257be0 100644 --- a/src/org/labkey/test/tests/AdminConsoleTest.java +++ b/src/org/labkey/test/tests/AdminConsoleTest.java @@ -234,10 +234,10 @@ public void testSiteBannerAPIConfiguration() throws Exception public void testUIOptionalFeatures() { goToAdminConsole(); - waitAndClickAndWait(Locator.linkWithText("optional features")); + var featureIds = List.of("extendedMetrics", "StageFileUploads"); - verifyOptionalFeatures(featureIds, OptionalFeaturesPage.OptionalFeatureType.Optional); + verifyOptionalFeatures("optional features", featureIds, OptionalFeaturesPage.OptionalFeatureType.Optional); } @Test @@ -247,7 +247,7 @@ public void testUIExperimentalFeatures() waitAndClickAndWait(Locator.linkWithText("experimental features")); var featureIds = List.of("queryBasedDatasets", "LinkedDatasetCheck", "blockMaliciousClients"); - verifyOptionalFeatures(featureIds, OptionalFeaturesPage.OptionalFeatureType.Experimental); + verifyOptionalFeatures("experimental features", featureIds, OptionalFeaturesPage.OptionalFeatureType.Experimental); } @Test @@ -382,8 +382,9 @@ public void testAdminConsoleCredits() assertTextPresent("JAR Files Distributed with the API Module"); } - private void verifyOptionalFeatures(List featureIds, OptionalFeaturesPage.OptionalFeatureType optionalFeatureType) + private void verifyOptionalFeatures(String linkText, List featureIds, OptionalFeaturesPage.OptionalFeatureType optionalFeatureType) { + waitAndClickAndWait(Locator.linkWithText(linkText)); var optionalFeaturesPage = new OptionalFeaturesPage(getDriver()); var cn = createDefaultConnection(); From 6a5da586f796ef9ae73cda8e7799e7325e3a13a4 Mon Sep 17 00:00:00 2001 From: ChrisJoosse Date: Mon, 14 Jul 2025 13:47:44 -0700 Subject: [PATCH 4/5] cleanup --- src/org/labkey/test/tests/AdminConsoleTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/labkey/test/tests/AdminConsoleTest.java b/src/org/labkey/test/tests/AdminConsoleTest.java index 4078257be0..d692fce1bb 100644 --- a/src/org/labkey/test/tests/AdminConsoleTest.java +++ b/src/org/labkey/test/tests/AdminConsoleTest.java @@ -244,7 +244,7 @@ public void testUIOptionalFeatures() public void testUIExperimentalFeatures() { goToAdminConsole(); - waitAndClickAndWait(Locator.linkWithText("experimental features")); + var featureIds = List.of("queryBasedDatasets", "LinkedDatasetCheck", "blockMaliciousClients"); verifyOptionalFeatures("experimental features", featureIds, OptionalFeaturesPage.OptionalFeatureType.Experimental); From 0f452a2bcdcb45998a4742d22543c1fbac29452f Mon Sep 17 00:00:00 2001 From: ChrisJoosse Date: Mon, 14 Jul 2025 20:50:52 -0700 Subject: [PATCH 5/5] more cr feedback --- .../core/admin/OptionalFeaturesPage.java | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/src/org/labkey/test/pages/core/admin/OptionalFeaturesPage.java b/src/org/labkey/test/pages/core/admin/OptionalFeaturesPage.java index 15749bcb91..49948af620 100644 --- a/src/org/labkey/test/pages/core/admin/OptionalFeaturesPage.java +++ b/src/org/labkey/test/pages/core/admin/OptionalFeaturesPage.java @@ -42,16 +42,6 @@ public ShowAdminPage goToAdminConsole() return new ShowAdminPage(getDriver()); } - public Map getFeatureMap() - { - return elementCache().getListItems(); - } - - public Set getFeatureIds() - { - return getFeatureMap().keySet(); - } - public boolean getFeatureStatus(String id) { return elementCache().getCheckboxById(id).get(); @@ -75,22 +65,6 @@ protected class ElementCache extends LabKeyPage.ElementCache public final WebElement listGroupElement = listGroupLoc.waitForElement(getDriver(), 1500); public final Locator listItemLabelLoc = Locator.tagWithClass("div", "list-group-item") .child(Locator.tag("Label")); - private Map _listItems; - - public Map getListItems() - { - if (_listItems == null) { - _listItems = new HashMap(); - for (WebElement el : listItemLabelLoc.findElements(listGroupElement)) { - WebElement idEl = Locator.tagWithAttribute("type", "checkbox") - .findElement(el); - WebElement labelEl = Locator.tagWithClass("span", "toggle-label-text") - .findElement(el); - _listItems.put(idEl.getText(), labelEl.getText()); - } - } - return _listItems; - } public Checkbox getCheckboxById(String id) {