diff --git a/src/org/labkey/test/components/ui/entities/ParentEntityEditPanel.java b/src/org/labkey/test/components/ui/entities/ParentEntityEditPanel.java index 202a677f91..b5b543ec55 100644 --- a/src/org/labkey/test/components/ui/entities/ParentEntityEditPanel.java +++ b/src/org/labkey/test/components/ui/entities/ParentEntityEditPanel.java @@ -1,5 +1,7 @@ package org.labkey.test.components.ui.entities; +import org.awaitility.Awaitility; +import org.hamcrest.CoreMatchers; import org.junit.Assert; import org.labkey.test.BootstrapLocators; import org.labkey.test.Locator; @@ -14,6 +16,7 @@ import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedConditions; +import java.time.Duration; import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -185,11 +188,16 @@ public void clickSave(int waitTime) Panel detailsPanel = new Panel.PanelFinder(getDriver()).withTitle(parentType).waitFor(getDriver()); if (!selections.isEmpty()) { - for (String selection : selections) - { - getWrapper().quickWait().until(ExpectedConditions.visibilityOf( - Locator.linkWithText(selection).findWhenNeeded(detailsPanel))); - } + // Just wait for the correct number of parents/sources to appear for now. + Locator.CssLocator rowLocator = Locator.css(".grid-panel tbody tr"); + Awaitility.await("Total " + parentType + " grid rows").atMost(Duration.ofSeconds(2)) + .until(() -> rowLocator.findElements(detailsPanel).size(), CoreMatchers.equalTo(selections.size())); + // Issue 53915: Lineage panel grids don't show IDs or links for parent sequences and molecules in Biologics + // for (String selection : selections) + // { + // getWrapper().quickWait().until(ExpectedConditions.visibilityOf( + // Locator.linkWithText(selection).findWhenNeeded(detailsPanel))); + // } } else { diff --git a/src/org/labkey/test/params/ContainerInfo.java b/src/org/labkey/test/params/ContainerInfo.java index 56def804c6..13a6227019 100644 --- a/src/org/labkey/test/params/ContainerInfo.java +++ b/src/org/labkey/test/params/ContainerInfo.java @@ -37,7 +37,7 @@ protected ContainerInfo(String name, ContainerInfo parentContainer, String folde { if (TestProperties.isTestRunningOnTeamCity()) { - String name = TestDataGenerator.randomName(folderName, TestDataGenerator.randomInt(0, 5), 5, RANDOM_CHARSET, null); + String name = TestDataGenerator.randomName(folderName, TestDataGenerator.randomInt(0, 5), 5, RANDOM_CHARSET, null).name(); if (name.startsWith("@")) { // Folder name may not begin with '@' diff --git a/src/org/labkey/test/tests/LinkedSchemaTest.java b/src/org/labkey/test/tests/LinkedSchemaTest.java index 26ad54e0ba..ec601d72cb 100644 --- a/src/org/labkey/test/tests/LinkedSchemaTest.java +++ b/src/org/labkey/test/tests/LinkedSchemaTest.java @@ -1051,7 +1051,7 @@ private void createExperiment(String externalProject, String subFolder, String s goToProjectHome(externalProject); clickTab("Experiment"); waitAndClickAndWait(Locator.linkContainingText("Create Run Group")); - setFormElement(Locator.name("name"), "Parent Run Group"); + setFormElement(Locator.name("Name"), "Parent Run Group"); clickButton("Submit"); _containerHelper.createSubfolder(externalProject, subFolder); @@ -1059,7 +1059,7 @@ private void createExperiment(String externalProject, String subFolder, String s // Create a RunGroup in the subfolder. clickTab("Experiment"); waitAndClickAndWait(Locator.linkContainingText("Create Run Group")); - setFormElement(Locator.name("name"), subFolderRunGroup); + setFormElement(Locator.name("Name"), subFolderRunGroup); clickButton("Submit"); } diff --git a/src/org/labkey/test/util/RandomName.java b/src/org/labkey/test/util/RandomName.java new file mode 100644 index 0000000000..e45af8c0ad --- /dev/null +++ b/src/org/labkey/test/util/RandomName.java @@ -0,0 +1,26 @@ +package org.labkey.test.util; + +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +/** + * Record for a randomly generated name + * + * @param part The test-provided portion of the name + * @param name The full, randomly generated name + */ +public record RandomName(String part, String name) +{ + public RandomName(String part, String name) + { + this.part = part == null ? "" : part; // Don't trim + this.name = Objects.requireNonNull(name); + } + + @Override + public @NotNull String toString() + { + return name; + } +} diff --git a/src/org/labkey/test/util/TestDataGenerator.java b/src/org/labkey/test/util/TestDataGenerator.java index a35918e59c..21620a4f08 100644 --- a/src/org/labkey/test/util/TestDataGenerator.java +++ b/src/org/labkey/test/util/TestDataGenerator.java @@ -546,9 +546,9 @@ public static String randomMultiLineString(int size, @Nullable String exclusion) * @param exclusions characters that are to be excluded from the random parts of the name * @return a name with given characters that will be displayed as returned in the UI. */ - public static String randomName(@NotNull String part, int numStartChars, int numEndChars, String charSet, @Nullable String exclusions) + public static RandomName randomName(@NotNull String part, int numStartChars, int numEndChars, String charSet, @Nullable String exclusions) { - return (randomString(numStartChars, exclusions, charSet) + part + randomString(numEndChars, exclusions, charSet)).trim(); + return new RandomName(part, (randomString(numStartChars, exclusions, charSet) + part + randomString(numEndChars, exclusions, charSet)).trim()); } public static String randomDomainName() @@ -563,7 +563,7 @@ public static String randomDomainName(@Nullable String part) public static String randomInvalidDomainName(@Nullable String namePart, int numStartChars, int numEndChars) { - String domainName = randomName(namePart == null ? "" : namePart, numStartChars, numEndChars, ILLEGAL_DOMAIN_NAME_CHARSET, null); + String domainName = randomName(namePart == null ? "" : namePart, numStartChars, numEndChars, ILLEGAL_DOMAIN_NAME_CHARSET, null).name(); TestLogger.log("Generated random invalid domain name: " + domainName); return domainName; } @@ -583,20 +583,20 @@ public static String randomDomainName(@Nullable String namePart, @Nullable Domai */ public static String randomDomainName(@Nullable String namePart, @Nullable Integer numStartChars, @Nullable Integer numEndChars, @Nullable DomainKind domainKind) { - String _namePart = namePart == null ? "" : namePart; + namePart = namePart == null ? "" : namePart; DomainKind _domainKind = domainKind == null ? DomainKind.SampleSet : domainKind; String charSet = ALPHANUMERIC_STRING + DOMAIN_SPECIAL_STRING; int currentTries = 0; - String domainName = randomName(_namePart, getNumChars(numStartChars, 5), getNumChars(numEndChars, 50), charSet, null); - while (isDomainAndFieldNameInvalid(_domainKind, domainName, null)) + RandomName randomName = randomName(namePart, getNumChars(numStartChars, 5), getNumChars(numEndChars, 50), charSet, null); + while (isDomainAndFieldNameInvalid(_domainKind, randomName, null)) { - domainName = randomName(_namePart, getNumChars(numStartChars, 5), getNumChars(numEndChars, 50), charSet, null); + randomName = randomName(namePart, getNumChars(numStartChars, 5), getNumChars(numEndChars, 50), charSet, null); if (++currentTries >= MAX_RANDOM_TRIES) - throw new IllegalStateException("Failed to generate a valid domain name after " + MAX_RANDOM_TRIES + " tries. Last generated name: " + domainName); + throw new IllegalStateException("Failed to generate a valid domain name after " + MAX_RANDOM_TRIES + " tries. Last generated name: " + randomName); } // 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+", " "); + String domainName = randomName.name().replaceAll("\\s+", " "); TestLogger.log("Generated random domain name for domainKind " + _domainKind + ": " + domainName); return domainName; @@ -632,7 +632,7 @@ public static String randomFieldName(@NotNull String part, @Nullable Integer num + WIDE_PLACEHOLDER + REPEAT_PLACEHOLDER + ALL_CHARS_PLACEHOLDER; int currentTries = 0; - String randomFieldName = randomName(part, getNumChars(numStartChars, 5), getNumChars(numEndChars, 50), chars, exclusion); + RandomName randomFieldName = randomName(part, getNumChars(numStartChars, 5), getNumChars(numEndChars, 50), chars, exclusion); while (isDomainAndFieldNameInvalid(_domainKind, null, randomFieldName)) { randomFieldName = randomName(part, getNumChars(numStartChars, 5), getNumChars(numEndChars, 50), chars, exclusion); @@ -641,14 +641,11 @@ public static String randomFieldName(@NotNull String part, @Nullable Integer num } TestLogger.log("Generated random field name for domainKind " + _domainKind + ": " + randomFieldName); - return randomFieldName; + return randomFieldName.name(); } - private static boolean isDomainAndFieldNameInvalid(DomainKind domainKind, @Nullable String domainName, @Nullable String fieldName) + private static boolean isDomainAndFieldNameInvalid(DomainKind domainKind, @Nullable RandomName domainName, @Nullable RandomName fieldName) { - if (fieldName != null && fieldName.length() > 64 && fieldName.toLowerCase().contains("key")) // Not guaranteed but likely a list key - return true; // Issue 53706: List key field name length is limited to 64 characters - if (TestProperties.isRemoteNameValidationEnabled()) { return isNameInvalidRemote(domainKind, domainName, fieldName); @@ -659,7 +656,7 @@ private static boolean isDomainAndFieldNameInvalid(DomainKind domainKind, @Nulla } } - private static boolean isNameInvalidRemote(DomainKind domainKind, @Nullable String domainName, @Nullable String fieldName) + private static boolean isNameInvalidRemote(DomainKind domainKind, @Nullable RandomName domainName, @Nullable RandomName fieldName) { SimplePostCommand command = new SimplePostCommand("property", "validateDomainAndFieldNames"); JSONObject domainDesign = new JSONObject(); @@ -696,13 +693,16 @@ private static boolean isNameInvalidRemote(DomainKind domainKind, @Nullable Stri } } - public static boolean isNameInvalidLocal(DomainKind domainKind, @Nullable String domainName, @Nullable String fieldName) + private static final Pattern COLON_NAME_PATTERN = Pattern.compile(":[a-zA-Z]{3}"); // Avoid illegal patterns like ":Date" + private static boolean isNameInvalidLocal(DomainKind domainKind, @Nullable RandomName domainName, @Nullable RandomName fieldName) { if (domainName != null) { - if (!Character.isLetterOrDigit(domainName.charAt(0))) + if (domainName.name().isBlank()) + return true; + if (!Character.isLetterOrDigit(domainName.name().charAt(0))) return true; // domain needs to start with alphanumeric char - if (Pattern.matches("(.*\\s--[^ ].*)|(.*\\s-[^- ].*)", domainName)) + if (Pattern.matches("(.*\\s--[^ ].*)|(.*\\s-[^- ].*)", domainName.name())) return true; // domain name must not contain space followed by dash. (command like: Issue 49161) int maxLength = switch (domainKind) @@ -711,14 +711,22 @@ public static boolean isNameInvalidLocal(DomainKind domainKind, @Nullable String case SampleSet -> 100; default -> 200; // Sources, lists, and datasets allow 200 character names }; - if (domainName.length() > maxLength) + if (domainName.name().length() > maxLength) + return true; + if (COLON_NAME_PATTERN.matcher(domainName.name()) + .results().map(mr -> mr.group(0)) + .anyMatch(s -> !domainName.part().contains(s))) // Only check random portion of the name return true; } if (fieldName != null) { - if (fieldName.length() > 200) + if (fieldName.name().isBlank()) + return true; + if (fieldName.name().length() > 200) return true; - if (Pattern.matches(".*:[a-zA-Z]{3}.*", fieldName)) // Avoid illegal patterns like ":Date" + if (COLON_NAME_PATTERN.matcher(fieldName.name()) + .results().map(mr -> mr.group(0)) + .anyMatch(s -> !fieldName.part().contains(s))) // Only check random portion of the name return true; }