Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions src/org/labkey/test/LabKeySiteWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -1721,23 +1721,29 @@ protected void deletePipelineJob(@LoggedParam String jobDescription, @LoggedPara

public String getConversionErrorMessage(Object value, String fieldName, Class<?> targetClass)
{
return getConversionErrorMessage(value, fieldName, targetClass, true);
return getConversionErrorMessage(value, fieldName, targetClass, true, false);
}

// Note: Keep in sync with ConvertHelper.getStandardConversionErrorMessage()
// Example: "Could not convert value '2.34' (Double) for Boolean field 'Medical History.Dep Diagnosed in Last 18 Months'"
public String getConversionErrorMessage(Object value, String fieldName, Class<?> targetClass, boolean useUSDateParsing)
public String getConversionErrorMessage(Object value, String fieldName, Class<?> targetClass, boolean useUSDateParsing, boolean withoutSingleQuotes)
{
String errorMessage;
String fieldType = targetClass.getSimpleName();
String quote = withoutSingleQuotes ? "" : "'";

// Issue 50768: Need a better error message if date value is not in the expected format.
if (fieldType.equalsIgnoreCase("date") || fieldType.equalsIgnoreCase("datetime") || fieldType.equalsIgnoreCase("timestamp"))
{
String parsingMode = useUSDateParsing ? "U.S. date parsing (MDY)" : "Non-U.S. date parsing (DMY)";
return "'" + value + "' is not a valid " + fieldType + " for " + fieldName + " using " + parsingMode;
errorMessage = quote + value + quote + " is not a valid " + fieldType + " for " + quote + fieldName + quote + " using " + parsingMode;
}
else
{
errorMessage = "Could not convert value " + quote + value + quote + " (" + value.getClass().getSimpleName() + ") for " + fieldType + " field " + quote + fieldName + quote;
}

return "Could not convert value '" + value + "' (" + value.getClass().getSimpleName() + ") for " + fieldType + " field '" + fieldName + "'" ;
return errorMessage;
}

private ProductKey getProductConfiguration() throws IOException, CommandException
Expand Down
5 changes: 5 additions & 0 deletions src/org/labkey/test/Locator.java
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,11 @@ public static XPathLocator name(String name)
return tag("*").withAttribute("name", name);
}

public static XPathLocator nameContaining(String name)
{
return tag("*").withAttributeContaining("name", name);
}

