diff --git a/src/org/labkey/test/components/bootstrap/ModalDialog.java b/src/org/labkey/test/components/bootstrap/ModalDialog.java index f7b4cc4b80..dd970afa2c 100644 --- a/src/org/labkey/test/components/bootstrap/ModalDialog.java +++ b/src/org/labkey/test/components/bootstrap/ModalDialog.java @@ -15,6 +15,7 @@ */ package org.labkey.test.components.bootstrap; +import org.labkey.test.BootstrapLocators; import org.labkey.test.Locator; import org.labkey.test.WebDriverWrapper; import org.labkey.test.components.Component; @@ -67,7 +68,8 @@ protected void waitForReady() protected void waitForReady(ElementCache ec) { elementCache().body.isDisplayed(); // Make sure timeout doesn't get used up by waiting for the dialog to appear - WebDriverWrapper.waitFor(() -> elementCache().body.getText().length() > 0, "Modal dialog not ready", 2000); + WebDriverWrapper.waitFor(() -> !BootstrapLocators.loadingSpinner.areAnyVisible(getDriver()) && + elementCache().body.getText().length() > 0, "Modal dialog not ready.", 2_500); } @Override diff --git a/src/org/labkey/test/components/react/BaseReactSelect.java b/src/org/labkey/test/components/react/BaseReactSelect.java index e3df15bfae..145a939d3b 100644 --- a/src/org/labkey/test/components/react/BaseReactSelect.java +++ b/src/org/labkey/test/components/react/BaseReactSelect.java @@ -273,7 +273,8 @@ public T removeSelection(String value) private void attemptRemove(String value) { - WebElement removeBtn = Locators.removeMultiSelectValueButton(value).findElement(getComponentElement()); + WebElement removeBtn = Locators.removeMultiSelectValueButton(value) + .waitForElement(getComponentElement(), 1_000); removeBtn.click(); getWrapper().shortWait().until(ExpectedConditions.stalenessOf(removeBtn)); diff --git a/src/org/labkey/test/components/react/FilteringReactSelect.java b/src/org/labkey/test/components/react/FilteringReactSelect.java index 53e292f718..b98dde1345 100644 --- a/src/org/labkey/test/components/react/FilteringReactSelect.java +++ b/src/org/labkey/test/components/react/FilteringReactSelect.java @@ -94,8 +94,11 @@ public FilteringReactSelect typeAheadSelect(String value, String optionText, Str throw sere; } - if (!WebDriverWrapper.waitFor(()-> !isExpanded(), 1500)) // give it a moment to close, blur if it hasn't + if (Boolean.FALSE.equals(WebDriverWrapper.waitFor(()-> !isExpanded(), 1500))) // give it a moment to close, blur if it hasn't { + // Adding for debugging. Trying to see if there is any correlation between this and failures where a "Save" + // button is not enabled because it did not see the change event in the select control. + log("Firing the blur event on the input control for the react select."); getWrapper().fireEvent(elementCache().input, WebDriverWrapper.SeleniumEvent.blur); } diff --git a/src/org/labkey/test/components/ui/DeleteConfirmationDialog.java b/src/org/labkey/test/components/ui/DeleteConfirmationDialog.java index d03a089fce..e086e171d7 100644 --- a/src/org/labkey/test/components/ui/DeleteConfirmationDialog.java +++ b/src/org/labkey/test/components/ui/DeleteConfirmationDialog.java @@ -42,7 +42,7 @@ protected void waitForReady() { WebDriverWrapper.waitFor(()-> elementCache().body.isDisplayed() && !BootstrapLocators.loadingSpinner.existsIn(this), - "The 'Choose Samples to Add' dialog did not display.", 1_000); + "The delete confirmation dialog did not become ready.", 1_000); } public SourcePage cancelDelete() diff --git a/src/org/labkey/test/components/ui/domainproperties/EntityTypeDesigner.java b/src/org/labkey/test/components/ui/domainproperties/EntityTypeDesigner.java index 26e8da636e..078ba5b478 100644 --- a/src/org/labkey/test/components/ui/domainproperties/EntityTypeDesigner.java +++ b/src/org/labkey/test/components/ui/domainproperties/EntityTypeDesigner.java @@ -319,6 +319,8 @@ public T removeParentAlias(int index) public String getParentAlias(int index) { expandPropertiesPanel(); + WebDriverWrapper.waitFor(()->elementCache().parentAliases().size() > 0, + "There are no parent aliases visible.", 2_500); return elementCache().parentAlias(index).get(); } diff --git a/src/org/labkey/test/components/ui/entities/ParentEntityEditPanel.java b/src/org/labkey/test/components/ui/entities/ParentEntityEditPanel.java index 60b3b9ab07..50131d8da2 100644 --- a/src/org/labkey/test/components/ui/entities/ParentEntityEditPanel.java +++ b/src/org/labkey/test/components/ui/entities/ParentEntityEditPanel.java @@ -176,6 +176,10 @@ public void clickSave() */ public void clickSave(int waitTime) { + // Making changes, like to lineage, may cause a slight delay before the save button is enabled. + WebDriverWrapper.waitFor(()->elementCache().button("Save").isEnabled(), + "Save button is not enabled.", 2_500); + // The wait time is used here to validate the panel exits edit mode. clickButtonWaitForPanel(elementCache() .button("Save"), @@ -359,13 +363,19 @@ public ParentEntityEditPanel addParents(String typeName, List parentIds) .withNamedInput(String.format("parentEntityValue_%s", typeName)) .waitFor(this); + // Adding for debugging (trying to understand why save button is not enabled after setting). + getWrapper().log(String.format("Selections before adding: %s", selectParent.getSelections())); + for (String id : parentIds) { - int selCount = selectParent.getSelections().size(); selectParent.typeAheadSelect(id); - WebDriverWrapper.waitFor(()-> selectParent.getSelections().size() > selCount, 500); + WebDriverWrapper.waitFor(()-> selectParent.getSelections().contains(id) , + String.format("Parent '%s' was not added to the list.", parentIds), 2_500); } + // Adding for debugging (trying to understand why save button is not enabled after setting). + getWrapper().log(String.format("Selections after adding: %s", selectParent.getSelections())); + return this; } diff --git a/src/org/labkey/test/components/ui/grids/DetailTable.java b/src/org/labkey/test/components/ui/grids/DetailTable.java index 6f3785e294..4ca466c757 100644 --- a/src/org/labkey/test/components/ui/grids/DetailTable.java +++ b/src/org/labkey/test/components/ui/grids/DetailTable.java @@ -1,6 +1,7 @@ package org.labkey.test.components.ui.grids; import org.labkey.test.Locator; +import org.labkey.test.WebDriverWrapper; import org.labkey.test.components.Component; import org.labkey.test.components.WebDriverComponent; import org.openqa.selenium.By; @@ -120,9 +121,15 @@ public String getFieldValueByKey(String fieldKey) **/ public void clickField(String fieldCaption) { + String urlBefore = getWrapper().getCurrentRelativeURL().toLowerCase(); + // Should not click the container, it could be a td which would miss the clickable element. // Maybe this shouldn't assume an anchor but should be a generic(*)? Locator.tag("a").waitForElement(getField(fieldCaption), 1500).click(); + + WebDriverWrapper.waitFor(()->!urlBefore.equals(getWrapper().getCurrentRelativeURL().toLowerCase()), + String.format("Clicking field (link) '%s' did not navigate.", fieldCaption), 500); + } /** diff --git a/src/org/labkey/test/components/ui/grids/ResponsiveGrid.java b/src/org/labkey/test/components/ui/grids/ResponsiveGrid.java index fac2f820d5..fb9f723d8a 100644 --- a/src/org/labkey/test/components/ui/grids/ResponsiveGrid.java +++ b/src/org/labkey/test/components/ui/grids/ResponsiveGrid.java @@ -16,6 +16,7 @@ import org.labkey.test.components.ui.search.FilterExpressionPanel; import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.NotFoundException; +import org.openqa.selenium.StaleElementReferenceException; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedConditions; @@ -60,9 +61,10 @@ public WebElement getComponentElement() public Boolean isLoaded() { return getComponentElement().isDisplayed() && - !Locators.loadingGrid.existsIn(this) && + (!Locators.loadingGrid.existsIn(this) && !Locators.spinner.existsIn(this) && - Locator.tag("td").existsIn(this); + Locator.tag("td").existsIn(this)) || + getGridEmptyMessage().isPresent(); } protected void waitForLoaded() @@ -157,20 +159,22 @@ public T filterColumn(String columnLabel, Filter.Operator operator) public T filterColumn(String columnLabel, Filter.Operator operator, Object value) { T _this = getThis(); - GridFilterModal filterModal = initFilterColumn(columnLabel, operator, value); - filterModal.confirm(); + doAndWaitForUpdate(()->initFilterColumn(columnLabel, operator, value).confirm()); return _this; } public T filterColumn(String columnLabel, Filter.Operator operator1, Object value1, Filter.Operator operator2, Object value2) { T _this = getThis(); - GridFilterModal filterModal = initFilterColumn(columnLabel, null, null); - filterModal.selectExpressionTab().setFilters( - new FilterExpressionPanel.Expression(operator1, value1), - new FilterExpressionPanel.Expression(operator2, value2) - ); - filterModal.confirm(); + doAndWaitForUpdate(()-> { + GridFilterModal filterModal = initFilterColumn(columnLabel, null, null); + filterModal.selectExpressionTab().setFilters( + new FilterExpressionPanel.Expression(operator1, value1), + new FilterExpressionPanel.Expression(operator2, value2) + ); + filterModal.confirm(); + }); + return _this; } @@ -676,10 +680,18 @@ public Optional getGridEmptyMessage() { Optional msg = Optional.empty(); - WebElement tr = Locator.tagWithClass("tr", "grid-empty").refindWhenNeeded(this); - if(tr.isDisplayed()) + try + { + WebElement tr = Locator.tagWithClass("tr", "grid-empty").refindWhenNeeded(this); + if (tr.isDisplayed()) + { + msg = Optional.of(Locator.tag("td").findElement(tr).getText()); + } + } + catch (StaleElementReferenceException stale) { - msg = Optional.of(Locator.tag("td").findElement(tr).getText()); + getWrapper().log("Grid empty message was present but has now gone stale (went away)."); + msg = Optional.empty(); } return msg; diff --git a/src/org/labkey/test/components/ui/search/ManageSampleFinderViewsModal.java b/src/org/labkey/test/components/ui/search/ManageSampleFinderViewsModal.java index 27b06618da..89a5cf4916 100644 --- a/src/org/labkey/test/components/ui/search/ManageSampleFinderViewsModal.java +++ b/src/org/labkey/test/components/ui/search/ManageSampleFinderViewsModal.java @@ -7,9 +7,9 @@ import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; +import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; public class ManageSampleFinderViewsModal extends ModalDialog { @@ -23,22 +23,32 @@ protected ManageSampleFinderViewsModal(String title, WebDriver driver) super(new ModalDialog.ModalDialogFinder(driver).withTitle(title)); } + @Override + protected void waitForReady() + { + // This will wait for something to show up in the dialog body and if it is a spinner it will wait for it to go away. + super.waitForReady(); + + // Now, specifically check for: + // An input box (to save a new image). + // Or some grey text (get this when you delete all views and the dialog is still up). + // Or a list of existing views. + WebDriverWrapper.waitFor(()-> + Locator.tag("input").refindWhenNeeded(this).isDisplayed() || + Locator.tagWithClass("div", "grey-text").refindWhenNeeded(this). isDisplayed() || + Locator.tagWithClass("div", "row").refindWhenNeeded(this). isDisplayed(), + "Save view dialog did not display in time.", + 2_500); + } + public void editViewName(String viewName) { - WebElement editIcon = getViewEditIcon(viewName); - if (editIcon != null) - { - editIcon.click(); - } + getViewEditIcon(viewName).click(); } public void deleteView(String viewName) { - WebElement deleteIcon = getViewDeleteIcon(viewName); - if (deleteIcon != null) - { - deleteIcon.click(); - } + getViewDeleteIcon(viewName).click(); } public void deleteAllViews() @@ -51,27 +61,40 @@ public void deleteAllViews() } } + private static final String viewNameLocatorXpath = "//div[@class='modal-body']//div[contains(@class,'row')]//div[1]"; + public List getViews() { - return Locator.tagWithClass("div", "small-margin-bottom") - .findElements(getComponentElement()) - .stream() - .map(WebElement::getText) - .collect(Collectors.toList()); - } + Locator viewNameLocator = Locator.xpath(viewNameLocatorXpath); + // Wait until some view shows up. + WebDriverWrapper.waitFor(()->getWrapper().isElementPresent(viewNameLocator), + "No views are present.", 2_500); - public WebElement getView(String viewName) - { - List webElements = Locator.tagWithClass("div", "small-margin-bottom") - .findElements(getComponentElement()); + List views = new ArrayList<>(); + + // Get all the view names. + List elements = viewNameLocator.findElements(this); - for (WebElement element : webElements) + getWrapper().log(String.format("Found %d views in the dialog.", elements.size())); + + for(WebElement element : elements) { - if (element.getText().equals(viewName)) - return element; + views.add(element.getText()); } - return null; + return views; + } + + public WebElement getView(String viewName) + { + // Wait until some view shows up. + Locator viewNameLocator = Locator.xpath(viewNameLocatorXpath); + WebDriverWrapper.waitFor(()->getWrapper().isElementPresent(viewNameLocator), + "No views are present.", 2_500); + + // Get the row for the given view. + return Locator.xpath(String.format("//div[text()='%s']/parent::div[contains(@class,'row')]", viewName)) + .findElement(this); } public WebElement getViewIcon(String reportName, String iconCls) @@ -113,6 +136,8 @@ public String getInputValue() public ManageSampleFinderViewsModal setName(String name) { WebElement input = elementCache().nameInput; + WebDriverWrapper.waitFor(input::isDisplayed, "Name input field not visible.", 2_500); + getWrapper().actionClear(input); input.sendKeys(name); input.sendKeys(Keys.TAB); @@ -126,7 +151,7 @@ public void clickDone() public String getErrorMsg() { - if(WebDriverWrapper.waitFor(()->elementCache().errorMsg.isDisplayed(), 1_000)) + if(Boolean.TRUE.equals(WebDriverWrapper.waitFor(()->elementCache().errorMsg.isDisplayed(), 1_000))) return elementCache().errorMsg.getText(); else return "";