Skip to content

Commit 71e3bb6

Browse files
Merge 25.7 to 25.8
2 parents 8dabc44 + 46a1422 commit 71e3bb6

File tree

3 files changed

+195
-3
lines changed

3 files changed

+195
-3
lines changed

EHR_App/test/src/org/labkey/test/tests/EHR_AppTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import org.labkey.test.util.PostgresOnlyTest;
1313

1414
import java.io.File;
15+
import java.util.ArrayList;
16+
import java.util.List;
1517

1618
@Category({EHR.class})
1719
public class EHR_AppTest extends AbstractGenericEHRTest implements PostgresOnlyTest
@@ -120,6 +122,17 @@ public void preTest()
120122
goToEHRFolder();
121123
}
122124

125+
@Override
126+
protected List<String> skipLinksForValidation()
127+
{
128+
List<String> links = new ArrayList<>(super.skipLinksForValidation());
129+
links.add("Issue_Tracker");
130+
links.add("ehr-colonyOverview.view");
131+
links.add("ehr-updateTable.view");
132+
links.add("ehr-populateLookupData.view");
133+
return links;
134+
}
135+
123136
@Test
124137
public void testSteps()
125138
{

ehr/resources/views/dataAdmin.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@
103103
{queryName: 'dental_teeth', schemaName: 'ehr_lookups', title: 'Dental Teeth Field'},
104104

105105
{queryName: 'encounter_types', schemaName: 'ehr_lookups', title: 'Encounter Types'},
106-
{queryName: 'error_types', schemaName: 'ehr_lookups', title: 'Error Report Error Types'},
107106
{queryName: 'gender_codes', schemaName: 'ehr_lookups', title: 'Gender Codes'},
108107
{queryName: 'hematology_method', schemaName: 'ehr_lookups', title: 'Hematology Method'},
109108
{queryName: 'hematology_tests', schemaName: 'ehr_lookups', title: 'Hematology Tests'},

ehr/test/src/org/labkey/test/tests/ehr/AbstractGenericEHRTest.java

Lines changed: 182 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,28 @@
1616
package org.labkey.test.tests.ehr;
1717

1818
import org.json.JSONObject;
19-
import org.junit.Assert;
2019
import org.junit.Test;
2120
import org.labkey.remoteapi.CommandResponse;
2221
import org.labkey.remoteapi.SimplePostCommand;
2322
import org.labkey.test.Locator;
23+
import org.labkey.test.Locators;
2424
import org.labkey.test.pages.ehr.AnimalHistoryPage;
25+
import org.labkey.test.util.Crawler;
2526
import org.labkey.test.util.DataRegionTable;
27+
import org.labkey.test.util.EscapeUtil;
2628
import org.labkey.test.util.Ext4Helper;
2729
import org.labkey.test.util.LoggedParam;
2830
import org.labkey.test.util.PortalHelper;
2931
import org.labkey.test.util.ext4cmp.Ext4ComboRef;
3032
import org.labkey.test.util.external.labModules.LabModuleHelper;
33+
import org.labkey.test.util.selenium.WebDriverUtils;
3134
import org.openqa.selenium.By;
35+
import org.openqa.selenium.Keys;
36+
import org.openqa.selenium.WebDriverException;
3237
import org.openqa.selenium.WebElement;
3338

39+
import java.net.MalformedURLException;
40+
import java.net.URL;
3441
import java.util.ArrayList;
3542
import java.util.Arrays;
3643
import java.util.Collections;
@@ -43,6 +50,8 @@
4350
import java.util.UUID;
4451

4552
import static org.junit.Assert.assertEquals;
53+
import static org.junit.Assert.assertFalse;
54+
import static org.junit.Assert.assertTrue;
4655

4756
//Inherit from this class instead of AbstractEHRTest when you want to run these tests, which should work across all ehr modules
4857
public abstract class AbstractGenericEHRTest extends AbstractEHRTest
@@ -221,7 +230,7 @@ public void testCustomButtons()
221230
recallLocation();
222231
List<String> submenuItems = dr.getHeaderMenuOptions("More Actions");
223232
List<String> expectedSubmenu = Arrays.asList("Jump To History", "Return Distinct Values","Show Record History","Compare Weights","Edit Records");
224-
Assert.assertTrue("More actions menu did not contain expected options. Expected: " + expectedSubmenu + ", but found: " + submenuItems, submenuItems.containsAll(expectedSubmenu));
233+
assertTrue("More actions menu did not contain expected options. Expected: " + expectedSubmenu + ", but found: " + submenuItems, submenuItems.containsAll(expectedSubmenu));
225234
}
226235

227236
private void testUserAgainstAllStates(@LoggedParam EHRUser user)
@@ -300,6 +309,177 @@ private void testUserAgainstAllStates(@LoggedParam EHRUser user)
300309
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
301310
}
302311

