Skip to content
Merged
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
17 changes: 17 additions & 0 deletions tests/app/main/views/test_skip_link.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
def test_gc_header_skip_link_present(
client_request,
):
"""Test that gc_header partial contains a functioning skip link."""
# Test on a simple page that doesn't require API calls
page = client_request.get("main.welcome")

# Find skip link in gc_header partial
skip_link = page.find("a", href="#main_content")

assert skip_link is not None, "Skip link not found in gc_header"
assert "Skip to main content" in skip_link.get_text()
assert "skiplink" in skip_link.get("class", [])

# Verify the target exists
main_content = page.find(id="main_content")
assert main_content is not None, "Skip link target '#main_content' not found"
3 changes: 3 additions & 0 deletions tests_cypress/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ npx cypress install
| IMAP_PASSWORD | IMAP password of gmail account for NOTIFY_USER |

### Environment-specific values
Get those from admin and api .env files

TODO: CYPRESS_USER_PASSWORD is called CYPRESS_USER_PW_SECRET in the .env files

| key | description |
| ----------------------------- | ------------------------------------------------------ |
Expand Down
93 changes: 93 additions & 0 deletions tests_cypress/cypress/e2e/admin/a11y/skip_links.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/// <reference types="cypress" />

describe("Skip Links Accessibility", () => {
const pages = [
{ url: `/`, name: "Home" },
{ url: `/sign-in`, name: "Sign in" },
{ url: `/register`, name: "Register" },
{ url: `/features`, name: "Features" },
];

pages.forEach(({ url, name }) => {
it(`${name} page has functioning skip link`, () => {
cy.visit(url);

// Check that skip link exists
cy.get('a[href*="#main_content"]').should("exist").as("skipLink");

// Verify skip link is accessible and has appropriate text
cy.get("@skipLink")
.should("be.visible")
.should("contain.text", "Skip")
.should("have.attr", "href");

// Get the target ID from href
cy.get("@skipLink")
.invoke("attr", "href")
.then((href) => {
const targetId = href.replace("#", "");

// Verify target element exists
cy.get(`#${targetId}, #main_content, main, [role="main"]`)
.should("exist")
.as("mainContent");
});

// Test keyboard navigation functionality
cy.get("body").press(Cypress.Keyboard.Keys.TAB); // First tab should focus skip link
cy.focused().should("contain.text", "Skip");

// Test that clicking/activating skip link moves focus
cy.get("@skipLink").click();

// Verify focus moved to main content area
cy.focused().should("satisfy", ($el) => {
return (
$el.is('#main_content, #main-content, main, [role="main"]') ||
$el.closest('#main_content, #main-content, main, [role="main"]')
.length > 0
);
});
});
});

it("Skip link appears early in tab order", () => {
cy.visit(`/`);

// Start from body and tab through first few elements
cy.get("body").press(Cypress.Keyboard.Keys.TAB);

// First or second focusable element should be skip link
cy.focused().then(($focused) => {
if (!$focused.text().toLowerCase().includes("skip")) {
cy.get("body").press(Cypress.Keyboard.Keys.TAB);
cy.focused().should("contain.text", "Skip");
}
});
});

it("Skip link is visually hidden by default but visible on focus", () => {
cy.visit(`/`);

cy.get('a[href*="#main_content"]').as("skipLink");

// Skip link should be visually hidden initially or positioned off-screen
cy.get("@skipLink").should("satisfy", ($el) => {
const computedStyle = window.getComputedStyle($el[0]);
// Check for common hiding techniques
return (
computedStyle.position === "absolute" ||
computedStyle.opacity === "0" ||
computedStyle.height === "1px" ||
computedStyle.overflow === "hidden" ||
$el.hasClass("sr-only") ||
$el.hasClass("visually-hidden") ||
$el.hasClass("skiplink")
);
});

// When focused, skip link should become visible
cy.get("@skipLink").focus();
cy.get("@skipLink").should("be.visible");
});
});
Loading