instructure/canvas-lms · error · ElementNotFoundError
Element not found at path: #
Error message
Element not found at path: #{@path} What it means
Accessibility::ContentLoader.extract_element_from_content locates a specific HTML element inside a resource's HTML content by traversing a path (e.g. element indexes). If find_element_at_path returns nil — the path points past the document tree or into a mismatched structure — ElementNotFoundError is raised so the accessibility checker can report the content as unloadable rather than crashing on a nil element.
Solutions
- Re-fetch the content and recompute the element path before loading
- Validate the path length against the document depth (or catch ElementNotFoundError and return a 'content changed' response)
- Ensure upstream tools re-enumerate elements rather than persisting absolute paths
Example fix
// before loader = Accessibility::ContentLoader.new(resource, path: "0/3/1") loader.extract_element_from_content # raises if path stale // after begin loader.extract_element_from_content rescue Accessibility::ContentLoader::ElementNotFoundError path = recompute_path(resource) loader = Accessibility::ContentLoader.new(resource, path: path) loader.extract_element_from_content end
Defensive patterns
Strategy: try-catch
Validate before calling
def path_within_depth?(html, path)
depth = Nokogiri::HTML(html).traverse.size
path.split("/").map(&:to_i).all? { |i| i >= 0 } && path.split("/").length <= depth
end Try / catch
begin
html, metadata = loader.extract_element_from_content
rescue Accessibility::ContentLoader::ElementNotFoundError
Rails.logger.warn("stale accessibility path: #{loader.path}")
retry_with_recomputed_path(loader)
end Prevention
- Do not persist absolute element paths across content edits — recompute on demand
- Validate path indexes against current content before loading
- Handle content-change races by re-enumerating elements after edits
When it happens
Trigger: Requesting an accessibility preview/check of course content (assignment, page, etc.) with a path whose indexes exceed the actual DOM depth, or where the content was edited/reordered after the path was computed.
Common situations: Stale element paths cached before the content was edited; content without the expected structure (empty body, stripped HTML); concurrent edits changing sibling counts; tooling generating paths against a different content version.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
- [A11Y Scan] Skipped resource #
- [Accessibility::CategoryBreakdownService] dropping unknown…
- Allocation rule not found
- An old_id, '# ', referenced a non-existent # and was not…
- An old_integration_id, '#
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/fa2e3e913b92527b.
Report an issue: GitHub.
Appendix: source
Thrown at app/models/accessibility/content_loader.rb:57
def content
if @path.present?
html, metadata = extract_element_from_content
{ content: html, metadata: }
else
{ content: full_document, metadata: {} }
end
end
def full_document
resource_html_content
end
def extract_element_from_content
html_content = resource_html_content
element = find_element_at_path(html_content, @path)
raise ElementNotFoundError, "Element not found at path: #{@path}" unless element
html = generate_preview_html(element)
metadata = extract_metadata(element)
[html, metadata]
end
private
def resource_html_content
# Check if resource implements the new AccessibilityCheckable interface
if @resource.respond_to?(:scannable_content)
# New path for resources using AccessibilityCheckable (e.g., SyllabusResource)
@resource.scannable_content
else
# Legacy path for non-migrated resources
case @resource
when Assignment
@resource.descriptionView on GitHub (pinned to 1c9f0bb801)