Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ project.dependencies {
implementation("org.seleniumhq.selenium:selenium-remote-driver:${seleniumVersion}")
implementation("org.seleniumhq.selenium:selenium-chrome-driver:${seleniumVersion}")
implementation "org.seleniumhq.selenium:selenium-ie-driver:${seleniumVersion}"

implementation("com.deque.html.axe-core:selenium:${axeCoreSeleniumVersion}")

implementation("org.eclipse.jetty:jetty-util:${jettyVersion}")

implementation("com.google.guava:guava:${guavaVersion}")
Expand Down
2 changes: 2 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ assertjVersion=3.27.7

awaitilityVersion=4.3.0

axeCoreSeleniumVersion=4.11.1

lookfirstSardineVersion=5.13

jettyVersion=12.1.5
Expand Down
5 changes: 5 additions & 0 deletions src/org/labkey/test/TestProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,11 @@ public static boolean ignoreDatabaseNotSupportedException()
return "true".equals(System.getProperty("webtest.ignoreDatabaseNotSupportedException"));
}

public static boolean isAccessibilityCheckEnabled()
{
return "true".equals(System.getProperty("webtest.enableAccessibilityCheck", "true")); // TODO: default to "false" if lots of TeamCity noise
}

/**
* Parses system property 'webtest.server.startup.timeout' to determine maximum allowed server startup time.
* If property is not defined or is not an integer, it defaults to 120 seconds.
Expand Down
3 changes: 3 additions & 0 deletions src/org/labkey/test/WebDriverWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import org.labkey.test.pages.study.ManageStudyPage;
import org.labkey.test.pages.user.ShowUsersPage;
import org.labkey.test.selenium.EphemeralWebElement;
import org.labkey.test.util.AccessibilityUtils;
import org.labkey.test.util.CodeMirrorHelper;
import org.labkey.test.util.Crawler;
import org.labkey.test.util.EscapeUtil;
Expand Down Expand Up @@ -2134,6 +2135,8 @@ public long doAndMaybeWaitForPageToLoad(int msWait, Supplier<Boolean> action)
String warning = "Action doesn't define a page title";
addActionWarning(warning, getDriver().getCurrentUrl());
}

AccessibilityUtils.scanPage(getDriver());
}

return loadTimer.elapsed().toMillis();
Expand Down
12 changes: 12 additions & 0 deletions src/org/labkey/test/components/Component.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@
*/
package org.labkey.test.components;

import com.deque.html.axecore.results.Results;
import com.deque.html.axecore.selenium.AxeBuilder;
import org.apache.commons.lang3.NotImplementedException;
import org.jetbrains.annotations.NotNull;
import org.labkey.test.Locator;
import org.labkey.test.selenium.RefindingWebElement;
import org.labkey.test.util.AccessibilityUtils;
import org.labkey.test.util.TestLogger;
import org.labkey.test.util.selenium.WebDriverUtils;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.SearchContext;
Expand Down Expand Up @@ -79,12 +83,20 @@ protected EC elementCache()
// It succeeded, so it should be safe to get a fresh `newElementCache`
_elementCache = Objects.requireNonNull(newElementCache());
}

if (shouldScanAfterReady())
AccessibilityUtils.scanComponent(this);
}
return _elementCache;
}

protected void waitForReady() { }

protected boolean shouldScanAfterReady()
{
return false;
}

protected EC newElementCache()
{
throw new NotImplementedException("Please override newElementCache() in your component class");
Expand Down
6 changes: 6 additions & 0 deletions src/org/labkey/test/components/bootstrap/ModalDialog.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ protected void waitForReady()
!elementCache().body.getText().isEmpty(), "Modal dialog not ready.", 2_500);
}

@Override
protected boolean shouldScanAfterReady()
{
return true;
}

@Override
public WebElement getComponentElement()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ protected void waitForReady()
*/
public GridFilterModal selectField(CharSequence fieldIdentifier)
{
getWrapper().shortWait().until(ExpectedConditions.invisibilityOfElementLocated(Locator.byClass("field-modal__empty-msg")));
WebElement fieldItem = elementCache().findFieldOption(fieldIdentifier);
getWrapper().scrollIntoView(fieldItem);
String fieldLabel = WebElementUtils.getTextContent(fieldItem);
Expand Down
6 changes: 6 additions & 0 deletions src/org/labkey/test/pages/LabKeyPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@
*/
package org.labkey.test.pages;

import com.deque.html.axecore.results.Results;
import com.deque.html.axecore.selenium.AxeBuilder;
import org.labkey.test.BaseWebDriverTest;
import org.labkey.test.Locators;
import org.labkey.test.WebDriverWrapper;
import org.labkey.test.util.AccessibilityUtils;
import org.labkey.test.util.TestLogger;
import org.openqa.selenium.By;
import org.openqa.selenium.SearchContext;
import org.openqa.selenium.WebDriver;
Expand Down Expand Up @@ -45,6 +49,8 @@ public LabKeyPage(WrapsDriver test)
_test = (BaseWebDriverTest)test;
_wrapsDriver = test;
waitForPage();

AccessibilityUtils.scanPage(this);
}

public LabKeyPage(WebDriver driver)
Expand Down
77 changes: 77 additions & 0 deletions src/org/labkey/test/util/AccessibilityUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package org.labkey.test.util;

import com.deque.html.axecore.results.Results;
import com.deque.html.axecore.selenium.AxeBuilder;
import org.jspecify.annotations.NonNull;
import org.labkey.test.TestProperties;
import org.labkey.test.components.Component;
import org.labkey.test.pages.LabKeyPage;
import org.labkey.test.util.selenium.WebDriverUtils;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

public class AccessibilityUtils
{
public static void scanPage(WebDriver driver)
{
Results results = getAnalyzer().analyze(driver);
recordViolations(results, "page");
}

public static void scanPage(LabKeyPage<?> page)
{
Results results = getAnalyzer().analyze(WebDriverUtils.extractWrappedDriver(page.getWrappedDriver()));
recordViolations(results, page.getClass().getSimpleName());
}

public static void scanComponent(Component<?> component)
{
Results results = getAnalyzer().analyze(WebDriverUtils.extractWrappedDriver(component.getComponentElement()), component.getComponentElement());
recordViolations(results, component.getClass().getSimpleName());
}

private static @NonNull AxeBuilder getAnalyzer()
{
if (TestProperties.isAccessibilityCheckEnabled())
return new AxeBuilder();
else
return NoOpAxeBuilder.get();
}

private static void recordViolations(Results results, String component)
{
if (results.isErrored())
{
TestLogger.error("Accessibility violations found on " + component);
results.getViolations().forEach(violation -> TestLogger.error(violation.getDescription()));
}
}
}

class NoOpAxeBuilder extends AxeBuilder
{
private static final CachingSupplier<NoOpAxeBuilder> INSTANCE = new CachingSupplier<>(NoOpAxeBuilder::new);

static NoOpAxeBuilder get()
{
return INSTANCE.get();
}

@Override
public Results analyze(WebDriver webDriver, WebElement... context)
{
return new Results();
}

@Override
public Results analyze(WebDriver webDriver)
{
return new Results();
}

@Override
public Results analyze(WebDriver webDriver, boolean injectAxe)
{
return new Results();
}
}
Loading