From 8a64dacf29aee88895932a54a22a76ee2a81d01e Mon Sep 17 00:00:00 2001 From: Trey Chadick Date: Wed, 6 Aug 2025 17:11:04 -0700 Subject: [PATCH 1/5] Handle double spaces in some error messages (#2607) - Fix `EditableGridTest` and `ListLookupTest` to handle adjacent spaces in field names - Add `FieldInfo.getUiLabel` - Make `FieldInfo._namePart` final --- src/org/labkey/test/params/FieldInfo.java | 39 +++++++++++-------- .../tests/component/EditableGridTest.java | 7 ++-- .../test/tests/list/ListLookupTest.java | 22 ++++++++--- src/org/labkey/test/util/TextUtils.java | 5 ++- 4 files changed, 47 insertions(+), 26 deletions(-) diff --git a/src/org/labkey/test/params/FieldInfo.java b/src/org/labkey/test/params/FieldInfo.java index f962aa8241..073eaa5ca4 100644 --- a/src/org/labkey/test/params/FieldInfo.java +++ b/src/org/labkey/test/params/FieldInfo.java @@ -3,8 +3,10 @@ import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.labkey.test.params.FieldDefinition.ColumnType; +import org.labkey.test.util.CachingSupplier; import org.labkey.test.util.EscapeUtil; import org.labkey.test.util.TestDataGenerator; +import org.labkey.test.util.TextUtils; import java.util.Objects; import java.util.function.Consumer; @@ -17,22 +19,25 @@ public class FieldInfo implements CharSequence, WrapsFieldKey { private final FieldKey _fieldKey; - private final String _label; + private final String _rawLabel; private final ColumnType _columnType; private final Consumer _fieldDefinitionMutator; - private String _namePart; // used for random field generation to track the name part used + private final String _namePart; // used for random field generation to track the name part used + private final CachingSupplier _label = new CachingSupplier<>(() -> Objects.requireNonNullElseGet(getRawLabel(), () -> FieldDefinition.labelFromName(getName()))); + private final CachingSupplier _uiLabel = new CachingSupplier<>(() -> TextUtils.normalizeSpace(getLabel())); - private FieldInfo(FieldKey fieldKey, String label, ColumnType columnType, Consumer fieldDefinitionMutator) + private FieldInfo(FieldKey fieldKey, String label, ColumnType columnType, Consumer fieldDefinitionMutator, String namePart) { _fieldKey = fieldKey; - _label = label; + _rawLabel = label; _columnType = Objects.requireNonNullElse(columnType, ColumnType.String); _fieldDefinitionMutator = fieldDefinitionMutator; + _namePart = namePart; } public FieldInfo(String name, String label, ColumnType columnType) { - this(FieldKey.fromParts(name.trim()), label, columnType, null); + this(FieldKey.fromParts(name.trim()), label, columnType, null, name); } public FieldInfo(String name, String label) @@ -55,9 +60,7 @@ public FieldInfo(String name) */ public static FieldInfo random(String namePart, ColumnType columnType) { - FieldInfo field = new FieldInfo(TestDataGenerator.randomFieldName(namePart), columnType); - field.setNamePart(namePart); - return field; + return new FieldInfo(FieldKey.fromParts(TestDataGenerator.randomFieldName(namePart)), null, columnType, null, namePart); } /** @@ -82,19 +85,28 @@ public FieldInfo customizeFieldDefinition(Consumer fieldDefinit { throw new IllegalArgumentException("FieldDefinition customizer should not modify field label"); } - return new FieldInfo(_fieldKey, _label, _columnType, fieldDefinitionMutator); + return new FieldInfo(_fieldKey, _rawLabel, _columnType, fieldDefinitionMutator, _namePart); } @Contract(pure = true) protected String getRawLabel() { - return _label; + return _rawLabel; } @Contract(pure = true) public String getLabel() { - return Objects.requireNonNullElseGet(getRawLabel(), () -> FieldDefinition.labelFromName(_fieldKey.getName())); + return _label.get(); + } + + /** + * Get field label as it appears when rendered in browser + */ + @Contract(pure = true) + public String getUiLabel() + { + return _uiLabel.get(); } @Override @@ -175,11 +187,6 @@ private FieldDefinition getFieldDefinition(ColumnType columnType) return fieldDefinition; } - private void setNamePart(String namePart) - { - _namePart = namePart; - } - @Override public int length() { diff --git a/src/org/labkey/test/tests/component/EditableGridTest.java b/src/org/labkey/test/tests/component/EditableGridTest.java index 92a7cc60fb..d5cd5b240e 100644 --- a/src/org/labkey/test/tests/component/EditableGridTest.java +++ b/src/org/labkey/test/tests/component/EditableGridTest.java @@ -21,6 +21,7 @@ import org.labkey.test.params.experiment.SampleTypeDefinition; import org.labkey.test.params.list.IntListDefinition; import org.labkey.test.params.list.ListDefinition; +import org.labkey.test.util.TextUtils; import org.openqa.selenium.Dimension; import org.openqa.selenium.Keys; import org.openqa.selenium.WebElement; @@ -1341,11 +1342,11 @@ public void testInputCellValidation() log("Input empty string for required field should trigger cell warning."); testGrid.setCellValue(1, REQ_STR_FIELD, " "); checker().verifyEquals("Cell warning status not as expected at row " + 1 + " for col " + REQ_STR_FIELD.getLabel(), true, testGrid.hasCellError(1, REQ_STR_FIELD)); - checker().verifyEquals("Cell warning msg not as expected at row " + 1 + " for col " + REQ_STR_FIELD.getLabel(), REQ_STR_FIELD.getLabel() + " is required.", testGrid.getCellPopoverText(1, REQ_STR_FIELD)); + checker().verifyEquals("Cell warning msg not as expected at row " + 1 + " for col " + REQ_STR_FIELD.getLabel(), REQ_STR_FIELD.getUiLabel() + " is required.", testGrid.getCellPopoverText(1, REQ_STR_FIELD)); mouseOver(testGrid.getCell(0, "Row")); // dismiss warning popup testGrid.setCellValue(1, REQ_INT_FIELD, " "); checker().verifyEquals("Cell warning status not as expected at row " + 1 + " for col " + REQ_INT_FIELD.getLabel(), true, testGrid.hasCellError(1, REQ_INT_FIELD)); - checker().verifyEquals("Cell warning msg not as expected at row " + 1 + " for col " + REQ_INT_FIELD.getLabel(), REQ_INT_FIELD.getLabel() + " is required.", testGrid.getCellPopoverText(1, REQ_INT_FIELD)); + checker().verifyEquals("Cell warning msg not as expected at row " + 1 + " for col " + REQ_INT_FIELD.getLabel(), REQ_INT_FIELD.getUiLabel() + " is required.", testGrid.getCellPopoverText(1, REQ_INT_FIELD)); log("Correct values should remove cell warning, keep entering wrong values should update warning"); mouseOver(testGrid.getCell(0, "Row")); // dismiss warning popup @@ -1496,7 +1497,7 @@ private void verifyCellWarning(EditableGrid testGrid, List expectedWarni checker().verifyEquals("Cell warning status not as expected at row " + rowId + " for col " + field.getLabel(), !StringUtils.isEmpty(expectedWarning), testGrid.hasCellError(rowId, field)); if (!StringUtils.isEmpty(expectedWarning)) - checker().verifyEquals("Cell warning msg not as expected at row " + rowId + " for col " + field.getLabel(), expectedWarning, testGrid.getCellPopoverText(rowId, field)); + checker().verifyEquals("Cell warning msg not as expected at row " + rowId + " for col " + field.getLabel(), TextUtils.normalizeSpace(expectedWarning), testGrid.getCellPopoverText(rowId, field)); } } diff --git a/src/org/labkey/test/tests/list/ListLookupTest.java b/src/org/labkey/test/tests/list/ListLookupTest.java index 056a1a0115..1e36e7b33e 100644 --- a/src/org/labkey/test/tests/list/ListLookupTest.java +++ b/src/org/labkey/test/tests/list/ListLookupTest.java @@ -26,6 +26,8 @@ import java.util.Map; import static org.junit.Assert.assertEquals; +import static org.labkey.test.params.FieldDefinition.labelFromName; +import static org.labkey.test.util.TextUtils.normalizeSpace; // Issue 52098, Issue 49422 @Category({Daily.class, Data.class, Hosting.class}) @@ -122,7 +124,8 @@ public void testWithoutValidatorOrAlternateKeys() throws IOException, CommandExc String error = importDataPage .setText(bulkData) .submitExpectingError(); - checker().withScreenshot().verifyEquals("Error message for invalid primary key not as expected", "Could not convert value 'noneSuch' (String) for Integer field '" + lookFromLookupFieldName + "'", error); + checker().withScreenshot().verifyEquals("Error message for invalid primary key not as expected", + "Could not convert value 'noneSuch' (String) for Integer field '" + normalizeSpace(lookFromLookupFieldName) + "'", error); } @Test @@ -168,7 +171,8 @@ public void testWithoutValidatorWithAlternateKeys() throws IOException, CommandE .setText(bulkData) .setImportLookupByAlternateKey(true) .submitExpectingError(); - checker().withScreenshot().verifyEquals("Error message after supplying invalid alternate key not as expected", "Value 'NotAValue' not found for field " + lookFromLookupFieldName + " in the current context.", error); + checker().withScreenshot().verifyEquals("Error message after supplying invalid alternate key not as expected", + "Value 'NotAValue' not found for field " + normalizeSpace(lookFromLookupFieldName) + " in the current context.", error); } @Test @@ -196,13 +200,16 @@ public void testWithLookupValidatorWithoutAlternateKeys() throws IOException, Co String error = importDataPage .setText(tsvFromColumn(List.of(lookFromLookupFieldName, "1000"))) .submitExpectingError(); - checker().withScreenshot().verifyEquals("Error message for invalid primary key value not as expected", "Value '1000' was not present in lookup target 'lists." + lookToListName + "' for field '" + FieldDefinition.labelFromName(lookFromLookupFieldName) + "'", error); + checker().withScreenshot().verifyEquals("Error message for invalid primary key value not as expected", + "Value '1000' was not present in lookup target 'lists." + normalizeSpace(lookToListName) + + "' for field '" + normalizeSpace(labelFromName(lookFromLookupFieldName)) + "'", error); log("With lookup validation on, import data and provide an invalid primary key of type string."); error = importDataPage .setText(tsvFromColumn(List.of(lookFromLookupFieldName, "Look"))) .submitExpectingError(); - checker().withScreenshot().verifyEquals("Error message for invalid primary key type not as expected", "Could not convert value 'Look' (String) for Integer field '" + lookFromLookupFieldName + "'", error); + checker().withScreenshot().verifyEquals("Error message for invalid primary key type not as expected", + "Could not convert value 'Look' (String) for Integer field '" + normalizeSpace(lookFromLookupFieldName) + "'", error); } @Test @@ -246,14 +253,17 @@ public void testWithLookupValidatorAndAlternateKeys() throws IOException, Comman .setText(bulkData) .setImportLookupByAlternateKey(true) .submitExpectingError(); - checker().withScreenshot().verifyEquals("Error message for invalid string alternate key not as expected", "Value 'Invalid' not found for field " + lookFromLookupFieldName + " in the current context.", error); + checker().withScreenshot().verifyEquals("Error message for invalid string alternate key not as expected", + "Value 'Invalid' not found for field " + normalizeSpace(lookFromLookupFieldName) + " in the current context.", error); bulkData = tsvFromColumn(List.of(lookFromLookupFieldName, "1234")); error = importDataPage .setText(bulkData) .setImportLookupByAlternateKey(true) .submitExpectingError(); - checker().withScreenshot().verifyEquals("Error message for invalid number-like alternate key not as expected", "Value '1234' was not present in lookup target 'lists." + lookToListName + "' for field '" + FieldDefinition.labelFromName(lookFromLookupFieldName) + "'", error); + checker().withScreenshot().verifyEquals("Error message for invalid number-like alternate key not as expected", + "Value '1234' was not present in lookup target 'lists." + normalizeSpace(lookToListName) + + "' for field '" + normalizeSpace(labelFromName(lookFromLookupFieldName)) + "'", error); } diff --git a/src/org/labkey/test/util/TextUtils.java b/src/org/labkey/test/util/TextUtils.java index 5b8a585cfd..97ec58700e 100644 --- a/src/org/labkey/test/util/TextUtils.java +++ b/src/org/labkey/test/util/TextUtils.java @@ -16,7 +16,10 @@ private TextUtils() {} */ public static String normalizeSpace(String value) { - return NS_PATTERN.matcher(value).replaceAll(" ").trim(); + if (value == null) + return value; + else + return NS_PATTERN.matcher(value).replaceAll(" ").trim(); } public static List normalizeSpace(List values) From eb13dfc971eb46572583bc3ef1da612585d314c4 Mon Sep 17 00:00:00 2001 From: Trey Chadick Date: Fri, 8 Aug 2025 08:06:04 -0700 Subject: [PATCH 2/5] More helpers for tricky characters (#2612) --- src/org/labkey/test/WebDriverWrapper.java | 10 ++++------ src/org/labkey/test/params/FieldInfo.java | 12 ++++++++++-- src/org/labkey/test/util/TextSearcher.java | 11 +++-------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/org/labkey/test/WebDriverWrapper.java b/src/org/labkey/test/WebDriverWrapper.java index 28cca5bc2f..fe0077f074 100644 --- a/src/org/labkey/test/WebDriverWrapper.java +++ b/src/org/labkey/test/WebDriverWrapper.java @@ -1631,14 +1631,12 @@ public boolean isTextPresent(String... texts) if (htmlSource == null || !htmlSource.contains(text)) present.setFalse(); - return present.getValue(); + return present.get(); }; TextSearcher searcher = new TextSearcher(this); - searcher.setSearchTransformer(TextSearcher.TextTransformers.IDENTITY); - searcher.setSourceTransformer(TextSearcher.TextTransformers.IDENTITY); searcher.searchForTexts(handler, Arrays.asList(texts)); - return present.getValue(); + return present.get(); } public List getTextOrder(TextSearcher searcher, String... texts) @@ -1729,12 +1727,12 @@ public boolean isAnyTextPresent(String... texts) if (htmlSource.contains(text)) found.setTrue(); - return !found.getValue(); // stop searching if any value is found + return !found.get(); // stop searching if any value is found }; TextSearcher searcher = new TextSearcher(this); searcher.searchForTexts(handler, Arrays.asList(texts)); - return found.getValue(); + return found.get(); } /** diff --git a/src/org/labkey/test/params/FieldInfo.java b/src/org/labkey/test/params/FieldInfo.java index 073eaa5ca4..22077fbf95 100644 --- a/src/org/labkey/test/params/FieldInfo.java +++ b/src/org/labkey/test/params/FieldInfo.java @@ -24,7 +24,6 @@ public class FieldInfo implements CharSequence, WrapsFieldKey private final Consumer _fieldDefinitionMutator; private final String _namePart; // used for random field generation to track the name part used private final CachingSupplier _label = new CachingSupplier<>(() -> Objects.requireNonNullElseGet(getRawLabel(), () -> FieldDefinition.labelFromName(getName()))); - private final CachingSupplier _uiLabel = new CachingSupplier<>(() -> TextUtils.normalizeSpace(getLabel())); private FieldInfo(FieldKey fieldKey, String label, ColumnType columnType, Consumer fieldDefinitionMutator, String namePart) { @@ -106,7 +105,7 @@ public String getLabel() @Contract(pure = true) public String getUiLabel() { - return _uiLabel.get(); + return TextUtils.normalizeSpace(getLabel()); } @Override @@ -122,6 +121,15 @@ public String getName() return _fieldKey.getName(); } + /** + * Get column name quoted for use in queries and calculated field expressions + */ + @Contract(pure = true) + public String getSqlName() + { + return EscapeUtil.getSqlQuotedValue(_fieldKey.getName()); + } + /** * Get name escaped for use in sample or source name expressions */ diff --git a/src/org/labkey/test/util/TextSearcher.java b/src/org/labkey/test/util/TextSearcher.java index f52763f2e3..3ed4a79f06 100644 --- a/src/org/labkey/test/util/TextSearcher.java +++ b/src/org/labkey/test/util/TextSearcher.java @@ -22,6 +22,7 @@ import java.io.File; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.function.Function; import java.util.function.Supplier; @@ -59,10 +60,7 @@ public TextSearcher(final WebDriverWrapper test) public final TextSearcher setSourceTransformer(Function sourceTransformer) { - if (sourceTransformer == null) - this.sourceTransformer = TextTransformers.IDENTITY; - else - this.sourceTransformer = sourceTransformer; + this.sourceTransformer = Objects.requireNonNullElse(sourceTransformer, TextTransformers.IDENTITY); return this; } @@ -73,10 +71,7 @@ public final TextSearcher clearSourceTransformer() public final TextSearcher setSearchTransformer(Function searchTransformer) { - if (searchTransformer == null) - this.searchTransformer = TextTransformers.IDENTITY; - else - this.searchTransformer = searchTransformer; + this.searchTransformer = Objects.requireNonNullElse(searchTransformer, TextTransformers.IDENTITY); return this; } From 06c4adc19359ee66e4fb483e0a1903fde994d7d4 Mon Sep 17 00:00:00 2001 From: Nick Kerr Date: Mon, 11 Aug 2025 10:31:04 -0700 Subject: [PATCH 3/5] AuditLogHelper: return null instead of index error (#2610) --- .../labkey/test/tests/DomainDesignerTest.java | 2 +- .../test/tests/TextChoiceSampleTypeTest.java | 20 ++-- src/org/labkey/test/util/AuditLogHelper.java | 109 +++++++++--------- 3 files changed, 65 insertions(+), 66 deletions(-) diff --git a/src/org/labkey/test/tests/DomainDesignerTest.java b/src/org/labkey/test/tests/DomainDesignerTest.java index 8e4cd16241..479bea906b 100644 --- a/src/org/labkey/test/tests/DomainDesignerTest.java +++ b/src/org/labkey/test/tests/DomainDesignerTest.java @@ -282,7 +282,7 @@ public void testInvalidLookupDomainField() throws IOException, CommandException .clickSave(); AuditLogHelper.DetailedAuditEventRow expectedDomainEvent = new AuditLogHelper.DetailedAuditEventRow(null, listName, null, - "The name of the list domain 'InvalidLookUpNameList' was changed to 'InvalidLookUpNameList_edited'. The descriptor of domain InvalidLookUpNameList_edited was updated.", + String.format("The name of the list domain '%s' was changed to '%s'. The descriptor of domain %s was updated.", listName, editedListName, editedListName), "", null, null, "Name: " + listName + " > " + editedListName); boolean pass = _auditLogHelper.validateLastDomainAuditEvents(editedListName, getProjectName(), expectedDomainEvent, Collections.emptyMap()); checker().verifyTrue("The comment logged for the list renaming was not as expected", pass); diff --git a/src/org/labkey/test/tests/TextChoiceSampleTypeTest.java b/src/org/labkey/test/tests/TextChoiceSampleTypeTest.java index 6cf91f37fe..06ef616241 100644 --- a/src/org/labkey/test/tests/TextChoiceSampleTypeTest.java +++ b/src/org/labkey/test/tests/TextChoiceSampleTypeTest.java @@ -29,10 +29,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.stream.Collectors; @Category({Daily.class}) @@ -330,10 +328,6 @@ public void testUpdatingAndDeletingValuesInSampleType() throws IOException, Comm TestDataGenerator dataGenerator = createSampleType(sampleTypeName, namePrefix, textChoiceFieldName, expectedUnLockedValues); - // Add the list of the event ids to an ignore list so future tests don't look at them again. - Set ignoreIds = new HashSet<>(); - ignoreIds.addAll(_auditLogHelper.getDomainEventIds(getProjectName(), sampleTypeName, null)); - log("Create some samples that have TextChoice values set."); // Only assign a few of the values to samples (i.e. lock them). @@ -347,7 +341,7 @@ public void testUpdatingAndDeletingValuesInSampleType() throws IOException, Comm Map samplesWithTC = new HashMap<>(); int index = 0; - for(int i = 1; i <= 20; i++) + for (int i = 1; i <= 20; i++) { String sampleName = String.format("%s%d", namePrefix, i); @@ -357,10 +351,10 @@ public void testUpdatingAndDeletingValuesInSampleType() throws IOException, Comm String strFieldValue; // Give a TextChoice value to every other sample. - if(i%2 == 0) + if (i%2 == 0) { - if(index >= expectedLockedValues.size()) + if (index >= expectedLockedValues.size()) index = 0; String tcValue = expectedLockedValues.get(index); @@ -413,7 +407,7 @@ public void testUpdatingAndDeletingValuesInSampleType() throws IOException, Comm value = expectedUnLockedValues.get(0); fieldRow.selectTextChoiceValue(value); - if(checker().verifyTrue(String.format("Delete button is not enabled for value '%s', it should be.", value), + if (checker().verifyTrue(String.format("Delete button is not enabled for value '%s', it should be.", value), fieldRow.isTextChoiceDeleteButtonEnabled())) { fieldRow.deleteTextChoiceValue(value); @@ -474,13 +468,13 @@ public void testUpdatingAndDeletingValuesInSampleType() throws IOException, Comm // Construct a list of samples that have TextChoice set and what they are expected to be. List> expectedSamples = new ArrayList<>(); - for(Map.Entry entry : samplesWithTC.entrySet()) + for (Map.Entry entry : samplesWithTC.entrySet()) { String sampleId = entry.getKey(); // Need to special case for the TC value that was just updated. String sampleValue; - if(entry.getValue().equals(valueToUpdate)) + if (entry.getValue().equals(valueToUpdate)) { sampleValue = updatedValue; } @@ -618,7 +612,7 @@ public void testSetTextChoiceValueForSample() throws IOException, CommandExcepti List availableSamples = new ArrayList<>(); - for(int i = 1; i <= 5; i++) + for (int i = 1; i <= 5; i++) { Map sample = new HashMap<>(); String sampleName = String.format("%s%d", namePrefix, i); diff --git a/src/org/labkey/test/util/AuditLogHelper.java b/src/org/labkey/test/util/AuditLogHelper.java index 8914ff5190..73fb555877 100644 --- a/src/org/labkey/test/util/AuditLogHelper.java +++ b/src/org/labkey/test/util/AuditLogHelper.java @@ -1,6 +1,7 @@ package org.labkey.test.util; import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.json.JSONException; import org.labkey.remoteapi.CommandException; @@ -165,10 +166,12 @@ public void checkTimelineAuditEventDiffCount(String containerPath, List { checkAuditEventDiffCount(containerPath, getAuditEventNameFromURL(), expectedDiffCounts); } + public void checkAuditEventDiffCount(String containerPath, AuditEvent auditEventName, List expectedDiffCounts) throws IOException, CommandException { checkAuditEventDiffCount(containerPath, auditEventName, Collections.emptyList(), expectedDiffCounts); } + public void checkAuditEventDiffCount(String containerPath, AuditEvent auditEventName, List filters, List expectedDiffCounts) throws IOException, CommandException { Integer maxRows = expectedDiffCounts.size(); @@ -287,12 +290,13 @@ public boolean validateDomainPropertiesAuditLog(String domainName, Integer domai return true; Map actualAuditDetails = getDomainPropertyEvents(domainName, domainEventId); boolean pass = true; - if (expectedAuditDetails.keySet().size() != actualAuditDetails.keySet().size()) + if (expectedAuditDetails.size() != actualAuditDetails.size()) { pass = false; - TestLogger.log("Number of DomainPropertyAuditEvent events not as expected."); + TestLogger.log(String.format("Number of DomainPropertyAuditEvent events not as expected. Expected %d, Actual %d.", expectedAuditDetails.size(), actualAuditDetails.size())); } - for (String key : expectedAuditDetails.keySet()) + + for (String key : expectedAuditDetails.keySet()) { DetailedAuditEventRow expectedAuditDetail = expectedAuditDetails.get(key); DetailedAuditEventRow actualAuditDetail = actualAuditDetails.get(key); @@ -311,13 +315,19 @@ public boolean validateDomainPropertiesAuditLog(String domainName, Integer domai public boolean validateLastDomainAuditEvents(String domainName, String projectName, DetailedAuditEventRow expectedDomainEvent, Map expectedDomainPropertyEvents) { DetailedAuditEventRow latestDomainEvent = getLastDomainEvent(projectName, domainName); + if (latestDomainEvent == null) + { + TestLogger.log(String.format("No DomainAuditEvent found for domain '%s' in project '%s'.", domainName, projectName)); + return false; + } + boolean pass = validateDetailAuditLog(expectedDomainEvent, latestDomainEvent); return pass && validateDomainPropertiesAuditLog(domainName, latestDomainEvent.rowId, expectedDomainPropertyEvents); } public List getDomainEventIds(String projectName, String domainName, @Nullable Collection ignoreIds) { - List domainAuditEventAllRows = getDomainEventLog(projectName, domainName, ignoreIds); + List domainAuditEventAllRows = getDomainAuditEventLog(projectName, domainName, ignoreIds, null); List domainEventIds = new ArrayList<>(); domainAuditEventAllRows.forEach((event)->domainEventIds.add(event.rowId)); @@ -327,14 +337,20 @@ public List getDomainEventIds(String projectName, String domainName, @N return domainEventIds; } - public DetailedAuditEventRow getLastDomainEvent(String projectName, String domainName) + public @Nullable DetailedAuditEventRow getLastDomainEvent(String projectName, String domainName) { - return getDomainEventLog(projectName, domainName, null).get(0); + List eventLog = getDomainAuditEventLog(projectName, domainName, null, 1); + if (eventLog.isEmpty()) + return null; + return eventLog.get(0); } - public Integer getLastDomainEventId(String projectName, String domainName) + public @Nullable Integer getLastDomainEventId(String projectName, String domainName) { - return getLastDomainEvent(projectName, domainName).rowId; + DetailedAuditEventRow event = getLastDomainEvent(projectName, domainName); + if (event == null) + return null; + return event.rowId; } public static List propertyAuditColumns = List.of("type", "comment", "usercomment", "oldvalues", "newvalues", "datachanges"); @@ -363,8 +379,11 @@ public String getLogString() } } - public Map getDomainPropertyEvents(String domainName, Integer domainEventId) + public @NotNull Map getDomainPropertyEvents(String domainName, Integer domainEventId) { + if (domainEventId == null) + return Collections.emptyMap(); + List> allRows = getDomainPropertyEventLog(domainName, Collections.singletonList(domainEventId)); Map domainPropEventComments = new HashMap<>(); allRows.forEach((event)->{ @@ -378,8 +397,8 @@ public Map getDomainPropertyEvents(String domainN String dataChanges = getLogColumnDisplayValue(event, "dataChanges"); domainPropEventComments.put(propertyName, new DetailedAuditEventRow(rowId, propertyName, action, comment, userComment, oldValue, newValue, dataChanges)); }); - return domainPropEventComments; + return domainPropEventComments; } public Map getLastDomainPropertyEvents(String projectName, String domainName) @@ -395,18 +414,14 @@ public List getLastDomainPropertyValues(String projectName, String domai public List getDomainEventComments(String projectName, String domainName, @Nullable Collection ignoreIds) { - List domainAuditEventAllRows = getDomainEventLog(projectName, domainName, ignoreIds); - - List domainEventComments = new ArrayList<>(); - domainAuditEventAllRows.forEach((event)->domainEventComments.add(event.comment)); - return domainEventComments; + return getDomainAuditEventLog(projectName, domainName, ignoreIds, null).stream().map(event -> event.comment).toList(); } public Set getDomainEventIdsFromPropertyEvents(List> domainPropertyEventRows) { Set domainEventIds = new HashSet<>(); - for(Map row : domainPropertyEventRows) + for (Map row : domainPropertyEventRows) { domainEventIds.add(getLogColumnIntValue(row, "domaineventid")); } @@ -414,48 +429,43 @@ public Set getDomainEventIdsFromPropertyEvents(List return domainEventIds; } - private List getDomainEventLog(String projectName, String domainName, @Nullable Collection ignoreIds) + private List getDomainAuditEventLog(String projectName, String domainName, @Nullable Collection ignoreIds, @Nullable Integer maxRows) { TestLogger.log("Get a list of the Domain Events for project '" + projectName + "'. "); + domainName = domainName.trim(); Connection cn = WebTestHelper.getRemoteApiConnection(); SelectRowsCommand cmd = new SelectRowsCommand("auditLog", "DomainAuditEvent"); cmd.setRequiredVersion(9.1); cmd.setColumns(Arrays.asList("rowid", "domainuri", "domainname", "comment", "usercomment", "oldvalues", "newvalues", "datachanges")); cmd.addFilter("projectid/DisplayName", projectName, Filter.Operator.EQUAL); - if(null != ignoreIds) + cmd.addFilter("domainname", domainName, Filter.Operator.EQUAL); + if (null != ignoreIds) { - StringBuilder stringBuilder = new StringBuilder(); - ignoreIds.forEach((id)->{ - if(!stringBuilder.isEmpty()) - stringBuilder.append(";"); - stringBuilder.append(id); - }); - cmd.addFilter("rowId", stringBuilder, Filter.Operator.NOT_IN); + String rowIds = StringUtils.join(ignoreIds, ";"); + cmd.addFilter("rowId", rowIds, Filter.Operator.NOT_IN); } cmd.setContainerFilter(ContainerFilter.AllFolders); - cmd.setSorts(Arrays.asList(new Sort("RowId", Sort.Direction.DESCENDING))); + cmd.setSorts(List.of(new Sort("RowId", Sort.Direction.DESCENDING))); + + if (maxRows != null) + cmd.setMaxRows(maxRows); List> domainAuditEventAllRows = executeSelectCommand(cn, cmd); - TestLogger.log("Number of 'Domain Event' log entries for '" + projectName + "': " + domainAuditEventAllRows.size()); + TestLogger.log(String.format("Number of Domain Event log entries for domain '%s' in '%s': %d", domainName, projectName, domainAuditEventAllRows.size())); - TestLogger.log("Filter the list to look only at '" + domainName + "'."); List domainAuditEventRows = new ArrayList<>(); - for(Map row : domainAuditEventAllRows) + for (Map row : domainAuditEventAllRows) { - String domainName_ = getLogColumnValue(row, "domainname"); - - if(domainName_.trim().equalsIgnoreCase(domainName.trim())) - { - Integer rowId = getLogColumnIntValue(row, "rowid"); - String comment = getLogColumnValue(row, "comment"); - String userComment = getLogColumnValue(row, "usercomment"); - String oldValue = getLogColumnValue(row, "oldvalues"); - String newValue = getLogColumnValue(row, "newvalues"); - String dataChanges = getLogColumnDisplayValue(row, "dataChanges"); - domainAuditEventRows.add(new DetailedAuditEventRow(rowId, domainName, null, comment, userComment, oldValue, newValue, dataChanges)); - } + String eventDomainName = getLogColumnValue(row, "domainname"); + Integer rowId = getLogColumnIntValue(row, "rowid"); + String comment = getLogColumnValue(row, "comment"); + String userComment = getLogColumnValue(row, "usercomment"); + String oldValue = getLogColumnValue(row, "oldvalues"); + String newValue = getLogColumnValue(row, "newvalues"); + String dataChanges = getLogColumnDisplayValue(row, "dataChanges"); + domainAuditEventRows.add(new DetailedAuditEventRow(rowId, eventDomainName, null, comment, userComment, oldValue, newValue, dataChanges)); } return domainAuditEventRows; @@ -469,15 +479,10 @@ private List> getDomainPropertyEventLog(String domainName, @ cmd.setColumns(Arrays.asList("Created", "CreatedBy", "ImpersonatedBy", "propertyname", "action", "domainname", "domaineventid", "Comment", "UserComment", "oldvalues", "newvalues", "datachanges")); cmd.addFilter("domainname", domainName, Filter.Operator.EQUAL); - if(null != eventIds) + if (null != eventIds) { - StringBuilder stringBuilder = new StringBuilder(); - eventIds.forEach((id)->{ - if(!stringBuilder.isEmpty()) - stringBuilder.append(";"); - stringBuilder.append(id); - }); - cmd.addFilter("domaineventid/rowid", stringBuilder, Filter.Operator.IN); + String rowIds = StringUtils.join(eventIds, ";"); + cmd.addFilter("domaineventid/rowid", rowIds, Filter.Operator.IN); } cmd.setContainerFilter(ContainerFilter.AllFolders); @@ -494,7 +499,7 @@ private List> executeSelectCommand(Connection cn, SelectRows TestLogger.log("Number of rows: " + response.getRowCount()); rowsReturned.addAll(response.getRows()); } - catch(IOException | CommandException ex) + catch (IOException | CommandException ex) { // Just fail here, don't toss the exception up the stack. fail("There was a command exception when getting the log: " + ex); @@ -551,7 +556,7 @@ private String getLogColumnValue(Map rowEntry, String columnName return null; return value.toString(); } - catch(JSONException je) + catch (JSONException je) { // Just fail here, don't toss the exception up the stack. throw new IllegalArgumentException(je); @@ -577,7 +582,7 @@ private Integer getLogColumnIntValue(Map rowEntry, String column return null; return parseInt(strVal); } - catch(JSONException je) + catch (JSONException je) { // Just fail here, don't toss the exception up the stack. throw new IllegalArgumentException(je); From 435a4986893b1c77d3ccb50c9e953bddf7a17c6b Mon Sep 17 00:00:00 2001 From: Trey Chadick Date: Mon, 11 Aug 2025 12:29:19 -0700 Subject: [PATCH 4/5] Wait for system maintenance page to load when rerunning the task (#2615) --- src/org/labkey/test/WebDriverWrapper.java | 24 +++++++++++++++++++ .../admin/ConfigureSystemMaintenancePage.java | 5 ++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/org/labkey/test/WebDriverWrapper.java b/src/org/labkey/test/WebDriverWrapper.java index fe0077f074..83bd5dd672 100644 --- a/src/org/labkey/test/WebDriverWrapper.java +++ b/src/org/labkey/test/WebDriverWrapper.java @@ -75,6 +75,7 @@ import org.openqa.selenium.Keys; import org.openqa.selenium.NoAlertPresentException; import org.openqa.selenium.NoSuchElementException; +import org.openqa.selenium.NoSuchWindowException; import org.openqa.selenium.ScriptTimeoutException; import org.openqa.selenium.SearchContext; import org.openqa.selenium.StaleElementReferenceException; @@ -2132,6 +2133,29 @@ public long doAndMaybeWaitForPageToLoad(int msWait, Supplier action) return loadTimer.elapsed().toMillis(); } + public long doAndWaitForWindow(Runnable action, String windowName) + { + return doAndMaybeWaitForPageToLoad(10_000, () -> { + String initialWindow = getDriver().getWindowHandle(); + boolean targetWindowExists; + try + { + getDriver().switchTo().window(windowName); + getDriver().switchTo().window(initialWindow); + targetWindowExists = true; + } + catch (NoSuchWindowException e) + { + targetWindowExists = false; + } + + action.run(); + + getDriver().switchTo().window(windowName); + return targetWindowExists; + }); + } + public long doAndAcceptUnloadAlert(Runnable func, String partialAlertText) { return doAndWaitForPageToLoad(() -> diff --git a/src/org/labkey/test/pages/admin/ConfigureSystemMaintenancePage.java b/src/org/labkey/test/pages/admin/ConfigureSystemMaintenancePage.java index 15961fa834..abf65023eb 100644 --- a/src/org/labkey/test/pages/admin/ConfigureSystemMaintenancePage.java +++ b/src/org/labkey/test/pages/admin/ConfigureSystemMaintenancePage.java @@ -26,9 +26,8 @@ public static ConfigureSystemMaintenancePage beginAt(WebDriverWrapper webDriverW */ public PipelineStatusDetailsPage runMaintenanceTask(String description) { - click(Locator.tagWithAttribute("input", "type", "checkbox") - .followingSibling("a").withText(description)); - getDriver().switchTo().window("systemMaintenance"); + doAndWaitForWindow(() -> click(Locator.tagWithAttribute("input", "type", "checkbox") + .followingSibling("a").withText(description)), "systemMaintenance"); PipelineStatusDetailsPage pipelineStatusDetailsPage = new PipelineStatusDetailsPage(getDriver()); pipelineStatusDetailsPage.waitForComplete(); From ffd5c3d5e7bdec04034f225aa63c304c72b1d315 Mon Sep 17 00:00:00 2001 From: Trey Chadick Date: Mon, 11 Aug 2025 15:47:22 -0700 Subject: [PATCH 5/5] Wait for panel visibility in ShowAdminPage (#2620) --- .../pages/ConfigureReportsAndScriptsPage.java | 5 +- .../test/pages/core/admin/ShowAdminPage.java | 193 +++++++----------- 2 files changed, 73 insertions(+), 125 deletions(-) diff --git a/src/org/labkey/test/pages/ConfigureReportsAndScriptsPage.java b/src/org/labkey/test/pages/ConfigureReportsAndScriptsPage.java index 2f58e15881..6d65f16f46 100644 --- a/src/org/labkey/test/pages/ConfigureReportsAndScriptsPage.java +++ b/src/org/labkey/test/pages/ConfigureReportsAndScriptsPage.java @@ -27,6 +27,7 @@ import org.labkey.test.util.LogMethod; import org.labkey.test.util.LoggedParam; import org.labkey.test.util.TestLogger; +import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import java.io.File; @@ -45,7 +46,7 @@ public class ConfigureReportsAndScriptsPage extends LabKeyPage private static final String DEFAULT_ENGINE = "Mozilla Rhino"; private static final String EDIT_WINDOW_TITLE = "Edit Engine Configuration"; - public ConfigureReportsAndScriptsPage(WebDriverWrapper test) + public ConfigureReportsAndScriptsPage(WebDriver test) { super(test); waitForEnginesGrid(); @@ -54,7 +55,7 @@ public ConfigureReportsAndScriptsPage(WebDriverWrapper test) public static ConfigureReportsAndScriptsPage beginAt(WebDriverWrapper driver) { driver.beginAt(WebTestHelper.buildURL("core", "configureReportsAndScripts")); - return new ConfigureReportsAndScriptsPage(driver); + return new ConfigureReportsAndScriptsPage(driver.getDriver()); } public void waitForEnginesGrid() diff --git a/src/org/labkey/test/pages/core/admin/ShowAdminPage.java b/src/org/labkey/test/pages/core/admin/ShowAdminPage.java index 11af29747b..0df5e6ea13 100644 --- a/src/org/labkey/test/pages/core/admin/ShowAdminPage.java +++ b/src/org/labkey/test/pages/core/admin/ShowAdminPage.java @@ -28,10 +28,11 @@ import org.labkey.test.util.OptionalFeatureHelper; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.ExpectedConditions; import java.util.List; +import java.util.function.Function; -// TODO: Missing lots of functionality public class ShowAdminPage extends LabKeyPage { public ShowAdminPage(WebDriver driver) @@ -48,104 +49,105 @@ public static ShowAdminPage beginAt(WebDriverWrapper driver) public ShowAdminPage goToServerInformationSection() { elementCache().sectionServerInfo.click(); + shortWait().until(ExpectedConditions.visibilityOf(elementCache().serverInfoPanel)); return this; } public ShowAdminPage goToSettingsSection() { elementCache().sectionSettingsLinks.click(); + shortWait().until(ExpectedConditions.visibilityOf(elementCache().settingsPanel)); return this; } public ShowAdminPage goToModuleInformationSection() { elementCache().sectionModuleInfo.click(); + shortWait().until(ExpectedConditions.visibilityOf(elementCache().moduleInfoPanel)); return this; } - public ShowAdminPage goToActiveUsersSection() + public ShowAdminPage goToRecentUsersSection() { elementCache().sectionActiveUsers.click(); + shortWait().until(ExpectedConditions.visibilityOf(elementCache().recentUsersPanel)); return this; } - public List getActiveUsers() + public void clickSettingsLink(String settingsLink) { - goToActiveUsersSection(); - return getTexts(elementCache().findActiveUsers()); + goToSettingsSection(); + clickAndWait(Locator.linkWithText(settingsLink).findElement(elementCache().settingsPanel)); + } + + public T clickSettingsLink(String settingsLink, Function pageFactory) + { + clickSettingsLink(settingsLink); + return pageFactory.apply(getDriver()); + } + + public List getRecentUsers() + { + goToRecentUsersSection(); + return getTexts(elementCache().findRecentUsers()); } public String getServerGUID() { goToServerInformationSection(); - return elementCache().findServerGUID().getText(); + return elementCache().serverGuidEl.getText(); } public void clickAnalyticsSettings() { - goToSettingsSection(); - clickAndWait(elementCache().analyticsSettingsLink); + clickSettingsLink("analytics settings"); } public void clickAllowedExternalRedirectHosts() { - goToSettingsSection(); - clickAndWait(elementCache().externalRedirectHostLink); + clickSettingsLink("allowed external redirect hosts"); Locator.waitForAnyElement(shortWait(), Locator.tagWithText("span", "Done"), Locator.tagWithText("span", "Save")); } public ShowAuditLogPage clickAuditLog() { - goToSettingsSection(); - clickAndWait(elementCache().auditLogLink); - return new ShowAuditLogPage(getDriver()); + return clickSettingsLink("audit log", ShowAuditLogPage::new); } public ExternalSourcesPage clickAllowedExternalResourceHosts() { - goToSettingsSection(); - clickAndWait(elementCache().externalResourceHostsLink); - return new ExternalSourcesPage(getDriver()); + return clickSettingsLink("allowed external resource hosts", ExternalSourcesPage::new); } public AllowedFileExtensionAdminPage clickAllowedFileExtensions() { - goToSettingsSection(); - clickAndWait(elementCache().allowedFileExtensionLink); - return new AllowedFileExtensionAdminPage(getDriver()); + return clickSettingsLink("allowed file extensions", AllowedFileExtensionAdminPage::new); } public void clickAuditLogMaintenance() { - goToSettingsSection(); - clickAndWait(elementCache().auditLogMaintenanceLink); + clickSettingsLink("Audit Log Maintenance"); } + public LoginConfigurePage clickAuthentication() { - goToSettingsSection(); - clickAndWait(elementCache().authenticationLink); - return new LoginConfigurePage(getDriver()); + return clickSettingsLink("authentication", LoginConfigurePage::new); } public void clickConfigurePageElements() { - goToSettingsSection(); - clickAndWait(elementCache().configurePageElements); + clickSettingsLink("configure page elements"); Locator.waitForAnyElement(shortWait(), Locator.tagWithText("span", "Done"), Locator.tagWithText("span", "Save")); } public ComplianceSettingsAccountsPage clickComplianceSettings() { - goToSettingsSection(); - clickAndWait(elementCache().complianceSettings); - return new ComplianceSettingsAccountsPage(getDriver()); + return clickSettingsLink("Compliance Settings", ComplianceSettingsAccountsPage::new); } public DomainDesignerPage clickChangeUserProperties() { - goToSettingsSection(); - clickAndWait(elementCache().changeUserPropertiesLink); - return new DomainDesignerPage(getDriver()); + return clickSettingsLink("change user properties", DomainDesignerPage::new); } public void clickDeprecatedFeatures() @@ -156,130 +158,103 @@ public void clickDeprecatedFeatures() public void clickEmailCustomization() { - goToSettingsSection(); - clickAndWait(elementCache().emailCustomizationLink); + clickSettingsLink("email customization"); } public void clickNotificationServiceAdmin() { - goToSettingsSection(); - clickAndWait(elementCache().notificationServiceAdminLink); + clickSettingsLink("notification service admin"); } public ConfigureFileSystemAccessPage clickFiles() { - goToSettingsSection(); - clickAndWait(elementCache().filesLink); - return new ConfigureFileSystemAccessPage(getDriver()); + return clickSettingsLink("files", ConfigureFileSystemAccessPage::new); } public void clickFullTextSearch() { - goToSettingsSection(); - clickAndWait(elementCache().fullTextSearchLink); + clickSettingsLink("full-text search"); } public FolderTypePages clickFolderType() { - goToSettingsSection(); - clickAndWait(elementCache().folderTypeLink); - return new FolderTypePages(getDriver()); + return clickSettingsLink("folder types", FolderTypePages::new); } public SiteValidationPage clickSiteValidation() { - goToSettingsSection(); - clickAndWait(elementCache().siteValidationLink); - return new SiteValidationPage(getDriver()); + return clickSettingsLink("site validation", SiteValidationPage::new); } public LookAndFeelSettingsPage clickLookAndFeelSettings() { - goToSettingsSection(); - clickAndWait(elementCache().lookAndFeelSettingsLink); - return new LookAndFeelSettingsPage(getDriver()); + return clickSettingsLink("look and feel settings", LookAndFeelSettingsPage::new); } public void clickMasterPatientIndex() { - goToSettingsSection(); - clickAndWait(elementCache().masterPatientIndex); + clickSettingsLink("Master Patient Index"); } public void clickProfiler() { - goToSettingsSection(); - clickAndWait(elementCache().profilerLink); + clickSettingsLink("profiler"); } public void clickRunningThreads() { - goToSettingsSection(); - clickAndWait(elementCache().runningThreadsLink); + clickSettingsLink("running threads"); } public CustomizeSitePage clickSiteSettings() { - goToSettingsSection(); - clickAndWait(elementCache().siteSettingsLink); - return new CustomizeSitePage(getDriver()); + return clickSettingsLink("site settings", CustomizeSitePage::new); } public void clickSiteWideTerms() { - goToSettingsSection(); - clickAndWait(elementCache().siteWideTermsLink); + clickSettingsLink("site-wide terms of use"); } public ConfigureSystemMaintenancePage clickSystemMaintenance() { - goToSettingsSection(); - clickAndWait(elementCache().systemMaintenanceLink); - return new ConfigureSystemMaintenancePage(getDriver()); + return clickSettingsLink("system maintenance", ConfigureSystemMaintenancePage::new); } public void clickSystemProperties() { - goToSettingsSection(); - clickAndWait(elementCache().systemPropertiesLink); + clickSettingsLink("system properties"); } public ConfigureReportsAndScriptsPage clickViewsAndScripting() { - goToSettingsSection(); - clickAndWait(elementCache().viewsAndScriptingLink); - return new ConfigureReportsAndScriptsPage(this); + return clickSettingsLink("views and scripting", ConfigureReportsAndScriptsPage::new); } public void clickCredits() { - goToSettingsSection(); - clickAndWait(elementCache().creditsLink); + clickSettingsLink("credits"); } public void clickViewPrimarySiteLogFile() { - goToSettingsSection(); - clickAndWait(elementCache().viewPrimarySiteLogFileLink); + clickSettingsLink("view primary site log file"); } public void clickPostgresActivity() { - goToSettingsSection(); - clickAndWait(elementCache().postgresActivityLink); + clickSettingsLink("postgres activity"); } public void clickPostgresLocks() { - goToSettingsSection(); - clickAndWait(elementCache().postgresLocksLink); + clickSettingsLink("postgres locks"); } public List getAllAdminConsoleLinks() { goToSettingsSection(); - WebElement adminLinksContainer = Locator.id("links").findElement(getDriver()); - return Locator.tag("a").findElements(adminLinksContainer); + return Locator.tag("a").findElements(elementCache().settingsPanel); } @Override @@ -288,52 +263,24 @@ protected ElementCache newElementCache() return new ElementCache(); } - protected class ElementCache extends LabKeyPage.ElementCache - { - protected WebElement sectionServerInfo = Locator.linkWithText("Server Information").findWhenNeeded(this); - protected WebElement sectionSettingsLinks = Locator.linkWithText("Settings").findWhenNeeded(this); - protected WebElement sectionModuleInfo = Locator.linkWithText("Module Information").findWhenNeeded(this); - protected WebElement sectionActiveUsers = Locator.linkWithText("Active Users").findWhenNeeded(this); - - protected WebElement analyticsSettingsLink = Locator.linkWithText("analytics settings").findWhenNeeded(this); - protected WebElement externalRedirectHostLink = Locator.linkWithText("allowed external redirect hosts").findWhenNeeded(this); - protected WebElement externalResourceHostsLink = Locator.linkWithText("allowed external resource hosts").findWhenNeeded(this); - protected WebElement allowedFileExtensionLink = Locator.linkWithText("allowed file extensions").findWhenNeeded(this); - protected WebElement auditLogLink = Locator.linkWithText("audit log").findWhenNeeded(this); - protected WebElement auditLogMaintenanceLink = Locator.linkWithText("Audit Log Maintenance").findWhenNeeded(this); - protected WebElement authenticationLink = Locator.linkWithText("authentication").findWhenNeeded(this); - protected WebElement configurePageElements = Locator.linkWithText("configure page elements").findWhenNeeded(this); - protected WebElement complianceSettings = Locator.linkWithText("Compliance Settings").findWhenNeeded(this); - protected WebElement changeUserPropertiesLink = Locator.linkWithText("change user properties").findWhenNeeded(this); - protected WebElement emailCustomizationLink = Locator.linkWithText("email customization").findWhenNeeded(this); - protected WebElement notificationServiceAdminLink = Locator.linkWithText("notification service admin").findWhenNeeded(this); - protected WebElement filesLink = Locator.linkWithText("files").findWhenNeeded(this); - protected WebElement fullTextSearchLink = Locator.linkWithText("full-text search").findWhenNeeded(this); - protected WebElement folderTypeLink = Locator.linkWithText("folder types").findWhenNeeded(this); - protected WebElement siteValidationLink = Locator.linkWithText("site validation").findWhenNeeded(this); - protected WebElement lookAndFeelSettingsLink = Locator.linkWithText("look and feel settings").findWhenNeeded(this); - protected WebElement masterPatientIndex = Locator.linkWithText("Master Patient Index").findWhenNeeded(this); - protected WebElement profilerLink = Locator.linkWithText("profiler").findWhenNeeded(this); - protected WebElement runningThreadsLink = Locator.linkWithText("running threads").findWhenNeeded(this); - protected WebElement siteSettingsLink = Locator.linkWithText("site settings").findWhenNeeded(this); - protected WebElement siteWideTermsLink = Locator.linkContainingText("site-wide terms of use").findWhenNeeded(this); - protected WebElement systemMaintenanceLink = Locator.linkWithText("system maintenance").findWhenNeeded(this); - protected WebElement systemPropertiesLink = Locator.linkContainingText("system properties").findWhenNeeded(this); - protected WebElement viewsAndScriptingLink = Locator.linkWithText("views and scripting").findWhenNeeded(this); - protected WebElement creditsLink = Locator.linkWithText("credits").findWhenNeeded(this); - protected WebElement viewPrimarySiteLogFileLink = Locator.linkWithText("view primary site log file").findWhenNeeded(this); - - protected WebElement postgresActivityLink = Locator.linkWithText("postgres activity").findWhenNeeded(this); - protected WebElement postgresLocksLink = Locator.linkWithText("postgres locks").findWhenNeeded(this); - - protected List findActiveUsers() - { - return Locator.tagWithName("table", "activeUsers").append(Locator.tag("td").position(1)).findElements(this); - } + protected class ElementCache extends LabKeyPage.ElementCache + { + private final WebElement adminNavPanel = Locator.id("lk-admin-nav").findWhenNeeded(this); + private final WebElement sectionServerInfo = Locator.linkWithText("Server Information").findWhenNeeded(adminNavPanel); + private final WebElement sectionSettingsLinks = Locator.linkWithText("Settings").findWhenNeeded(adminNavPanel); + private final WebElement sectionModuleInfo = Locator.linkWithText("Module Information").findWhenNeeded(adminNavPanel); + private final WebElement sectionActiveUsers = Locator.linkWithText("Active Users").findWhenNeeded(adminNavPanel); - protected WebElement findServerGUID() + private final WebElement serverInfoPanel = Locator.id("info").withClass("lk-admin-section").findWhenNeeded(this); + private final WebElement settingsPanel = Locator.id("links").withClass("lk-admin-section").findWhenNeeded(this); + private final WebElement moduleInfoPanel = Locator.id("modules").withClass("lk-admin-section").findWhenNeeded(this); + private final WebElement recentUsersPanel = Locator.id("users").withClass("lk-admin-section").findWhenNeeded(this); + + private List findRecentUsers() { - return Locator.tagWithText("td", "Server GUID").followingSibling("td").findElement(this); + return Locator.tagWithName("table", "activeUsers").append(Locator.tag("td").position(1)).findElements(recentUsersPanel); } + + private final WebElement serverGuidEl = Locator.tagWithText("td", "Server GUID").followingSibling("td").findWhenNeeded(serverInfoPanel); } }