Skip to content

Tests for multi choice value#2872

Open
DariaBod wants to merge 30 commits intodevelopfrom
fb_mvtc_test
Open

Tests for multi choice value#2872
DariaBod wants to merge 30 commits intodevelopfrom
fb_mvtc_test

Conversation

@DariaBod
Copy link
Contributor

@DariaBod DariaBod commented Feb 5, 2026

Rationale

Some changes to support multi choice

Related Pull Requests

Changes

  • Added setAllowMultipleSelections method to check “Allow multiple selections” checkbox

  • Changed method initFilterColumn for work with filters for List values

  • Added method testMultiChoiceValues() to ListTest (scope: create, edit)

  • Created new shuffleSelect() for random amount of random values from list

  • Changed method selectOptionByText() for multi choice values. If we need value to be selected we first check if it’s selected and then click

@DariaBod DariaBod changed the title Fb mvtc test Tests for multi choice value Feb 5, 2026
@LabKey LabKey deleted a comment from github-actions bot Feb 5, 2026
-add new shuffleSelect
-replace my method with shuffleSelect
@DariaBod DariaBod requested a review from labkey-tchad February 5, 2026 23:18
Copy link
Contributor

@labkey-danield labkey-danield left a comment

Choose a reason for hiding this comment

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

Added a few comments.

Comment on lines 239 to 242
if(textChoiceValidator.getMultipleSelections())
{
fieldRow.setAllowMultipleSelections(textChoiceValidator.getMultipleSelections());
}
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())

Comment on lines +516 to +517
lookupSelect.clearSelection();

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?

Copy link
Member

Choose a reason for hiding this comment

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

I would expect a method called setCellValue to overwrite any existing value.

Comment on lines 239 to 242
if(textChoiceValidator.getMultipleSelections())
{
fieldRow.setAllowMultipleSelections(textChoiceValidator.getMultipleSelections());
}
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.

List<String> values = (List<String>) value;
filterModal.selectFacetTab().selectValue(values.get(0));
filterModal.selectFacetTab().checkValues(values.toArray(String[]::new));
filterModal.selectFacetTab().selectFilter(operator.getDisplayValue());
Copy link
Member

Choose a reason for hiding this comment

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

I think this will (or could) break functionality for other column types; the facet panel usually doesn't have a filter type.
Also, this wouldn't work for "Is Empty" or "Is Not Empty".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I’m planning to add support for 'Is Empty' and 'Is Not Empty' as well.

Regarding the functionality for other types: I’ve already found one test that was failing, so I added a check to the initFilterColumn method. It now checks if the filter type selector is present on the page before trying to interact with it. After this change, the test is passing.

Does this approach sound okay to you?

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)

Comment on lines 1705 to 1710
table.clickInsertNewRow();
String valuesToChoose = tcValues.subList(1, 3).stream()
.sorted()
.collect(Collectors.joining(" "));
Locator loc = Locator.nameContaining(EscapeUtil.getFormFieldName(columnName));
selectOptionByText(loc, valuesToChoose);
Copy link
Member

Choose a reason for hiding this comment

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

clickInsertNewRow returns a page object that should have methods for setting fields.

Comment on lines 4062 to 4068
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;
}
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).

Copy link
Member

@labkey-tchad labkey-tchad left a comment

Choose a reason for hiding this comment

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

I think Xing is going to work on the Java remote API. This PR is blocked by that update.

Comment on lines +516 to +517
lookupSelect.clearSelection();

Copy link
Member

Choose a reason for hiding this comment

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

I would expect a method called setCellValue to overwrite any existing value.

* 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)
Copy link
Member

Choose a reason for hiding this comment

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

A slightly more descriptive method name.

Suggested change
public void selectFilter(Filter.Operator operator)
public void selectArrayFilterOperator(Filter.Operator operator)

Comment on lines +147 to +148
protected final ReactSelect filterTypeSelects =
new ReactSelect.ReactSelectFinder(getDriver()).index(0).findWhenNeeded(this);
Copy link
Member

Choose a reason for hiding this comment

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

Making name consistent with method rename suggestion.

Suggested change
protected final ReactSelect filterTypeSelects =
new ReactSelect.ReactSelectFinder(getDriver()).index(0).findWhenNeeded(this);
protected final ReactSelect arrayFilterOperatorSelect =
new ReactSelect.ReactSelectFinder(getDriver()).index(0).findWhenNeeded(this);

