diff --git a/modules/simpletest/resources/scripts/validationTest/actionUrlTest.js b/modules/simpletest/resources/scripts/validationTest/actionUrlTest.js index d26732f654..22cdfe5a6f 100644 --- a/modules/simpletest/resources/scripts/validationTest/actionUrlTest.js +++ b/modules/simpletest/resources/scripts/validationTest/actionUrlTest.js @@ -21,7 +21,9 @@ function doTest() if (contextPath.length > 0) { var baseUrl = LABKEY.ActionURL.getBaseURL(); - if (!baseUrl.endsWith(contextPath + "/")) + var suffix = contextPath + "/"; + var idx = baseUrl.indexOf(suffix); + if (idx === -1 || idx !== baseUrl.length - suffix.length) errors[errors.length] = new Error("ActionURL.getBaseURL() = " + baseUrl); } diff --git a/src/org/labkey/test/LabKeySiteWrapper.java b/src/org/labkey/test/LabKeySiteWrapper.java index a118aece2c..ee5863824e 100644 --- a/src/org/labkey/test/LabKeySiteWrapper.java +++ b/src/org/labkey/test/LabKeySiteWrapper.java @@ -405,11 +405,14 @@ public void attemptSignIn(String email, String password) setFormElement(Locator.id("email"), email); setFormElement(Locator.id("password"), password); WebElement signInButton = Locator.lkButton("Sign In").findElement(getDriver()); - signInButton.click(); - shortWait().until(ExpectedConditions.invisibilityOfElementLocated(Locator.byClass("signing-in-msg"))); - shortWait().until(ExpectedConditions.or( - ExpectedConditions.stalenessOf(signInButton), // Successful login - ExpectedConditions.presenceOfElementLocated(Locators.labkeyError.withText()))); // Error during sign-in + doAndMaybeWaitForPageToLoad(10_000, () -> { + signInButton.click(); + shortWait().until(ExpectedConditions.invisibilityOfElementLocated(Locator.byClass("signing-in-msg"))); + shortWait().until(ExpectedConditions.or( + ExpectedConditions.stalenessOf(signInButton), // Successful login + ExpectedConditions.presenceOfElementLocated(Locators.labkeyError.withText()))); // Error during sign-in + return ExpectedConditions.stalenessOf(signInButton).apply(null); + }); } public void signInShouldFail(String email, String password, String... expectedMessages) diff --git a/src/org/labkey/test/Locators.java b/src/org/labkey/test/Locators.java index ec7ec8fdb4..9140833f14 100644 --- a/src/org/labkey/test/Locators.java +++ b/src/org/labkey/test/Locators.java @@ -30,6 +30,7 @@ private Locators() { } public static final Locator.XPathLocator folderTab = Locator.tagWithClass("div", "lk-nav-tabs-ct").append(Locator.tagWithClass("ul", "lk-nav-tabs")).childTag("li"); public static final Locator.XPathLocator panelWebpartTitle = Locator.byClass("labkey-wp-title-text"); public static final Locator.XPathLocator folderTitle = Locator.tagWithClass("a", "lk-body-title-folder"); + public static final Locator.XPathLocator loadingSpinner = Locator.byClass("fa-spinner"); public static Locator.XPathLocator headerContainer() { diff --git a/src/org/labkey/test/TestScrubber.java b/src/org/labkey/test/TestScrubber.java index 82f19b110a..4c12237c11 100644 --- a/src/org/labkey/test/TestScrubber.java +++ b/src/org/labkey/test/TestScrubber.java @@ -20,6 +20,7 @@ import org.labkey.remoteapi.Connection; import org.labkey.remoteapi.SimplePostCommand; import org.labkey.test.components.html.Checkbox; +import org.labkey.test.components.pipeline.PipelineTriggerWizard; import org.labkey.test.pages.core.admin.AllowedFileExtensionAdminPage; import org.labkey.test.pages.core.admin.BaseSettingsPage; import org.labkey.test.pages.core.admin.ConfigureFileSystemAccessPage; @@ -160,6 +161,16 @@ public void cleanSiteSettings() TestLogger.error("Failed to reset site look and feel properties after test.", e); } + try + { + // Disable all pipeline triggers so that they don't show up as memory leaks in subsequent tests. + PipelineTriggerWizard.disableAllPipelineTriggers(createDefaultConnection()); + } + catch (Exception e) + { + TestLogger.error("Failed to disable pipeline triggers after test", e); + } + } @LogMethod(quiet = true) diff --git a/src/org/labkey/test/WebDriverWrapper.java b/src/org/labkey/test/WebDriverWrapper.java index 7d1d4b6e63..b51b78bfc1 100644 --- a/src/org/labkey/test/WebDriverWrapper.java +++ b/src/org/labkey/test/WebDriverWrapper.java @@ -3000,14 +3000,17 @@ public void selectFolderTreeItem(String folderName) /** * Move mouse to the upper left corner of the document to dismiss tooltips and the like - * Will scroll page if necessary */ public void mouseOut() { try { - scrollToTop(); - new Actions(getDriver()).moveToLocation(0, 0).perform(); + new Actions(getDriver()) + .moveToLocation(0, 0) + // Add a little wiggle to make sure tooltips notice + .moveByOffset(4, 4) + .moveByOffset(-2, -2) + .perform(); } catch (WebDriverException ignore) { } } diff --git a/src/org/labkey/test/components/domain/DomainDesigner.java b/src/org/labkey/test/components/domain/DomainDesigner.java index 4c67a177da..dbdb95fe6b 100644 --- a/src/org/labkey/test/components/domain/DomainDesigner.java +++ b/src/org/labkey/test/components/domain/DomainDesigner.java @@ -48,7 +48,7 @@ public class ElementCache extends BaseDomainDesigner.ElementCache protected final DomainPanel propertiesPanel = new DomainPanel.DomainPanelFinder(getDriver()).index(0) .timeout(WAIT_FOR_JAVASCRIPT).findWhenNeeded(this); protected final DomainFormPanel fieldsPanel = new DomainFormPanel.DomainFormPanelFinder(getDriver()) - .index(getFieldPanelIndex()).timeout(1000).findWhenNeeded(); + .index(getFieldPanelIndex()).timeout(2_000).findWhenNeeded(); protected int getFieldPanelIndex() { diff --git a/src/org/labkey/test/components/pipeline/PipelineTriggerWizard.java b/src/org/labkey/test/components/pipeline/PipelineTriggerWizard.java index 6cb734f92d..65dd4320e0 100644 --- a/src/org/labkey/test/components/pipeline/PipelineTriggerWizard.java +++ b/src/org/labkey/test/components/pipeline/PipelineTriggerWizard.java @@ -16,6 +16,10 @@ package org.labkey.test.components.pipeline; import org.jetbrains.annotations.NotNull; +import org.labkey.remoteapi.CommandException; +import org.labkey.remoteapi.Connection; +import org.labkey.remoteapi.query.ContainerFilter; +import org.labkey.remoteapi.query.Filter; import org.labkey.test.Locator; import org.labkey.test.WebDriverWrapper; import org.labkey.test.WebTestHelper; @@ -24,11 +28,14 @@ import org.labkey.test.components.html.Checkbox; import org.labkey.test.components.html.Input; import org.labkey.test.components.html.OptionSelect; +import org.labkey.test.util.query.QueryApiHelper; import org.openqa.selenium.Alert; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedConditions; +import java.io.IOException; +import java.util.List; import java.util.Map; import static org.junit.Assert.assertTrue; @@ -61,6 +68,18 @@ public static PipelineTriggerWizard beginAt(WebDriverWrapper driver, String cont return new PipelineTriggerWizard(driver.getDriver()); } + public static void disableAllPipelineTriggers(Connection connection) throws IOException, CommandException + { + QueryApiHelper queryApiHelper = new QueryApiHelper(connection, "/", "pipeline", "TriggerConfigurations"); + List> triggers = queryApiHelper.selectRows(List.of("rowId", "enabled"), + List.of(new Filter("enabled", true)), List.of(), ContainerFilter.AllFolders).getRows(); + if (!triggers.isEmpty()) + { + triggers.forEach(trigger -> trigger.put("enabled", false)); + queryApiHelper.updateRows(triggers); + } + } + @Override public WebElement getComponentElement() { diff --git a/src/org/labkey/test/components/ui/grids/EditableGrid.java b/src/org/labkey/test/components/ui/grids/EditableGrid.java index 2e7dba6535..15a33ee79e 100644 --- a/src/org/labkey/test/components/ui/grids/EditableGrid.java +++ b/src/org/labkey/test/components/ui/grids/EditableGrid.java @@ -1160,6 +1160,8 @@ public String getErrorPopoverText(int row, CharSequence columnIdentifier) */ public String getCellPopoverText(int row, CharSequence columnIdentifier) { + dismissPopover(); // Other popovers can block the target cell + getWrapper().mouseOver(Locator.tag("td").findElement(getRow(row))); // Avoid passing over any header cells on the way to the target cell WebElement cellDiv = Locator.tagWithClass("div", "cellular-display").findElement(getCell(row, columnIdentifier)); getWrapper().mouseOver(cellDiv); // cause the tooltip to be present return Optional.ofNullable(WebDriverWrapper.waitFor(()-> Locators.popover.findElementOrNull(getDriver()), 1000)) @@ -1170,7 +1172,9 @@ public String getCellPopoverText(int row, CharSequence columnIdentifier) public void dismissPopover() { Locators.popover.findOptionalElement(getDriver()).ifPresent(popover -> { + getWrapper().mouseOver(popover); getWrapper().mouseOut(); + getWrapper().mouseOver(elementCache().getGridHeaderManager().getColumnHeader(0).getElement()); getWrapper().shortWait().until(ExpectedConditions.invisibilityOf(popover)); }); } diff --git a/src/org/labkey/test/components/ui/grids/FieldSelectionDialog.java b/src/org/labkey/test/components/ui/grids/FieldSelectionDialog.java index 994ee13f92..35d0a8140c 100644 --- a/src/org/labkey/test/components/ui/grids/FieldSelectionDialog.java +++ b/src/org/labkey/test/components/ui/grids/FieldSelectionDialog.java @@ -400,6 +400,7 @@ public FieldSelectionDialog removeAllSelectedFields() continue; } + getWrapper().mouseOver(removeIcon); removeIcon.click(); } diff --git a/src/org/labkey/test/components/ui/grids/QueryGrid.java b/src/org/labkey/test/components/ui/grids/QueryGrid.java index 6ede621b9d..b1ad592b0e 100644 --- a/src/org/labkey/test/components/ui/grids/QueryGrid.java +++ b/src/org/labkey/test/components/ui/grids/QueryGrid.java @@ -221,18 +221,18 @@ public QueryGrid waitForRecordCount(int expectedCount, int milliseconds) @Override public void doAndWaitForUpdate(Runnable func) { - waitForLoaded(); - Optional optionalStatus = elementCache().selectionStatusContainerLoc.findOptionalElement(elementCache()); + super.doAndWaitForUpdate(() -> + { + WebElement status = hasSelectColumn() ? Locators.selectionStatusContainerLoc.waitForElement(this, 5_000) : null; - func.run(); + func.run(); - optionalStatus.ifPresent(el -> { - getWrapper().shortWait().until(ExpectedConditions.stalenessOf(el)); - elementCache().selectionStatusContainerLoc.waitForElement(this, 5_000); + if (status != null) + { + getWrapper().shortWait().until(ExpectedConditions.stalenessOf(status)); + Locators.selectionStatusContainerLoc.waitForElement(this, 5_000); + } }); - - waitForLoaded(); - clearElementCache(); } @@ -789,19 +789,24 @@ protected ElementCache elementCache() return (ElementCache) super.elementCache(); } + protected static class Locators + { + static final Locator.XPathLocator selectionStatusContainerLoc = Locator.byClass("selection-status"); + } + protected class ElementCache extends ResponsiveGrid.ElementCache { + final GridBar gridBar = new GridBar.GridBarFinder().findWhenNeeded(QueryGrid.this); - WebElement saveViewButton = Locator.button("Save").findWhenNeeded(getDriver()); + final WebElement saveViewButton = Locator.button("Save").findWhenNeeded(getDriver()); final BootstrapMenu viewMenu = new MultiMenu.MultiMenuFinder(getDriver()).withText("Views").findWhenNeeded(this); - final Locator.XPathLocator selectionStatusContainerLoc = Locator.tagWithClass("div", "selection-status"); - final Locator selectAllBtnLoc = selectionStatusContainerLoc.append(Locator.tagWithClass("span", "selection-status__select-all") - .child(Locator.buttonContainingText("Select"))); - final Locator clearBtnLoc = selectionStatusContainerLoc.append(Locator.byClass("selection-status__clear-all") - .child(Locator.tag("button"))); + final Locator selectAllBtnLoc = Locators.selectionStatusContainerLoc.append(Locator.byClass("selection-status__select-all") + .childTag("button")); + final Locator clearBtnLoc = Locators.selectionStatusContainerLoc.append(Locator.byClass("selection-status__clear-all") + .childTag("button")); final WebElement filterStatusPanel = Locator.css("div.grid-panel__filter-status").findWhenNeeded(this); diff --git a/src/org/labkey/test/components/ui/grids/ResponsiveGrid.java b/src/org/labkey/test/components/ui/grids/ResponsiveGrid.java index 199f30c04e..5242306ec2 100644 --- a/src/org/labkey/test/components/ui/grids/ResponsiveGrid.java +++ b/src/org/labkey/test/components/ui/grids/ResponsiveGrid.java @@ -70,8 +70,8 @@ public Boolean isLoaded() return getComponentElement().isDisplayed() && !Locators.loadingGrid.existsIn(this) && !Locators.spinner.existsIn(this) && - (Locator.tag("td").existsIn(this) || - getGridEmptyMessage().isPresent()); + (Locator.tag("td").existsIn(this) || + getGridEmptyMessage().isPresent()); } protected void waitForLoaded() @@ -82,6 +82,8 @@ protected void waitForLoaded() @Override public void doAndWaitForUpdate(Runnable func) { + waitForLoaded(); + // Look at WebDriverWrapper.doAndWaitForElementToRefresh for an example. func.run(); @@ -838,7 +840,7 @@ protected boolean hasSelectColumn() return hasSelectColumn; } - ReactCheckBox selectAllCheckbox = new ReactCheckBox(Locator.xpath("//th/input[@type='checkbox']").findWhenNeeded(this)) + final ReactCheckBox selectAllCheckbox = new ReactCheckBox(Locator.xpath("//th/input[@type='checkbox']").findWhenNeeded(this)) { @Override public void toggle() diff --git a/src/org/labkey/test/components/ui/lineage/LineageGraph.java b/src/org/labkey/test/components/ui/lineage/LineageGraph.java index 2dd8384866..7f25c466ed 100644 --- a/src/org/labkey/test/components/ui/lineage/LineageGraph.java +++ b/src/org/labkey/test/components/ui/lineage/LineageGraph.java @@ -1,6 +1,8 @@ package org.labkey.test.components.ui.lineage; import org.labkey.test.Locator; +import org.labkey.test.Locators; +import org.labkey.test.WebDriverWrapper; import org.labkey.test.components.Component; import org.labkey.test.components.WebDriverComponent; import org.labkey.test.components.react.Tabs; @@ -48,6 +50,16 @@ public static LineageGraph showLineageGraph(WebDriver driver) return lineageGraph; } + @Override + protected void waitForReady() + { + WebDriverWrapper.waitFor(()-> + elementCache().visGraphContainer.isDisplayed() && + elementCache().nodeDetailContainer.isDisplayed() && + !Locators.loadingSpinner.existsIn(this), + "lineage graph did not load in time", 5_000); + } + public Map getCurrentNodeData() { return elementCache().detailTable().getTableDataByLabel(); @@ -178,16 +190,16 @@ final WebElement lineageItem(String name, WebElement tabPanel) // container for the details of the currently-selected node final WebElement nodeDetailContainer = Locator.tagWithClass("div", "lineage-node-detail-container") .findWhenNeeded(this).withTimeout(4000); - WebElement componentDetailImage = Locator.tagWithClass("i", "component-detail--child--img") + final WebElement componentDetailImage = Locator.tagWithClass("i", "component-detail--child--img") .child(Locator.tag("img")).findWhenNeeded(nodeDetailContainer); - WebElement nodeDetailName = Locator.tagWithClass("h4", "lineage-name-data") + final WebElement nodeDetailName = Locator.tagWithClass("h4", "lineage-name-data") .findWhenNeeded(nodeDetailContainer); - WebElement nodeDetailLinksContainer = Locator.tagWithClass("div", "lineage-node-detail") + final WebElement nodeDetailLinksContainer = Locator.tagWithClass("div", "lineage-node-detail") .findWhenNeeded(nodeDetailContainer); - WebElement nodeOverviewLink = Locator.linkWithSpan("Overview").withClass("lineage-data-link--text") + final WebElement nodeOverviewLink = Locator.linkWithSpan("Overview").withClass("lineage-data-link--text") .findWhenNeeded(nodeDetailLinksContainer); - Locator lineageLinkLoc = Locator.linkWithSpan("Lineage").withClass("lineage-data-link--text"); - WebElement nodeLineageLink = lineageLinkLoc.findWhenNeeded(nodeDetailLinksContainer); + final Locator lineageLinkLoc = Locator.linkWithSpan("Lineage").withClass("lineage-data-link--text"); + final WebElement nodeLineageLink = lineageLinkLoc.findWhenNeeded(nodeDetailLinksContainer); Tabs nodeDetailsTabs() { return new Tabs.TabsFinder(getDriver()).findWhenNeeded(nodeDetailContainer); @@ -198,9 +210,9 @@ DetailTable detailTable() return new DetailTable.DetailTableFinder(getDriver()).waitFor(nodeDetailContainer); } - WebElement nodeDetails = Locator.tagWithClass("div", "lineage-node-detail") + final WebElement nodeDetails = Locator.tagWithClass("div", "lineage-node-detail") .findWhenNeeded(nodeDetailContainer).withTimeout(3000); - WebElement nodeDetailsName = Locator.tagWithClass("div", "lineage-name-data") + final WebElement nodeDetailsName = Locator.tagWithClass("div", "lineage-name-data") .findWhenNeeded(nodeDetails); NodeDetailGroup summaryList(String nodeLabel) diff --git a/src/org/labkey/test/params/property/DomainProps.java b/src/org/labkey/test/params/property/DomainProps.java index 6b5ddc3193..34027f712d 100644 --- a/src/org/labkey/test/params/property/DomainProps.java +++ b/src/org/labkey/test/params/property/DomainProps.java @@ -36,10 +36,9 @@ public DomainResponse execute(Connection connection, String folderPath) throws I DomainResponse response = super.execute(connection, folderPath); - TestLogger.log("Successfully created domain, '%s':\n%s" - .formatted( - response.getDomain().getName(), - response.getDomain().toJSONObject().toString(2))); + TestLogger.log().debug("Successfully created domain, '{}':\n{}", + () -> response.getDomain().getName(), + () -> response.getDomain().toJSONObject().toString(2)); return response; } diff --git a/src/org/labkey/test/tests/ExpTest.java b/src/org/labkey/test/tests/ExpTest.java index 4e6dc9febe..f3649ced84 100644 --- a/src/org/labkey/test/tests/ExpTest.java +++ b/src/org/labkey/test/tests/ExpTest.java @@ -87,12 +87,12 @@ public void testSteps() }); clickButton("Setup"); // Don't upload file. Uploading file creates 'exp.Data' entries that we don't want. - setPipelineRoot(TestFileUtils.getSampleData("xarfiles/expVerify").getAbsolutePath()); + setPipelineRoot(TestFileUtils.getSampleData("xarfiles/expVerify").getParentFile().getAbsolutePath()); clickFolder(FOLDER_NAME); clickButton("Process and Import Data"); - _fileBrowserHelper.importFile("experiment.xar.xml", "Import Experiment"); + _fileBrowserHelper.importFile("/expVerify/experiment.xar.xml", "Import Experiment"); Date importDate = new Date(); // Import timestamp will have various formats applied to it clickAndWait(Locator.linkWithText("Data Pipeline")); waitForPipelineJobsToComplete(1, false); diff --git a/src/org/labkey/test/util/DataRegion.java b/src/org/labkey/test/util/DataRegion.java index 4ff3baabaf..364809a7a5 100644 --- a/src/org/labkey/test/util/DataRegion.java +++ b/src/org/labkey/test/util/DataRegion.java @@ -37,6 +37,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.TreeMap; import java.util.regex.Pattern; @@ -190,7 +191,7 @@ protected String getTableId() { if (_tableId == null) { - String id = _el.getAttribute("id"); + String id = Objects.requireNonNull(_el.getAttribute("id"), "Table has no ID"); if (id.endsWith("-form")) _tableId = id.replace("-form", ""); else @@ -328,7 +329,7 @@ public static Locator.XPathLocator form(String regionName) } } - public class ElementCache extends Component.ElementCache + public class ElementCache extends Component.ElementCache { protected ElementCache() { @@ -412,7 +413,7 @@ protected BootstrapMenu getHeaderMenu(String text) private abstract class BaseDataRegionApi { - final String regionJS = "LABKEY.DataRegions['" + getDataRegionName().replaceAll("'", "\\\\'") + "']"; + final CachingSupplier regionJS = new CachingSupplier<>(() -> "LABKEY.DataRegions['" + getDataRegionName().replaceAll("'", "\\\\'") + "']"); public void executeScript(String methodWithArgs, Object... args) { @@ -421,7 +422,7 @@ public void executeScript(String methodWithArgs, Object... args) public T executeScript(String methodWithArgs, Class expectedResultType, Object... args) { - return getWrapper().executeScript((expectedResultType != null ? "return " : "") + regionJS + "." + methodWithArgs, expectedResultType, args); + return getWrapper().executeScript((expectedResultType != null ? "return " : "") + regionJS.get() + "." + methodWithArgs, expectedResultType, args); } public void callMethod(String apiMethodName, Object... args) @@ -459,7 +460,7 @@ public T executeScript(String methodWithArgs, Class expectedResultType, O { MutableObject result = new MutableObject<>(); doAndWaitForUpdate(() -> result.setValue(super.executeScript(methodWithArgs, expectedResultType, args))); - return result.getValue(); + return result.get(); } } }