diff --git a/src/org/labkey/test/components/ui/domainproperties/EntityTypeDesigner.java b/src/org/labkey/test/components/ui/domainproperties/EntityTypeDesigner.java index 6308060144..e69e56a5ca 100644 --- a/src/org/labkey/test/components/ui/domainproperties/EntityTypeDesigner.java +++ b/src/org/labkey/test/components/ui/domainproperties/EntityTypeDesigner.java @@ -436,7 +436,7 @@ Optional optionalWarningAlert() public final WebElement helpTarget(String divLabelText) { - return Locator.xpath(String.format("//div[text()='%s']//span[@class='label-help-target']", divLabelText)).findWhenNeeded(this); + return Locator.xpath(String.format("//span[text()='%s']//div[@class='overlay-trigger']", divLabelText)).findWhenNeeded(this); } // Tool tips exist on the page, outside the scope of the domainDesigner, so scope the search accordingly. diff --git a/src/org/labkey/test/components/ui/entities/EntityBulkDialog.java b/src/org/labkey/test/components/ui/entities/EntityBulkDialog.java new file mode 100644 index 0000000000..4cedf24526 --- /dev/null +++ b/src/org/labkey/test/components/ui/entities/EntityBulkDialog.java @@ -0,0 +1,217 @@ +package org.labkey.test.components.ui.entities; + +import org.apache.commons.lang3.StringUtils; +import org.labkey.api.util.Pair; +import org.labkey.test.Locator; +import org.labkey.test.components.bootstrap.ModalDialog; +import org.labkey.test.components.html.Checkbox; +import org.labkey.test.components.html.Input; +import org.labkey.test.components.react.FilteringReactSelect; +import org.labkey.test.components.react.ReactDateTimePicker; +import org.labkey.test.components.react.ReactSelect; +import org.labkey.test.components.react.ToggleButton; +import org.labkey.test.components.ui.files.FileAttachmentContainer; +import org.labkey.test.params.FieldKey; +import org.openqa.selenium.WebElement; + +import java.util.HashMap; +import java.util.Map; + +import static org.labkey.test.WebDriverWrapper.WAIT_FOR_JAVASCRIPT; + +/** + * Abstract base for {@link EntityBulkInsertDialog} and {@link EntityBulkUpdateDialog}. + */ +public abstract class EntityBulkDialog extends ModalDialog +{ + protected int _changeCounter = 0; + + protected EntityBulkDialog(ModalDialogFinder finder) + { + super(finder); + } + + /** + * @param fieldIdentifier Identifier for the field; name ({@link String}) or fieldKey ({@link FieldKey}) + * @return current value of the specified field + */ + public String getTextArea(CharSequence fieldIdentifier) + { + return elementCache().textArea(fieldIdentifier).get(); + } + + /** + * @param fieldIdentifier Identifier for the field; name ({@link String}) or fieldKey ({@link FieldKey}) + * @return current value of the specified field + */ + public String getNumericField(CharSequence fieldIdentifier) + { + return elementCache().textInput(fieldIdentifier).get(); + } + + /** + * @param fieldIdentifier Identifier for the field; name ({@link String}) or fieldKey ({@link FieldKey}) + * @return current value of the specified field + */ + public boolean getBooleanField(CharSequence fieldIdentifier) + { + return elementCache().checkBox(fieldIdentifier).get(); + } + + public String getFieldValue(WebElement input) + { + String value = input.getText(); + if (StringUtils.isEmpty(value)) + value = input.getAttribute("value"); + if (StringUtils.isEmpty(value)) + value = input.getAttribute("placeholder"); + if (StringUtils.isEmpty(value) && "checkbox".equals(input.getAttribute("type"))) + value = input.getAttribute("title"); + return value; + } + + protected String getValueForReactSelect(ReactSelect reactSelect) + { + if (!reactSelect.getSelections().isEmpty()) + { + return reactSelect.getSelections().get(0); + } + else + { + return ""; + } + } + + private WebElement getAmountUnitsRow() + { + String fieldLabel = "Amount and Units"; + return elementCache().formRowByControlLabel(fieldLabel); + } + + public Pair getAmountUnit() + { + String amountVal = getFieldValue(getAmountInput()); + String unitVal = Locator.tagWithClass("div", "select-input__value-container").withoutAttribute("type", "hidden").findElement(getAmountUnitsRow()).getText(); + return new Pair<>(amountVal, unitVal); + } + + public Pair getAmountUnitValue() + { + enableAmountUnit(); + String amountVal = getWrapper().getFormElement(getAmountInput()); + String unitVal = getValueForReactSelect(getAmountUnitSelect()); + return new Pair<>(amountVal, unitVal); + } + + public void enableAmountUnit() + { + ToggleButton toggle = new ToggleButton.ToggleButtonFinder(getDriver()).findOrNull(getAmountUnitsRow()); + if (toggle != null && !toggle.isOn()) + { + toggle.set(true); + _changeCounter++; + } + } + + public void disableAmountUnit() + { + ToggleButton toggle = new ToggleButton.ToggleButtonFinder(getDriver()).findOrNull(getAmountUnitsRow()); + if(toggle != null && toggle.isOn()) + { + toggle.set(false); + _changeCounter--; + } + } + + private WebElement getAmountInput() + { + return elementCache().amountInputLoc.findElement(getAmountUnitsRow()); + } + + public ReactSelect getAmountUnitSelect() + { + enableAmountUnit(); + return new ReactSelect(getAmountUnitsRow(), getDriver()); + } + + public void setAmountUnit(String amount, String unit) + { + enableAmountUnit(); + + if (amount != null) + getWrapper().setFormElement(getAmountInput(), amount); + if (unit != null) + { + ReactSelect reactSelect = getAmountUnitSelect(); + if (!unit.isEmpty()) + reactSelect.select(unit); + else + reactSelect.clearSelection(); + } + + if (amount != null && unit != null) + _changeCounter++; + } + + @Override + protected abstract ElementCache newElementCache(); + + @Override + protected ElementCache elementCache() + { + return (ElementCache) super.elementCache(); + } + + protected abstract class ElementCache extends ModalDialog.ElementCache + { + protected final Locator textInputLoc = Locator.tagWithAttribute("input", "type", "text"); + protected final Locator checkBoxLoc = Locator.tagWithAttribute("input", "type", "checkbox"); + protected final Locator.XPathLocator amountInputLoc = Locator.tag("input").withAttribute("aria-label", "Amount"); + + protected final Map _rows = new HashMap<>(); + + /** + * Returns the form row div that contains the controls for the given field. + */ + public abstract WebElement formRow(CharSequence fieldIdentifier); + + // For composite fields (e.g. StoredAmount + Units) that render a
label instead of