*/
public boolean isFiltersPresented()
{
return waitFor(() -> isVisible(elementCache().filterTypeSelects), 1000);
Copy link
Member

Choose a reason for hiding this comment

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

We shouldn't wait for things that might not be present, especially when the most common state is for it to not be present.

Comment on lines +119 to +127
WebElement field = elementCache().findField(fieldName, true);
List<WebElement> options = field.findElements(By.tagName("option"));
//unselect all options that selected but shouldn't be selected
options.forEach(option ->
{
if (!values.contains(option.getText()) && option.getAttribute("selected") != null) option.click();
});
values.forEach(value -> selectOptionByText(field, value));
return this;
Copy link
Member

Choose a reason for hiding this comment

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

WebDriver's Select class handles multi-select.

Suggested change
WebElement field = elementCache().findField(fieldName, true);
List<WebElement> options = field.findElements(By.tagName("option"));
//unselect all options that selected but shouldn't be selected
options.forEach(option ->
{
if (!values.contains(option.getText()) && option.getAttribute("selected") != null) option.click();
});
values.forEach(value -> selectOptionByText(field, value));
return this;
WebElement field = new Select(elementCache().findField(fieldName, true));
field.deselectAll();
values.forEach(value -> field.selectByVisibleText(value));
return this;

return this;
}

public UpdateQueryRowPage setField(String fieldName, List<String> values)
Copy link
Member

Choose a reason for hiding this comment

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

This can replace the existing setMultiValueField that was added last week. It only has one usage and doesn't actually support multiple values.

Comment on lines +239 to +249
fieldRow.setAllowMultipleSelections(false);
}
else if (validator instanceof FieldDefinition.MultiValueTextChoiceValidator multiValueTextChoiceValidator)
{
// MultiValueTextChoice is a field type; implemented using a special validator. TextChoice field cannot have other validators.
if (validators.size() > 1)
{
throw new IllegalArgumentException("TextChoice fields cannot have additional validators.");
}
fieldRow.setTextChoiceValues(multiValueTextChoiceValidator.getValues());
fieldRow.setAllowMultipleSelections(true);
Copy link
Member

Choose a reason for hiding this comment

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

MultiValueTextChoiceValidator is redundant. We can just check the column type to decide whether to allow multiple selections.

Suggested change
fieldRow.setAllowMultipleSelections(false);
}
else if (validator instanceof FieldDefinition.MultiValueTextChoiceValidator multiValueTextChoiceValidator)
{
// MultiValueTextChoice is a field type; implemented using a special validator. TextChoice field cannot have other validators.
if (validators.size() > 1)
{
throw new IllegalArgumentException("TextChoice fields cannot have additional validators.");
}
fieldRow.setTextChoiceValues(multiValueTextChoiceValidator.getValues());
fieldRow.setAllowMultipleSelections(true);
fieldRow.setAllowMultipleSelections(fieldDefinition.getType() == FieldDefinition.ColumnType.MultiValueTextChoice);
}

* to an external data source. The user only provides the list of (string) values that the field will display in the dropdown.
* Another difference is that there can only be one TextChoice on a field, whereas you can have multiple validators on a field.
*/
public static class MultiValueTextChoiceValidator extends FieldValidator<MultiValueTextChoiceValidator>
Copy link
Member

Choose a reason for hiding this comment

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

MultiValueTextChoiceValidator is redundant and should be removed.

Comment on lines 243 to +247

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);
Copy link
Member

Choose a reason for hiding this comment

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

IN isn't actually a list operator (this component just uses it as a signal to use the checkboxes). It shouldn't be grouped with them. This list should also be static and contain the empty operators.

Suggested change
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);
public static final List<Filter.Operator> ARRAY_OPERATORS = List.of(CONTAINS_ALL, CONTAINS_ANY, CONTAINS_EXACTLY, CONTAINS_NONE, DOES_NOT_CONTAIN_EXACTLY, IS_EMPTY, IS_NOT_EMPTY);
private GridFilterModal initFilterColumn(CharSequence columnIdentifier, Filter.Operator operator, Object value)
{

Comment on lines -241 to 266
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 if (value == null)
{
filterModal.selectFacetTab().selectFilter(operator);
}
Copy link
Member

Choose a reason for hiding this comment

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

Now that I have a better understanding of these new filter operators, I have a better idea for this method. We don't actually need to check whether the operator select is present; for the new operators, the select will always be present.
Something like this:

if (operator.equals(Filter.Operator.IN) && value instanceof List<?> || ARRAY_OPERATORS.contains(operator))
{
    FilterFacetedPanel filterPanel = filterModal.selectFacetTab();
    if (ARRAY_OPERATORS.contains(operator))
    {
        filterPanel.selectFilter(operator);
    }
    if (value != null)
    {
        List<String> values = (List<String>) value;
        filterPanel.selectValue(values.get(0));
        filterPanel.checkValues(values.toArray(String[]::new));
    }
}

@XingY
Copy link
Contributor

XingY commented Feb 17, 2026

I think Xing is going to work on the Java remote API. This PR is blocked by that update.

LabKey/labkey-api-java#88

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants