From f833a34fb368d0e32aecc86dc47ab74746d8ad77 Mon Sep 17 00:00:00 2001 From: XingY Date: Tue, 3 Mar 2026 12:29:39 -0800 Subject: [PATCH 1/3] Making it harder to delete all rows from lists --- .../test/tests/list/ListDeleteTest.java | 388 ++++++++++++++++++ 1 file changed, 388 insertions(+) create mode 100644 src/org/labkey/test/tests/list/ListDeleteTest.java diff --git a/src/org/labkey/test/tests/list/ListDeleteTest.java b/src/org/labkey/test/tests/list/ListDeleteTest.java new file mode 100644 index 0000000000..de470569ac --- /dev/null +++ b/src/org/labkey/test/tests/list/ListDeleteTest.java @@ -0,0 +1,388 @@ +package org.labkey.test.tests.list; + +import org.apache.hc.core5.http.HttpStatus; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.labkey.api.collections.CaseInsensitiveHashMap; +import org.labkey.remoteapi.CommandException; +import org.labkey.test.BaseWebDriverTest; +import org.labkey.test.Locator; +import org.labkey.test.TestFileUtils; +import org.labkey.test.WebTestHelper; +import org.labkey.test.categories.Daily; +import org.labkey.test.categories.Data; +import org.labkey.test.categories.Hosting; +import org.labkey.test.components.list.ManageListsGrid; +import org.labkey.test.pages.list.BeginPage; +import org.labkey.test.util.DataRegionTable; +import org.labkey.test.params.FieldDefinition; +import org.labkey.test.params.list.IntListDefinition; +import org.labkey.test.params.list.VarListDefinition; +import org.labkey.test.util.DomainUtils; +import org.labkey.test.util.TestDataGenerator; +import org.labkey.test.util.TestUser; +import org.openqa.selenium.By; +import org.openqa.selenium.support.ui.ExpectedConditions; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.labkey.test.util.PermissionsHelper.EDITOR_ROLE; + +@Category({Daily.class, Data.class, Hosting.class}) +@BaseWebDriverTest.ClassTimeout(minutes = 5) +public class ListDeleteTest extends BaseWebDriverTest +{ + private static final String PROJECT_NAME = "ListDeleteTest"; + private static final String PROJECT_PATH = "/" + PROJECT_NAME; + private static final String SUBFOLDER_A_NAME = "SubfolderA"; + private static final String SUBFOLDER_A_PATH = PROJECT_PATH + "/" + SUBFOLDER_A_NAME; + private static final String SUBFOLDER_B_NAME = "SubfolderB"; + private static final String SUBFOLDER_B_PATH = PROJECT_PATH + "/" + SUBFOLDER_B_NAME; + + private static final TestUser LIST_DESIGNER_USER = new TestUser("listdesigner@listdelete.test"); + + private static final String attachmentFieldName = TestDataGenerator.randomFieldName("Attachment", null, DomainUtils.DomainKind.IntList); + private static final String booleanFieldName = TestDataGenerator.randomFieldName("Boolean", null, DomainUtils.DomainKind.IntList); + private static final String integerFieldName = TestDataGenerator.randomFieldName("Integer", null, DomainUtils.DomainKind.IntList); + private static final String stringFieldName = TestDataGenerator.randomFieldName("String", null, DomainUtils.DomainKind.IntList); + + private static final String autoIncrementKeyFieldName1 = TestDataGenerator.randomFieldName("Key", null, DomainUtils.DomainKind.IntList); + + protected static final File IMG_FILE = TestFileUtils.getSampleData("InlineImages/help.jpg"); // use in Project + protected static final File PDF_FILE = TestFileUtils.getSampleData("InlineImages/agraph.pdf"); // use in Subfolder A + protected static final File TXT_FILE = TestFileUtils.getSampleData("InlineImages/test.txt"); // use in Subfolder B + + private static IntListDefinition LIST_1; // int list with attachment column + private static VarListDefinition LIST_2; // var list with attachment column, keyed by string + + @BeforeClass + public static void setupProject() throws Exception + { + ListDeleteTest init = getCurrentTest(); + init.doSetup(); + } + + private void doSetup() throws Exception + { + // Create project and subfolders + _containerHelper.createProject(getProjectName(), null); + _containerHelper.createSubfolder(getProjectName(), SUBFOLDER_A_NAME); + _containerHelper.createSubfolder(getProjectName(), SUBFOLDER_B_NAME); + + // Create user with Assay Designer + Editor permissions + LIST_DESIGNER_USER.create(this) + .setInitialPassword() + .addPermission(EDITOR_ROLE, PROJECT_PATH) + .addPermission("Assay Designer", PROJECT_PATH) + .addPermission(EDITOR_ROLE, SUBFOLDER_A_PATH) + .addPermission("Assay Designer", SUBFOLDER_A_PATH) + .addPermission(EDITOR_ROLE, SUBFOLDER_B_PATH); + + var conn = createDefaultConnection(); + + // Create list 1 with attachment column + var list1Name = DomainUtils.DomainKind.IntList.randomName("DEL1"); + LIST_1 = (IntListDefinition) new IntListDefinition(list1Name, autoIncrementKeyFieldName1) + .setFields(List.of( + new FieldDefinition(attachmentFieldName, FieldDefinition.ColumnType.Attachment), + new FieldDefinition(booleanFieldName, FieldDefinition.ColumnType.Boolean), + new FieldDefinition(integerFieldName, FieldDefinition.ColumnType.Integer), + new FieldDefinition(stringFieldName, FieldDefinition.ColumnType.String) + )); + LIST_1.getCreateCommand().execute(conn, PROJECT_PATH); + + // Create list 2 — var list with attachment column, keyed by string + var stringKeyField = new FieldDefinition(stringFieldName); + var list2Name = DomainUtils.DomainKind.VarList.randomName("DEL2"); + LIST_2 = (VarListDefinition) new VarListDefinition(list2Name) + .setKeyName(stringKeyField.getName()) + .setFields(List.of( + stringKeyField, + new FieldDefinition(attachmentFieldName, FieldDefinition.ColumnType.Attachment), + new FieldDefinition(booleanFieldName, FieldDefinition.ColumnType.Boolean), + new FieldDefinition(integerFieldName, FieldDefinition.ColumnType.Integer) + )); + LIST_2.getCreateCommand().execute(conn, PROJECT_PATH); + + // Populate data in project folder for both lists + populateList1(PROJECT_PATH, IMG_FILE); + populateList2(PROJECT_PATH, IMG_FILE); + + // Populate data in subfolder A for both lists + populateList1(SUBFOLDER_A_PATH, PDF_FILE); + populateList2(SUBFOLDER_A_PATH, PDF_FILE); + + // Populate data in subfolder B for both lists + populateList1(SUBFOLDER_B_PATH, TXT_FILE); + populateList2(SUBFOLDER_B_PATH, TXT_FILE); + } + + private void populateList1(String containerPath, File attachment) throws IOException, CommandException + { + var dataGenerator = LIST_1.getTestDataGenerator(containerPath) + .addDataSupplier(attachmentFieldName, () -> attachment); + + // Insert rows with attachment values via UI (only way to provide attachment values) + var attachmentRows = dataGenerator.withGeneratedRows(1) + .getRows(); + + _listHelper.beginAtList(containerPath, LIST_1.getName()); + var newRow = new CaseInsensitiveHashMap<>(); + newRow.putAll(attachmentRows.getFirst()); + _listHelper.insertNewRow(newRow, false); + } + + private void populateList2(String containerPath, File attachment) throws IOException, CommandException + { + var dataGenerator = LIST_2.getTestDataGenerator(containerPath) + .addDataSupplier(stringFieldName, () -> "String" + containerPath) + .addDataSupplier(attachmentFieldName, () -> attachment); + + // Insert rows without attachments via API + var attachmentRows = dataGenerator.withGeneratedRows(1) + .getRows(); + + _listHelper.beginAtList(containerPath, LIST_2.getName()); + var newRow = new CaseInsensitiveHashMap<>(); + newRow.putAll(attachmentRows.getFirst()); + _listHelper.insertNewRow(newRow, false); + } + + private void verifyConfirmationPage(String containerPath, List listNames) + { + // Navigate to manage lists page, clear all row selections, verify delete button is disabled + var listsPage = BeginPage.beginAt(this, containerPath); + var grid = listsPage.getGrid(); + grid.uncheckAllOnPage(); + var deleteButton = grid.getHeaderButton("Delete"); + Assert.assertTrue("Delete button should be disabled when no rows selected", + deleteButton.getAttribute("class").contains("disabled")); + + // Select lists, verify delete menu is enabled and has 2 options + selectLists(grid, listNames); + var menuOptions = grid.getHeaderMenuOptions("Delete"); + Assert.assertEquals("Expected 2 delete menu options", + List.of("Delete List", "Delete All Data from List"), menuOptions); + + // Click DELETE -> "Delete List", verify landing on confirmation page with expected text, cancel + listsPage = BeginPage.beginAt(this, containerPath); + grid = listsPage.getGrid(); + selectLists(grid, listNames); + grid.clickHeaderMenu("Delete", true, "Delete List"); + assertTextPresent("Are you sure you want to delete the following Lists?"); + for (String listName : listNames) + assertElementPresent(Locator.linkWithText(listName)); + clickButton("Cancel"); + + // Click DELETE -> "Delete All Data from List", verify landing on confirmation page with expected text, cancel + listsPage = BeginPage.beginAt(this, containerPath); + grid = listsPage.getGrid(); + selectLists(grid, listNames); + grid.clickHeaderMenu("Delete", true, "Delete All Data from List"); + assertTextPresent("Are you sure you want to delete all data"); + assertTextPresent("This action cannot be undone and will result in an empty list."); + for (String listName : listNames) + { + assertElementPresent(Locator.linkWithText(listName)); + } + assertTextPresent("1 row"); + clickButton("Cancel"); + } + + private void selectLists(ManageListsGrid grid, List listNames) + { + for (String listName : listNames) + grid.checkCheckbox(grid.getRowIndex("Name", listName)); + } + + private void verifyListRowCount(String containerPath, String listName, int expectedCount) + { + _listHelper.beginAtList(containerPath, listName); + var table = new DataRegionTable("query", getDriver()); + Assert.assertEquals("Expected " + expectedCount + " rows in " + listName + " at " + containerPath, + expectedCount, table.getDataRowCount()); + } + + private void verifyListDataWithAttachment(String containerPath, String listName, int expectedCount, String attachmentFileName) + { + _listHelper.beginAtList(containerPath, listName); + var table = new DataRegionTable("query", getDriver()); + Assert.assertEquals("Expected " + expectedCount + " rows in " + listName + " at " + containerPath, + expectedCount, table.getDataRowCount()); + + if (attachmentFileName.contains(IMG_FILE.getName())) + { + log("Hover over the thumbnail for the image and make sure the pop-up is as expected."); + // Mouse over the logo, migh help with the following mouse over the image. + mouseOver(Locator.tagWithAttributeContaining("img", "src", IMG_FILE.getName())); + sleep(500); + mouseOver(Locator.xpath("//img[contains(@title, '" + IMG_FILE.getName() + "')]")); + longWait().until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("#helpDiv"))); + String src = Locator.xpath("//div[@id='helpDiv']//img[contains(@src, 'download')]").findElement(getDriver()).getAttribute("src"); + assertTrue("Wrong image in popup: " + src, src.contains(IMG_FILE.getName())); + assertEquals("Bad response from image pop-up", HttpStatus.SC_OK, WebTestHelper.getHttpResponse(src).getResponseCode()); + + } + else + { + File download = doAndWaitForDownload(() -> click(Locator.linkWithText(attachmentFileName))); + Assert.assertTrue("Downloaded attachment should exist: " + attachmentFileName, download.exists()); + } + + } + + /** + * Verifies list deletion and data truncation across folders and permission levels. + * + *
    + *
  1. Verify "Delete List" and "Delete All Data from List" confirmation pages render correctly + * for single and multi-list selections across project and subfolders.
  2. + *
  3. Impersonate a non-admin designer user (Editor + Assay Designer) and verify they see a + * simple "Delete" button (no "Delete All Data" menu option) and can reach the delete + * confirmation page. Cancel without deleting.
  4. + *
  5. As admin, truncate all list data from Subfolder A. Verify both lists are empty in + * Subfolder A while data and attachments remain intact in the project and Subfolder B.
  6. + *
  7. As admin, truncate only LIST_2 data from the project. Verify LIST_2 is empty in the + * project while LIST_1 data in the project and all data in Subfolder B are unaffected.
  8. + *
  9. Impersonate the designer user again. Verify no Delete button appears in Subfolder B + * (where the user lacks Assay Designer permission). Delete LIST_1 from Subfolder A and + * LIST_2 from the project, verifying the list definitions are removed.
  10. + *
+ */ + @Test + public void testDeleteListData() throws IOException, CommandException + { + verifyConfirmationPage(getProjectName(), List.of(LIST_1.getName())); + verifyConfirmationPage(getProjectName(), List.of(LIST_1.getName(), LIST_2.getName())); + verifyConfirmationPage(SUBFOLDER_A_PATH, List.of(LIST_1.getName())); + verifyConfirmationPage(SUBFOLDER_B_PATH, List.of(LIST_1.getName(), LIST_2.getName())); + + LIST_DESIGNER_USER.impersonate(); + + // verify DESIGNER don't see the menu option to "Delete All Data from List", only "Delete" button + var listsPage = BeginPage.beginAt(this, getProjectName()); + var grid = listsPage.getGrid(); + grid.uncheckAllOnPage(); + var deleteButton = grid.getHeaderButton("Delete"); + Assert.assertTrue("Delete button should be disabled when no rows selected", + deleteButton.getAttribute("class").contains("disabled")); + + // Select lists, verify delete menu is enabled and has 2 options + selectLists(grid, List.of(LIST_1.getName(), LIST_2.getName())); + // verify DELETE button is enabled, verify click DELETE land on confirmation page, click cancel + Assert.assertFalse("Delete button should be enabled when rows are selected", + deleteButton.getAttribute("class").contains("disabled")); + grid.clickHeaderButton("Delete"); + assertTextPresent("Are you sure you want to delete the following Lists?"); + for (String listName : List.of(LIST_1.getName(), LIST_2.getName())) + assertElementPresent(Locator.linkWithText(listName)); + clickButton("Cancel"); + stopImpersonating(); + + // Verify deleting data from Subfolder A doesn't impact lists or data in project folder or Subfolder B + listsPage = BeginPage.beginAt(this, SUBFOLDER_A_PATH); + grid = listsPage.getGrid(); + selectLists(grid, List.of(LIST_1.getName(), LIST_2.getName())); + grid.clickHeaderMenu("Delete", true, "Delete All Data from List"); + assertTextPresent("Are you sure you want to delete all data"); + assertTextPresent("This action cannot be undone and will result in an empty list."); + for (String listName : List.of(LIST_1.getName(), LIST_2.getName())) + { + assertElementPresent(Locator.linkWithText(listName)); + assertTextPresent("1 row"); + } + clickButton("Confirm Delete All Data"); + + // Verify data deleted in Subfolder A — both lists should be empty + verifyListRowCount(SUBFOLDER_A_PATH, LIST_1.getName(), 0); + verifyListRowCount(SUBFOLDER_A_PATH, LIST_2.getName(), 0); + + // Verify data still exists in project folder + // Go to LIST_1, verify grid is not empty, verify attachment can still be downloaded successfully + verifyListDataWithAttachment(PROJECT_PATH, LIST_1.getName(), 1, IMG_FILE.getName()); + + // Go to Subfolder B, go to LIST_2, verify data present, verify attachment can still be downloaded successfully + verifyListDataWithAttachment(SUBFOLDER_B_PATH, LIST_2.getName(), 1, TXT_FILE.getName()); + + // Now delete just LIST_2 data from project folder + listsPage = BeginPage.beginAt(this, PROJECT_PATH); + grid = listsPage.getGrid(); + selectLists(grid, List.of(LIST_2.getName())); + grid.clickHeaderMenu("Delete", true, "Delete All Data from List"); + + // Verify confirmation page + assertTextPresent("Are you sure you want to delete all data"); + assertElementPresent(Locator.linkWithText(LIST_2.getName())); + assertElementNotPresent(Locator.linkWithText(LIST_1.getName())); + assertTextPresent("1 row"); + clickButton("Confirm Delete All Data"); + + // Verify data deleted from LIST_2 in project + verifyListRowCount(PROJECT_PATH, LIST_2.getName(), 0); + + // Verify data still present in LIST_2 in Subfolder B and in LIST_1 in both folders + verifyListRowCount(SUBFOLDER_B_PATH, LIST_2.getName(), 1); + verifyListRowCount(PROJECT_PATH, LIST_1.getName(), 1); + verifyListRowCount(SUBFOLDER_B_PATH, LIST_1.getName(), 1); + + LIST_DESIGNER_USER.impersonate(); + + // From Subfolder B, verify LIST_DESIGNER_USER cannot delete LIST_1 or LIST_2 + // since they don't have designer permission in the sub folder + listsPage = BeginPage.beginAt(this, SUBFOLDER_B_PATH); + grid = listsPage.getGrid(); + Assert.assertFalse("Delete button should not be present without designer permission", + grid.hasHeaderMenu("Delete")); + + // From Subfolder A, verify LIST_DESIGNER_USER can delete LIST_1 + listsPage = BeginPage.beginAt(this, SUBFOLDER_A_PATH); + grid = listsPage.getGrid(); + selectLists(grid, List.of(LIST_1.getName())); + grid.clickHeaderButtonAndWait("Delete"); + assertTextPresent("Are you sure you want to delete the following Lists?"); + assertElementPresent(Locator.linkWithText(LIST_1.getName())); + clickButton("Confirm Delete"); + + // Verify LIST_1 is deleted successfully + listsPage = BeginPage.beginAt(this, PROJECT_PATH); + grid = listsPage.getGrid(); + Assert.assertFalse("LIST_1 should no longer exist", + grid.getListNames().contains(LIST_1.getName())); + Assert.assertTrue("LIST_2 should still exist", + grid.getListNames().contains(LIST_2.getName())); + + // From project folder, verify LIST_DESIGNER_USER can delete LIST_2 + selectLists(grid, List.of(LIST_2.getName())); + grid.clickHeaderButtonAndWait("Delete"); + assertTextPresent("Are you sure you want to delete the following Lists?"); + assertElementPresent(Locator.linkWithText(LIST_2.getName())); + clickButton("Confirm Delete"); + + stopImpersonating(); + } + + @Override + protected void doCleanup(boolean afterTest) + { + super.doCleanup(afterTest); + _userHelper.deleteUsers(afterTest, LIST_DESIGNER_USER); + } + + @Override + protected String getProjectName() + { + return PROJECT_NAME; + } + + @Override + public List getAssociatedModules() + { + return List.of("list"); + } +} \ No newline at end of file From a76bc138985a5ff2c64eea9939301e3f33290a12 Mon Sep 17 00:00:00 2001 From: XingY Date: Tue, 3 Mar 2026 18:03:55 -0800 Subject: [PATCH 2/3] code review changes --- src/org/labkey/test/BaseWebDriverTest.java | 10 +++ .../test/components/list/ManageListsGrid.java | 21 ++++++- .../test/pages/list/ConfirmDeletePage.java | 10 ++- .../test/tests/InlineImagesListTest.java | 8 +-- .../test/tests/list/ListDeleteTest.java | 61 ++++++------------- 5 files changed, 61 insertions(+), 49 deletions(-) diff --git a/src/org/labkey/test/BaseWebDriverTest.java b/src/org/labkey/test/BaseWebDriverTest.java index bd10151d43..10d9d5ad98 100644 --- a/src/org/labkey/test/BaseWebDriverTest.java +++ b/src/org/labkey/test/BaseWebDriverTest.java @@ -22,6 +22,7 @@ import org.apache.commons.lang3.time.FastDateFormat; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; +import org.apache.hc.core5.http.HttpStatus; import org.awaitility.Awaitility; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -2734,6 +2735,15 @@ private File clickExportImageIcon(String chartParentCls, int chartIndex, Locator } } + public void verifyImagePopupInGrid(File imageFile) + { + mouseOver(Locator.xpath("//img[contains(@title, '" + imageFile.getName() + "')]")); + longWait().until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("#helpDiv"))); + String src = Locator.xpath("//div[@id='helpDiv']//img[contains(@src, 'download')]").findElement(getDriver()).getAttribute("src"); + assertTrue("Wrong image in popup: " + src, src.contains(imageFile.getName())); + assertEquals("Bad response from image pop-up", HttpStatus.SC_OK, WebTestHelper.getHttpResponse(src).getResponseCode()); + } + public List> loadTsv(File tsv) { try (TabLoader loader = new TabLoader(tsv, true)) diff --git a/src/org/labkey/test/components/list/ManageListsGrid.java b/src/org/labkey/test/components/list/ManageListsGrid.java index cbf794da7c..ed40c070cb 100644 --- a/src/org/labkey/test/components/list/ManageListsGrid.java +++ b/src/org/labkey/test/components/list/ManageListsGrid.java @@ -53,11 +53,30 @@ public File exportSelectedLists() public BeginPage deleteSelectedLists() { - clickHeaderButtonAndWait("Delete"); + if (getHeaderButton("Delete").getAttribute("class").contains("labkey-menu-button")) + clickHeaderMenu("Delete", true, "Delete List"); + else + clickHeaderButtonAndWait("Delete"); + ConfirmDeletePage confirmPage = new ConfirmDeletePage(getDriver()); return confirmPage.confirmDelete(); } + public BeginPage deleteAllDataFromSelectedLists() + { + clickHeaderMenu("Delete", true, "Delete All Data from List"); + ConfirmDeletePage confirmPage = new ConfirmDeletePage(getDriver(), "Confirm Delete All Data"); + return confirmPage.confirmDelete(); + } + + public ManageListsGrid selectLists(List listNames) + { + for (String listName : listNames) + checkCheckbox(getRowIndex("Name", listName)); + + return this; + } + public List getListNames() { return getColumnDataAsText("Name"); diff --git a/src/org/labkey/test/pages/list/ConfirmDeletePage.java b/src/org/labkey/test/pages/list/ConfirmDeletePage.java index 544b066aaf..a115d2c9bc 100644 --- a/src/org/labkey/test/pages/list/ConfirmDeletePage.java +++ b/src/org/labkey/test/pages/list/ConfirmDeletePage.java @@ -8,9 +8,17 @@ public class ConfirmDeletePage extends LabKeyPage { + private String _deleteBtnText; + public ConfirmDeletePage(WebDriver driver) + { + this(driver, "Confirm Delete"); + } + + public ConfirmDeletePage(WebDriver driver, String deleteBtnText) { super(driver); + _deleteBtnText = deleteBtnText; } public BeginPage confirmDelete() @@ -27,6 +35,6 @@ protected ElementCache newElementCache() protected class ElementCache extends LabKeyPage.ElementCache { - WebElement deleteButton = Locator.lkButton("Confirm Delete").findWhenNeeded(this); + WebElement deleteButton = Locator.lkButton(_deleteBtnText == null ? "Confirm Delete" : _deleteBtnText).findWhenNeeded(this); } } diff --git a/src/org/labkey/test/tests/InlineImagesListTest.java b/src/org/labkey/test/tests/InlineImagesListTest.java index 07c9d9ddcc..7a3ee99a4c 100644 --- a/src/org/labkey/test/tests/InlineImagesListTest.java +++ b/src/org/labkey/test/tests/InlineImagesListTest.java @@ -219,11 +219,7 @@ public final void testList() throws Exception // Mouse over the logo, migh help with the following mouse over the image. mouseOver(Locator.tagWithAttributeContaining("img", "src", "logo.image")); sleep(500); - mouseOver(Locator.xpath("//img[contains(@title, '" + LRG_PNG_FILE.getName() + "')]")); - longWait().until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("#helpDiv"))); - String src = Locator.xpath("//div[@id='helpDiv']//img[contains(@src, 'download')]").findElement(getDriver()).getAttribute("src"); - assertTrue("Wrong image in popup: " + src, src.contains(LRG_PNG_FILE.getName())); - assertEquals("Bad response from image pop-up", HttpStatus.SC_OK, WebTestHelper.getHttpResponse(src).getResponseCode()); + verifyImagePopupInGrid(LRG_PNG_FILE); // Commenting out for now. There is a random behavior where sometimes the thumbnail image will not show up when you move from one cell to another. /* @@ -303,7 +299,7 @@ public final void testList() throws Exception sleep(500); mouseOver(Locator.xpath("//img[contains(@title, '" + LRG_PNG_FILE.getName() + "')]")); shortWait().until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("#helpDiv"))); - src = Locator.xpath("//div[@id='helpDiv']//img[contains(@src, 'download')]").findElement(getDriver()).getAttribute("src"); + var src = Locator.xpath("//div[@id='helpDiv']//img[contains(@src, 'download')]").findElement(getDriver()).getAttribute("src"); assertTrue("Wrong image in popup: " + src, src.contains(LRG_PNG_FILE.getName())); assertEquals("Bad response from image pop-up", HttpStatus.SC_OK, WebTestHelper.getHttpResponse(src).getResponseCode()); diff --git a/src/org/labkey/test/tests/list/ListDeleteTest.java b/src/org/labkey/test/tests/list/ListDeleteTest.java index de470569ac..90f89aa0a3 100644 --- a/src/org/labkey/test/tests/list/ListDeleteTest.java +++ b/src/org/labkey/test/tests/list/ListDeleteTest.java @@ -1,7 +1,5 @@ package org.labkey.test.tests.list; -import org.apache.hc.core5.http.HttpStatus; -import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -10,11 +8,9 @@ import org.labkey.test.BaseWebDriverTest; import org.labkey.test.Locator; import org.labkey.test.TestFileUtils; -import org.labkey.test.WebTestHelper; import org.labkey.test.categories.Daily; import org.labkey.test.categories.Data; import org.labkey.test.categories.Hosting; -import org.labkey.test.components.list.ManageListsGrid; import org.labkey.test.pages.list.BeginPage; import org.labkey.test.util.DataRegionTable; import org.labkey.test.params.FieldDefinition; @@ -23,15 +19,11 @@ import org.labkey.test.util.DomainUtils; import org.labkey.test.util.TestDataGenerator; import org.labkey.test.util.TestUser; -import org.openqa.selenium.By; -import org.openqa.selenium.support.ui.ExpectedConditions; import java.io.File; import java.io.IOException; import java.util.List; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import static org.labkey.test.util.PermissionsHelper.EDITOR_ROLE; @Category({Daily.class, Data.class, Hosting.class}) @@ -161,19 +153,19 @@ private void verifyConfirmationPage(String containerPath, List listNames var grid = listsPage.getGrid(); grid.uncheckAllOnPage(); var deleteButton = grid.getHeaderButton("Delete"); - Assert.assertTrue("Delete button should be disabled when no rows selected", + checker().withScreenshot().verifyTrue("Delete button should be disabled when no rows selected", deleteButton.getAttribute("class").contains("disabled")); // Select lists, verify delete menu is enabled and has 2 options - selectLists(grid, listNames); + grid.selectLists(listNames); var menuOptions = grid.getHeaderMenuOptions("Delete"); - Assert.assertEquals("Expected 2 delete menu options", + checker().withScreenshot().verifyEquals("Expected 2 delete menu options", List.of("Delete List", "Delete All Data from List"), menuOptions); // Click DELETE -> "Delete List", verify landing on confirmation page with expected text, cancel listsPage = BeginPage.beginAt(this, containerPath); grid = listsPage.getGrid(); - selectLists(grid, listNames); + grid.selectLists(listNames); grid.clickHeaderMenu("Delete", true, "Delete List"); assertTextPresent("Are you sure you want to delete the following Lists?"); for (String listName : listNames) @@ -183,7 +175,7 @@ private void verifyConfirmationPage(String containerPath, List listNames // Click DELETE -> "Delete All Data from List", verify landing on confirmation page with expected text, cancel listsPage = BeginPage.beginAt(this, containerPath); grid = listsPage.getGrid(); - selectLists(grid, listNames); + grid.selectLists(listNames); grid.clickHeaderMenu("Delete", true, "Delete All Data from List"); assertTextPresent("Are you sure you want to delete all data"); assertTextPresent("This action cannot be undone and will result in an empty list."); @@ -195,17 +187,11 @@ private void verifyConfirmationPage(String containerPath, List listNames clickButton("Cancel"); } - private void selectLists(ManageListsGrid grid, List listNames) - { - for (String listName : listNames) - grid.checkCheckbox(grid.getRowIndex("Name", listName)); - } - private void verifyListRowCount(String containerPath, String listName, int expectedCount) { _listHelper.beginAtList(containerPath, listName); var table = new DataRegionTable("query", getDriver()); - Assert.assertEquals("Expected " + expectedCount + " rows in " + listName + " at " + containerPath, + checker().withScreenshot().verifyEquals("Expected " + expectedCount + " rows in " + listName + " at " + containerPath, expectedCount, table.getDataRowCount()); } @@ -213,26 +199,19 @@ private void verifyListDataWithAttachment(String containerPath, String listName, { _listHelper.beginAtList(containerPath, listName); var table = new DataRegionTable("query", getDriver()); - Assert.assertEquals("Expected " + expectedCount + " rows in " + listName + " at " + containerPath, + checker().withScreenshot().verifyEquals("Expected " + expectedCount + " rows in " + listName + " at " + containerPath, expectedCount, table.getDataRowCount()); if (attachmentFileName.contains(IMG_FILE.getName())) { log("Hover over the thumbnail for the image and make sure the pop-up is as expected."); - // Mouse over the logo, migh help with the following mouse over the image. - mouseOver(Locator.tagWithAttributeContaining("img", "src", IMG_FILE.getName())); - sleep(500); - mouseOver(Locator.xpath("//img[contains(@title, '" + IMG_FILE.getName() + "')]")); - longWait().until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("#helpDiv"))); - String src = Locator.xpath("//div[@id='helpDiv']//img[contains(@src, 'download')]").findElement(getDriver()).getAttribute("src"); - assertTrue("Wrong image in popup: " + src, src.contains(IMG_FILE.getName())); - assertEquals("Bad response from image pop-up", HttpStatus.SC_OK, WebTestHelper.getHttpResponse(src).getResponseCode()); + verifyImagePopupInGrid(IMG_FILE); } else { File download = doAndWaitForDownload(() -> click(Locator.linkWithText(attachmentFileName))); - Assert.assertTrue("Downloaded attachment should exist: " + attachmentFileName, download.exists()); + checker().withScreenshot().verifyTrue("Downloaded attachment should exist: " + attachmentFileName, download.exists()); } } @@ -270,13 +249,13 @@ public void testDeleteListData() throws IOException, CommandException var grid = listsPage.getGrid(); grid.uncheckAllOnPage(); var deleteButton = grid.getHeaderButton("Delete"); - Assert.assertTrue("Delete button should be disabled when no rows selected", + checker().withScreenshot().verifyTrue("Delete button should be disabled when no rows selected", deleteButton.getAttribute("class").contains("disabled")); - // Select lists, verify delete menu is enabled and has 2 options - selectLists(grid, List.of(LIST_1.getName(), LIST_2.getName())); + // Select lists, verify delete button is enabled + grid.selectLists(List.of(LIST_1.getName(), LIST_2.getName())); // verify DELETE button is enabled, verify click DELETE land on confirmation page, click cancel - Assert.assertFalse("Delete button should be enabled when rows are selected", + checker().withScreenshot().verifyFalse("Delete button should be enabled when rows are selected", deleteButton.getAttribute("class").contains("disabled")); grid.clickHeaderButton("Delete"); assertTextPresent("Are you sure you want to delete the following Lists?"); @@ -288,7 +267,7 @@ public void testDeleteListData() throws IOException, CommandException // Verify deleting data from Subfolder A doesn't impact lists or data in project folder or Subfolder B listsPage = BeginPage.beginAt(this, SUBFOLDER_A_PATH); grid = listsPage.getGrid(); - selectLists(grid, List.of(LIST_1.getName(), LIST_2.getName())); + grid.selectLists(List.of(LIST_1.getName(), LIST_2.getName())); grid.clickHeaderMenu("Delete", true, "Delete All Data from List"); assertTextPresent("Are you sure you want to delete all data"); assertTextPresent("This action cannot be undone and will result in an empty list."); @@ -313,7 +292,7 @@ public void testDeleteListData() throws IOException, CommandException // Now delete just LIST_2 data from project folder listsPage = BeginPage.beginAt(this, PROJECT_PATH); grid = listsPage.getGrid(); - selectLists(grid, List.of(LIST_2.getName())); + grid.selectLists(List.of(LIST_2.getName())); grid.clickHeaderMenu("Delete", true, "Delete All Data from List"); // Verify confirmation page @@ -337,13 +316,13 @@ public void testDeleteListData() throws IOException, CommandException // since they don't have designer permission in the sub folder listsPage = BeginPage.beginAt(this, SUBFOLDER_B_PATH); grid = listsPage.getGrid(); - Assert.assertFalse("Delete button should not be present without designer permission", + checker().withScreenshot().verifyFalse("Delete button should not be present without designer permission", grid.hasHeaderMenu("Delete")); // From Subfolder A, verify LIST_DESIGNER_USER can delete LIST_1 listsPage = BeginPage.beginAt(this, SUBFOLDER_A_PATH); grid = listsPage.getGrid(); - selectLists(grid, List.of(LIST_1.getName())); + grid.selectLists(List.of(LIST_1.getName())); grid.clickHeaderButtonAndWait("Delete"); assertTextPresent("Are you sure you want to delete the following Lists?"); assertElementPresent(Locator.linkWithText(LIST_1.getName())); @@ -352,13 +331,13 @@ public void testDeleteListData() throws IOException, CommandException // Verify LIST_1 is deleted successfully listsPage = BeginPage.beginAt(this, PROJECT_PATH); grid = listsPage.getGrid(); - Assert.assertFalse("LIST_1 should no longer exist", + checker().withScreenshot().verifyFalse("LIST_1 should no longer exist", grid.getListNames().contains(LIST_1.getName())); - Assert.assertTrue("LIST_2 should still exist", + checker().withScreenshot().verifyTrue("LIST_2 should still exist", grid.getListNames().contains(LIST_2.getName())); // From project folder, verify LIST_DESIGNER_USER can delete LIST_2 - selectLists(grid, List.of(LIST_2.getName())); + grid.selectLists(List.of(LIST_2.getName())); grid.clickHeaderButtonAndWait("Delete"); assertTextPresent("Are you sure you want to delete the following Lists?"); assertElementPresent(Locator.linkWithText(LIST_2.getName())); From 28de296e123cecdc8f91ae5de8e0f159c5e825f8 Mon Sep 17 00:00:00 2001 From: XingY Date: Wed, 4 Mar 2026 18:36:14 -0800 Subject: [PATCH 3/3] Fix tests --- .../labkey/test/components/list/ManageListsGrid.java | 2 +- src/org/labkey/test/tests/TriggerScriptTest.java | 11 +++++------ src/org/labkey/test/tests/list/ListDeleteTest.java | 2 ++ 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/org/labkey/test/components/list/ManageListsGrid.java b/src/org/labkey/test/components/list/ManageListsGrid.java index ed40c070cb..c654aa62bb 100644 --- a/src/org/labkey/test/components/list/ManageListsGrid.java +++ b/src/org/labkey/test/components/list/ManageListsGrid.java @@ -53,7 +53,7 @@ public File exportSelectedLists() public BeginPage deleteSelectedLists() { - if (getHeaderButton("Delete").getAttribute("class").contains("labkey-menu-button")) + if (getHeaderButton("Delete").getAttribute("class").contains("labkey-down-arrow")) clickHeaderMenu("Delete", true, "Delete List"); else clickHeaderButtonAndWait("Delete"); diff --git a/src/org/labkey/test/tests/TriggerScriptTest.java b/src/org/labkey/test/tests/TriggerScriptTest.java index 41dd5ddd77..cc89fd6093 100644 --- a/src/org/labkey/test/tests/TriggerScriptTest.java +++ b/src/org/labkey/test/tests/TriggerScriptTest.java @@ -824,12 +824,11 @@ public void updateDataSetRow(int id, String tableName, Map data) */ private void cleanUpListRows() { - goToManagedList(LIST_NAME); - clickButton("Delete All Rows", 0); - waitForElement(Locator.xpath("//*[text()='Confirm Deletion']")); - clickButton("Yes", 0); - waitForText("Success"); - clickButton("OK"); + var listsPage = goToManageLists(); + var grid = listsPage.getGrid(); + grid.uncheckAllOnPage(); + grid.selectLists(List.of(LIST_NAME)); + grid.deleteAllDataFromSelectedLists(); } /** diff --git a/src/org/labkey/test/tests/list/ListDeleteTest.java b/src/org/labkey/test/tests/list/ListDeleteTest.java index 90f89aa0a3..862a0d3713 100644 --- a/src/org/labkey/test/tests/list/ListDeleteTest.java +++ b/src/org/labkey/test/tests/list/ListDeleteTest.java @@ -292,6 +292,7 @@ public void testDeleteListData() throws IOException, CommandException // Now delete just LIST_2 data from project folder listsPage = BeginPage.beginAt(this, PROJECT_PATH); grid = listsPage.getGrid(); + grid.uncheckAllOnPage(); grid.selectLists(List.of(LIST_2.getName())); grid.clickHeaderMenu("Delete", true, "Delete All Data from List"); @@ -322,6 +323,7 @@ public void testDeleteListData() throws IOException, CommandException // From Subfolder A, verify LIST_DESIGNER_USER can delete LIST_1 listsPage = BeginPage.beginAt(this, SUBFOLDER_A_PATH); grid = listsPage.getGrid(); + grid.uncheckAllOnPage(); grid.selectLists(List.of(LIST_1.getName())); grid.clickHeaderButtonAndWait("Delete"); assertTextPresent("Are you sure you want to delete the following Lists?");