Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ Optional<WebElement> 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.
Expand Down
217 changes: 217 additions & 0 deletions src/org/labkey/test/components/ui/entities/EntityBulkDialog.java
Original file line number Diff line number Diff line change
@@ -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<String, String> getAmountUnit()
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Maybe getAmountAndUnitsReadOnlyValues or something to distinguish it from getAmountUnitValue.
This could also return a record instead of a pair for better readability, but not necessary.

{
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<String, String> getAmountUnitValue()
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

getAmountAndUnitsValues? or getAmountAndUnitsEditableValues?

{
enableAmountUnit();
String amountVal = getWrapper().getFormElement(getAmountInput());
String unitVal = getValueForReactSelect(getAmountUnitSelect());
return new Pair<>(amountVal, unitVal);
}

public void enableAmountUnit()
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
public void enableAmountUnit()
public void enableAmountAndUnits()

{
ToggleButton toggle = new ToggleButton.ToggleButtonFinder(getDriver()).findOrNull(getAmountUnitsRow());
if (toggle != null && !toggle.isOn())
{
toggle.set(true);
_changeCounter++;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Is _changeCounter meant to increment by only one when potentially both fields are updated?

}
}

public void disableAmountUnit()
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
public void disableAmountUnit()
public void disableAmountAndUnits()

{
ToggleButton toggle = new ToggleButton.ToggleButtonFinder(getDriver()).findOrNull(getAmountUnitsRow());
if(toggle != null && toggle.isOn())
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
if(toggle != null && toggle.isOn())
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++;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same question here about whether this is one change or two.

}

@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");
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
protected final Locator checkBoxLoc = Locator.tagWithAttribute("input", "type", "checkbox");
protected final Locator checkboxLoc = Locator.tagWithAttribute("input", "type", "checkbox");

protected final Locator.XPathLocator amountInputLoc = Locator.tag("input").withAttribute("aria-label", "Amount");

protected final Map<String, WebElement> _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 <div> label instead of <label for="...">,
private WebElement formRowByControlLabel(String fieldLabel)
{
return _rows.computeIfAbsent(fieldLabel, k ->
Locator.tagWithClass("div", "row")
.withChild(Locator.tagWithClass("div", "control-label").withText(fieldLabel))
.waitForElement(this, WAIT_FOR_JAVASCRIPT));
}

public FilteringReactSelect selectionField(CharSequence fieldIdentifier)
{
return new FilteringReactSelect(formRow(fieldIdentifier), getDriver());
}

public Checkbox checkBox(CharSequence fieldIdentifier)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
public Checkbox checkBox(CharSequence fieldIdentifier)
public Checkbox checkbox(CharSequence fieldIdentifier)

{
return new Checkbox(checkBoxLoc.findElement(formRow(fieldIdentifier)));
}

public Input textInput(CharSequence fieldIdentifier)
{
return new Input(textInputLoc.findElement(formRow(fieldIdentifier)), getDriver());
}

public Input textArea(CharSequence fieldIdentifier)
{
return new Input(Locator.tag("textarea").findElement(formRow(fieldIdentifier)), getDriver());
}

public ReactDateTimePicker dateInput(CharSequence fieldIdentifier)
{
return new ReactDateTimePicker.ReactDateTimeInputFinder(getDriver()).find(formRow(fieldIdentifier));
}

public FileAttachmentContainer fileUploadField(CharSequence fieldIdentifier)
{
return new FileAttachmentContainer(formRow(fieldIdentifier), getDriver());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,9 @@
import org.labkey.test.BootstrapLocators;
import org.labkey.test.Locator;
import org.labkey.test.WebDriverWrapper;
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.html.RadioButton;
import org.labkey.test.components.react.FilteringReactSelect;
import org.labkey.test.components.react.ReactDateTimePicker;
import org.labkey.test.components.ui.files.FileAttachmentContainer;
import org.labkey.test.params.FieldDefinition;
import org.labkey.test.params.FieldKey;
import org.openqa.selenium.StaleElementReferenceException;
Expand All @@ -27,7 +23,7 @@
/**
* `fieldIdentifier` arguments accept field names or {@link FieldKey}s
*/
public class EntityBulkInsertDialog extends ModalDialog
public class EntityBulkInsertDialog extends EntityBulkDialog
{
public EntityBulkInsertDialog(WebDriver driver)
{
Expand Down Expand Up @@ -172,15 +168,6 @@ public EntityBulkInsertDialog setTextArea(CharSequence fieldIdentifier, String v
return this;
}

/**
* @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})
* @param value value to set
Expand Down Expand Up @@ -212,15 +199,6 @@ public EntityBulkInsertDialog setNumericField(CharSequence fieldIdentifier, Stri
return this;
}

/**
* @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})
* @param selectValues values to select
Expand All @@ -239,8 +217,7 @@ public EntityBulkInsertDialog setSelectionField(CharSequence fieldIdentifier, Li
*/
public List<String> getSelectionField(CharSequence fieldIdentifier)
{
FilteringReactSelect reactSelect = elementCache().selectionField(fieldIdentifier);
return reactSelect.getSelections();
return elementCache().selectionField(fieldIdentifier).getSelections();
}

/**
Expand All @@ -251,8 +228,7 @@ public List<String> getSelectionField(CharSequence fieldIdentifier)
*/
public EntityBulkInsertDialog clearSelectionField(CharSequence fieldIdentifier)
{
FilteringReactSelect reactSelect = elementCache().selectionField(fieldIdentifier);
reactSelect.clearSelection();
elementCache().selectionField(fieldIdentifier).clearSelection();
return this;
}

Expand Down Expand Up @@ -319,20 +295,10 @@ public String getDateTimeField(CharSequence fieldIdentifier)
*/
public EntityBulkInsertDialog setBooleanField(CharSequence fieldIdentifier, boolean checked)
{
Checkbox box = elementCache().checkBox(fieldIdentifier);
box.set(checked);
elementCache().checkBox(fieldIdentifier).set(checked);
return this;
}

/**
* @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();
}

/**
* @param fieldIdentifier Identifier for the field; name ({@link String}) or fieldKey ({@link FieldKey})
* @param file file to attach
Expand Down Expand Up @@ -452,55 +418,21 @@ protected ElementCache elementCache()
return (ElementCache) super.elementCache();
}

protected class ElementCache extends ModalDialog.ElementCache
protected class ElementCache extends EntityBulkDialog.ElementCache
{
public final Locator validationMessage = Locator.tagWithClass("span", "validation-message");

private final Map<String, WebElement> _rows = new HashMap<>();

@Override
public WebElement formRow(CharSequence fieldIdentifier)
{
String fieldKey = FieldKey.fromName(fieldIdentifier).toString();
return _rows.computeIfAbsent(fieldKey, fk ->
Locator.tagWithClass("div", "row")
// TODO: Shouldn't need to be case-insensitive. Parent/source lookups have weird casing
.withChild(Locator.tagWithAttributeIgnoreCase("label", "for", fieldKey))
.withDescendant(Locator.tagWithAttributeIgnoreCase("label", "for", fieldKey))
.findElement(this));
}

public FilteringReactSelect selectionField(CharSequence fieldIdentifier)
{
return new FilteringReactSelect(formRow(fieldIdentifier), getDriver());
}

public Checkbox checkBox(CharSequence fieldIdentifier)
{
WebElement row = formRow(fieldIdentifier);
return new Checkbox(checkBoxLoc.findElement(row));
}

public Input textInput(CharSequence fieldIdentifier)
{
WebElement inputEl = textInputLoc.findElement(formRow(fieldIdentifier));
return new Input(inputEl, getDriver());
}

public Input textArea(CharSequence fieldIdentifier)
{
WebElement inputEl = Locator.tag("textarea").findElement(formRow(fieldIdentifier));
return new Input(inputEl, getDriver());
}

public ReactDateTimePicker dateInput(CharSequence fieldIdentifier)
{
return new ReactDateTimePicker.ReactDateTimeInputFinder(getDriver()).find(formRow(fieldIdentifier));
}

public FileAttachmentContainer fileUploadField(CharSequence fieldIdentifier)
{
return new FileAttachmentContainer(formRow(fieldIdentifier), getDriver());
}

public List<WebElement> fieldLabels()
{
return fieldLabels.findElements(this);
Expand Down Expand Up @@ -533,9 +465,6 @@ public List<WebElement> fieldLabels()
WebElement alert = Locator.tagWithClassContaining("div", "alert-danger")
.findWhenNeeded(getComponentElement());

final Locator textInputLoc = Locator.tagWithAttribute("input", "type", "text");
final Locator checkBoxLoc = Locator.tagWithAttribute("input", "type", "checkbox");
final Locator fieldLabels = Locator.tag("hr").followingSibling("div").childTag("label");
final Locator fieldLabels = Locator.tag("hr").followingSibling("div").child(Locator.byClass("control-label"));
}

}
Loading
Loading