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 2abd3f59fc..86e1c27efd 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 858a67b383..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) { } } @@ -3709,6 +3712,9 @@ private void setHtml5Input(WebElement input, String inputType, String value) case "date": setHtml5DateInput(input, value); break; + case "datetime-local": + setHtml5DateTimeInput(input, value); + break; case "password": case "search": setInput(input, value); // These don't require special handling, don't output warning @@ -3737,6 +3743,21 @@ private void setHtml5DateInput(WebElement el, String text) } } + private void setHtml5DateTimeInput(WebElement el, String text) + { + String inputFormat = "yyyy-MM-dd'T'HH:mm"; + SimpleDateFormat inputFormatter = new SimpleDateFormat(inputFormat); + + try + { + setHtml5DateTimeInput(el, inputFormatter.parse(text)); + } + catch (ParseException e) + { + throw new IllegalArgumentException("Unable to parse date " + text + ". Format should be " + inputFormat); + } + } + private void setHtml5DateInput(WebElement el, Date date) { // Firefox requires ISO date format (yyyy-MM-dd) @@ -3749,6 +3770,18 @@ private void setHtml5DateInput(WebElement el, Date date) el.sendKeys(formDate); } + private void setHtml5DateTimeInput(WebElement el, Date date) + { + // Firefox and Chrome want different formats, neither of which align with all online guidance to use ISO-style + String formFormat = isFirefox() ? "MMddyyyy hh:mm a" : "MM-dd-yyyy'\t'hh:mma"; + SimpleDateFormat formFormatter = new SimpleDateFormat(formFormat); + String formDate = formFormatter.format(date); + + fireEvent(el, SeleniumEvent.focus); + executeScript("arguments[0].value = ''", el); + el.sendKeys(formDate); + } + private void setHtml5NumberInput(WebElement el, String text) { 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/domain/DomainFormPanel.java b/src/org/labkey/test/components/domain/DomainFormPanel.java index 615622a4df..c71f237e18 100644 --- a/src/org/labkey/test/components/domain/DomainFormPanel.java +++ b/src/org/labkey/test/components/domain/DomainFormPanel.java @@ -47,7 +47,7 @@ private DomainFormPanel(WebElement element, WebDriver driver) @Override protected void waitForReady() { - waitFor(() -> !BootstrapLocators.loadingSpinner.existsIn(this), "Loading spinner still present", 2_000); + waitFor(() -> !BootstrapLocators.loadingSpinner.existsIn(this), "Loading spinner still present", 10_000); } public static List advancedSettingsFromFieldDefinition(FieldDefinition def) 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/react/QueryChartPanel.java b/src/org/labkey/test/components/react/QueryChartPanel.java index 9ba0135e58..4aa22782cf 100644 --- a/src/org/labkey/test/components/react/QueryChartPanel.java +++ b/src/org/labkey/test/components/react/QueryChartPanel.java @@ -3,8 +3,8 @@ import org.labkey.test.Locator; import org.labkey.test.components.Component; import org.labkey.test.components.WebDriverComponent; -import org.labkey.test.components.html.BootstrapMenu; import org.labkey.test.components.ui.grids.QueryGrid; +import org.labkey.test.components.ui.grids.ResponsiveGrid; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedConditions; @@ -38,11 +38,7 @@ public QueryChartDialog clickEdit() public File clickExport(String subMenuText) { - elementCache().exportMenu.expand(); - return getWrapper().doAndWaitForDownload(() -> - Locator.tagWithClass("li", "lk-menu-item") - .descendant(Locator.tagContainingText("a", subMenuText)) - .findElement(elementCache().headingEl).click()); + return getWrapper().doAndWaitForDownload(() -> elementCache().exportMenu.doMenuAction(subMenuText)); } public String getTitle() @@ -55,13 +51,27 @@ public WebElement getSvgChart() return Locator.byClass("svg-chart__chart").childTag("svg").waitForElement(this, WAIT_FOR_JAVASCRIPT); } - public QueryGrid clickClose() + public ResponsiveGrid getCurveStatsGrid() + { + return new ResponsiveGrid.ResponsiveGridFinder(getDriver()).waitFor(elementCache().curveStatsPanel); + } + + public boolean isCurveStatsPanelPresent() + { + return ElementCache.curveStatsPanelLoc.findOptionalElement(this).isPresent(); + } + + public File exportCurveStats(String type) + { + return getWrapper().doAndWaitForDownload(() -> elementCache().exportStatsMenu.doMenuAction(type)); + } + + public void clickClose() { var btn = elementCache().closeButton; getWrapper().shortWait().until(ExpectedConditions.elementToBeClickable(btn)); btn.click(); getWrapper().shortWait().until(ExpectedConditions.stalenessOf(btn)); - return _queryGrid; } @Override @@ -88,11 +98,17 @@ protected class ElementCache extends Component.ElementCache .findWhenNeeded(this).withTimeout(2000); public final WebElement editButton = Locator.tagWithAttribute("button", "title", "Edit chart") .findWhenNeeded(headingEl); - public final BootstrapMenu exportMenu = new MultiMenu.MultiMenuFinder(getDriver()).withButtonClass("chart-panel-export-btn").findWhenNeeded(headingEl); + public final MultiMenu exportMenu = new MultiMenu.MultiMenuFinder(getDriver()) + .withButtonClass("chart-panel-export-btn") + .findWhenNeeded(headingEl); public final WebElement closeButton = Locator.tagWithAttribute("button", "title", "Hide chart") .findWhenNeeded(headingEl); public final WebElement titleElement= Locator.tagWithClass("div", "chart-panel__heading-title") .findWhenNeeded(headingEl); + public static final Locator curveStatsPanelLoc = Locator.byClass("curve-fit-statistics"); + public final WebElement curveStatsPanel = curveStatsPanelLoc.findWhenNeeded(this).withTimeout(WAIT_FOR_JAVASCRIPT); + public final WebElement curveStatsHeader = Locator.byClass("curve-fit-statistics__header").findWhenNeeded(curveStatsPanel); + public final MultiMenu exportStatsMenu = new MultiMenu.MultiMenuFinder(getDriver()).findWhenNeeded(curveStatsHeader); } 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 e8bfad7634..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() @@ -995,6 +997,12 @@ public ResponsiveGridFinder inParentWithId(String id) return this; } + public ResponsiveGridFinder inParentWithClass(String className) + { + _locator = Locator.byClass(className).child(Locators.responsiveGrid()); + return this; + } + public ResponsiveGridFinder withGridId(String id) { _locator = Locators.responsiveGrid(id); 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/pages/core/admin/ShowAdminPage.java b/src/org/labkey/test/pages/core/admin/ShowAdminPage.java index 0df5e6ea13..7f448e18b6 100644 --- a/src/org/labkey/test/pages/core/admin/ShowAdminPage.java +++ b/src/org/labkey/test/pages/core/admin/ShowAdminPage.java @@ -251,6 +251,11 @@ public void clickPostgresLocks() clickSettingsLink("postgres locks"); } + public void clickPostgresTableSizes() + { + clickSettingsLink("postgres table sizes"); + } + public List getAllAdminConsoleLinks() { goToSettingsSection(); 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/tests/PostgresQueriesTest.java b/src/org/labkey/test/tests/PostgresQueriesTest.java index dc1838e43b..bce53b2f03 100644 --- a/src/org/labkey/test/tests/PostgresQueriesTest.java +++ b/src/org/labkey/test/tests/PostgresQueriesTest.java @@ -66,6 +66,9 @@ public void testQueries() throws IOException, CommandException goToAdminConsole().clickPostgresLocks(); verifyLocksGrid(); + // Verify locks grid as site admin + goToAdminConsole().clickPostgresTableSizes(); + verifyTableSizesGrid(); // Verify project admin gets a 401 for locks grid pushLocation(); @@ -150,6 +153,17 @@ private void verifyLocksGrid() Assertions.assertThat(cols).as("pg_locks columns").contains("Locktype", "Virtualtransaction"); } + private void verifyTableSizesGrid() + { + assertTextPresent("pg_tablesizes"); + DataRegionTable table = new DataRegionTable("query", this); + List cols = table.getColumnLabels(); + Assertions.assertThat(cols).as("pg_tablesizes columns").contains("Table Schema", "Table Name", "Table Size", "Index Size", "Total Size"); + // Check a couple of expected tables + table.setFilter("table_schema", "Equals", "audit"); + assertTextPresent("queryupdateauditdomain", "userauditdomain"); + } + private void verifyActivityGrid(boolean expectDelete) { assertTextPresent("pg_stat_activity"); diff --git a/src/org/labkey/test/tests/visualization/GenericChartsTest.java b/src/org/labkey/test/tests/visualization/GenericChartsTest.java index 1e810331dc..8858b3cf43 100644 --- a/src/org/labkey/test/tests/visualization/GenericChartsTest.java +++ b/src/org/labkey/test/tests/visualization/GenericChartsTest.java @@ -22,17 +22,22 @@ import org.labkey.test.components.ChartLayoutDialog; import org.labkey.test.pages.TimeChartWizard; import org.labkey.test.tests.ReportTest; +import org.labkey.test.util.CodeMirrorHelper; import org.labkey.test.util.Ext4Helper; import org.labkey.test.util.LogMethod; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedConditions; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import static org.hamcrest.number.OrderingComparison.greaterThanOrEqualTo; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -158,11 +163,21 @@ protected Map export(String type, String xAxis, String yAxis) log("Export as PNG"); File png = clickExportPNGIcon("chart-render-div", 0); exported.put("png", png); + try + { + BufferedImage image = ImageIO.read(png); + // Issue 53390: Improve resolution of PNG exports + checker().verifyThat("Exported PNG height", image.getHeight(), greaterThanOrEqualTo(2000)); + } + catch (IOException e) + { + checker().recordError(new RuntimeException("Failed to read exported PNG: " + png, e)); + } log("Export to script."); Assert.assertEquals("Unexpected number of export script icons", 1, getExportScriptIconCount("chart-render-div")); clickExportScriptIcon("chart-render-div", 0); - String exportScript = _extHelper.getCodeMirrorValue("export-script-textarea"); + String exportScript = new CodeMirrorHelper(this, "export-script-textarea").getCodeMirrorValue(); log("Validate that the script is as expected."); assertTrue("Script did not contain expected text: '" + type + "' ", exportScript.toLowerCase().contains(type.toLowerCase())); diff --git a/src/org/labkey/test/util/DataRegion.java b/src/org/labkey/test/util/DataRegion.java index 83304f6299..0bf85649a5 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 @@ -334,7 +335,7 @@ public static Locator.XPathLocator form(String regionName) } } - public class ElementCache extends Component.ElementCache + public class ElementCache extends Component.ElementCache { protected ElementCache() { @@ -418,7 +419,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) { @@ -427,7 +428,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) @@ -465,7 +466,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(); } } }