From 18295eddbbac3ead40aebdd42c6d8f7d686751ee Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Thu, 9 Jul 2026 16:35:35 -0700 Subject: [PATCH 1/5] Clean up some shared app admin pages --- .../components/ManageSampleStatusesPanel.java | 3 + .../pages/experiment/DeleteDataClassPage.java | 46 ++++++++++++ .../pages/experiment/ShowDataClassPage.java | 70 +++++++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 src/org/labkey/test/pages/experiment/DeleteDataClassPage.java create mode 100644 src/org/labkey/test/pages/experiment/ShowDataClassPage.java diff --git a/src/org/labkey/test/components/ManageSampleStatusesPanel.java b/src/org/labkey/test/components/ManageSampleStatusesPanel.java index 22cb170d0e..4b7b6547ef 100644 --- a/src/org/labkey/test/components/ManageSampleStatusesPanel.java +++ b/src/org/labkey/test/components/ManageSampleStatusesPanel.java @@ -31,6 +31,9 @@ import java.util.List; import java.util.stream.Collectors; +/** + * Wraps 'components/samples/ManageSampleStatusesPanel.tsx' + */ public class ManageSampleStatusesPanel extends WebDriverComponent { private final WebDriver _driver; diff --git a/src/org/labkey/test/pages/experiment/DeleteDataClassPage.java b/src/org/labkey/test/pages/experiment/DeleteDataClassPage.java new file mode 100644 index 0000000000..da348af9c6 --- /dev/null +++ b/src/org/labkey/test/pages/experiment/DeleteDataClassPage.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2026 LabKey Corporation + * + * 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 org.labkey.test.pages.experiment; + +import org.labkey.test.Locator; +import org.labkey.test.pages.LabKeyPage; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; + + +public class DeleteDataClassPage extends LabKeyPage +{ + public DeleteDataClassPage(WebDriver driver) + { + super(driver); + } + + public void confirmDelete() + { + clickAndWait(elementCache().deleteButton); + } + + @Override + protected ElementCache newElementCache() + { + return new ElementCache(); + } + + protected class ElementCache extends LabKeyPage.ElementCache + { + WebElement deleteButton = Locator.lkButton("Confirm Delete").findWhenNeeded(this); + } +} diff --git a/src/org/labkey/test/pages/experiment/ShowDataClassPage.java b/src/org/labkey/test/pages/experiment/ShowDataClassPage.java new file mode 100644 index 0000000000..b79f7a8c86 --- /dev/null +++ b/src/org/labkey/test/pages/experiment/ShowDataClassPage.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2026 LabKey Corporation + * + * 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 org.labkey.test.pages.experiment; + +import org.labkey.test.Locator; +import org.labkey.test.WebDriverWrapper; +import org.labkey.test.WebTestHelper; +import org.labkey.test.pages.LabKeyPage; +import org.labkey.test.util.DataRegionTable; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; + +import java.util.Map; + +public class ShowDataClassPage extends LabKeyPage +{ + public ShowDataClassPage(WebDriver driver) + { + super(driver); + } + + public static ShowDataClassPage beginAt(WebDriverWrapper webDriverWrapper, String containerPath, int rowId) + { + webDriverWrapper.beginAt(WebTestHelper.buildURL("experiment", containerPath, "showDataClass", Map.of("rowId", rowId))); + return new ShowDataClassPage(webDriverWrapper.getDriver()); + } + + public CreateDataClassPage clickEditDataClass() + { + clickAndWait(elementCache().editDataClassButton); + return new CreateDataClassPage(getDriver()); + } + + public DeleteDataClassPage clickDeleteDataClass() + { + clickAndWait(elementCache().deleteDataClassButton); + return new DeleteDataClassPage(getDriver()); + } + + public DataRegionTable getDataRegion() + { + return elementCache()._dataRegionTable; + } + + @Override + protected ElementCache newElementCache() + { + return new ElementCache(); + } + + protected class ElementCache extends LabKeyPage.ElementCache + { + private final WebElement editDataClassButton = Locator.lkButton("Edit Data Class").findWhenNeeded(this); + private final WebElement deleteDataClassButton = Locator.lkButton("Delete Data Class").findWhenNeeded(this); + private final DataRegionTable _dataRegionTable = new DataRegionTable.DataRegionFinder(getDriver()).withName("query").findWhenNeeded(this); + } +} From b54c4ae53d88014b42c7ae26b22a12c0e7094154 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Thu, 9 Jul 2026 18:11:58 -0700 Subject: [PATCH 2/5] Merge UserMenu components --- src/org/labkey/test/BootstrapLocators.java | 4 ++ src/org/labkey/test/LabKeySiteWrapper.java | 5 +- .../test/components/html/BootstrapMenu.java | 10 +++ .../components/ui/navigation/UserMenu.java | 67 ------------------- 4 files changed, 16 insertions(+), 70 deletions(-) delete mode 100644 src/org/labkey/test/components/ui/navigation/UserMenu.java diff --git a/src/org/labkey/test/BootstrapLocators.java b/src/org/labkey/test/BootstrapLocators.java index bdf1e7551a..07f57e48fc 100644 --- a/src/org/labkey/test/BootstrapLocators.java +++ b/src/org/labkey/test/BootstrapLocators.java @@ -37,6 +37,10 @@ public static Locator.XPathLocator button(String text) return button().withText(text); } + public static final String APP_USER_MENU_CLASS = "user-dropdown"; + // Part of '@labkey/components/navigation/UserMenuGroup.tsx' + public static final Locator appUserMenu = Locator.byClass(APP_USER_MENU_CLASS); + // '@labkey/components/base/Alert.tsx' public static final Locator infoBanner = Locator.tagWithClass("div", BannerType.INFO.getCss()); public static final Locator successBanner = Locator.tagWithClass("div", BannerType.SUCCESS.getCss()); diff --git a/src/org/labkey/test/LabKeySiteWrapper.java b/src/org/labkey/test/LabKeySiteWrapper.java index 26d3eb52f3..a8f2ba8e02 100644 --- a/src/org/labkey/test/LabKeySiteWrapper.java +++ b/src/org/labkey/test/LabKeySiteWrapper.java @@ -51,7 +51,6 @@ import org.labkey.test.components.dumbster.EmailRecordTable; import org.labkey.test.components.html.RadioButton; import org.labkey.test.components.html.SiteNavBar; -import org.labkey.test.components.ui.navigation.UserMenu; import org.labkey.test.pages.core.admin.CustomizeSitePage; import org.labkey.test.pages.core.admin.ShowAdminPage; import org.labkey.test.pages.core.login.SignInPage; @@ -147,7 +146,7 @@ public void simpleSignIn() { beginAtAcceptingAlerts(buildURL("login", "login")); waitForAnyElement("Should be on login or Home portal", Locator.id("email"), SiteNavBar.Locators.userMenu, - UserMenu.appUserMenu()); + BootstrapLocators.appUserMenu); } if (PasswordUtil.getUsername().equals(getCurrentUser())) @@ -162,7 +161,7 @@ public void simpleSignIn() // verify we're signed in now if (!waitFor(() -> { - if(isElementPresent(UserMenu.appUserMenu())) + if (isElementPresent(BootstrapLocators.appUserMenu)) { goToHome(); } diff --git a/src/org/labkey/test/components/html/BootstrapMenu.java b/src/org/labkey/test/components/html/BootstrapMenu.java index 7a8cabc47a..7f938647e1 100644 --- a/src/org/labkey/test/components/html/BootstrapMenu.java +++ b/src/org/labkey/test/components/html/BootstrapMenu.java @@ -29,11 +29,21 @@ public class BootstrapMenu extends BaseBootstrapMenu { /* componentElement should contain the toggle anchor *and* the UL containing list items */ + public BootstrapMenu(WebElement componentElement, WebDriver driver) + { + super(componentElement, driver); + } + + /** + * @deprecated Non-standard parameter order + */ + @Deprecated (since = "26.8") public BootstrapMenu(WebDriver driver, WebElement componentElement) { super(componentElement, driver); } + static public BootstrapMenuFinder finder(WebDriver driver) { return new BootstrapMenuFinder(driver); diff --git a/src/org/labkey/test/components/ui/navigation/UserMenu.java b/src/org/labkey/test/components/ui/navigation/UserMenu.java deleted file mode 100644 index 4dd9c4c516..0000000000 --- a/src/org/labkey/test/components/ui/navigation/UserMenu.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2019-2026 LabKey Corporation - * - * 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 org.labkey.test.components.ui.navigation; - -import org.labkey.test.Locator; -import org.labkey.test.components.html.BootstrapMenu; -import org.labkey.test.components.react.MultiMenu; -import org.labkey.test.pages.LabKeyPage; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebElement; - -import java.util.function.BiFunction; - -/** - * Base class for app user menus implemented using @labkey-components/UserMenu - */ -public abstract class UserMenu extends BootstrapMenu -{ - private static final String MENU_CLASS = "user-dropdown"; - - protected UserMenu(WebElement element, WebDriver driver) - { - super(driver, element); - } - - protected static SimpleWebDriverComponentFinder baseMenuFinder(WebDriver driver, BiFunction factory) - { - return new MultiMenu.MultiMenuFinder(driver).withClass(MENU_CLASS).wrap(factory); - } - - // TODO: Placeholder for product update - protected LabKeyPage login() - { - clickSubMenu(true, "Sign In"); - return new LabKeyPage<>(getDriver()); - } - - // TODO: Placeholder for product update - protected LabKeyPage logout() - { - clickSubMenu(true, "Sign Out"); - return new LabKeyPage<>(getDriver()); - } - - /** - * The button for the drop down user menu. The user menu on the SampleManager app is an example of this button. - * - * @return A locator to the drop down user menu button. - */ - public static Locator appUserMenu() - { - return Locator.byClass(MENU_CLASS).childTag("a"); - } -} From 6a6f5101e089bb3d8a51f8e3de457f818c5d537f Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Fri, 10 Jul 2026 15:55:14 -0700 Subject: [PATCH 3/5] Pull up nav bar methods --- .../test/components/ui/navigation/NavBar.java | 135 ------- src/org/labkey/test/util/ui/BannerHelper.java | 372 ++++++++++++++++++ 2 files changed, 372 insertions(+), 135 deletions(-) delete mode 100644 src/org/labkey/test/components/ui/navigation/NavBar.java create mode 100644 src/org/labkey/test/util/ui/BannerHelper.java diff --git a/src/org/labkey/test/components/ui/navigation/NavBar.java b/src/org/labkey/test/components/ui/navigation/NavBar.java deleted file mode 100644 index 35b6de009c..0000000000 --- a/src/org/labkey/test/components/ui/navigation/NavBar.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) 2019-2026 LabKey Corporation - * - * 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 org.labkey.test.components.ui.navigation; - -import org.labkey.test.Locator; -import org.labkey.test.components.Component; -import org.labkey.test.components.WebDriverComponent; -import org.labkey.test.components.html.Input; -import org.labkey.test.components.react.MultiMenu; -import org.labkey.test.components.ui.notifications.ServerNotificationMenu; -import org.labkey.test.components.ui.search.SampleFinder; -import org.labkey.test.util.search.HasSearchResults; -import org.openqa.selenium.Keys; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebElement; - -public abstract class NavBar extends WebDriverComponent -{ - private final WebDriver _driver; - private final WebElement _navBarElement; - - protected NavBar(WebDriver driver) - { - this(Locator.tagWithClass("nav", "navbar").findElement(driver), driver); - } - - protected NavBar(WebElement element, WebDriver driver) - { - _navBarElement = element; - _driver = driver; - } - - public void clickHeaderLogo() - { - elementCache().headerLogo.click(); - } - - public String getHeaderLogoImgSrc() - { - return elementCache().headerLogoImage.getAttribute("src"); - } - - public HasSearchResults searchFor(String searchString) - { - elementCache().searchBox.set(searchString); - elementCache().searchBox.getComponentElement().sendKeys(Keys.ENTER); - return null; - } - - public FindByIdsDialog findBySampleIds() - { - elementCache().searchMenu.doMenuAction("Find Samples by ID"); - return new FindByIdsDialog(getDriver()); - } - - public FindByIdsDialog findByBarcodes() - { - elementCache().searchMenu.doMenuAction("Find Samples by Barcode"); - return new FindByIdsDialog(getDriver()); - } - - public SampleFinder goToSampleFinder() - { - elementCache().searchMenu.doMenuAction("Sample Finder"); - return new SampleFinder(getDriver()); - } - - public String getDisplayedProjectName() - { - return elementCache().projectNameDisplay.getText(); - } - - public String getUserAvatarSource() - { - return elementCache().userIcon.getAttribute("src"); - } - - /** - * Get the {@link ServerNotificationMenu} on the menu bar. - * - * @return A {@link ServerNotificationMenu} - */ - public ServerNotificationMenu getNotificationMenu() - { - return elementCache().notificationsMenu; - } - - public ProductMenu getProductMenu() - { - return elementCache().productMenu; - } - - public abstract UserMenu getUserMenu(); - - @Override - public WebElement getComponentElement() - { - return _navBarElement; - } - - @Override - public WebDriver getDriver() - { - return _driver; - } - - @Override - protected abstract ElementCache newElementCache(); - - protected abstract class ElementCache extends Component.ElementCache - { - public WebElement headerLogo = Locator.tagWithClass("a", "header-logo__link").refindWhenNeeded(this); - public WebElement headerLogoImage = Locator.tagWithClass("img", "header-logo__image").refindWhenNeeded(this); - public WebElement userMenuButton = Locator.tagWithId("a", "user-menu-dropdown").refindWhenNeeded(this); - public WebElement userIcon = Locator.tagWithAttribute("img", "alt", "User Avatar").refindWhenNeeded(this); - public WebElement projectNameDisplay = Locator.tagWithClass("span", "project-name").refindWhenNeeded(this); - public Input searchBox = Input.Input(Locator.tagWithClass("input", "navbar__search-input"), getDriver()).refindWhenNeeded(this); - public MultiMenu searchMenu = new MultiMenu.MultiMenuFinder(getDriver()).withButtonClass("navbar__find-and-search-button").refindWhenNeeded(this); - public final ProductMenu productMenu = ProductMenu.finder(getDriver()).timeout(1000).refindWhenNeeded(this); - public final ServerNotificationMenu notificationsMenu = ServerNotificationMenu.finder(getDriver()).timeout(1000).refindWhenNeeded(this); - } -} diff --git a/src/org/labkey/test/util/ui/BannerHelper.java b/src/org/labkey/test/util/ui/BannerHelper.java new file mode 100644 index 0000000000..3fc0690d44 --- /dev/null +++ b/src/org/labkey/test/util/ui/BannerHelper.java @@ -0,0 +1,372 @@ +/* + * Copyright (c) 2021-2026 LabKey Corporation. All rights reserved. No portion of this work may be reproduced + * in any form or by any electronic or mechanical means without written permission from LabKey Corporation. + */ +package org.labkey.test.util.ui; + +import org.junit.Assert; +import org.labkey.test.BootstrapLocators; +import org.labkey.test.Locator; +import org.labkey.test.WebDriverWrapper; +import org.openqa.selenium.NoSuchElementException; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.WrapsDriver; +import org.openqa.selenium.support.ui.WebDriverWait; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +public class BannerHelper +{ + // Use a supplier to allow instantiation in test constructor. + private final WrapsDriver _driver; + + public BannerHelper(WrapsDriver wrapsDriver) + { + _driver = wrapsDriver; + } + + private WebDriver getDriver() + { + return _driver.getWrappedDriver(); + } + + /** + * Wait one second for a error banner to show up. + * @return String of the banner text empty if not present. + */ + public String waitForError() + { + return waitForError(1_000); + } + + /** + * Wait a given amount of time for a error banner to show up. + * @param waitTime Amount of time to wait in milliseconds. + * @return String of the banner text empty if not present. + */ + public String waitForError(int waitTime) + { + return waitForBanner(BootstrapLocators.errorBanner, waitTime); + } + + /** + * Get a reference to the error banner element. + * @return WebElement referencing the error banner. + */ + public WebElement errorBanner() + { + return BootstrapLocators.errorBanner.findOptionalElement(getDriver()).orElse(null); + } + + /** + * Get the text of the error banner. + * @return Text of the banner. + */ + public String errorBannerText() + { + try + { + return errorBanner().getText(); + } + catch(NullPointerException npe) + { + return ""; + } + } + + /** + * Wait one second for a warning banner to show up. + * @return String of the banner text empty if not present. + */ + public String waitForWarning() + { + return waitForWarning(1_000); + } + + /** + * Wait a given amount of time for a warning banner to show up. + * @param waitTime Amount of time to wait in milliseconds. + * @return String of the banner text empty if not present. + */ + public String waitForWarning(int waitTime) + { + return waitForBanner(BootstrapLocators.warningBanner, waitTime); + } + + /** + * Get a reference to the warning banner element. + * @return WebElement referencing the warning banner. + */ + public WebElement warningBanner() + { + return BootstrapLocators.warningBanner.findElementOrNull(getDriver()); + } + + /** + * Get the text of the warning banner. + * @return Text of the banner. + */ + public String warningBannerText() + { + try + { + return warningBanner().getText(); + } + catch(NullPointerException npe) + { + return ""; + } + } + + /** + * Wait one second for a info banner to show up. + * @return String of the banner text empty if not present. + */ + public String waitForInfo() + { + return waitForInfo(1_000); + } + + /** + * Wait a given amount of time for a info banner to show up. + * @param waitTime Amount of time to wait in milliseconds. + * @return String of the banner text empty if not present. + */ + public String waitForInfo(int waitTime) + { + return waitForBanner(BootstrapLocators.infoBanner, waitTime); + } + + /** + * Get a reference to the info banner element. + * @return WebElement referencing the info banner. + */ + public WebElement infoBanner() + { + return BootstrapLocators.infoBanner.existsIn(getDriver()) ? BootstrapLocators.infoBanner.findElement(getDriver()) : null; + } + + /** + * Get the text of the info banner. + * @return Text of the banner. + */ + public String infoBannerText() + { + try + { + return infoBanner().getText(); + } + catch(NullPointerException npe) + { + return ""; + } + } + + /** + * Wait one second for a success banner to show up. + * @return String of the banner text empty if not present. + */ + public String waitForSuccess() + { + return waitForSuccess(10_000); + } + + /** + * Wait a given amount of time for a success banner to show up. + * @param waitTime Amount of time to wait in milliseconds. + * @return String of the banner text empty if not present. + */ + public String waitForSuccess(int waitTime) + { + return waitForBanner(BootstrapLocators.successBanner, waitTime); + } + + /** + * Get a reference to the success banner element. + * @return WebElement referencing the success banner. + */ + public WebElement successBanner() + { + return BootstrapLocators.successBanner.existsIn(getDriver()) ? BootstrapLocators.successBanner.findElement(getDriver()) : null; + } + + /** + * Get the text of the success banner. + * @return Text of the banner. + */ + public String successBannerText() + { + try + { + return successBanner().getText(); + } + catch (NullPointerException npe) + { + return ""; + } + } + + /** + * Click the link or button that appears in the success banner message. + * + * @param text text of the link or button + */ + public void clickSuccessBannerLink(String text) + { + WebElement banner = successBanner(); + WebElement link = Locator.linkContainingText(text).findElementOrNull(banner); + if (link != null) + link.click(); + else + banner.findElement(Locator.buttonContainingText(text)).click(); + } + + private String waitForBanner(Locator bannerLocator, int waitTime) + { + try + { + return bannerLocator.waitForElement(getDriver(), waitTime).getText(); + } + catch (NoSuchElementException nsee) + { + return ""; + } + } + + /** + * Wait for any banner to appear regardless of it's type. + * If no banner appears an empty string is returned. + * @return Text of the banner if present empty string otherwise. + */ + public String waitForAnyBanner() + { + try + { + WebElement alert = Locator.waitForAnyElement(new WebDriverWait(getDriver(), Duration.ofSeconds(10)), + BootstrapLocators.errorBanner, BootstrapLocators.infoBanner, BootstrapLocators.warningBanner, BootstrapLocators.successBanner); + return alert.getText(); + } + catch(NoSuchElementException nsee) + { + return ""; + } + } + + /** + * Get a reference to any banner element that might be there. + * @return WebElement referencing the success banner. Null if nothing there. + */ + public WebElement anyBanner() + { + return Locator.findAnyElementOrNull(getDriver(), + BootstrapLocators.errorBanner, BootstrapLocators.infoBanner, BootstrapLocators.warningBanner, BootstrapLocators.successBanner); + } + + /** + * Get the text of the banner regardless of the banner type. + * If no banner is present empty string is returned. + * + * @return Text from any banner present, empty string if none present. + */ + public String anyBannerText() + { + WebElement alert = Locator.findAnyElementOrNull(getDriver(), + BootstrapLocators.errorBanner, BootstrapLocators.infoBanner, BootstrapLocators.warningBanner, BootstrapLocators.successBanner); + if (alert != null) + return alert.getText(); + else + return ""; + } + + /** + * Get the text from all of the banners that are shown regardless of type. If there are no banners an empty + * collection is returned. + * + * @return List of strings containing the text from all of the banners present. + */ + public List allBannersTexts() + { + List bannersTexts = new ArrayList<>(); + + List alerts = Locator.findElements(getDriver(), + BootstrapLocators.errorBanner, BootstrapLocators.infoBanner, BootstrapLocators.warningBanner, BootstrapLocators.successBanner); + alerts.forEach(a -> bannersTexts.add(a.getText())); + + return bannersTexts; + } + + /** + * Dismiss a banner message regardless of its type. + * If there are multiple banner messages this will delete the first one. + */ + public void dismissBannerMessage() + { + if(anyBanner() != null) + { + WebElement alert = Locator.tagWithClass("div" , "alert").findWhenNeeded(getDriver()); + Locator.xpath("//i[contains(@class, 'fa-times-circle')]").findElement(alert).click(); + WebDriverWrapper.waitFor(()->!alert.isDisplayed(), "Banner message was not dismissed.", 2_500); + } + } + + /** + * Dismisses all banner messages currently displayed on the page. + * + * This method attempts to remove all banner messages by repeatedly locating and clicking + * the close button of the banners. The process is repeated up to 25 times or until no banners + * are present. Each iteration includes a short delay to allow banners to be dismissed properly + * before the next attempt. + * + * If banners remain after 25 attempts, an assertion failure is triggered. + * + * Banners are identified using their respective CSS classes, and the click action is performed + * on the close icon within each banner. + * + * Throws: + * - AssertionError: If all banners are not dismissed after 25 attempts. + */ + public void dismissAllBannerMessages() + { + int tries = 0; + while ((anyBanner() != null) && (tries < 25)) + { + WebElement alert = Locator.byClass("alert").findWhenNeeded(getDriver()); + Locator.byClass("fa-times-circle").findElement(alert).click(); + WebDriverWrapper.sleep(250); + tries++; + } + + Assert.assertNull("Tried 25 times to delete all banners, but there are still banners present.", anyBanner()); + } + + public WebElement getReleaseBanner() + { + return Locator.tagWithClass("div" , "release-note-container").findElementOrNull(getDriver()); + } + + public boolean hasReleaseBanner() + { + return getReleaseBanner() != null; + } + + public void dismissReleaseNoteBanner() + { + WebElement releaseBanner = getReleaseBanner(); + if (releaseBanner != null) + Locator.xpath("//i[contains(@class, 'fa-times-circle')]").findElement(releaseBanner).click(); + } + + public void clickReleaseNoteLink() + { + getReleaseBanner().findElement(Locator.linkContainingText("See what's new.")).click(); + } + + public String getReleaseBannerMsg() + { + return Locator.tagWithClass("div", "notification-item").findElement(getReleaseBanner()).getText().trim(); + } + +} From 84ac9ecd81a8215624eb6704e3d8a209ee2ba7e5 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Fri, 17 Jul 2026 15:53:28 -0700 Subject: [PATCH 4/5] Fix some errors --- src/org/labkey/test/util/TestUser.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/org/labkey/test/util/TestUser.java b/src/org/labkey/test/util/TestUser.java index a762437703..40519f8c88 100644 --- a/src/org/labkey/test/util/TestUser.java +++ b/src/org/labkey/test/util/TestUser.java @@ -213,4 +213,10 @@ private WebDriverWrapper getWrapper() } return _test; } + + @Override + public String toString() + { + return getEmail(); + } } From e3d2321fc4797c7d9cab454689a22180ba1b5862 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Fri, 17 Jul 2026 16:44:34 -0700 Subject: [PATCH 5/5] Move BannerHelper back to lims --- src/org/labkey/test/util/ui/BannerHelper.java | 372 ------------------ 1 file changed, 372 deletions(-) delete mode 100644 src/org/labkey/test/util/ui/BannerHelper.java diff --git a/src/org/labkey/test/util/ui/BannerHelper.java b/src/org/labkey/test/util/ui/BannerHelper.java deleted file mode 100644 index 3fc0690d44..0000000000 --- a/src/org/labkey/test/util/ui/BannerHelper.java +++ /dev/null @@ -1,372 +0,0 @@ -/* - * Copyright (c) 2021-2026 LabKey Corporation. All rights reserved. No portion of this work may be reproduced - * in any form or by any electronic or mechanical means without written permission from LabKey Corporation. - */ -package org.labkey.test.util.ui; - -import org.junit.Assert; -import org.labkey.test.BootstrapLocators; -import org.labkey.test.Locator; -import org.labkey.test.WebDriverWrapper; -import org.openqa.selenium.NoSuchElementException; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.WrapsDriver; -import org.openqa.selenium.support.ui.WebDriverWait; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; - -public class BannerHelper -{ - // Use a supplier to allow instantiation in test constructor. - private final WrapsDriver _driver; - - public BannerHelper(WrapsDriver wrapsDriver) - { - _driver = wrapsDriver; - } - - private WebDriver getDriver() - { - return _driver.getWrappedDriver(); - } - - /** - * Wait one second for a error banner to show up. - * @return String of the banner text empty if not present. - */ - public String waitForError() - { - return waitForError(1_000); - } - - /** - * Wait a given amount of time for a error banner to show up. - * @param waitTime Amount of time to wait in milliseconds. - * @return String of the banner text empty if not present. - */ - public String waitForError(int waitTime) - { - return waitForBanner(BootstrapLocators.errorBanner, waitTime); - } - - /** - * Get a reference to the error banner element. - * @return WebElement referencing the error banner. - */ - public WebElement errorBanner() - { - return BootstrapLocators.errorBanner.findOptionalElement(getDriver()).orElse(null); - } - - /** - * Get the text of the error banner. - * @return Text of the banner. - */ - public String errorBannerText() - { - try - { - return errorBanner().getText(); - } - catch(NullPointerException npe) - { - return ""; - } - } - - /** - * Wait one second for a warning banner to show up. - * @return String of the banner text empty if not present. - */ - public String waitForWarning() - { - return waitForWarning(1_000); - } - - /** - * Wait a given amount of time for a warning banner to show up. - * @param waitTime Amount of time to wait in milliseconds. - * @return String of the banner text empty if not present. - */ - public String waitForWarning(int waitTime) - { - return waitForBanner(BootstrapLocators.warningBanner, waitTime); - } - - /** - * Get a reference to the warning banner element. - * @return WebElement referencing the warning banner. - */ - public WebElement warningBanner() - { - return BootstrapLocators.warningBanner.findElementOrNull(getDriver()); - } - - /** - * Get the text of the warning banner. - * @return Text of the banner. - */ - public String warningBannerText() - { - try - { - return warningBanner().getText(); - } - catch(NullPointerException npe) - { - return ""; - } - } - - /** - * Wait one second for a info banner to show up. - * @return String of the banner text empty if not present. - */ - public String waitForInfo() - { - return waitForInfo(1_000); - } - - /** - * Wait a given amount of time for a info banner to show up. - * @param waitTime Amount of time to wait in milliseconds. - * @return String of the banner text empty if not present. - */ - public String waitForInfo(int waitTime) - { - return waitForBanner(BootstrapLocators.infoBanner, waitTime); - } - - /** - * Get a reference to the info banner element. - * @return WebElement referencing the info banner. - */ - public WebElement infoBanner() - { - return BootstrapLocators.infoBanner.existsIn(getDriver()) ? BootstrapLocators.infoBanner.findElement(getDriver()) : null; - } - - /** - * Get the text of the info banner. - * @return Text of the banner. - */ - public String infoBannerText() - { - try - { - return infoBanner().getText(); - } - catch(NullPointerException npe) - { - return ""; - } - } - - /** - * Wait one second for a success banner to show up. - * @return String of the banner text empty if not present. - */ - public String waitForSuccess() - { - return waitForSuccess(10_000); - } - - /** - * Wait a given amount of time for a success banner to show up. - * @param waitTime Amount of time to wait in milliseconds. - * @return String of the banner text empty if not present. - */ - public String waitForSuccess(int waitTime) - { - return waitForBanner(BootstrapLocators.successBanner, waitTime); - } - - /** - * Get a reference to the success banner element. - * @return WebElement referencing the success banner. - */ - public WebElement successBanner() - { - return BootstrapLocators.successBanner.existsIn(getDriver()) ? BootstrapLocators.successBanner.findElement(getDriver()) : null; - } - - /** - * Get the text of the success banner. - * @return Text of the banner. - */ - public String successBannerText() - { - try - { - return successBanner().getText(); - } - catch (NullPointerException npe) - { - return ""; - } - } - - /** - * Click the link or button that appears in the success banner message. - * - * @param text text of the link or button - */ - public void clickSuccessBannerLink(String text) - { - WebElement banner = successBanner(); - WebElement link = Locator.linkContainingText(text).findElementOrNull(banner); - if (link != null) - link.click(); - else - banner.findElement(Locator.buttonContainingText(text)).click(); - } - - private String waitForBanner(Locator bannerLocator, int waitTime) - { - try - { - return bannerLocator.waitForElement(getDriver(), waitTime).getText(); - } - catch (NoSuchElementException nsee) - { - return ""; - } - } - - /** - * Wait for any banner to appear regardless of it's type. - * If no banner appears an empty string is returned. - * @return Text of the banner if present empty string otherwise. - */ - public String waitForAnyBanner() - { - try - { - WebElement alert = Locator.waitForAnyElement(new WebDriverWait(getDriver(), Duration.ofSeconds(10)), - BootstrapLocators.errorBanner, BootstrapLocators.infoBanner, BootstrapLocators.warningBanner, BootstrapLocators.successBanner); - return alert.getText(); - } - catch(NoSuchElementException nsee) - { - return ""; - } - } - - /** - * Get a reference to any banner element that might be there. - * @return WebElement referencing the success banner. Null if nothing there. - */ - public WebElement anyBanner() - { - return Locator.findAnyElementOrNull(getDriver(), - BootstrapLocators.errorBanner, BootstrapLocators.infoBanner, BootstrapLocators.warningBanner, BootstrapLocators.successBanner); - } - - /** - * Get the text of the banner regardless of the banner type. - * If no banner is present empty string is returned. - * - * @return Text from any banner present, empty string if none present. - */ - public String anyBannerText() - { - WebElement alert = Locator.findAnyElementOrNull(getDriver(), - BootstrapLocators.errorBanner, BootstrapLocators.infoBanner, BootstrapLocators.warningBanner, BootstrapLocators.successBanner); - if (alert != null) - return alert.getText(); - else - return ""; - } - - /** - * Get the text from all of the banners that are shown regardless of type. If there are no banners an empty - * collection is returned. - * - * @return List of strings containing the text from all of the banners present. - */ - public List allBannersTexts() - { - List bannersTexts = new ArrayList<>(); - - List alerts = Locator.findElements(getDriver(), - BootstrapLocators.errorBanner, BootstrapLocators.infoBanner, BootstrapLocators.warningBanner, BootstrapLocators.successBanner); - alerts.forEach(a -> bannersTexts.add(a.getText())); - - return bannersTexts; - } - - /** - * Dismiss a banner message regardless of its type. - * If there are multiple banner messages this will delete the first one. - */ - public void dismissBannerMessage() - { - if(anyBanner() != null) - { - WebElement alert = Locator.tagWithClass("div" , "alert").findWhenNeeded(getDriver()); - Locator.xpath("//i[contains(@class, 'fa-times-circle')]").findElement(alert).click(); - WebDriverWrapper.waitFor(()->!alert.isDisplayed(), "Banner message was not dismissed.", 2_500); - } - } - - /** - * Dismisses all banner messages currently displayed on the page. - * - * This method attempts to remove all banner messages by repeatedly locating and clicking - * the close button of the banners. The process is repeated up to 25 times or until no banners - * are present. Each iteration includes a short delay to allow banners to be dismissed properly - * before the next attempt. - * - * If banners remain after 25 attempts, an assertion failure is triggered. - * - * Banners are identified using their respective CSS classes, and the click action is performed - * on the close icon within each banner. - * - * Throws: - * - AssertionError: If all banners are not dismissed after 25 attempts. - */ - public void dismissAllBannerMessages() - { - int tries = 0; - while ((anyBanner() != null) && (tries < 25)) - { - WebElement alert = Locator.byClass("alert").findWhenNeeded(getDriver()); - Locator.byClass("fa-times-circle").findElement(alert).click(); - WebDriverWrapper.sleep(250); - tries++; - } - - Assert.assertNull("Tried 25 times to delete all banners, but there are still banners present.", anyBanner()); - } - - public WebElement getReleaseBanner() - { - return Locator.tagWithClass("div" , "release-note-container").findElementOrNull(getDriver()); - } - - public boolean hasReleaseBanner() - { - return getReleaseBanner() != null; - } - - public void dismissReleaseNoteBanner() - { - WebElement releaseBanner = getReleaseBanner(); - if (releaseBanner != null) - Locator.xpath("//i[contains(@class, 'fa-times-circle')]").findElement(releaseBanner).click(); - } - - public void clickReleaseNoteLink() - { - getReleaseBanner().findElement(Locator.linkContainingText("See what's new.")).click(); - } - - public String getReleaseBannerMsg() - { - return Locator.tagWithClass("div", "notification-item").findElement(getReleaseBanner()).getText().trim(); - } - -}