From 692d85d422b7c06e98a3f05734504347069e6802 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Tue, 9 Jun 2026 13:55:09 -0700 Subject: [PATCH] Page header components --- .../test/components/ui/AppPageHeader.java | 155 ++++++++++++++++++ .../test/components/ui/PageDetailHeader.java | 107 ++++++++++++ .../test/util/selenium/WebElementUtils.java | 31 ++++ 3 files changed, 293 insertions(+) create mode 100644 src/org/labkey/test/components/ui/AppPageHeader.java create mode 100644 src/org/labkey/test/components/ui/PageDetailHeader.java diff --git a/src/org/labkey/test/components/ui/AppPageHeader.java b/src/org/labkey/test/components/ui/AppPageHeader.java new file mode 100644 index 0000000000..83a059fac9 --- /dev/null +++ b/src/org/labkey/test/components/ui/AppPageHeader.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2018-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; + +import org.labkey.test.Locator; +import org.labkey.test.components.Component; +import org.labkey.test.components.WebDriverComponent; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; + +import static org.labkey.test.util.selenium.WebElementUtils.tryMapElement; + +/** + * Wraps component + */ +public class AppPageHeader extends WebDriverComponent +{ + private final WebElement _el; + private final WebDriver _driver; + + protected AppPageHeader(WebElement element, WebDriver driver) + { + _el = element; + _driver = driver; + } + + @Override + public WebElement getComponentElement() + { + return _el; + } + + @Override + public WebDriver getDriver() + { + return _driver; + } + + /** + * Gets the text of the page header. If there is no header returns an empty string. + * + * @return Text from the page title, empty string if element is not there. + */ + public String getTitle() + { + return tryMapElement(elementCache().title, WebElement::getText); + } + + /** + * Get the text of the subtitle of the page. If there is no subtitle, return an empty string. + * + * @return Text from the page subtitle, empty string if element is not there. + */ + public String getSubtitle() + { + return tryMapElement(elementCache().subtitle, WebElement::getText); + } + + /** + * Get the text of the description of the page. If there is no description, returns an empty string. + * + * @return Text from the page description, empty string if element is not there. + */ + public String getDescription() + { + return tryMapElement(elementCache().description, WebElement::getText); + } + + /** + * @throws UnsupportedOperationException Label color is not supported by AppPageHeader. + */ + public String getLabelColor() + { + throw new UnsupportedOperationException("Label color is not supported by AppPageHeader."); + } + + /** + * Get the source file of the page icon. If there is no icon returns an empty string. + * + * @return The 'src' attribute header icon, empty string if element is not there. + */ + public String getIconSource() + { + return tryMapElement(elementCache().icon, el -> el.getDomAttribute("src")); + } + + @Override + protected ElementCache newElementCache() + { + return new ElementCache(); + } + + protected class ElementCache extends Component.ElementCache + { + public final WebElement icon = getIconLocator().findWhenNeeded(this); + public final WebElement title = getTitleLocator().findWhenNeeded(this); + public final WebElement subtitle = getSubtitleLocator().findWhenNeeded(this); + public final WebElement description = getDescriptionLocator().findWhenNeeded(this); + + protected Locator.XPathLocator getIconLocator() + { + return Locator.byClass("app-page-header__icon"); + } + + protected Locator.XPathLocator getTitleLocator() + { + return Locator.byClass("app-page-header__title"); + } + + protected Locator.XPathLocator getSubtitleLocator() + { + return Locator.byClass("app-page-header__subtitle"); + } + + protected Locator.XPathLocator getDescriptionLocator() + { + return Locator.byClass("app-page-header__description"); + } + } + + public static class AppPageHeaderFinder extends WebDriverComponentFinder + { + private final Locator.XPathLocator _baseLocator = Locator.byClass("app-page-header"); + + public AppPageHeaderFinder(WebDriver driver) + { + super(driver); + } + + @Override + protected AppPageHeader construct(WebElement el, WebDriver driver) + { + return new AppPageHeader(el, driver); + } + + @Override + protected Locator locator() + { + return _baseLocator; + } + } +} diff --git a/src/org/labkey/test/components/ui/PageDetailHeader.java b/src/org/labkey/test/components/ui/PageDetailHeader.java new file mode 100644 index 0000000000..8c73c4ea62 --- /dev/null +++ b/src/org/labkey/test/components/ui/PageDetailHeader.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2018-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; + +import org.labkey.test.Locator; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; + +import static org.labkey.test.util.selenium.WebElementUtils.tryMapElement; + +/** + * Wraps component + */ +public class PageDetailHeader extends AppPageHeader +{ + protected PageDetailHeader(WebElement element, WebDriver driver) + { + super(element, driver); + } + + /** + * Get the rgb style value for the label color in the header + * + * @return A string such as "rgb(104, 204, 202)" as used in the "color-icon__circle-small" "i" element in the detail header or the empty string if element is not there. + */ + @Override + public String getLabelColor() + { + return tryMapElement(elementCache().colorIcon, el -> el.getCssValue("background-color")); + } + + @Override + protected ElementCache elementCache() + { + return (ElementCache) super.elementCache(); + } + + @Override + protected ElementCache newElementCache() + { + return new ElementCache(); + } + + protected class ElementCache extends AppPageHeader.ElementCache + { + @Override + protected Locator.XPathLocator getTitleLocator() + { + return Locator.byClass("detail__header--name"); + } + + @Override + protected Locator.XPathLocator getDescriptionLocator() + { + return Locator.byClass("detail__header--desc"); + } + + @Override + protected Locator.XPathLocator getSubtitleLocator() + { + return Locator.byClass("detail-subtitle"); + } + + @Override + protected Locator.XPathLocator getIconLocator() + { + return Locator.byClass("detail__header-icon"); + } + + final WebElement colorIcon = Locator.byClass("color-icon__circle-small").findWhenNeeded(subtitle); + } + + public static class PageDetailHeaderFinder extends WebDriverComponentFinder + { + private final Locator.XPathLocator _baseLocator = Locator.byClass("page-header"); + + public PageDetailHeaderFinder(WebDriver driver) + { + super(driver); + } + + @Override + protected PageDetailHeader construct(WebElement el, WebDriver driver) + { + return new PageDetailHeader(el, driver); + } + + @Override + protected Locator locator() + { + return _baseLocator; + } + } +} diff --git a/src/org/labkey/test/util/selenium/WebElementUtils.java b/src/org/labkey/test/util/selenium/WebElementUtils.java index a62edd0fbd..5aee1264cb 100644 --- a/src/org/labkey/test/util/selenium/WebElementUtils.java +++ b/src/org/labkey/test/util/selenium/WebElementUtils.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.function.Function; import static org.labkey.test.Locator.NBSP; @@ -137,4 +138,34 @@ public static boolean checkVisibility(WebElement element) return false; } } + + /** + * Convenience method to extract some attribute from a WebElement. Stale or missing elements will return a default. + * @param element WebElement to inspect; may be stale or backed by a missing DOM node + * @param mapper function applied to {@code element} to extract the desired value + * @param defaultValue value returned when {@code element} is stale or missing + * @return the mapped value, or {@code defaultValue} if the element is unavailable + * @param type of the extracted value + */ + public static T tryMapElement(WebElement element, Function mapper, T defaultValue) + { + try + { + return mapper.apply(element); + } + catch (NoSuchElementException | StaleElementReferenceException _) { } + + return defaultValue; + } + + /** + * Convenience overload of {@link #tryMapElement(WebElement, Function, Object)} that defaults to an empty string. + * @param element WebElement to inspect; may be stale or backed by a missing DOM node + * @param mapper function applied to {@code element} to extract a String value + * @return the mapped value, or {@code ""} if the element is unavailable + */ + public static String tryMapElement(WebElement element, Function mapper) + { + return tryMapElement(element, mapper, ""); + } }