From 252df22adcf773cb0b788639c9d2ee52003676f4 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Tue, 12 Aug 2025 16:03:24 -0700 Subject: [PATCH 01/27] Add domain name fuzzing to many tests --- src/org/labkey/test/util/DomainUtils.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/org/labkey/test/util/DomainUtils.java b/src/org/labkey/test/util/DomainUtils.java index 9cc20bce6b..9266495c22 100644 --- a/src/org/labkey/test/util/DomainUtils.java +++ b/src/org/labkey/test/util/DomainUtils.java @@ -95,11 +95,18 @@ public static void ensureDeleted(String containerPath, String schema, String tab } public enum DomainKind { - DataClass, - SampleSet, + Assay, + DataClass, // aka "Sources" + SampleSet, // aka "Sample Type" IntList, VarList, StudyDatasetDate, - StudyDatasetVisit + StudyDatasetVisit, + ; + + public String randomName(String namePart) + { + return TestDataGenerator.randomDomainName(namePart, this); + } } } From 8e8b93b8af8580722ff3da65615387fbc23eeda5 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Thu, 14 Aug 2025 17:36:42 -0700 Subject: [PATCH 02/27] Fuzzing fixes --- .../test/components/ui/grids/FieldReferenceManager.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/org/labkey/test/components/ui/grids/FieldReferenceManager.java b/src/org/labkey/test/components/ui/grids/FieldReferenceManager.java index cb2c4de07c..d70a851fd8 100644 --- a/src/org/labkey/test/components/ui/grids/FieldReferenceManager.java +++ b/src/org/labkey/test/components/ui/grids/FieldReferenceManager.java @@ -1,5 +1,6 @@ package org.labkey.test.components.ui.grids; +import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.labkey.test.params.FieldKey; @@ -132,7 +133,11 @@ else if (_fieldLabels.size() < _fieldReferences.size()) } } - return null; + String capitalized = StringUtils.capitalize(label); + if (capitalized.equals(label)) + return null; + else + return findColumnHeaderByLabel(capitalized); // Handle domain names that aren't capitalized } public static class FieldReference From 740149e68154f066249c6263c3aac465d4a24f24 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Fri, 15 Aug 2025 17:30:18 -0700 Subject: [PATCH 03/27] More fuzzing fixes --- src/org/labkey/test/AppLocators.java | 8 +++++ src/org/labkey/test/Locators.java | 2 ++ .../test/selenium/ReclickingWebElement.java | 6 ++++ .../test/tests/TextChoiceSampleTypeTest.java | 34 ++++++++++++++----- src/org/labkey/test/util/AuditLogHelper.java | 26 ++++++++++++++ 5 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 src/org/labkey/test/AppLocators.java diff --git a/src/org/labkey/test/AppLocators.java b/src/org/labkey/test/AppLocators.java new file mode 100644 index 0000000000..d61f6980e5 --- /dev/null +++ b/src/org/labkey/test/AppLocators.java @@ -0,0 +1,8 @@ +package org.labkey.test; + +public abstract class AppLocators +{ + private AppLocators() {} + + public static final Locator.XPathLocator detailHeaderName = Locator.tagWithClass("h2", "detail__header--name"); +} diff --git a/src/org/labkey/test/Locators.java b/src/org/labkey/test/Locators.java index bd688dfced..ec7ec8fdb4 100644 --- a/src/org/labkey/test/Locators.java +++ b/src/org/labkey/test/Locators.java @@ -17,6 +17,8 @@ public abstract class Locators { + private Locators() { } + public static final Locator documentRoot = Locator.css(":root"); public static final Locator.IdLocator folderMenu = Locator.id("folderBar"); public static final Locator.XPathLocator labkeyError = Locator.byClass("labkey-error"); diff --git a/src/org/labkey/test/selenium/ReclickingWebElement.java b/src/org/labkey/test/selenium/ReclickingWebElement.java index 65dc66e8e6..453f9dd5ec 100644 --- a/src/org/labkey/test/selenium/ReclickingWebElement.java +++ b/src/org/labkey/test/selenium/ReclickingWebElement.java @@ -15,6 +15,8 @@ */ package org.labkey.test.selenium; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Strings; import org.apache.commons.lang3.mutable.Mutable; import org.apache.commons.lang3.mutable.MutableObject; import org.jetbrains.annotations.NotNull; @@ -214,6 +216,10 @@ private void revealElement(WebElement el, String shortMessage) Locator.XPathLocator interceptingElLoc = parseInterceptingElementLoc(shortMessage); if (interceptingElLoc != null) { + if (Strings.CS.containsAny(interceptingElLoc.toString(), "popover", "tip")) + { + new Actions(getDriver()).moveToLocation(0,0).perform(); + } List interceptingElements = interceptingElLoc.findElements(getDriver()); TestLogger.debug("Found %s element(s) matching extracted locator: %s".formatted(interceptingElements.size(), shortMessage)); if (interceptingElements.size() == 1) diff --git a/src/org/labkey/test/tests/TextChoiceSampleTypeTest.java b/src/org/labkey/test/tests/TextChoiceSampleTypeTest.java index e14f10e49c..ff9ddaf7f5 100644 --- a/src/org/labkey/test/tests/TextChoiceSampleTypeTest.java +++ b/src/org/labkey/test/tests/TextChoiceSampleTypeTest.java @@ -450,12 +450,29 @@ public void testUpdatingAndDeletingValuesInSampleType() throws IOException, Comm updatePage.clickSave(); log("Validate that the expected rows after the update are in the log."); - String fieldOldValues = "Name=TextChoice_Field_1&Type=String&Scale=4000&PHI=Not%20PHI&DefaultScale=Linear&Required=false&Hidden=false&MvEnabled=false&Measure=false&Dimension=false" + - "&ShownInInsert=true&ShownInDetails=true&ShownInUpdate=true&ShownInLookupView=false&RecommendedVariable=false&ExcludedFromShifting=false&Scannable=false" + - "&DefaultValueType=Editable%20default&Validator=Text%20Choice%20Validator%2C%20%C3%85%5C%7C%C3%85%7CBB%7CCC%7CDD%7CE%20E%20E%7C%C2%83%C2%83%7CGG%7CH%2C%20Text%20Choice%20Validator"; - String fieldUpdateValues = "Name=TextChoice_Field_1&Type=String&Scale=4000&PHI=Not%20PHI&DefaultScale=Linear&Required=false&Hidden=false&MvEnabled=false&Measure=false&Dimension=false" + - "&ShownInInsert=true&ShownInDetails=true&ShownInUpdate=true&ShownInLookupView=false&RecommendedVariable=false&ExcludedFromShifting=false&Scannable=false" + - "&DefaultValueType=Editable%20default&Validator=Text%20Choice%20Validator%2C%20BB%7CCC%20and%20here%20is%20an%20update%7CE%20E%20E%7CGG%7CH%7C%C2%83%C2%83%20updated%7C%C3%85%5C%7C%C3%85%2C%20Text%20Choice%20Validator"; + String fieldSharedValues = AuditLogHelper.encodeValues( + "Name", textChoiceFieldName, + "Type", "String", + "Scale", "4000", + "PHI", "Not PHI", + "DefaultScale", "Linear", + "Required", "false", + "Hidden", "false", + "MvEnabled", "false", + "Measure", "false", + "Dimension", "false", + "ShownInInsert", "true", + "ShownInDetails", "true", + "ShownInUpdate", "true", + "ShownInLookupView", "false", + "RecommendedVariable", "false", + "ExcludedFromShifting", "false", + "Scannable", "false", + "DefaultValueType", "Editable default"); + String fieldOldValues = fieldSharedValues + "&" + AuditLogHelper.encodeValues( + "Validator", "Text Choice Validator, \u00C5\\|\u00C5|BB|CC|DD|E E E|\u0083\u0083|GG|H, Text Choice Validator"); + String fieldUpdateValues = fieldSharedValues + "&" + AuditLogHelper.encodeValues( + "Validator", "Text Choice Validator, BB|CC and here is an update|E E E|GG|H|\u0083\u0083 updated|\u00C5\\|\u00C5, Text Choice Validator"); AuditLogHelper.DetailedAuditEventRow fieldEvent = new AuditLogHelper.DetailedAuditEventRow(null, textChoiceFieldName, "Modified", "The following property was updated: Validator", "", fieldOldValues, fieldUpdateValues, null); AuditLogHelper.DetailedAuditEventRow expectedDomainEvent = new AuditLogHelper.DetailedAuditEventRow(null, sampleTypeName, null, @@ -548,9 +565,8 @@ public void testUpdatingAndDeletingValuesInSampleType() throws IOException, Comm updatePage.clickSave(); log("Validate that the expected rows after the update are in the log."); - String fieldUpdateValues2 = "Name=TextChoice_Field_1&Type=String&Scale=4000&PHI=Not%20PHI&DefaultScale=Linear&Required=false&Hidden=false&MvEnabled=false&Measure=false" + - "&Dimension=false&ShownInInsert=true&ShownInDetails=true&ShownInUpdate=true&ShownInLookupView=false&RecommendedVariable=false&ExcludedFromShifting=false" + - "&Scannable=false&DefaultValueType=Editable%20default&Validator=Text%20Choice%20Validator%2C%20BB%7CCC%20and%20here%20is%20an%20update%7CE%20E%20E%7CGG%7CH%20no%20change%7C%C2%83%C2%83%20updated%7C%C3%85%5C%7C%C3%85%2C%20Text%20Choice%20Validator"; + String fieldUpdateValues2 = fieldSharedValues + "&" + AuditLogHelper.encodeValues( + "Validator", "Text Choice Validator, BB|CC and here is an update|E E E|GG|H no change|\u0083\u0083 updated|\u00C5\\|\u00C5, Text Choice Validator"); fieldEvent = new AuditLogHelper.DetailedAuditEventRow(null, textChoiceFieldName, "Modified", "The following property was updated: Validator", "", fieldUpdateValues, fieldUpdateValues2, null); pass = _auditLogHelper.validateLastDomainAuditEvents(sampleTypeName, getProjectName(), expectedDomainEvent, Map.of(textChoiceFieldName, fieldEvent)); diff --git a/src/org/labkey/test/util/AuditLogHelper.java b/src/org/labkey/test/util/AuditLogHelper.java index a91b5c377f..694d024055 100644 --- a/src/org/labkey/test/util/AuditLogHelper.java +++ b/src/org/labkey/test/util/AuditLogHelper.java @@ -453,6 +453,32 @@ public String getLogString() } } + /** + * URL-encode fields and values for {@link DetailedAuditEventRow#newValues} or {@link DetailedAuditEventRow#oldValues} + * @param pairs alternating field names and their associated values + * @return URL-encoded String for use in DetailedAuditEventRow + */ + public static String encodeValues(String... pairs) + { + if (pairs.length % 2 != 0) + { + throw new IllegalArgumentException("pairs length must be even"); + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < pairs.length; i = i + 2) + { + if (!sb.isEmpty()) + sb.append("&"); + sb.append(EscapeUtil.encode(pairs[i])).append("=").append(EscapeUtil.encode(pairs[i + 1])); + } + return sb.toString(); + } + + public static String formatDataChange(String name, String oldValue, String newValue) + { + return name + ": " + oldValue + " > " + newValue; + } + public @NotNull Map getDomainPropertyEvents(String domainName, Integer domainEventId) { if (domainEventId == null) From 62864118d4891f9f649ed11864bfd07b76fd6bdb Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Mon, 18 Aug 2025 17:02:41 -0700 Subject: [PATCH 04/27] Fix URL encoding of pipeline job notifications and fix more fuzzing failures --- .../test/selenium/ReclickingWebElement.java | 2 +- src/org/labkey/test/util/AuditLogHelper.java | 17 +++++++++- src/org/labkey/test/util/ExcelHelper.java | 31 +++++++++++++++++-- src/org/labkey/test/util/URLBuilder.java | 9 +++++- 4 files changed, 54 insertions(+), 5 deletions(-) diff --git a/src/org/labkey/test/selenium/ReclickingWebElement.java b/src/org/labkey/test/selenium/ReclickingWebElement.java index 453f9dd5ec..2bcda34d9b 100644 --- a/src/org/labkey/test/selenium/ReclickingWebElement.java +++ b/src/org/labkey/test/selenium/ReclickingWebElement.java @@ -216,7 +216,7 @@ private void revealElement(WebElement el, String shortMessage) Locator.XPathLocator interceptingElLoc = parseInterceptingElementLoc(shortMessage); if (interceptingElLoc != null) { - if (Strings.CS.containsAny(interceptingElLoc.toString(), "popover", "tip")) + if (Strings.CS.containsAny(interceptingElLoc.toString(), "popover", "ws-pre-wrap", "tip")) { new Actions(getDriver()).moveToLocation(0,0).perform(); } diff --git a/src/org/labkey/test/util/AuditLogHelper.java b/src/org/labkey/test/util/AuditLogHelper.java index 694d024055..14b6601d54 100644 --- a/src/org/labkey/test/util/AuditLogHelper.java +++ b/src/org/labkey/test/util/AuditLogHelper.java @@ -469,11 +469,26 @@ public static String encodeValues(String... pairs) { if (!sb.isEmpty()) sb.append("&"); - sb.append(EscapeUtil.encode(pairs[i])).append("=").append(EscapeUtil.encode(pairs[i + 1])); + sb.append(encodeValue(pairs[i])).append("=").append(encodeValue(pairs[i + 1])); } return sb.toString(); } + /** + * Perform selective URL encoding for use {@link DetailedAuditEventRow#newValues} or + * {@link DetailedAuditEventRow#oldValues} + * @param value raw key or value + * @return Partially URL-encoded value + */ + private static String encodeValue(String value) + { + return value + .replace("%", "%25") // '%' needs to be first + .replace(" ", "%20") + .replace("&", "%26") + .replace("=", "%3D"); + } + public static String formatDataChange(String name, String oldValue, String newValue) { return name + ": " + oldValue + " > " + newValue; diff --git a/src/org/labkey/test/util/ExcelHelper.java b/src/org/labkey/test/util/ExcelHelper.java index 16b0c5c1ab..143fbf9995 100644 --- a/src/org/labkey/test/util/ExcelHelper.java +++ b/src/org/labkey/test/util/ExcelHelper.java @@ -15,6 +15,7 @@ */ package org.labkey.test.util; +import org.apache.commons.lang3.StringUtils; import org.apache.poi.ss.format.CellGeneralFormatter; import org.apache.poi.ss.formula.FormulaParseException; import org.apache.poi.ss.usermodel.Cell; @@ -28,6 +29,7 @@ import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.apache.poi.ss.util.WorkbookUtil; import org.jetbrains.annotations.Nullable; import java.io.File; @@ -35,6 +37,7 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -266,7 +269,7 @@ public static Map>> loadData(File file) { try (Workbook workbook = ExcelHelper.create(file)) { - Map>> allData = new LinkedHashMap<>(); + Map>> allData = new WorsheetMap(); for (int s = 0; s < workbook.getNumberOfSheets(); s++) { @@ -291,11 +294,35 @@ public static Map>> loadData(File file) allData.put(sheet.getSheetName(), rowMaps); } - return allData; + return Collections.unmodifiableMap(allData); } catch (IOException e) { throw new RuntimeException(e); } } + + /** + * Truncate and make safe a proposed Excel sheet name + * @see org.labkey.api.data.ExcelWriter#cleanSheetName(String) + */ + public static String sheetName(String sheetName) + { + return WorkbookUtil.createSafeSheetName(StringUtils.truncate(sheetName, 31), '_'); + } } + +class WorsheetMap extends LinkedHashMap>> +{ + @Override + public List> get(Object key) + { + return super.get(ExcelHelper.sheetName((String) key)); + } + + @Override + public List> getOrDefault(Object key, List> defaultValue) + { + return super.getOrDefault(ExcelHelper.sheetName((String) key), defaultValue); + } +} \ No newline at end of file diff --git a/src/org/labkey/test/util/URLBuilder.java b/src/org/labkey/test/util/URLBuilder.java index 03a976c626..5a6785976f 100644 --- a/src/org/labkey/test/util/URLBuilder.java +++ b/src/org/labkey/test/util/URLBuilder.java @@ -106,11 +106,18 @@ public URLBuilder setQuery(Map query) public URLBuilder setAppResourcePath(Object... pathParts) { List encodedParts = Arrays.stream(pathParts).map(Objects::requireNonNull).map(String::valueOf) - .map(s -> EscapeUtil.encode(s).replace("+", " ")).collect(Collectors.toList()); + .map(this::encodeAppResourcePathPart).collect(Collectors.toList()); setFragment("/" + String.join("/", encodedParts)); return this; } + private String encodeAppResourcePathPart(String pathPart) + { + return EscapeUtil.encode(pathPart) + .replace("%28", "(") + .replace("%29", ")"); + } + /** * Append a fragment to the URL.
* e.g. setResourcePath("marker") will append "#marker" to the built URL From b36375e02849a1b55ad2343c6e02896dc1e4d649 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Tue, 19 Aug 2025 16:59:45 -0700 Subject: [PATCH 05/27] More test fixes --- src/org/labkey/test/WebDriverWrapper.java | 6 +++++ .../components/ui/grids/EditableGrid.java | 25 +++++++++++++++++++ .../test/selenium/ReclickingWebElement.java | 12 +++++---- src/org/labkey/test/util/AuditLogHelper.java | 1 + 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/org/labkey/test/WebDriverWrapper.java b/src/org/labkey/test/WebDriverWrapper.java index 2fec90445a..29de0211d1 100644 --- a/src/org/labkey/test/WebDriverWrapper.java +++ b/src/org/labkey/test/WebDriverWrapper.java @@ -1127,6 +1127,12 @@ public String call() } } + @Contract(pure = true) + public WebDriverWait quickWait() + { + return new WebDriverWait(getDriver(), Duration.ofSeconds(1)); + } + @Contract(pure = true) public WebDriverWait shortWait() { diff --git a/src/org/labkey/test/components/ui/grids/EditableGrid.java b/src/org/labkey/test/components/ui/grids/EditableGrid.java index c0a0d5442a..b9448bfc98 100644 --- a/src/org/labkey/test/components/ui/grids/EditableGrid.java +++ b/src/org/labkey/test/components/ui/grids/EditableGrid.java @@ -769,6 +769,31 @@ public List getFilteredDropdownListForCell(int row, CharSequence columnI return lookupSelect.getOptions(); } + /** + * Values will be quoted appropriately for pasting into editable grid lookups. + */ + public static String getPastableColumn(List values) + { + List valueList = new ArrayList<>(); + for (Object value : values) + { + String strVal = CSVFormat.DEFAULT.format(value); // Just quote commas + valueList.add(strVal); + } + return String.join("\n", valueList); + } + + /** + * Pastes text to a single column of the grid. + * @param columnIdentifier fieldKey, name, or label of column + * @param pasteValues list of values to paste + * @return A Reference to this editableGrid object. + */ + public EditableGrid pasteColumn(CharSequence columnIdentifier, List pasteValues) + { + return pasteFromCell(0, columnIdentifier, getPastable(pasteValues), false); + } + /** * Pastes delimited text to the grid, via a single target. The component is clever enough to target * text into cells based on text delimiters; thus we can paste a square of data into the grid. diff --git a/src/org/labkey/test/selenium/ReclickingWebElement.java b/src/org/labkey/test/selenium/ReclickingWebElement.java index 2bcda34d9b..cf0e34f256 100644 --- a/src/org/labkey/test/selenium/ReclickingWebElement.java +++ b/src/org/labkey/test/selenium/ReclickingWebElement.java @@ -15,7 +15,6 @@ */ package org.labkey.test.selenium; -import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Strings; import org.apache.commons.lang3.mutable.Mutable; import org.apache.commons.lang3.mutable.MutableObject; @@ -51,7 +50,7 @@ public class ReclickingWebElement extends WebElementDecorator { // Extract the element info from ElementClickInterceptedException message. - private static final Pattern interceptingElPattern = Pattern.compile("Element .* is not clickable .*<(?[a-zA-Z]+) (?.+)> obscures it"); + private static final Pattern interceptingElPattern = Pattern.compile("Element .* is not clickable .*<(?[a-zA-Z0-9]+) (?.+)> obscures it"); private static final Pattern elAttributePattern = Pattern.compile("(?[a-zA-Z-]+)=\"(?[^\"]+)\""); public ReclickingWebElement(@NotNull WebElement decoratedElement) @@ -216,12 +215,15 @@ private void revealElement(WebElement el, String shortMessage) Locator.XPathLocator interceptingElLoc = parseInterceptingElementLoc(shortMessage); if (interceptingElLoc != null) { - if (Strings.CS.containsAny(interceptingElLoc.toString(), "popover", "ws-pre-wrap", "tip")) + List interceptingElements = interceptingElLoc.findElements(getDriver()); + TestLogger.debug("Found %s element(s) matching extracted locator: %s".formatted(interceptingElements.size(), shortMessage)); + + if (Strings.CI.containsAny(interceptingElLoc.toString(), "popover", "ws-pre-wrap", "tip")) { + // Move mouse to corner to dismiss tooltips new Actions(getDriver()).moveToLocation(0,0).perform(); } - List interceptingElements = interceptingElLoc.findElements(getDriver()); - TestLogger.debug("Found %s element(s) matching extracted locator: %s".formatted(interceptingElements.size(), shortMessage)); + if (interceptingElements.size() == 1) { //noinspection ResultOfMethodCallIgnored diff --git a/src/org/labkey/test/util/AuditLogHelper.java b/src/org/labkey/test/util/AuditLogHelper.java index 14b6601d54..080f0494f9 100644 --- a/src/org/labkey/test/util/AuditLogHelper.java +++ b/src/org/labkey/test/util/AuditLogHelper.java @@ -486,6 +486,7 @@ private static String encodeValue(String value) .replace("%", "%25") // '%' needs to be first .replace(" ", "%20") .replace("&", "%26") + .replace("/", "%2F") .replace("=", "%3D"); } From 28b5c62a8dbc9d42045e219a66a2745845c37519 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Tue, 19 Aug 2025 17:17:22 -0700 Subject: [PATCH 06/27] Fix build --- src/org/labkey/test/components/ui/grids/EditableGrid.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/labkey/test/components/ui/grids/EditableGrid.java b/src/org/labkey/test/components/ui/grids/EditableGrid.java index b9448bfc98..b700412119 100644 --- a/src/org/labkey/test/components/ui/grids/EditableGrid.java +++ b/src/org/labkey/test/components/ui/grids/EditableGrid.java @@ -791,7 +791,7 @@ public static String getPastableColumn(List values) */ public EditableGrid pasteColumn(CharSequence columnIdentifier, List pasteValues) { - return pasteFromCell(0, columnIdentifier, getPastable(pasteValues), false); + return pasteFromCell(0, columnIdentifier, getPastableColumn(pasteValues), false); } /** From 3b6dd5af15c27754d3e7113c453e6091bca46c17 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Tue, 19 Aug 2025 17:21:26 -0700 Subject: [PATCH 07/27] Minor fix --- src/org/labkey/test/components/ui/grids/EditableGrid.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/org/labkey/test/components/ui/grids/EditableGrid.java b/src/org/labkey/test/components/ui/grids/EditableGrid.java index b700412119..2e7dba6535 100644 --- a/src/org/labkey/test/components/ui/grids/EditableGrid.java +++ b/src/org/labkey/test/components/ui/grids/EditableGrid.java @@ -791,6 +791,8 @@ public static String getPastableColumn(List values) */ public EditableGrid pasteColumn(CharSequence columnIdentifier, List pasteValues) { + if (pasteValues.isEmpty()) + throw new IllegalArgumentException("No paste values provided"); return pasteFromCell(0, columnIdentifier, getPastableColumn(pasteValues), false); } From debaf2adf0dff167129bcf83afd7d31bf35b6ff0 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Tue, 19 Aug 2025 17:37:42 -0700 Subject: [PATCH 08/27] Pager fix --- src/org/labkey/test/components/ui/Pager.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/org/labkey/test/components/ui/Pager.java b/src/org/labkey/test/components/ui/Pager.java index f364bed03a..6d87817239 100644 --- a/src/org/labkey/test/components/ui/Pager.java +++ b/src/org/labkey/test/components/ui/Pager.java @@ -93,8 +93,7 @@ private int pageSize(String pageSize) // only works on GridPanel } else { - // Tooltip sometimes blocks button. Click active option to dismiss menu. - activeLi.click(); + elementCache().jumpToDropdown.collapse(); } return initialSize; From a355ea717eb8735f76bff03351b3aab49136d755 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Wed, 20 Aug 2025 12:13:25 -0700 Subject: [PATCH 09/27] Better fix for encoding --- src/org/labkey/test/util/AuditLogHelper.java | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/org/labkey/test/util/AuditLogHelper.java b/src/org/labkey/test/util/AuditLogHelper.java index 080f0494f9..0517ab041f 100644 --- a/src/org/labkey/test/util/AuditLogHelper.java +++ b/src/org/labkey/test/util/AuditLogHelper.java @@ -482,12 +482,19 @@ public static String encodeValues(String... pairs) */ private static String encodeValue(String value) { - return value - .replace("%", "%25") // '%' needs to be first - .replace(" ", "%20") - .replace("&", "%26") - .replace("/", "%2F") - .replace("=", "%3D"); + return EscapeUtil.encode(value) + .replace("%28", "(") + .replace("%29", ")"); +// return value +// .replace("%", "%25") // '%' needs to be first +// .replace(" ", "%20") +// .replace("$", "%24") +// .replace("&", "%26") +// .replace("'", "%27") +// .replace("/", "%2F") +// .replace("=", "%3D") +// .replace("{", "%7B") +// .replace("}", "%7D"); } public static String formatDataChange(String name, String oldValue, String newValue) From 7e4e8c624a1c72633ea56d2b756950062a6ae63b Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Wed, 20 Aug 2025 13:35:46 -0700 Subject: [PATCH 10/27] Remove selenium.By usages --- .../ui/notifications/ServerNotificationItem.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/org/labkey/test/components/ui/notifications/ServerNotificationItem.java b/src/org/labkey/test/components/ui/notifications/ServerNotificationItem.java index 16d0dec587..b668106f8e 100644 --- a/src/org/labkey/test/components/ui/notifications/ServerNotificationItem.java +++ b/src/org/labkey/test/components/ui/notifications/ServerNotificationItem.java @@ -7,6 +7,7 @@ import org.labkey.test.util.TestLogger; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.ExpectedConditions; /** *

@@ -73,14 +74,7 @@ else if (status.contains("is-complete")) */ public String getMessage() { - if(elementCache().message.isDisplayed()) - { - return elementCache().message.getText(); - } - else - { - return ""; - } + return getWrapper().shortWait().until(ExpectedConditions.visibilityOf(elementCache().message)).getText(); } /** From 805f62e723fcb9a4eeb79b8aae8f9ede6796f2fd Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Wed, 20 Aug 2025 14:10:58 -0700 Subject: [PATCH 11/27] Adjust wait --- .../ui/notifications/ServerNotificationItem.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/org/labkey/test/components/ui/notifications/ServerNotificationItem.java b/src/org/labkey/test/components/ui/notifications/ServerNotificationItem.java index b668106f8e..298446e9a2 100644 --- a/src/org/labkey/test/components/ui/notifications/ServerNotificationItem.java +++ b/src/org/labkey/test/components/ui/notifications/ServerNotificationItem.java @@ -44,6 +44,12 @@ public WebElement getComponentElement() return componentElement; } + @Override + protected void waitForReady() + { + getWrapper().quickWait().until(ExpectedConditions.visibilityOf(getComponentElement())); + } + /** * Get the status as indicated by the icon. * @@ -74,7 +80,14 @@ else if (status.contains("is-complete")) */ public String getMessage() { - return getWrapper().shortWait().until(ExpectedConditions.visibilityOf(elementCache().message)).getText(); + if (elementCache().message.isDisplayed()) + { + return elementCache().message.getText(); + } + else + { + return ""; + } } /** From 430b04cd53496be304c6b1de40deabe2c2441646 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Wed, 20 Aug 2025 14:36:09 -0700 Subject: [PATCH 12/27] Comment --- src/org/labkey/test/util/URLBuilder.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/org/labkey/test/util/URLBuilder.java b/src/org/labkey/test/util/URLBuilder.java index 5a6785976f..4f49b5485b 100644 --- a/src/org/labkey/test/util/URLBuilder.java +++ b/src/org/labkey/test/util/URLBuilder.java @@ -114,6 +114,7 @@ public URLBuilder setAppResourcePath(Object... pathParts) private String encodeAppResourcePathPart(String pathPart) { return EscapeUtil.encode(pathPart) + // We generally don't encode parentheses in app resource paths .replace("%28", "(") .replace("%29", ")"); } From f037db3abf439b1905cd3372436c8817e8d49edb Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Wed, 20 Aug 2025 14:37:30 -0700 Subject: [PATCH 13/27] Fix EOF --- src/org/labkey/test/util/ExcelHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/labkey/test/util/ExcelHelper.java b/src/org/labkey/test/util/ExcelHelper.java index 143fbf9995..cb09a721ee 100644 --- a/src/org/labkey/test/util/ExcelHelper.java +++ b/src/org/labkey/test/util/ExcelHelper.java @@ -325,4 +325,4 @@ public List> getOrDefault(Object key, List Date: Wed, 20 Aug 2025 14:39:22 -0700 Subject: [PATCH 14/27] Fix comments --- src/org/labkey/test/util/AuditLogHelper.java | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/org/labkey/test/util/AuditLogHelper.java b/src/org/labkey/test/util/AuditLogHelper.java index 0517ab041f..8a5b3d8fc5 100644 --- a/src/org/labkey/test/util/AuditLogHelper.java +++ b/src/org/labkey/test/util/AuditLogHelper.java @@ -483,18 +483,9 @@ public static String encodeValues(String... pairs) private static String encodeValue(String value) { return EscapeUtil.encode(value) + // Parentheses aren't encoded .replace("%28", "(") .replace("%29", ")"); -// return value -// .replace("%", "%25") // '%' needs to be first -// .replace(" ", "%20") -// .replace("$", "%24") -// .replace("&", "%26") -// .replace("'", "%27") -// .replace("/", "%2F") -// .replace("=", "%3D") -// .replace("{", "%7B") -// .replace("}", "%7D"); } public static String formatDataChange(String name, String oldValue, String newValue) From e3e1e89b9cd6815a7068f33a60cd1642cb25711f Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Thu, 21 Aug 2025 12:56:27 -0700 Subject: [PATCH 15/27] TestDataUtils enhancements --- .../labkey/test/util/TestDataGenerator.java | 74 +------------ .../labkey/test/util/data/RecordIterator.java | 100 ++++++++++++++++++ .../labkey/test/util/data/TestDataUtils.java | 5 + 3 files changed, 108 insertions(+), 71 deletions(-) create mode 100644 src/org/labkey/test/util/data/RecordIterator.java diff --git a/src/org/labkey/test/util/TestDataGenerator.java b/src/org/labkey/test/util/TestDataGenerator.java index 27f0f41cfb..06fba994e2 100644 --- a/src/org/labkey/test/util/TestDataGenerator.java +++ b/src/org/labkey/test/util/TestDataGenerator.java @@ -40,6 +40,7 @@ import org.labkey.test.WebTestHelper; import org.labkey.test.params.FieldDefinition; import org.labkey.test.util.data.ColumnNameMapper; +import org.labkey.test.util.data.RecordIterator; import org.labkey.test.util.data.TestDataUtils; import org.labkey.test.util.query.QueryApiHelper; @@ -55,7 +56,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; import java.util.concurrent.ThreadLocalRandom; @@ -770,7 +770,7 @@ public String getDataAsTsv() */ public File writeData(String fileName) { - return writeData(fileName, new FileRowIterator(getFieldsForFile(), _rows)); + return writeData(fileName, new RecordIterator(getFieldsForFile(), _rows)); } /** @@ -781,7 +781,7 @@ public File writeData(String fileName) */ public File writeData(String fileName, int numRows) { - return writeData(fileName, new FileRowIterator(getFieldsForFile(), this::generateRow, numRows)); + return writeData(fileName, new RecordIterator(getFieldsForFile(), this::generateRow, numRows)); } /** @@ -981,71 +981,3 @@ public static boolean doesDomainExists(final String containerPath, final String } } - -class FileRowIterator implements Iterator> -{ - private final List headers; - private final Iterator> rows; - - private boolean firstRow = true; - - public FileRowIterator(@NotNull List headers, @NotNull Iterator> rows) - { - this.headers = Objects.requireNonNull(headers); - this.rows = Objects.requireNonNull(rows); - } - - public FileRowIterator(@NotNull List headers, @NotNull Supplier> rowSupplier, final int rowCount) - { - this(headers, new Iterator<>() - { - int count = 0; - - @Override - public boolean hasNext() - { - return count < rowCount; - } - - @Override - public Map next() - { - count++; - return rowSupplier.get(); - } - }); - } - - public FileRowIterator(@NotNull List headers, @NotNull List> rows) - { - this(headers, rows.iterator()); - } - - @Override - public boolean hasNext() - { - return firstRow || rows.hasNext(); - } - - @Override - public List next() - { - if (!hasNext()) - throw new NoSuchElementException(); - - if (firstRow) - { - firstRow = false; - return Collections.unmodifiableList(headers); - } - else - { - return rowMapToList(rows.next()); - } - } - - private List rowMapToList(Map row) - { - return headers.stream().map(h -> row.getOrDefault(h, "")).toList(); - } -} \ No newline at end of file diff --git a/src/org/labkey/test/util/data/RecordIterator.java b/src/org/labkey/test/util/data/RecordIterator.java new file mode 100644 index 0000000000..5dce259569 --- /dev/null +++ b/src/org/labkey/test/util/data/RecordIterator.java @@ -0,0 +1,100 @@ +package org.labkey.test.util.data; + +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.function.Supplier; + +public class RecordIterator implements Iterator> +{ + private final Collection headers; + private final Iterator> rowIterator; + + private boolean firstRow = true; + + public RecordIterator(@NotNull Collection headers, @NotNull Iterator> rowIterator) + { + this.headers = Objects.requireNonNull(headers); + Objects.requireNonNull(rowIterator); + this.rowIterator = new Iterator<>() + { + @Override + public boolean hasNext() + { + return rowIterator.hasNext(); + } + + @Override + public Map next() + { + return Collections.unmodifiableMap(rowIterator.next()); + } + }; + } + + public RecordIterator(@NotNull List headers, @NotNull Supplier> rowSupplier, final int rowCount) + { + this.headers = Objects.requireNonNull((Collection) headers); + this.rowIterator = new Iterator<>() + { + int count = 0; + + @Override + public boolean hasNext() + { + return count < rowCount; + } + + @Override + public Map next() + { + count++; + return rowSupplier.get(); + } + }; + } + + public RecordIterator(@NotNull Collection headers, @NotNull List> rows) + { + this(headers, rows.iterator()); + } + + public RecordIterator(@NotNull List> rows) + { + this(rows.get(0).keySet(), rows); + } + + @Override + public boolean hasNext() + { + return firstRow || rowIterator.hasNext(); + } + + @Override + public List next() + { + if (!hasNext()) + throw new NoSuchElementException(); + + if (firstRow) + { + firstRow = false; + return List.copyOf(headers); + } + else + { + return rowMapToList(rowIterator.next()); + } + } + + private List rowMapToList(Map row) + { + return headers.stream().map(h -> row.getOrDefault(h, "")).toList(); + } +} diff --git a/src/org/labkey/test/util/data/TestDataUtils.java b/src/org/labkey/test/util/data/TestDataUtils.java index 5b662c60d1..3eb899cb29 100644 --- a/src/org/labkey/test/util/data/TestDataUtils.java +++ b/src/org/labkey/test/util/data/TestDataUtils.java @@ -455,6 +455,11 @@ public static List> replaceMapKeys(List> rowMaps, return updatedRows; } + public static File writeDataToFile(String fileName, List> rowMaps) throws IOException + { + return writeRowsToFile(fileName, new RecordIterator(rowMaps)); + } + public static File writeRowsToFile(String fileName, List> rows) throws IOException { return writeRowsToFile(fileName, rows.iterator()); From df702508263366986d15df3bbb0c6b522a5a4fc4 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Thu, 21 Aug 2025 18:40:34 -0700 Subject: [PATCH 16/27] More LKB fuzzing --- src/org/labkey/test/util/DomainUtils.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/org/labkey/test/util/DomainUtils.java b/src/org/labkey/test/util/DomainUtils.java index 9266495c22..5b650d9ce6 100644 --- a/src/org/labkey/test/util/DomainUtils.java +++ b/src/org/labkey/test/util/DomainUtils.java @@ -100,6 +100,7 @@ public enum DomainKind { SampleSet, // aka "Sample Type" IntList, VarList, + Picklist, StudyDatasetDate, StudyDatasetVisit, ; From c6b9630aa1aa5a775a4bb16e79ff94949e984e60 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Fri, 22 Aug 2025 13:53:34 -0700 Subject: [PATCH 17/27] More fuzzing fixes --- src/org/labkey/test/util/TestDataGenerator.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/org/labkey/test/util/TestDataGenerator.java b/src/org/labkey/test/util/TestDataGenerator.java index 06fba994e2..bf4bd747f1 100644 --- a/src/org/labkey/test/util/TestDataGenerator.java +++ b/src/org/labkey/test/util/TestDataGenerator.java @@ -594,7 +594,8 @@ public static String randomDomainName(@Nullable String namePart, @Nullable Integ } // Multiple spaces in the UI are collapsed into a single space. If we need to test for handling of multiple spaces, we'll not use this generator - domainName = domainName.replaceAll("\\s+", " "); + // Capitalize to make it easier to match grid header labels + domainName = StringUtils.capitalize(domainName).replaceAll("\\s+", " "); TestLogger.log("Generated random domain name for domainKind " + _domainKind + ": " + domainName); return domainName; From 0b8bbcbb9d909a3872ec03e8cf03e789abd95cc5 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Mon, 25 Aug 2025 11:33:51 -0700 Subject: [PATCH 18/27] Share app resource path encoder --- src/org/labkey/test/util/EscapeUtil.java | 24 ++++++++++++++++++++++++ src/org/labkey/test/util/URLBuilder.java | 16 +--------------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/org/labkey/test/util/EscapeUtil.java b/src/org/labkey/test/util/EscapeUtil.java index ad88def79c..2968be0ca5 100644 --- a/src/org/labkey/test/util/EscapeUtil.java +++ b/src/org/labkey/test/util/EscapeUtil.java @@ -17,13 +17,16 @@ import org.apache.commons.text.StringEscapeUtils; import org.eclipse.jetty.util.URIUtil; +import org.jetbrains.annotations.NotNull; import org.labkey.test.params.FieldKey; import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -200,4 +203,25 @@ public static String escapeForNameExpression(String name) return nameExpressionNeedsEscaping.matcher(name).replaceAll("\\\\$1"); } + /** + * Generate an app path for use as a URL fragment. Parts will be encoded and joined.
+ * e.g. encodeAppResourcePath("samples", "my samples") will return "/samples/my%20samples"
+ * + * @param pathParts Parts to be combined into an app path. Most likely strings and/or Integers + * @return encoded resource path + */ + public static @NotNull String encodeAppResourcePath(Object... pathParts) + { + List encodedParts = Arrays.stream(pathParts).map(Objects::requireNonNull).map(String::valueOf) + .map(EscapeUtil::encodeAppResourcePathPart).collect(Collectors.toList()); + return "/" + String.join("/", encodedParts); + } + + private static String encodeAppResourcePathPart(String pathPart) + { + return encode(pathPart) + // We generally don't encode parentheses in app resource paths + .replace("%28", "(") + .replace("%29", ")"); + } } diff --git a/src/org/labkey/test/util/URLBuilder.java b/src/org/labkey/test/util/URLBuilder.java index 4f49b5485b..8b585fee9c 100644 --- a/src/org/labkey/test/util/URLBuilder.java +++ b/src/org/labkey/test/util/URLBuilder.java @@ -5,11 +5,8 @@ import org.labkey.test.WebTestHelper; import java.nio.charset.Charset; -import java.util.Arrays; -import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.stream.Collectors; import static org.labkey.test.WebTestHelper.getBaseURL; @@ -105,18 +102,7 @@ public URLBuilder setQuery(Map query) */ public URLBuilder setAppResourcePath(Object... pathParts) { - List encodedParts = Arrays.stream(pathParts).map(Objects::requireNonNull).map(String::valueOf) - .map(this::encodeAppResourcePathPart).collect(Collectors.toList()); - setFragment("/" + String.join("/", encodedParts)); - return this; - } - - private String encodeAppResourcePathPart(String pathPart) - { - return EscapeUtil.encode(pathPart) - // We generally don't encode parentheses in app resource paths - .replace("%28", "(") - .replace("%29", ")"); + return setFragment(EscapeUtil.encodeAppResourcePath(pathParts)); } /** From 047351876788b1a0086c909552c396c107b707d5 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Mon, 25 Aug 2025 12:21:06 -0700 Subject: [PATCH 19/27] Fix expected file names --- src/org/labkey/test/TestFileUtils.java | 41 +++++++------------------- 1 file changed, 10 insertions(+), 31 deletions(-) diff --git a/src/org/labkey/test/TestFileUtils.java b/src/org/labkey/test/TestFileUtils.java index 1848c71a7b..a860e0481e 100644 --- a/src/org/labkey/test/TestFileUtils.java +++ b/src/org/labkey/test/TestFileUtils.java @@ -19,6 +19,7 @@ import org.apache.commons.compress.archivers.ArchiveStreamFactory; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; +import org.apache.commons.io.FileSystem; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; @@ -27,8 +28,6 @@ import org.apache.pdfbox.Loader; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.text.PDFTextStripper; -import org.apache.poi.xssf.streaming.SXSSFRow; -import org.apache.poi.xssf.streaming.SXSSFWorkbook; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openpgp.PGPCompressedData; import org.bouncycastle.openpgp.PGPEncryptedDataList; @@ -41,12 +40,10 @@ import org.bouncycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder; import org.bouncycastle.util.io.Streams; import org.jetbrains.annotations.NotNull; -import org.labkey.serverapi.reader.Readers; import org.openqa.selenium.NotFoundException; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; -import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; @@ -71,6 +68,7 @@ import java.util.List; import java.util.Set; import java.util.TreeSet; +import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.zip.GZIPInputStream; import java.util.zip.ZipEntry; @@ -707,34 +705,15 @@ public static byte[] decrypt(byte[] encrypted, char[] passPhrase) throws IOExcep return Streams.readAll(ld.getInputStream()); } + private static final Pattern badChars = Pattern.compile("[\\\\:/\\[\\]?*|]"); - public static File convertTabularToXlsx(File tabularFile, String delimiter, String sheetName, String xlsxFileName) throws IOException, PGPException + /** + * Determining expected file names for downloaded files that are named according to some + * value that might include characters that are not legal for files + * @see FileSystem#toLegalFileName(String, char) + */ + public static String makeLegalFileName(String candidate) { - File excelFile = new File(getTestTempDir(), xlsxFileName); - FileUtils.forceMkdirParent(excelFile); - - try(SXSSFWorkbook workBook = new SXSSFWorkbook(1000); // holds 1000 rows at a time - BufferedReader br = Readers.getReader(tabularFile); - FileOutputStream out = new FileOutputStream(excelFile)) - { - var sheet = workBook.createSheet(sheetName); - - String currentLine; - int rowNum=0; - - while ((currentLine = br.readLine()) != null) - { - String str[] = currentLine.split(delimiter); - SXSSFRow currentRow = sheet.createRow(rowNum); - for (int i = 0; i < str.length; i++) - { - currentRow.createCell(i).setCellValue(str[i]); - } - rowNum++; - } - workBook.write(out); // flush remaining rows - } - - return excelFile; + return badChars.matcher(candidate).replaceAll("_"); } } From 0848b8e14b28c53d07519ea31473150c7322889e Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Tue, 26 Aug 2025 14:21:57 -0700 Subject: [PATCH 20/27] fuzzing fixes --- src/org/labkey/test/selenium/ReclickingWebElement.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/org/labkey/test/selenium/ReclickingWebElement.java b/src/org/labkey/test/selenium/ReclickingWebElement.java index cf0e34f256..0417cf1c19 100644 --- a/src/org/labkey/test/selenium/ReclickingWebElement.java +++ b/src/org/labkey/test/selenium/ReclickingWebElement.java @@ -15,7 +15,6 @@ */ package org.labkey.test.selenium; -import org.apache.commons.lang3.Strings; import org.apache.commons.lang3.mutable.Mutable; import org.apache.commons.lang3.mutable.MutableObject; import org.jetbrains.annotations.NotNull; @@ -212,18 +211,15 @@ private void revealElement(WebElement el, String shortMessage) if (!blockResolved) { + // Move mouse to corner to dismiss tooltips + new Actions(getDriver()).moveToLocation(0,0).perform(); + Locator.XPathLocator interceptingElLoc = parseInterceptingElementLoc(shortMessage); if (interceptingElLoc != null) { List interceptingElements = interceptingElLoc.findElements(getDriver()); TestLogger.debug("Found %s element(s) matching extracted locator: %s".formatted(interceptingElements.size(), shortMessage)); - if (Strings.CI.containsAny(interceptingElLoc.toString(), "popover", "ws-pre-wrap", "tip")) - { - // Move mouse to corner to dismiss tooltips - new Actions(getDriver()).moveToLocation(0,0).perform(); - } - if (interceptingElements.size() == 1) { //noinspection ResultOfMethodCallIgnored From 964d3f06bf70722932c7660289a06e99368c2271 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Wed, 27 Aug 2025 13:24:45 -0700 Subject: [PATCH 21/27] More fixes --- .../ui/grids/FieldSelectionDialog.java | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/org/labkey/test/components/ui/grids/FieldSelectionDialog.java b/src/org/labkey/test/components/ui/grids/FieldSelectionDialog.java index c7c01bbcae..994ee13f92 100644 --- a/src/org/labkey/test/components/ui/grids/FieldSelectionDialog.java +++ b/src/org/labkey/test/components/ui/grids/FieldSelectionDialog.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.stream.Collectors; +import static org.labkey.test.util.TextUtils.normalizeSpace; import static org.labkey.test.util.selenium.WebElementUtils.getTextContent; /** @@ -180,13 +181,13 @@ public WebElement getAvailableFieldElement(FieldKey fieldKey) if(iterator.hasNext()) { // If the field is already expanded don't try to expand it. - if(!isFieldKeyExpanded(elementCache().findAvailableField(fieldKey.toString()))) - expandOrCollapseByFieldKey(fieldKey.toString(), true); + if(!isFieldKeyExpanded(elementCache().findAvailableField(fieldKey))) + expandOrCollapseByFieldKey(fieldKey, true); } } - return elementCache().findAvailableField(fieldKey.toString()); + return elementCache().findAvailableField(fieldKey); } /** @@ -195,7 +196,7 @@ public WebElement getAvailableFieldElement(FieldKey fieldKey) * @param fieldKey The data-fieldkey value of the field to expand. * @param expand True to expand false to collapse. */ - private void expandOrCollapseByFieldKey(String fieldKey, boolean expand) + private void expandOrCollapseByFieldKey(FieldKey fieldKey, boolean expand) { WebElement listItem = elementCache().findAvailableField(fieldKey); @@ -433,7 +434,7 @@ public FieldSelectionDialog setFieldLabel(String fieldName, String newFieldLabel */ public FieldSelectionDialog setFieldLabel(FieldKey fieldKey, String newFieldLabel) { - WebElement listItem = elementCache().findSelectedField(fieldKey.toString()); + WebElement listItem = elementCache().findSelectedField(fieldKey); WebElement updateIcon = Locator.tagWithClass("span", "edit-inline-field__toggle").findWhenNeeded(listItem); updateIcon.click(); @@ -453,9 +454,9 @@ public FieldSelectionDialog setFieldLabel(FieldKey fieldKey, String newFieldLabe getWrapper().mouseOver(elementCache().title); // Dismiss tooltip - WebDriverWrapper.waitFor(()->!elementCache().fieldLabelEdit.isDisplayed() && - elementCache().getListItemElement(elementCache().selectedFieldsPanel, newFieldLabel).isDisplayed(), + WebDriverWrapper.waitFor(()->!elementCache().fieldLabelEdit.isDisplayed(), String.format("New field label '%s' is not in the list.", newFieldLabel), 500); + Assert.assertEquals("Label after update", normalizeSpace(newFieldLabel), elementCache().getFieldLabel(fieldKey)); return this; } @@ -503,10 +504,10 @@ private List getSelectedListItems(String fieldLabel) * @param beforeTarget Will the field being moved go before (above) or after (below) the target field. * @return This dialog. */ - public FieldSelectionDialog repositionField(String fieldToMove, String targetField, boolean beforeTarget) + public FieldSelectionDialog repositionField(FieldKey fieldToMove, FieldKey targetField, boolean beforeTarget) { - WebElement elementToMove = elementCache().getListItemElement(elementCache().selectedFieldsPanel, fieldToMove); - WebElement elementTarget = elementCache().getListItemElement(elementCache().selectedFieldsPanel, targetField); + WebElement elementToMove = elementCache().findSelectedField(fieldToMove); + WebElement elementTarget = elementCache().findSelectedField(targetField); int yBefore = elementToMove.getRect().getY(); @@ -633,28 +634,27 @@ protected List getListItemElements(WebElement panel, String fieldLab .findElements(panel); } - // Will get the first list item that matches the fieldLabel. - protected WebElement getListItemElement(WebElement panel, String fieldLabel) + protected String getFieldLabel(FieldKey fieldKey) { - return Locator.tagWithClass("div", "list-group-item") - .withDescendant(Locator.tagWithClass("div", "field-caption").withText(fieldLabel)) - .findElement(panel); + return Locator.tagWithClass("div", "field-caption") + .findElement(findFieldRow(fieldKey, selectedFieldsPanel)) + .getText(); } - protected WebElement findSelectedField(String fieldKey) + protected WebElement findSelectedField(FieldKey fieldKey) { return findFieldRow(fieldKey, selectedFieldsPanel); } - protected WebElement findAvailableField(String fieldKey) + protected WebElement findAvailableField(FieldKey fieldKey) { return findFieldRow(fieldKey, availableFieldsPanel); } - protected WebElement findFieldRow(String fieldKey, WebElement panel) + protected WebElement findFieldRow(FieldKey fieldKey, WebElement panel) { return Locator.tagWithClass("div", "list-group-item") - .withAttributeIgnoreCase("data-fieldkey", fieldKey) + .withAttributeIgnoreCase("data-fieldkey", fieldKey.toString()) .findElement(panel); } From f156c5b56302c94a8f7b8d2f7b0b7eb3ccc10216 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Thu, 28 Aug 2025 16:42:30 -0700 Subject: [PATCH 22/27] More fuzzing fixes and SchemaKey helper --- src/org/labkey/test/BaseWebDriverTest.java | 52 ++++++---- .../ui/entities/EntityBulkUpdateDialog.java | 2 +- .../ui/grids/FieldReferenceManager.java | 2 +- src/org/labkey/test/params/FieldKey.java | 99 ++++--------------- src/org/labkey/test/params/QueryKey.java | 91 +++++++++++++++++ src/org/labkey/test/params/SchemaKey.java | 46 +++++++++ 6 files changed, 192 insertions(+), 100 deletions(-) create mode 100644 src/org/labkey/test/params/QueryKey.java create mode 100644 src/org/labkey/test/params/SchemaKey.java diff --git a/src/org/labkey/test/BaseWebDriverTest.java b/src/org/labkey/test/BaseWebDriverTest.java index 6b34832353..ab7c6a4a49 100644 --- a/src/org/labkey/test/BaseWebDriverTest.java +++ b/src/org/labkey/test/BaseWebDriverTest.java @@ -67,6 +67,7 @@ import org.labkey.test.params.ContainerInfo; import org.labkey.test.params.FieldDefinition; import org.labkey.test.params.FieldKey; +import org.labkey.test.params.SchemaKey; import org.labkey.test.teamcity.TeamCityUtils; import org.labkey.test.util.APIAssayHelper; import org.labkey.test.util.APIContainerHelper; @@ -139,6 +140,7 @@ import java.util.Date; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; @@ -2186,32 +2188,38 @@ public void searchFor(final String projectName, String searchFor, final Integer assertTextPresent(searchFor); } } - public void selectSchema(String schemaName) + + /** + * @param schemaKey encoded schema key + */ + public void selectSchema(String schemaKey) { - String[] schemaParts = schemaName.split("\\."); - selectSchema(schemaParts); + selectSchema(SchemaKey.parse(schemaKey)); } - // Helper methods for interacting with the query schema browser + public void selectSchema(String[] schemaParts) { - StringBuilder schemaWithParents = new StringBuilder(); - String separator = ""; - for (String schemaPart : schemaParts) + selectSchema(SchemaKey.fromParts(schemaParts)); + } + + public void selectSchema(SchemaKey schemaKey) + { + Iterator iterator = schemaKey.getIterator(); + while (iterator.hasNext()) { - schemaWithParents.append(separator).append(schemaPart); - separator = "."; + schemaKey = iterator.next(); - Locator.XPathLocator loc = Locator.tag("tr").withClass("x4-grid-row").append("/td/div/span").withText(schemaPart).precedingSibling("img").withClass("x4-tree-icon"); + Locator.XPathLocator loc = Locator.tag("tr").withClass("x4-grid-row").append("/td/div/span").withText(schemaKey.getName()).precedingSibling("img").withClass("x4-tree-icon"); //first load of schemas might a few seconds shortWait().until(ExpectedConditions.elementToBeClickable(loc)); - Locator.XPathLocator selectedSchema = Locator.xpath("//tr").withClass("x4-grid-row-selected").append("/td/div/span").withText(schemaPart); + Locator.XPathLocator selectedSchema = Locator.xpath("//tr").withClass("x4-grid-row-selected").append("/td/div/span").withText(schemaKey.getName()); - if (getDriver().getCurrentUrl().endsWith("schemaName=" + schemaPart)) + if (getDriver().getCurrentUrl().endsWith("schemaName=" + EscapeUtil.encode(schemaKey.toString()))) waitForElement(selectedSchema); if (isElementPresent(selectedSchema)) continue; // already selected - log("Selecting schema " + schemaWithParents + " in the schema browser..."); + log("Selecting schema " + schemaKey.getFullName() + " in the schema browser..."); waitForElementToDisappear(Locator.xpath("//tbody[starts-with(@id, 'treeview')]/tr[not(starts-with(@id, 'treeview'))]")); // select/expand tree node try @@ -2242,19 +2250,23 @@ public void selectSchema(String[] schemaParts) public void selectQuery(String schemaName, String queryName) { - selectQuery(schemaName.split("\\."), queryName); + selectQuery(SchemaKey.parse(schemaName), queryName); + } + + public void selectQuery(String[] schemaParts, String queryName) + { + selectQuery(SchemaKey.fromParts(schemaParts), queryName); } - public void selectQuery(String[] schemaPart, String queryName) + public void selectQuery(SchemaKey schemaKey, String queryName) { - String schemaName = StringUtils.join(schemaPart, "."); - log("Selecting query " + schemaName + "." + queryName + " in the schema browser..."); - selectSchema(schemaPart); + log("Selecting query " + schemaKey.getFullName() + "." + queryName + " in the schema browser..."); + selectSchema(schemaKey); mouseOver(Locator.byClass(".x4-tab-button")); // Move away from schema tree to dismiss tooltip - waitAndClick(Ext4Helper.Locators.tab(schemaName)); // Click schema tab to make sure query list is visible + waitAndClick(Ext4Helper.Locators.tab(schemaKey.getFullName())); // Click schema tab to make sure query list is visible WebElement queryLink = Locator.tagWithClass("table", "lk-qd-coltable").append(Locator.tagWithClass("span", "labkey-link")).withText(queryName).notHidden().waitForElement(getDriver(), WAIT_FOR_JAVASCRIPT); queryLink.click(); - waitForElement(Locator.tagWithClass("div", "lk-qd-name").startsWith(schemaName + "." + queryName), 30000); + waitForElement(Locator.tagWithClass("div", "lk-qd-name").startsWith(schemaKey.getFullName() + "." + queryName), 30000); } public DataRegionTable viewQueryData(String schemaName, String queryName) diff --git a/src/org/labkey/test/components/ui/entities/EntityBulkUpdateDialog.java b/src/org/labkey/test/components/ui/entities/EntityBulkUpdateDialog.java index 09aee4dcc9..ad057ae7a7 100644 --- a/src/org/labkey/test/components/ui/entities/EntityBulkUpdateDialog.java +++ b/src/org/labkey/test/components/ui/entities/EntityBulkUpdateDialog.java @@ -335,7 +335,7 @@ public List getFieldNames() List labels = Locator.tagWithClass("label", "control-label").withAttribute("for") .waitForElements(elementCache(), 2_000); - return labels.stream().map(a -> FieldKey.fromFieldKey(a.getDomAttribute("for")).getName()).toList(); + return labels.stream().map(a -> FieldKey.fromFieldKey(a.getDomAttribute("for")).getFullName()).toList(); } public EntityBulkUpdateDialog waitForFieldsToBe(List expectedFieldNames, int waitMilliseconds) diff --git a/src/org/labkey/test/components/ui/grids/FieldReferenceManager.java b/src/org/labkey/test/components/ui/grids/FieldReferenceManager.java index d70a851fd8..1515a140f1 100644 --- a/src/org/labkey/test/components/ui/grids/FieldReferenceManager.java +++ b/src/org/labkey/test/components/ui/grids/FieldReferenceManager.java @@ -170,7 +170,7 @@ public String getLabel() public String getName() { - return getFieldKey().getName(); + return getFieldKey().getFullName(); } public int getDomIndex() diff --git a/src/org/labkey/test/params/FieldKey.java b/src/org/labkey/test/params/FieldKey.java index 6ccc37387d..20bd931dee 100644 --- a/src/org/labkey/test/params/FieldKey.java +++ b/src/org/labkey/test/params/FieldKey.java @@ -4,46 +4,20 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -import java.util.Iterator; import java.util.List; -public final class FieldKey implements CharSequence, WrapsFieldKey +public final class FieldKey extends QueryKey implements CharSequence, WrapsFieldKey { - private static final String[] ILLEGAL = {"$", "/", "&", "}", "~", ",", "."}; - private static final String[] REPLACEMENT = {"$D", "$S", "$A", "$B", "$T", "$C", "$P"}; - public static final FieldKey EMPTY = new FieldKey(null, ""); // Useful as a sort of FieldKey builder starting point public static final FieldKey SOURCES_FK = FieldKey.fromParts("DataInputs"); public static final FieldKey PARENTS_FK = FieldKey.fromParts("MaterialInputs"); - private static final String SEPARATOR = "/"; - - private final FieldKey _parent; - private final String _name; - private final String _fieldKey; + private static final String DIVIDER = "/"; private FieldKey(FieldKey parent, String child) { - if (parent != null && !parent.isEmpty()) - { - _parent = parent; - _name = parent.getName() + SEPARATOR + child; - _fieldKey = parent + SEPARATOR + encodePart(child); - } - else - { - _parent = null; - _name = child; - _fieldKey = encodePart(child); - } - } - - public static List getIllegalChars() - { - return List.of(ILLEGAL); + super(parent, child); } public static FieldKey fromParts(List parts) @@ -71,7 +45,7 @@ public static FieldKey fromParts(String... parts) { try { - return fromParts(Arrays.stream(fieldKey.toString().split(SEPARATOR)).map(FieldKey::decodePart).toList()); + return fromParts(Arrays.stream(fieldKey.toString().split(DIVIDER)).map(FieldKey::decodePart).toList()); } catch (IllegalArgumentException iae) { @@ -93,21 +67,6 @@ public static FieldKey fromName(CharSequence nameOrFieldKey) return fromParts(nameOrFieldKey.toString()); } - public static String encodePart(String str) - { - return StringUtils.replaceEach(str, ILLEGAL, REPLACEMENT); - } - - public static String decodePart(String str) - { - return StringUtils.replaceEach(str, REPLACEMENT, ILLEGAL); - } - - public FieldKey getParent() - { - return _parent; - } - public FieldKey child(String... parts) { return child(Arrays.asList(parts)); @@ -127,77 +86,61 @@ public FieldKey child(List parts) return child; } - public Iterator getIterator() - { - List ancestors = new ArrayList<>(); - FieldKey temp = this; - - while (temp != null) - { - ancestors.add(temp); - temp = temp.getParent(); - } + // QueryKey - Collections.reverse(ancestors); - - return ancestors.iterator(); - } - - public String getName() + @Override + protected String getDivider() { - return _name; + return DIVIDER; } - /** - * Inverse of {@link #fromParts(String...)} - * @return decoded parts of the field key - */ - public String[] getNameArray() + @Override + protected FieldKey getThis() { - return Arrays.stream(_fieldKey.split(SEPARATOR)).map(FieldKey::decodePart).toArray(String[]::new); + return this; } + // WrapsFieldKey + @Override public FieldKey getFieldKey() { return this; } - @Override - public @NotNull String toString() - { - return _fieldKey; - } + // CharSequence @Override public int length() { - return _fieldKey.length(); + return toString().length(); } @Override public char charAt(int index) { - return _fieldKey.charAt(index); + return toString().charAt(index); } @Override public @NotNull CharSequence subSequence(int start, int end) { - return _fieldKey.subSequence(start, end); + return toString().subSequence(start, end); } + // Object + @Override public boolean equals(Object o) { if (!(o instanceof FieldKey fieldKey)) return false; - return _fieldKey.equalsIgnoreCase(fieldKey._fieldKey); // FieldKeys aren't case-sensitive? + return toString().equalsIgnoreCase(fieldKey.toString()); // FieldKeys aren't case-sensitive? } @Override public int hashCode() { - return _fieldKey.toLowerCase().hashCode(); // FieldKeys aren't case-sensitive? + return toString().toLowerCase().hashCode(); // FieldKeys aren't case-sensitive? } } diff --git a/src/org/labkey/test/params/QueryKey.java b/src/org/labkey/test/params/QueryKey.java new file mode 100644 index 0000000000..5f2b051303 --- /dev/null +++ b/src/org/labkey/test/params/QueryKey.java @@ -0,0 +1,91 @@ +package org.labkey.test.params; + +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +public abstract class QueryKey> +{ + private static final String[] ILLEGAL = {"$", "/", "&", "}", "~", ",", "."}; + private static final String[] REPLACEMENT = {"$D", "$S", "$A", "$B", "$T", "$C", "$P"}; + + private final T _parent; + private final String _name; + private final String _encodedKey; + + protected QueryKey(T parent, String name) + { + _parent = parent; + _name = name; + + if (parent != null && !parent.getName().isEmpty()) + { + _encodedKey = parent + getDivider() + encodePart(name); + } + else + { + _encodedKey = encodePart(name); + } + } + + protected abstract String getDivider(); + + protected abstract T getThis(); + + public static List getIllegalChars() + { + return List.of(ILLEGAL); + } + + public static String encodePart(String str) + { + return StringUtils.replaceEach(str, ILLEGAL, REPLACEMENT); + } + + public static String decodePart(String str) + { + return StringUtils.replaceEach(str, REPLACEMENT, ILLEGAL); + } + + public T getParent() + { + return _parent; + } + + public Iterator getIterator() + { + List ancestors = new ArrayList<>(); + T temp = getThis(); + + while (temp != null) + { + ancestors.add(temp); + temp = temp.getParent(); + } + + Collections.reverse(ancestors); + + return ancestors.iterator(); + } + + public String getName() + { + return _name; + } + + public String getFullName() + { + return decodePart(_encodedKey); + } + + @Override + public @NotNull String toString() + { + return _encodedKey; + } + +} diff --git a/src/org/labkey/test/params/SchemaKey.java b/src/org/labkey/test/params/SchemaKey.java new file mode 100644 index 0000000000..6f57b20608 --- /dev/null +++ b/src/org/labkey/test/params/SchemaKey.java @@ -0,0 +1,46 @@ +package org.labkey.test.params; + +import java.util.Arrays; +import java.util.List; + +public class SchemaKey extends QueryKey +{ + + private SchemaKey(SchemaKey parent, String child) + { + super(parent, child); + } + + public static SchemaKey fromParts(List parts) + { + SchemaKey schemaKey = null; + for (String part : parts) + { + schemaKey = new SchemaKey(schemaKey, part); + } + return schemaKey; + } + + public static SchemaKey fromParts(String... parts) + { + return fromParts(Arrays.asList(parts)); + } + + public static SchemaKey parse(String schemaKey) + { + return fromParts(Arrays.stream(schemaKey.split("\\.")).map(QueryKey::decodePart).toList()); + } + + @Override + protected String getDivider() + { + return "."; + } + + @Override + protected SchemaKey getThis() + { + return this; + } + +} From 449c02807dae724b14676b5957b57e8036c3e4a0 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Fri, 29 Aug 2025 09:01:02 -0700 Subject: [PATCH 23/27] Fix fieldkey empty parent --- .../ui/grids/FieldReferenceManager.java | 9 +-------- src/org/labkey/test/params/FieldKey.java | 3 +-- src/org/labkey/test/params/QueryKey.java | 18 +++++++++++++++--- src/org/labkey/test/params/SchemaKey.java | 7 +------ 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/org/labkey/test/components/ui/grids/FieldReferenceManager.java b/src/org/labkey/test/components/ui/grids/FieldReferenceManager.java index 1515a140f1..e23de2f26f 100644 --- a/src/org/labkey/test/components/ui/grids/FieldReferenceManager.java +++ b/src/org/labkey/test/components/ui/grids/FieldReferenceManager.java @@ -192,14 +192,7 @@ protected FieldKey fieldKeySupplier() path = getElement().getDomAttribute("id"); } - if (path != null) - { - return FieldKey.fromFieldKey(path); - } - else - { - return FieldKey.EMPTY; - } + return FieldKey.fromFieldKey(path); } } } diff --git a/src/org/labkey/test/params/FieldKey.java b/src/org/labkey/test/params/FieldKey.java index 20bd931dee..9b441cc92e 100644 --- a/src/org/labkey/test/params/FieldKey.java +++ b/src/org/labkey/test/params/FieldKey.java @@ -9,7 +9,6 @@ public final class FieldKey extends QueryKey implements CharSequence, WrapsFieldKey { - public static final FieldKey EMPTY = new FieldKey(null, ""); // Useful as a sort of FieldKey builder starting point public static final FieldKey SOURCES_FK = FieldKey.fromParts("DataInputs"); public static final FieldKey PARENTS_FK = FieldKey.fromParts("MaterialInputs"); @@ -22,7 +21,7 @@ private FieldKey(FieldKey parent, String child) public static FieldKey fromParts(List parts) { - return EMPTY.child(parts); + return QueryKey.fromParts(FieldKey::new, parts); } public static FieldKey fromParts(String... parts) diff --git a/src/org/labkey/test/params/QueryKey.java b/src/org/labkey/test/params/QueryKey.java index 5f2b051303..91b7eecc02 100644 --- a/src/org/labkey/test/params/QueryKey.java +++ b/src/org/labkey/test/params/QueryKey.java @@ -7,6 +7,8 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Objects; +import java.util.function.BiFunction; public abstract class QueryKey> { @@ -17,12 +19,12 @@ public abstract class QueryKey> private final String _name; private final String _encodedKey; - protected QueryKey(T parent, String name) + protected QueryKey(T parent, @NotNull String name) { + _name = Objects.requireNonNull(name); _parent = parent; - _name = name; - if (parent != null && !parent.getName().isEmpty()) + if (parent != null) { _encodedKey = parent + getDivider() + encodePart(name); } @@ -36,6 +38,16 @@ protected QueryKey(T parent, String name) protected abstract T getThis(); + protected static T fromParts(BiFunction factory, List parts) + { + T fieldKey = null; + for (String part : parts) + { + fieldKey = factory.apply(fieldKey, part); + } + return fieldKey; + } + public static List getIllegalChars() { return List.of(ILLEGAL); diff --git a/src/org/labkey/test/params/SchemaKey.java b/src/org/labkey/test/params/SchemaKey.java index 6f57b20608..a2d0283a12 100644 --- a/src/org/labkey/test/params/SchemaKey.java +++ b/src/org/labkey/test/params/SchemaKey.java @@ -13,12 +13,7 @@ private SchemaKey(SchemaKey parent, String child) public static SchemaKey fromParts(List parts) { - SchemaKey schemaKey = null; - for (String part : parts) - { - schemaKey = new SchemaKey(schemaKey, part); - } - return schemaKey; + return QueryKey.fromParts(SchemaKey::new, parts); } public static SchemaKey fromParts(String... parts) From c3442b2e41e0839e196c69e29d567b4dbea38ff8 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Wed, 3 Sep 2025 17:32:15 -0700 Subject: [PATCH 24/27] Fix some file references --- src/org/labkey/test/util/FileBrowserHelper.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/org/labkey/test/util/FileBrowserHelper.java b/src/org/labkey/test/util/FileBrowserHelper.java index b4ac3b149e..d5547f0b24 100644 --- a/src/org/labkey/test/util/FileBrowserHelper.java +++ b/src/org/labkey/test/util/FileBrowserHelper.java @@ -204,9 +204,10 @@ private void selectFolderTreeNode(@LoggedParam String nodeId) } else if (!folderTreeNode.getAttribute("class").contains("x4-grid-row-selected")) { + WebElement folderIcon = Locator.byClass("x4-tree-icon").findElement(folderTreeNode); // Scroll bars get in the way sometimes, need to scroll folder tree manually - scrollIntoView(folderTreeNode); - doAndWaitForFileListRefresh(folderTreeNode::click); + scrollIntoView(folderIcon); + doAndWaitForFileListRefresh(folderIcon::click); } } From 80f5c8fde4e67471ce03ae62f0d05effaf2d2a03 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Fri, 5 Sep 2025 12:35:40 -0700 Subject: [PATCH 25/27] Small fixes --- .../test/components/ui/entities/EntityBulkUpdateDialog.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/org/labkey/test/components/ui/entities/EntityBulkUpdateDialog.java b/src/org/labkey/test/components/ui/entities/EntityBulkUpdateDialog.java index ad057ae7a7..eb02a00ee1 100644 --- a/src/org/labkey/test/components/ui/entities/EntityBulkUpdateDialog.java +++ b/src/org/labkey/test/components/ui/entities/EntityBulkUpdateDialog.java @@ -83,6 +83,7 @@ public EntityBulkUpdateDialog setEditableState(CharSequence fieldIdentifier, boo toggle.set(enable); if (enable) _changeCounter++; else _changeCounter--; + getWrapper().mouseOut(); // Toggle is dangerously close to field info tooltip } return this; } From d24aac9b8d5c4f6d831824ccee5b146124767bf2 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Fri, 5 Sep 2025 13:25:10 -0700 Subject: [PATCH 26/27] Don't capitalize --- src/org/labkey/test/util/TestDataGenerator.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/org/labkey/test/util/TestDataGenerator.java b/src/org/labkey/test/util/TestDataGenerator.java index bf4bd747f1..06fba994e2 100644 --- a/src/org/labkey/test/util/TestDataGenerator.java +++ b/src/org/labkey/test/util/TestDataGenerator.java @@ -594,8 +594,7 @@ public static String randomDomainName(@Nullable String namePart, @Nullable Integ } // Multiple spaces in the UI are collapsed into a single space. If we need to test for handling of multiple spaces, we'll not use this generator - // Capitalize to make it easier to match grid header labels - domainName = StringUtils.capitalize(domainName).replaceAll("\\s+", " "); + domainName = domainName.replaceAll("\\s+", " "); TestLogger.log("Generated random domain name for domainKind " + _domainKind + ": " + domainName); return domainName; From 7facef141ce26ac8cb3f3c19b71fac164f676948 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Mon, 8 Sep 2025 15:07:25 -0700 Subject: [PATCH 27/27] Log temp files found --- src/org/labkey/test/WebDriverWrapper.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/org/labkey/test/WebDriverWrapper.java b/src/org/labkey/test/WebDriverWrapper.java index facaf7550d..37438a8e8b 100644 --- a/src/org/labkey/test/WebDriverWrapper.java +++ b/src/org/labkey/test/WebDriverWrapper.java @@ -2454,11 +2454,17 @@ public static File[] getNewFiles(int minFileCount, File downloadDir, @Nullable F }, "File(s) did not appear in download dir: " + downloadDir.toString(), WAIT_FOR_PAGE); + List tempFiles = new ArrayList<>(); waitFor(() -> { final File[] files = downloadDir.listFiles(tempFilesFilter); + tempFiles.clear(); + if (files != null) + { + tempFiles.addAll(Arrays.asList(files)); + } return files != null && files.length == 0; }, - "Temp files remain in download dir: " + downloadDir, WAIT_FOR_PAGE); + () -> "Temp files remain in download dir: " + downloadDir + ": " + tempFiles.stream().map(File::getName).collect(Collectors.joining(", ")), WAIT_FOR_PAGE); MutableInt downloadSize = new MutableInt(-1); MutableInt stabilityDuration = new MutableInt(0);