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

  1. Re-fetch the content and recompute the element path before loading
  2. Validate the path length against the document depth (or catch ElementNotFoundError and return a 'content changed' response)
  3. 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

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


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.description

View on GitHub (pinned to 1c9f0bb801)