312+
// Clicks the link and switches to the window if it's a viable link. Otherwise throws an exception.
313+
private void verifyLInk(WebElement link)
314+
{
315+
int winCount = getDriver().getWindowHandles().size();
316+
link.sendKeys(Keys.chord(WebDriverUtils.MODIFIER_KEY, Keys.ENTER));
317+
318+
// Short wait for a new window to open. If not then throw exception
319+
boolean winOpen = waitFor(() -> getDriver().getWindowHandles().size() > winCount, 1000);
320+
if (!winOpen)
321+
throw new IllegalStateException("Link did not open new window in tab.");
322+
323+
List<String> windows = new ArrayList<>(getDriver().getWindowHandles());
324+
getDriver().switchTo().window(windows.get(1));
325+
}
326+
327+
protected List<String> skipLinksForValidation()
328+
{
329+
return List.of(
330+
"showAllErrors.view",
331+
"query-exportRowsExcel.view",
332+
"ldk-runNotification.view" // need to scope notifications to enabled modules then can remove this
333+
); // Override if there are links to pages that are known to throw errors
334+
}
335+
336+
protected List<String> skipLinksForCrawling()
337+
{
338+
return List.of(
339+
"project-begin.view",
340+
"query-begin.view",
341+
"query-searchPanel.view",
342+
"query-executeQuery.view",
343+
"study-manageStudy.view",
344+
"ehr-animalHistory.view",
345+
"ehr-updateQuery.view",
346+
"ehr-updateTable.view",
347+
"ehr-populateLookupData.view",
348+
"ehr-ensureQcStates.view",
349+
"ehr-ehrTemplates.view",
350+
"ehr-primeDataEntryCache.view",
351+
"ehr-cacheLivingAnimals.view",
352+
"core-modulePropertyAdmin.view",
353+
"dataintegration-begin.view",
354+
"ldk-updateQuery",
355+
"junit-begin.view",
356+
"admin-",
357+
"ehr-datasets.view"
358+
);
359+
}
360+
361+
private String validLink(WebElement anchor)
362+
{
363+
String href = anchor.getDomAttribute("href");
364+
if (href != null && !href.startsWith("#") && !href.equalsIgnoreCase("undefined"))
365+
{
366+
String decodedHref = EscapeUtil.decodeUriPath(href);
367+
if (skipLinksForValidation().stream().anyMatch(s -> decodedHref.toLowerCase().contains(s.toLowerCase())))
368+
{
369+
log(href + " is specified as an exception to link validation. Skipping validation.");
370+
return null;
371+
}
372+
373+
// Ensure link is not external
374+
try
375+
{
376+
URL url = new URL(href);
377+
if (!url.getHost().equalsIgnoreCase(getURL().getHost()))
378+
{
379+
log(href + " is an external link. Skipping validation.");
380+
return null;
381+
}
382+
}
383+
catch (MalformedURLException e)
384+
{
385+
// not a full URL so not external. Carry on.
386+
}
387+
388+
// scope this to admin, ehr folder and subfolders
389+
String controller = new Crawler.ControllerActionId(decodedHref).getController();
390+
if (!decodedHref.contains(getContainerPath()) && !controller.equalsIgnoreCase("admin"))
391+
{
392+
log(href + " is in a different folder than the EHR folder, " + getContainerPath() + ". Skipping validation.");
393+
return null;
394+
}
395+
}
396+
397+
if (!anchor.isDisplayed())
398+
return null;
399+
400+
boolean clickable = true;
401+
String validUrl = null;
402+
403+
try
404+
{
405+
verifyLInk(anchor);
406+
}
407+
catch (WebDriverException | IllegalStateException e)
408+
{
409+
clickable = false;
410+
}
411+
412+
if (clickable)
413+
{
414+
// Give page time to load
415+
boolean loaded = waitFor(() -> (getDriver().getCurrentUrl() != null && !getDriver().getCurrentUrl().equalsIgnoreCase("about:blank")), WAIT_FOR_PAGE);
416+
assertTrue("Link " + href + " did not load in " + WAIT_FOR_PAGE + "ms.", loaded);
417+
418+
// Assert page is not empty and does not have errors
419+
URL url = getURL();
420+
assertFalse("URL " + url + " is empty.", isPageEmpty());
421+
assertNoLabKeyErrors();
422+
423+
// assertNoLabKeyErrors does not catch all types of errors
424+
assertElementNotPresent("LabKey error found for URL " + url, Locators.labkeyErrorHeading);
425+
426+
// record link as valid and cleanup
427+
validUrl = url.toString();
428+
getDriver().close();
429+
switchToWindow(0);
430+
}
431+
432+
return validUrl;
433+
}
434+
435+
private void validatePageLinks(Set<String> crawledLinks)
436+
{
437+
log("Validating links on " + getURL());
438+
439+
// Find all anchors in the body content area, excluding buttons and those in data regions
440+
List<WebElement> 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'))]"));
441+
442+
log(anchors.size() + " possible links found.");
443+
int validatedCount = 0;
444+
Set<String> validLinksOnPage = new HashSet<>();
445+
for (WebElement anchor : anchors)
446+
{
447+
// Only validate links once
448+
String href = anchor.getDomAttribute("href");
449+
if (href != null && (validLinksOnPage.contains(href) || crawledLinks.contains(href)))
450+
continue;
451+
452+
// Validate and record valid links
453+
String validUrl = validLink(anchor);
454+
if (validUrl != null)
455+
{
456+
validatedCount++;
457+
validLinksOnPage.add(validUrl);
458+
}
459+
460+
}
461+
log(validatedCount + " links validated.");
462+
463+
// Recursively crawl valid links that have not yet been crawled and aren't listed as a skip.
464+
for (String s : validLinksOnPage)
465+
{
466+
if (!crawledLinks.contains(s) && skipLinksForCrawling().stream().noneMatch(link -> s.toLowerCase().contains(link.toLowerCase())))
467+
{
468+
beginAt(s);
469+
crawledLinks.add(s); // mark page as crawled to avoid loops
470+
validatePageLinks(crawledLinks);
471+
}
472+
}
473+
}
474+
475+
@Test
476+
public void testCrawlEhrLinks()
477+
{
478+
goToEHRFolder();
479+
Set<String> crawledLinks = new HashSet<>();
480+
validatePageLinks(crawledLinks);
481+
}
482+
303483
@Test
304484
public void testCalculatedAgeColumns()
305485
{

0 commit comments

Comments
 (0)