projectdiscovery/katana · warning

ErrElementNotVisible

ErrElementNotVisible

Error message

element not visible

What it means

ErrElementNotVisible is returned by dispatchCrawlAction when the DOM element targeted by a crawl action cannot be made visible/interactable in the headless browser. Crawl counts it as a consecutive failure and skips the action instead of aborting the whole crawl.

Source

Thrown at pkg/engine/headless/crawler/crawler.go:484

			return err
		}
	}

	err = c.crawlGraph.AddPageState(*pageState)
	if err != nil {
		return err
	}

	// TODO: Check if the page opened new sub pages and if so capture their
	// navigation as well as close them so the state change can work.

	if len(navigations) == 0 && c.crawlQueue.Size() == 0 {
		return ErrNoCrawlingAction
	}
	return nil
}

var ErrElementNotVisible = errors.New("element not visible")

func (c *Crawler) executeCrawlStateAction(action *types.Action, page *browser.BrowserPage) error {
	return runWithActionHooks(c.options.Hooks, page, action, func() error {
		return c.dispatchCrawlAction(action, page)
	})
}

func (c *Crawler) dispatchCrawlAction(action *types.Action, page *browser.BrowserPage) error {
	var err error
	switch action.Type {
	case types.ActionTypeLoadURL:
		// Apply a timeout to every critical Rod call.
		pTimeout := page.Timeout(c.options.PageMaxTimeout)

		if err := pTimeout.Navigate(action.Input); err != nil {
			return err
		}
		if err = page.WaitPageLoadHeurisitics(); err != nil {

View on GitHub (pinned to e3e742739c)

Solutions

  1. Let the crawler skip it — it increments consecutiveFailures and continues automatically
  2. Re-run with slower pacing / wait options so dynamic UI has time to become visible
  3. Audit the target page for overlays (cookie banners) and pre-dismiss via custom hooks
  4. Update or loosen selectors in custom actions so they target always-visible elements

Example fix

// before: strict selector hitting hidden element
action.Selector = "#submit-button" // display:none until form filled
// after: trigger the visibility first
action.Selector = "form.open #submit-button" // wait for state where button is visible
Defensive patterns

Strategy: retry

Validate before calling

// Before executing, check the element exists and is interactable:
el, err := page.Element(action.Selector)
if err != nil || el == nil { skip }

Type guard

func isElementNotVisible(err error) bool { return errors.Is(err, crawler.ErrElementNotVisible) }

Try / catch

if errors.Is(err, crawler.ErrElementNotVisible) {
    consecutiveFailures++
    time.Sleep(backoff)
    continue // retry later or skip action
}

Prevention

When it happens

Trigger: executeCrawlStateAction → dispatchCrawlAction finds an element matched by the action's selector but its visibility check fails (crawler.go:528: if !visible return ErrElementNotVisible), e.g. element hidden behind CSS (display:none), off-viewport, or covered by overlays.

Common situations: Crawling SPAs with lazy-loaded or modal-gated UI; consent banners covering interactive elements; animations delaying visibility when the action runs; stale selectors after page mutations.

Related errors


AI-assisted analysis of projectdiscovery/katana@e3e742739c (2026-09-03). Data as JSON: /api/errors/89eae6a6f4e79ac5. Report an issue: GitHub.