From d342fdaecbad7ef6d1784c44f102ec18c7f62ee4 Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Sun, 20 Jul 2025 11:11:13 -0700 Subject: [PATCH 01/19] Link crawler test --- .../tests/ehr/AbstractGenericEHRTest.java | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java index 420181f31..2e43266a7 100644 --- a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java +++ b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java @@ -21,6 +21,7 @@ import org.labkey.remoteapi.CommandResponse; import org.labkey.remoteapi.SimplePostCommand; import org.labkey.test.Locator; +import org.labkey.test.Locators; import org.labkey.test.pages.ehr.AnimalHistoryPage; import org.labkey.test.util.DataRegionTable; import org.labkey.test.util.Ext4Helper; @@ -29,8 +30,11 @@ import org.labkey.test.util.ext4cmp.Ext4ComboRef; import org.labkey.test.util.external.labModules.LabModuleHelper; import org.openqa.selenium.By; +import org.openqa.selenium.WebDriverException; import org.openqa.selenium.WebElement; +import java.net.MalformedURLException; +import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -43,6 +47,7 @@ import java.util.UUID; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; //Inherit from this class instead of AbstractEHRTest when you want to run these tests, which should work across all ehr modules public abstract class AbstractGenericEHRTest extends AbstractEHRTest @@ -300,6 +305,144 @@ private void testUserAgainstAllStates(@LoggedParam EHRUser user) resetErrors(); //note: inserting records without permission will log errors by design. the UI should prevent this from happening, so we want to be aware if it does occur } + protected List skipLinksForValidation() + { + return List.of( + "showAllErrors.view", + "query-exportRowsExcel.view", + "ldk-runNotification.view" // need to scope notifications to enabled modules then can remove this + ); // Override if there are links to pages that are known to throw errors + } + + protected List skipLinksForCrawling() + { + return List.of( + "project-begin.view", + "query-begin.view", + "query-searchPanel.view", + "query-executeQuery.view", + "study-manageStudy.view", + "ehr-animalHistory.view", + "ehr-updateQuery.view", + "ehr-updateTable.view", + "ehr-populateLookupData.view", + "ehr-ensureQcStates.view", + "ehr-ehrTemplates.view", + "ehr-primeDataEntryCache.view", + "ehr-cacheLivingAnimals.view", + "core-modulePropertyAdmin.view", + "dataintegration-begin.view", + "ldk-updateQuery", + "junit-begin.view" + ); + } + + private String validLink(WebElement anchor) + { + boolean clickable = false; + String validUrl = null; + + String href = anchor.getDomAttribute("href"); + if (href != null && !href.startsWith("#")) + { + if (skipLinksForValidation().stream().anyMatch(s -> href.toLowerCase().contains(s.toLowerCase()))) + { + log(href + " is specified as an exception to link validation. Skipping validation."); + return validUrl; + } + + // Ensure link is not external + try + { + URL url = new URL(href); + if (!url.getHost().equalsIgnoreCase(getURL().getHost())) + { + log(href + " is an external link. Skipping validation."); + return validUrl; + } + } + catch (MalformedURLException e) + { + // not a full URL so not external. Carry on. + } + + // scope this to ehr folder and subfolders + if (!href.contains(getContainerPath())) + { + log(href + " is in a different folder. Skipping validation."); + return validUrl; + } + } + + if (anchor.isDisplayed() && anchor.isEnabled()) + { + clickable = true; + + try + { + openLinkInNewWindow(anchor); + } + catch (WebDriverException | IllegalStateException e) + { + clickable = false; + } + + if (clickable) + { + assertFalse(isPageEmpty()); + assertNoLabKeyErrors(); + assertElementNotPresent(Locators.labkeyErrorHeading); + validUrl = getURL().toString(); // wait all the way to here before declaring link valid to handle different types of links + switchToWindow(0); + quietlyCloseExtraWindows(); + } + } + return validUrl; + } + + private void validatePageLinks(Set crawledLinks) + { + log("Validating links on " + getURL()); + List anchors = getDriver().findElements(By.xpath("//div[contains(concat(' ', normalize-space(@class), ' '), ' lk-body-ct ')]//a[not(ancestor::form[@data-region-form]) and not(@role='button') and not(contains(@class, 'labkey-button'))]")); + + log(anchors.size() + " possible links found."); + int validatedCount = 0; + Set validLinksOnPage = new HashSet<>(); + for (WebElement anchor : anchors) + { + String href = anchor.getDomAttribute("href"); + if (href != null && validLinksOnPage.contains(href)) + continue; + + String validUrl = validLink(anchor); + if (validUrl != null) + { + validatedCount++; + validLinksOnPage.add(validUrl); + } + + } + log(validatedCount + " links validated."); + + for (String s : validLinksOnPage) + { + if (!crawledLinks.contains(s) && skipLinksForCrawling().stream().noneMatch(link -> s.toLowerCase().contains(link.toLowerCase()))) + { + beginAt(s); + crawledLinks.add(s); // mark page as crawled to avoid loops + validatePageLinks(crawledLinks); + } + } + } + + @Test + public void crawlEhrLinks() + { + goToEHRFolder(); + Set crawledLinks = new HashSet<>(); + validatePageLinks(crawledLinks); + } + @Test public void testCalculatedAgeColumns() { From f6de2fa492faf56e99f8aa976a58691b8caaa531 Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Sun, 20 Jul 2025 11:11:30 -0700 Subject: [PATCH 02/19] remove unused lookup --- ehr/resources/views/dataAdmin.html | 1 - 1 file changed, 1 deletion(-) diff --git a/ehr/resources/views/dataAdmin.html b/ehr/resources/views/dataAdmin.html index 32c86d004..66abe4756 100644 --- a/ehr/resources/views/dataAdmin.html +++ b/ehr/resources/views/dataAdmin.html @@ -103,7 +103,6 @@ {queryName: 'dental_teeth', schemaName: 'ehr_lookups', title: 'Dental Teeth Field'}, {queryName: 'encounter_types', schemaName: 'ehr_lookups', title: 'Encounter Types'}, - {queryName: 'error_types', schemaName: 'ehr_lookups', title: 'Error Report Error Types'}, {queryName: 'gender_codes', schemaName: 'ehr_lookups', title: 'Gender Codes'}, {queryName: 'hematology_method', schemaName: 'ehr_lookups', title: 'Hematology Method'}, {queryName: 'hematology_tests', schemaName: 'ehr_lookups', title: 'Hematology Tests'}, From 56ed54f8ef5dceeb22ebf844c371e3369fe69cf2 Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Thu, 24 Jul 2025 16:21:25 -0700 Subject: [PATCH 03/19] decode url --- .../org/labkey/test/tests/ehr/AbstractGenericEHRTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java index 2e43266a7..a87d72ee0 100644 --- a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java +++ b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java @@ -24,6 +24,7 @@ import org.labkey.test.Locators; import org.labkey.test.pages.ehr.AnimalHistoryPage; import org.labkey.test.util.DataRegionTable; +import org.labkey.test.util.EscapeUtil; import org.labkey.test.util.Ext4Helper; import org.labkey.test.util.LoggedParam; import org.labkey.test.util.PortalHelper; @@ -367,9 +368,9 @@ private String validLink(WebElement anchor) } // scope this to ehr folder and subfolders - if (!href.contains(getContainerPath())) + if (!EscapeUtil.decodeUriPath(href).contains(getContainerPath())) { - log(href + " is in a different folder. Skipping validation."); + log(href + " is in a different folder than the EHR folder, " + getContainerPath() + ". Skipping validation."); return validUrl; } } From b0cf8a82dc41d702a2b09bdf3626ac328a7953b7 Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Thu, 24 Jul 2025 17:10:04 -0700 Subject: [PATCH 04/19] Decoding --- .../org/labkey/test/tests/ehr/AbstractGenericEHRTest.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java index a87d72ee0..32e5dd1ae 100644 --- a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java +++ b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java @@ -346,7 +346,8 @@ private String validLink(WebElement anchor) String href = anchor.getDomAttribute("href"); if (href != null && !href.startsWith("#")) { - if (skipLinksForValidation().stream().anyMatch(s -> href.toLowerCase().contains(s.toLowerCase()))) + String decodedHref = EscapeUtil.decodeUriPath(href); + if (skipLinksForValidation().stream().anyMatch(s -> decodedHref.toLowerCase().contains(s.toLowerCase()))) { log(href + " is specified as an exception to link validation. Skipping validation."); return validUrl; @@ -367,8 +368,8 @@ private String validLink(WebElement anchor) // not a full URL so not external. Carry on. } - // scope this to ehr folder and subfolders - if (!EscapeUtil.decodeUriPath(href).contains(getContainerPath())) + // scope this to admin, ehr folder and subfolders + if (!decodedHref.contains(getContainerPath()) && !decodedHref.startsWith("/admin")) { log(href + " is in a different folder than the EHR folder, " + getContainerPath() + ". Skipping validation."); return validUrl; From 9f115974219aea33230ed6f4c64fd73020b74ebb Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Thu, 24 Jul 2025 17:48:43 -0700 Subject: [PATCH 05/19] Better messages --- .../org/labkey/test/tests/ehr/AbstractGenericEHRTest.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java index 32e5dd1ae..35305bdb7 100644 --- a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java +++ b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java @@ -391,10 +391,11 @@ private String validLink(WebElement anchor) if (clickable) { - assertFalse(isPageEmpty()); + URL url = getURL(); + assertFalse("URL " + url + " is empty.", isPageEmpty()); assertNoLabKeyErrors(); - assertElementNotPresent(Locators.labkeyErrorHeading); - validUrl = getURL().toString(); // wait all the way to here before declaring link valid to handle different types of links + assertElementNotPresent("LabKey error found for URL " + url, Locators.labkeyErrorHeading); + validUrl = url.toString(); // wait all the way to here before declaring link valid to handle different types of links switchToWindow(0); quietlyCloseExtraWindows(); } From 644a8e54c63037bfee16c781bb6e63748a21c0ed Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Thu, 24 Jul 2025 20:56:24 -0700 Subject: [PATCH 06/19] rename --- .../src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java index 35305bdb7..38bcd6421 100644 --- a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java +++ b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java @@ -439,7 +439,7 @@ private void validatePageLinks(Set crawledLinks) } @Test - public void crawlEhrLinks() + public void testCrawlEhrLinks() { goToEHRFolder(); Set crawledLinks = new HashSet<>(); From 41a2c78d68cf7e9763cd045a42cd8183f1295ffd Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Thu, 24 Jul 2025 21:33:54 -0700 Subject: [PATCH 07/19] test fix --- .../src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java index 38bcd6421..907e897fe 100644 --- a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java +++ b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java @@ -391,6 +391,7 @@ private String validLink(WebElement anchor) if (clickable) { + waitFor(() -> (getDriver().getCurrentUrl() != null && !getDriver().getCurrentUrl().equalsIgnoreCase("about:blank")), WAIT_FOR_JAVASCRIPT); URL url = getURL(); assertFalse("URL " + url + " is empty.", isPageEmpty()); assertNoLabKeyErrors(); From a9c18e1607d23729695511d181e524d015f59797 Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Fri, 25 Jul 2025 09:47:24 -0700 Subject: [PATCH 08/19] testing --- .../tests/ehr/AbstractGenericEHRTest.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java index 907e897fe..6a184f490 100644 --- a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java +++ b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java @@ -391,14 +391,20 @@ private String validLink(WebElement anchor) if (clickable) { - waitFor(() -> (getDriver().getCurrentUrl() != null && !getDriver().getCurrentUrl().equalsIgnoreCase("about:blank")), WAIT_FOR_JAVASCRIPT); - URL url = getURL(); - assertFalse("URL " + url + " is empty.", isPageEmpty()); - assertNoLabKeyErrors(); - assertElementNotPresent("LabKey error found for URL " + url, Locators.labkeyErrorHeading); - validUrl = url.toString(); // wait all the way to here before declaring link valid to handle different types of links - switchToWindow(0); - quietlyCloseExtraWindows(); + if (waitFor(() -> (getDriver().getCurrentUrl() != null && !getDriver().getCurrentUrl().equalsIgnoreCase("about:blank")), WAIT_FOR_JAVASCRIPT)) + { + URL url = getURL(); + assertFalse("URL " + url + " is empty.", isPageEmpty()); + assertNoLabKeyErrors(); + assertElementNotPresent("LabKey error found for URL " + url, Locators.labkeyErrorHeading); + validUrl = url.toString(); // wait all the way to here before declaring link valid to handle different types of links + switchToWindow(0); + quietlyCloseExtraWindows(); + } + else + { + log("Link " + href + " did not load properly."); + } } } return validUrl; From f808ae37984484c2e7bb9e956d0ae102fe39761a Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Fri, 25 Jul 2025 09:47:38 -0700 Subject: [PATCH 09/19] fix ehr app test --- .../test/src/org/labkey/test/tests/EHR_AppTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/EHR_App/test/src/org/labkey/test/tests/EHR_AppTest.java b/EHR_App/test/src/org/labkey/test/tests/EHR_AppTest.java index 585b7874c..4ac797f71 100644 --- a/EHR_App/test/src/org/labkey/test/tests/EHR_AppTest.java +++ b/EHR_App/test/src/org/labkey/test/tests/EHR_AppTest.java @@ -12,6 +12,8 @@ import org.labkey.test.util.PostgresOnlyTest; import java.io.File; +import java.util.ArrayList; +import java.util.List; @Category({EHR.class}) public class EHR_AppTest extends AbstractGenericEHRTest implements PostgresOnlyTest @@ -120,6 +122,17 @@ public void preTest() goToEHRFolder(); } + @Override + protected List skipLinksForValidation() + { + List links = new ArrayList<>(super.skipLinksForValidation()); + links.add("Issue_Tracker"); + links.add("ehr-colonyOverview.view"); + links.add("ehr-updateTable.view"); + links.add("ehr-populateLookupData.view"); + return links; + } + @Test public void testSteps() { From 8f917f6e69c772d45e5f896cb1be4b937f11a63e Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Fri, 25 Jul 2025 22:34:30 -0700 Subject: [PATCH 10/19] Longer load wait and cleanup --- .../tests/ehr/AbstractGenericEHRTest.java | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java index 6a184f490..aa0043bd5 100644 --- a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java +++ b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java @@ -16,7 +16,6 @@ package org.labkey.test.tests.ehr; import org.json.JSONObject; -import org.junit.Assert; import org.junit.Test; import org.labkey.remoteapi.CommandResponse; import org.labkey.remoteapi.SimplePostCommand; @@ -49,6 +48,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; //Inherit from this class instead of AbstractEHRTest when you want to run these tests, which should work across all ehr modules public abstract class AbstractGenericEHRTest extends AbstractEHRTest @@ -227,7 +227,7 @@ public void testCustomButtons() recallLocation(); List submenuItems = dr.getHeaderMenuOptions("More Actions"); List expectedSubmenu = Arrays.asList("Jump To History", "Return Distinct Values","Show Record History","Compare Weights","Edit Records"); - Assert.assertTrue("More actions menu did not contain expected options. Expected: " + expectedSubmenu + ", but found: " + submenuItems, submenuItems.containsAll(expectedSubmenu)); + assertTrue("More actions menu did not contain expected options. Expected: " + expectedSubmenu + ", but found: " + submenuItems, submenuItems.containsAll(expectedSubmenu)); } private void testUserAgainstAllStates(@LoggedParam EHRUser user) @@ -391,20 +391,22 @@ private String validLink(WebElement anchor) if (clickable) { - if (waitFor(() -> (getDriver().getCurrentUrl() != null && !getDriver().getCurrentUrl().equalsIgnoreCase("about:blank")), WAIT_FOR_JAVASCRIPT)) - { - URL url = getURL(); - assertFalse("URL " + url + " is empty.", isPageEmpty()); - assertNoLabKeyErrors(); - assertElementNotPresent("LabKey error found for URL " + url, Locators.labkeyErrorHeading); - validUrl = url.toString(); // wait all the way to here before declaring link valid to handle different types of links - switchToWindow(0); - quietlyCloseExtraWindows(); - } - else - { - log("Link " + href + " did not load properly."); - } + // Give page time to load + boolean loaded = waitFor(() -> (getDriver().getCurrentUrl() != null && !getDriver().getCurrentUrl().equalsIgnoreCase("about:blank")), WAIT_FOR_PAGE); + assertTrue("Link " + href + " did not load in " + WAIT_FOR_PAGE + "ms.", loaded); + + // Assert page is not empty and does not have errors + URL url = getURL(); + assertFalse("URL " + url + " is empty.", isPageEmpty()); + assertNoLabKeyErrors(); + + // assertNoLabKeyErrors does not catch all types of errors + assertElementNotPresent("LabKey error found for URL " + url, Locators.labkeyErrorHeading); + + // record link as valid and cleanup + validUrl = url.toString(); + switchToWindow(0); + quietlyCloseExtraWindows(); } } return validUrl; From ea28d704cfb3fb1764a9a03f904329dbc1c1125f Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Sun, 27 Jul 2025 22:10:36 -0700 Subject: [PATCH 11/19] comments --- .../org/labkey/test/tests/ehr/AbstractGenericEHRTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java index aa0043bd5..6166de2af 100644 --- a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java +++ b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java @@ -415,6 +415,8 @@ private String validLink(WebElement anchor) private void validatePageLinks(Set crawledLinks) { log("Validating links on " + getURL()); + + // Find all anchors in the body content area, excluding buttons and those in data regions List anchors = getDriver().findElements(By.xpath("//div[contains(concat(' ', normalize-space(@class), ' '), ' lk-body-ct ')]//a[not(ancestor::form[@data-region-form]) and not(@role='button') and not(contains(@class, 'labkey-button'))]")); log(anchors.size() + " possible links found."); @@ -422,10 +424,12 @@ private void validatePageLinks(Set crawledLinks) Set validLinksOnPage = new HashSet<>(); for (WebElement anchor : anchors) { + // Only validate links once String href = anchor.getDomAttribute("href"); if (href != null && validLinksOnPage.contains(href)) continue; + // Validate and record valid links String validUrl = validLink(anchor); if (validUrl != null) { @@ -436,6 +440,7 @@ private void validatePageLinks(Set crawledLinks) } log(validatedCount + " links validated."); + // Recursively crawl valid links that have not yet been crawled and aren't listed as a skip. for (String s : validLinksOnPage) { if (!crawledLinks.contains(s) && skipLinksForCrawling().stream().noneMatch(link -> s.toLowerCase().contains(link.toLowerCase()))) From 43197a639dd0e94959afaaff6ce04ab359140b78 Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Mon, 28 Jul 2025 13:52:24 -0700 Subject: [PATCH 12/19] Update test --- .../src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java index 6166de2af..f23e94306 100644 --- a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java +++ b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java @@ -382,7 +382,7 @@ private String validLink(WebElement anchor) try { - openLinkInNewWindow(anchor); + openLinkInNewWindowOrThrow(anchor); } catch (WebDriverException | IllegalStateException e) { From b0e22ac33611a4d7d4725398892f1d55c71fd318 Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Tue, 29 Jul 2025 14:28:47 -0700 Subject: [PATCH 13/19] Update ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java Co-authored-by: Trey Chadick --- .../src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java index f23e94306..4b0a7e2f3 100644 --- a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java +++ b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java @@ -350,7 +350,7 @@ private String validLink(WebElement anchor) if (skipLinksForValidation().stream().anyMatch(s -> decodedHref.toLowerCase().contains(s.toLowerCase()))) { log(href + " is specified as an exception to link validation. Skipping validation."); - return validUrl; + return null; } // Ensure link is not external From 890742e86b0a03920bb45b2c18f11315b75926eb Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Tue, 29 Jul 2025 14:29:07 -0700 Subject: [PATCH 14/19] Update ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java Co-authored-by: Trey Chadick --- .../src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java index 4b0a7e2f3..67f288108 100644 --- a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java +++ b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java @@ -360,7 +360,7 @@ private String validLink(WebElement anchor) if (!url.getHost().equalsIgnoreCase(getURL().getHost())) { log(href + " is an external link. Skipping validation."); - return validUrl; + return null; } } catch (MalformedURLException e) From 15a7bff42364c126c9a98ef9b65751d68b1aebe4 Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Tue, 29 Jul 2025 14:29:15 -0700 Subject: [PATCH 15/19] Update ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java Co-authored-by: Trey Chadick --- .../src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java index 67f288108..6e0218d7d 100644 --- a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java +++ b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java @@ -372,7 +372,7 @@ private String validLink(WebElement anchor) if (!decodedHref.contains(getContainerPath()) && !decodedHref.startsWith("/admin")) { log(href + " is in a different folder than the EHR folder, " + getContainerPath() + ". Skipping validation."); - return validUrl; + return null; } } From 0dcd01fce2b3e8f140a56df98adc7a84622aba7c Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Wed, 30 Jul 2025 09:25:40 -0700 Subject: [PATCH 16/19] Code review feedback --- .../tests/ehr/AbstractGenericEHRTest.java | 74 ++++++++++--------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java index 6e0218d7d..52d9decbf 100644 --- a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java +++ b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java @@ -22,6 +22,7 @@ import org.labkey.test.Locator; import org.labkey.test.Locators; import org.labkey.test.pages.ehr.AnimalHistoryPage; +import org.labkey.test.util.Crawler; import org.labkey.test.util.DataRegionTable; import org.labkey.test.util.EscapeUtil; import org.labkey.test.util.Ext4Helper; @@ -334,15 +335,13 @@ protected List skipLinksForCrawling() "core-modulePropertyAdmin.view", "dataintegration-begin.view", "ldk-updateQuery", - "junit-begin.view" + "junit-begin.view", + "admin-" ); } private String validLink(WebElement anchor) { - boolean clickable = false; - String validUrl = null; - String href = anchor.getDomAttribute("href"); if (href != null && !href.startsWith("#")) { @@ -369,46 +368,49 @@ private String validLink(WebElement anchor) } // scope this to admin, ehr folder and subfolders - if (!decodedHref.contains(getContainerPath()) && !decodedHref.startsWith("/admin")) + String controller = new Crawler.ControllerActionId(decodedHref).getController(); + if (!decodedHref.contains(getContainerPath()) && !controller.equalsIgnoreCase("admin")) { log(href + " is in a different folder than the EHR folder, " + getContainerPath() + ". Skipping validation."); return null; } } - if (anchor.isDisplayed() && anchor.isEnabled()) - { - clickable = true; + if (!anchor.isDisplayed()) + return null; - try - { - openLinkInNewWindowOrThrow(anchor); - } - catch (WebDriverException | IllegalStateException e) - { - clickable = false; - } + boolean clickable = true; + String validUrl = null; - if (clickable) - { - // Give page time to load - boolean loaded = waitFor(() -> (getDriver().getCurrentUrl() != null && !getDriver().getCurrentUrl().equalsIgnoreCase("about:blank")), WAIT_FOR_PAGE); - assertTrue("Link " + href + " did not load in " + WAIT_FOR_PAGE + "ms.", loaded); - - // Assert page is not empty and does not have errors - URL url = getURL(); - assertFalse("URL " + url + " is empty.", isPageEmpty()); - assertNoLabKeyErrors(); - - // assertNoLabKeyErrors does not catch all types of errors - assertElementNotPresent("LabKey error found for URL " + url, Locators.labkeyErrorHeading); - - // record link as valid and cleanup - validUrl = url.toString(); - switchToWindow(0); - quietlyCloseExtraWindows(); - } + try + { + openLinkInNewWindowOrThrow(anchor); + } + catch (WebDriverException | IllegalStateException e) + { + clickable = false; } + + if (clickable) + { + // Give page time to load + boolean loaded = waitFor(() -> (getDriver().getCurrentUrl() != null && !getDriver().getCurrentUrl().equalsIgnoreCase("about:blank")), WAIT_FOR_PAGE); + assertTrue("Link " + href + " did not load in " + WAIT_FOR_PAGE + "ms.", loaded); + + // Assert page is not empty and does not have errors + URL url = getURL(); + assertFalse("URL " + url + " is empty.", isPageEmpty()); + assertNoLabKeyErrors(); + + // assertNoLabKeyErrors does not catch all types of errors + assertElementNotPresent("LabKey error found for URL " + url, Locators.labkeyErrorHeading); + + // record link as valid and cleanup + validUrl = url.toString(); + getDriver().close(); + switchToWindow(0); + } + return validUrl; } @@ -426,7 +428,7 @@ private void validatePageLinks(Set crawledLinks) { // Only validate links once String href = anchor.getDomAttribute("href"); - if (href != null && validLinksOnPage.contains(href)) + if (href != null && (validLinksOnPage.contains(href) || crawledLinks.contains(href))) continue; // Validate and record valid links From 2f1d99af41c4c93cb8fab980f51afe447bbff945 Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Wed, 30 Jul 2025 09:34:33 -0700 Subject: [PATCH 17/19] skip crawling datasets view --- .../src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java index 52d9decbf..dae77e91d 100644 --- a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java +++ b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java @@ -336,7 +336,8 @@ protected List skipLinksForCrawling() "dataintegration-begin.view", "ldk-updateQuery", "junit-begin.view", - "admin-" + "admin-", + "ehr-datasets.view" ); } From c389b0754674369e4b338aa3e3576b946a01c495 Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Wed, 30 Jul 2025 15:39:24 -0700 Subject: [PATCH 18/19] Handle "undefined" href --- .../src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java index dae77e91d..d9a538137 100644 --- a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java +++ b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java @@ -344,7 +344,7 @@ protected List skipLinksForCrawling() private String validLink(WebElement anchor) { String href = anchor.getDomAttribute("href"); - if (href != null && !href.startsWith("#")) + if (href != null && !href.startsWith("#") && !href.equalsIgnoreCase("undefined")) { String decodedHref = EscapeUtil.decodeUriPath(href); if (skipLinksForValidation().stream().anyMatch(s -> decodedHref.toLowerCase().contains(s.toLowerCase()))) From d871bfa578dea6659de6644554a66f72d8d571a1 Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Sun, 3 Aug 2025 07:40:07 -0700 Subject: [PATCH 19/19] verify link function --- .../tests/ehr/AbstractGenericEHRTest.java | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java index d9a538137..6543a9171 100644 --- a/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java +++ b/ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java @@ -30,7 +30,9 @@ import org.labkey.test.util.PortalHelper; import org.labkey.test.util.ext4cmp.Ext4ComboRef; import org.labkey.test.util.external.labModules.LabModuleHelper; +import org.labkey.test.util.selenium.WebDriverUtils; import org.openqa.selenium.By; +import org.openqa.selenium.Keys; import org.openqa.selenium.WebDriverException; import org.openqa.selenium.WebElement; @@ -307,6 +309,21 @@ private void testUserAgainstAllStates(@LoggedParam EHRUser user) resetErrors(); //note: inserting records without permission will log errors by design. the UI should prevent this from happening, so we want to be aware if it does occur } + // Clicks the link and switches to the window if it's a viable link. Otherwise throws an exception. + private void verifyLInk(WebElement link) + { + int winCount = getDriver().getWindowHandles().size(); + link.sendKeys(Keys.chord(WebDriverUtils.MODIFIER_KEY, Keys.ENTER)); + + // Short wait for a new window to open. If not then throw exception + boolean winOpen = waitFor(() -> getDriver().getWindowHandles().size() > winCount, 1000); + if (!winOpen) + throw new IllegalStateException("Link did not open new window in tab."); + + List windows = new ArrayList<>(getDriver().getWindowHandles()); + getDriver().switchTo().window(windows.get(1)); + } + protected List skipLinksForValidation() { return List.of( @@ -385,7 +402,7 @@ private String validLink(WebElement anchor) try { - openLinkInNewWindowOrThrow(anchor); + verifyLInk(anchor); } catch (WebDriverException | IllegalStateException e) {