public static CssLocator css(String selector)
{
return new CssLocator(selector);
Expand Down
7 changes: 7 additions & 0 deletions src/org/labkey/test/WebDriverWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -4059,6 +4059,13 @@ public void selectOptionByText(Locator locator, String text)

public void selectOptionByText(WebElement selectElement, String value)
{
if(Boolean.parseBoolean(selectElement.getAttribute("multiple"))) {
List<WebElement> elems = selectElement.findElements(Locator.tag("option"));
elems.forEach(element->{
if(value.contains(element.getAttribute("value")) ^ element.isSelected()) element.click();
});
return;
}
Comment on lines +4062 to +4068
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is less precise than we like for broadly available helpers; it would get confused by selection options that have overlapping text. I would suggest having a separate method for multi-select (selectOptionsByText(WebElement selectElement, List<String> values).

Select select = new Select(selectElement);
select.selectByVisibleText(value);
}
Expand Down
9 changes: 9 additions & 0 deletions src/org/labkey/test/components/domain/DomainFieldRow.java
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,11 @@ public DomainFieldRow clickRemoveOntologyConcept()
// behind the scenes. Because of that the validator aspect of the TextChoice field is hidden from the user (just like
// it is in the product).

public void setAllowMultipleSelections(Boolean allowMultipleSelections)
{
elementCache().allowMultipleSelectionsCheckbox.set(allowMultipleSelections);
}

/**
* Set the list of allowed values for a TextChoice field.
*
Expand Down Expand Up @@ -1702,6 +1707,10 @@ protected class ElementCache extends WebDriverComponent.ElementCache
public final WebElement domainWarningIcon = Locator.tagWithClass("span", "domain-warning-icon")
.findWhenNeeded(this);

// text choice field option
public final Checkbox allowMultipleSelectionsCheckbox = new Checkbox(Locator.tagWithClass("input", "domain-text-choice-multi")
.refindWhenNeeded(this).withTimeout(WAIT_FOR_JAVASCRIPT));

// lookup field options
public final Select lookupContainerSelect = SelectWrapper.Select(Locator.name("domainpropertiesrow-lookupContainer"))
.findWhenNeeded(this);
Expand Down
4 changes: 4 additions & 0 deletions src/org/labkey/test/components/domain/DomainFormPanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,10 @@ else if (validator instanceof FieldDefinition.TextChoiceValidator textChoiceVali
throw new IllegalArgumentException("TextChoice fields cannot have additional validators.");
}
fieldRow.setTextChoiceValues(textChoiceValidator.getValues());
if(textChoiceValidator.getMultipleSelections())
{
fieldRow.setAllowMultipleSelections(textChoiceValidator.getMultipleSelections());
}
Comment on lines +239 to +242
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would make it impossible to disable multi-select through this method.
This should either call setAllowMultipleSelections unconditionally or the condition should check whether textChoiceValidator.getMultipleSelections() != null.

Comment on lines +239 to +242
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will only set it if textChoiceValidator.getMultipleSelections is true, correct? This is ok if you are creating a field, but wouldn't work as expected if the test is editing an existing field and wants to change to field from a multi-choice to a single choice.
Perhaps a better check would be:
if(null != textChoiceValidator.getMultipleSelections())

}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,19 @@ public EntityBulkUpdateDialog setSelectionField(CharSequence fieldIdentifier, Li
return this;
}

/**
* Clear the field (fieldIdentifier).
*
* @param fieldIdentifier Identifier for the field; name ({@link String}) or fieldKey ({@link FieldKey})
* @return this component
*/
public EntityBulkUpdateDialog clearSelection(CharSequence fieldIdentifier)
{
FilteringReactSelect reactSelect = enableSelectionField(fieldIdentifier);
reactSelect.clearSelection();
return this;
}

/**
* @param fieldIdentifier Identifier for the field; name ({@link String}) or fieldKey ({@link FieldKey})
* @param selectValue value to select
Expand Down
3 changes: 3 additions & 0 deletions src/org/labkey/test/components/ui/grids/EditableGrid.java
Original file line number Diff line number Diff line change
Expand Up @@ -507,11 +507,14 @@ public WebElement setCellValue(int row, CharSequence columnIdentifier, Object va

if (value instanceof List)
{

// If this is a list assume that it will need a lookup.
List<String> values = (List) value;

ReactSelect lookupSelect = elementCache().lookupSelect(gridCell);

lookupSelect.clearSelection();

Comment on lines +516 to +517
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this work if a test is trying to add to an existing selections?

lookupSelect.open();

for (String _value : values)
Expand Down
42 changes: 36 additions & 6 deletions src/org/labkey/test/components/ui/grids/ResponsiveGrid.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.labkey.test.components.react.ReactCheckBox;
import org.labkey.test.components.ui.grids.FieldReferenceManager.FieldReference;
import org.labkey.test.components.ui.search.FilterExpressionPanel;
import org.labkey.test.components.ui.search.FilterFacetedPanel;
import org.labkey.test.params.FieldKey;
import org.labkey.test.util.selenium.WebElementUtils;
import org.openqa.selenium.Keys;
Expand All @@ -40,6 +41,14 @@
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.labkey.remoteapi.query.Filter.Operator.CONTAINS_ALL;
import static org.labkey.remoteapi.query.Filter.Operator.CONTAINS_ANY;
import static org.labkey.remoteapi.query.Filter.Operator.CONTAINS_EXACTLY;
import static org.labkey.remoteapi.query.Filter.Operator.CONTAINS_NONE;
import static org.labkey.remoteapi.query.Filter.Operator.DOES_NOT_CONTAIN_EXACTLY;
import static org.labkey.remoteapi.query.Filter.Operator.IN;
import static org.labkey.remoteapi.query.Filter.Operator.IS_EMPTY;
import static org.labkey.remoteapi.query.Filter.Operator.IS_NOT_EMPTY;
import static org.labkey.test.WebDriverWrapper.waitFor;

public class ResponsiveGrid<T extends ResponsiveGrid<?>> extends WebDriverComponent<ResponsiveGrid<T>.ElementCache> implements UpdatingComponent
Expand Down Expand Up @@ -234,15 +243,22 @@ public String filterColumnExpectingError(CharSequence columnIdentifier, Filter.O

private GridFilterModal initFilterColumn(CharSequence columnIdentifier, Filter.Operator operator, Object value)
{
List<Filter.Operator> listOperators = List.of(IN, CONTAINS_ALL, CONTAINS_ANY, CONTAINS_EXACTLY, CONTAINS_NONE,
DOES_NOT_CONTAIN_EXACTLY, IS_EMPTY, IS_NOT_EMPTY);
clickColumnMenuItem(columnIdentifier, "Filter...", false);
GridFilterModal filterModal = new GridFilterModal(getDriver(), this);
if (operator != null)
{
if (operator.equals(Filter.Operator.IN) && value instanceof List<?>)
if (listOperators.contains(operator) && value instanceof List<?>)
{
List<String> values = (List<String>) value;
filterModal.selectFacetTab().selectValue(values.get(0));
filterModal.selectFacetTab().checkValues(values.toArray(String[]::new));
FilterFacetedPanel filterPanel = filterModal.selectFacetTab();
filterPanel.selectValue(values.get(0));
filterPanel.checkValues(values.toArray(String[]::new));
if (filterPanel.isFiltersPresented())
{
filterPanel.selectFilter(operator);
}
}
else
filterModal.selectExpressionTab().setFilter(new FilterExpressionPanel.Expression(operator, value));
Expand Down Expand Up @@ -328,6 +344,7 @@ public void clickColumnMenuItem(CharSequence columnIdentifier, String menuText,
WebElement menu = Locator.css("ul.grid-header-cell__dropdown-menu.open").findWhenNeeded(getDriver());
WebElement menuItem = Locator.css("li > a").containing(menuText).findWhenNeeded(menu);
waitFor(menuItem::isDisplayed, 1000);
dismissPopover();
if (waitForUpdate)
doAndWaitForUpdate(menuItem::click);
else
Expand Down Expand Up @@ -384,15 +401,16 @@ public T selectRow(int index, boolean checked)

/**
* Finds the first row with the specified texts in the specified columns, and sets its checkbox
* @param partialMap key-column (fieldKey, name, or label), value-text in that column
* @param checked the desired checkbox state
*
* @param partialMap key-column (fieldKey, name, or label), value-text in that column
* @param checked the desired checkbox state
* @return this grid
*/
public T selectRow(Map<String, String> partialMap, boolean checked)
{
GridRow row = getRow(partialMap);
selectRowAndVerifyCheckedCounts(row, checked);
getWrapper().log("Row described by map ["+partialMap+"] selection state set to + ["+row.isSelected()+"]");
getWrapper().log("Row described by map [" + partialMap + "] selection state set to + [" + row.isSelected() + "]");

return getThis();
}
Expand Down Expand Up @@ -800,6 +818,17 @@ public Optional<String> getGridEmptyMessage()
return msg;
}

public void dismissPopover()
{
getWrapper().mouseOut();
Locators.popover.findOptionalElement(getDriver()).ifPresent(popover -> {
getWrapper().mouseOver(popover);
getWrapper().mouseOut();
getWrapper().mouseOver(elementCache().getGridHeaderManager().getColumnHeader(0).getElement());
getWrapper().shortWait().until(ExpectedConditions.invisibilityOf(popover));
});
}

public List<FieldReference> getHeaders()
{
return Collections.unmodifiableList(elementCache().findHeaders());
Expand Down Expand Up @@ -971,6 +1000,7 @@ static public Locator.XPathLocator responsiveGridByBaseId(String baseGridId)
static final Locator emptyGrid = Locator.css("tbody tr.grid-empty");
static final Locator spinner = Locator.byClass("fa-spinner");
static final Locator headerCells = Locator.tagWithClass("th", "grid-header-cell");
static final Locator popover = Locator.byClass("popover");
static public Locator.XPathLocator headerCellBody(String label)
{
return Locator.tagWithClass("div", "grid-header-cell__body")
Expand Down
23 changes: 23 additions & 0 deletions src/org/labkey/test/components/ui/search/FilterFacetedPanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@
import org.labkey.test.components.WebDriverComponent;
import org.labkey.test.components.html.Checkbox;
import org.labkey.test.components.html.Input;
import org.labkey.test.components.react.ReactSelect;
import org.labkey.test.components.ui.FilterStatusValue;
import org.labkey.remoteapi.query.Filter;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;

import java.util.List;
import java.util.stream.Collectors;

import static org.labkey.test.WebDriverWrapper.waitFor;
import static org.labkey.test.components.html.Input.Input;
import static org.labkey.test.util.samplemanagement.SMTestUtils.isVisible;

public class FilterFacetedPanel extends WebDriverComponent<FilterFacetedPanel.ElementCache>
{
Expand Down Expand Up @@ -48,6 +52,23 @@ public void selectValue(String value)
elementCache().findCheckboxLabel(value).click();
}

/**
* Check that filter choosing option exists on the page.
*/
public boolean isFiltersPresented()
{
return waitFor(() -> isVisible(elementCache().filterTypeSelects), 1000);
}

/**
* Select a filer by clicking its label. Right now this method relevant only for multi-value text choice.
* @param operator desired filter value
*/
public void selectFilter(Filter.Operator operator)
{
elementCache().filterTypeSelects.select(operator.getDisplayValue());
}
Comment on lines 55 to 70
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update this comment to describe this method (looks leftover from the selectValue). The comment should also mention that this is only relevant to multi-value text choice fields.

This could also take a Filter.Operator instead of a String for some extra type-safety.


/**
* Check single facet value by label to see if it is checked or not.
* @param value desired value
Expand Down Expand Up @@ -123,6 +144,8 @@ protected class ElementCache extends Component<?>.ElementCache
{
protected final Input filterInput =
Input(Locator.id("filter-faceted__typeahead-input"), getDriver()).findWhenNeeded(this);
protected final ReactSelect filterTypeSelects =
new ReactSelect.ReactSelectFinder(getDriver()).index(0).findWhenNeeded(this);
protected final WebElement checkboxSection =
Locator.byClass("labkey-wizard-pills").index(0).refindWhenNeeded(this);
protected final Locator.XPathLocator checkboxLabelLoc
Expand Down
5 changes: 5 additions & 0 deletions src/org/labkey/test/components/ui/search/SampleFinder.java
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ public void removeSearchCard(String queryName)
elementCache().findFilterCard(queryName).clickRemove();
}

public EntityFieldFilterModal editSearchCard(String queryName)
{
return elementCache().findFilterCard(queryName).clickEdit();
}

/**
* Reset the sample finder to its initial state, with no search criteria
*/
Expand Down
2 changes: 1 addition & 1 deletion src/org/labkey/test/pages/DatasetInsertPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ private void tryInsert(Map<String, String> values)
{
for (Map.Entry<String, String> entry : values.entrySet())
{
WebElement fieldInput = Locator.name(EscapeUtil.getFormFieldName(entry.getKey())).findElement(getDriver());
WebElement fieldInput = Locator.nameContaining(EscapeUtil.getFormFieldName(entry.getKey())).findElement(getDriver());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a more precise locator and add a comment explaining why this can't match the name exactly. (See UpdateQueryRowPage)

String type = fieldInput.getAttribute("type");
switch (type)
{
Expand Down
6 changes: 3 additions & 3 deletions src/org/labkey/test/pages/query/UpdateQueryRowPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import org.labkey.test.util.EscapeUtil;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

import java.io.File;
import java.util.HashMap;
import java.util.Map;
Expand Down Expand Up @@ -99,7 +98,7 @@ public UpdateQueryRowPage setField(String fieldName, String value)
WebElement field = elementCache().findField(fieldName);
if (field.getTagName().equals("select"))
{
setField(fieldName, OptionSelect.SelectOption.textOption(value));
selectOptionByText(field, value);
}
else
{
Expand Down Expand Up @@ -186,7 +185,8 @@ WebElement findField(String name)
{
if (!fieldMap.containsKey(name))
{
fieldMap.put(name, Locator.name(EscapeUtil.getFormFieldName(name)).findElement(this));
// Multi-value text choice fields are prepended with "[]"
fieldMap.put(name, Locator.tag("*").attributeStartsWith("name", EscapeUtil.getFormFieldName(name)).findElement(this));
}
return fieldMap.get(name);
}
Expand Down
20 changes: 20 additions & 0 deletions src/org/labkey/test/params/FieldDefinition.java
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,13 @@ public FieldDefinition setTextChoiceValues(List<String> values)
return this;
}

public FieldDefinition setMultiChoiceValues(List<String> values)
{
Assert.assertEquals("Invalid field type for text choice values.", ColumnType.TextChoice, getType());
setValidators(List.of(new FieldDefinition.TextChoiceValidator(values).setMultipleSelections()));
return this;
}
Comment on lines +478 to +483
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a separate ColumnType for Multi Choice. That would make our API helpers able to deal with Multi-choice columns and it more closely matches the product design. It would also make setMultiChoiceValues redundant.


public ExpSchema.DerivationDataScopeType getAliquotOption()
{
return _aliquotOption;
Expand Down Expand Up @@ -1112,6 +1119,8 @@ public static class TextChoiceValidator extends FieldValidator<TextChoiceValidat
{
private final List<String> _values;

private Boolean multipleSelections = false;

public TextChoiceValidator(List<String> values)
{
// The TextChoice validator only has a name and no description or message.
Expand Down Expand Up @@ -1143,6 +1152,17 @@ public List<String> getValues()
return _values;
}

public TextChoiceValidator setMultipleSelections()
{
this.multipleSelections = true;
return this;
}

public Boolean getMultipleSelections()
{
return this.multipleSelections;
}

}

}
Expand Down
4 changes: 2 additions & 2 deletions src/org/labkey/test/tests/InlineImagesListTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ public final void testList() throws Exception
importFilePathError(listImportPage, "1", PDF_FILE.getName());
importFilePathError(listImportPage, "5", PDF_FILE.getName());

String attachmentError = "Can't upload '%s' to field %s with type Attachment.";
String attachmentError = "Cannot upload '%s' to Attachment type field '%s'.";
String attachmentPdfError = String.format(attachmentError, PDF_FILE.getName(), LIST_ATTACHMENT01_NAME);
String attachmentAbsentError = String.format(attachmentError, "Absent.txt", LIST_ATTACHMENT01_NAME);
verifyQueryAPI("lists", LIST_NAME, Map.of(LIST_KEY_NAME, 5, LIST_ATTACHMENT01_NAME, PDF_FILE.getName()), true, "Row 1: " + attachmentPdfError);
Expand All @@ -397,7 +397,7 @@ private void importFilePathError(ImportDataPage listImportPage, String key, Stri
listImportPage.submitExpectingError();
try
{
String expectedError = "Row 1: Can't upload '" + attachmentValue + "' to field " + LIST_ATTACHMENT01_NAME + " with type Attachment.";
String expectedError = "Row 1: Cannot upload '" + attachmentValue + "' to Attachment type field '" + LIST_ATTACHMENT01_NAME + "'.";
checker().withScreenshot("import_error").verifyTrue("Invalid attachment error not as expected", isElementPresent(Locator.tagWithClass("div", "labkey-error").withText(expectedError)));
}
catch(NoSuchElementException nse)
Expand Down
Loading