grafana/k6 · error
node is either not visible or not an HTMLElement
Error message
node is either not visible or not an HTMLElement: %w
What it means
Element-screenshot step: after scrolling into view, boundingBox() failed, so the module reports the element is either not visible or not an HTMLElement. boundingBox needs the element's box model via CDP; failures mean the node has no box (not rendered), was detached, or is not an element node (e.g. a text node handle).
Solutions
- Ensure the element is actually visible and expanded (open the accordion/tab, remove display:none) before the screenshot.
- Re-query the element right before capturing so it cannot be detached.
- Wait for visible state and for animations/transitions to finish.
- Check the wrapped error for detaches — 'node is detached from the document'.
Example fix
// before
await page.click('#details-summary'); // panel animating open
await page.$('#details-body').screenshot({ path: 'd.png' });
// after
await page.click('#details-summary');
const el = page.$('#details-body');
await el.waitFor({ state: 'visible' });
await el.screenshot({ path: 'd.png' }); Defensive patterns
Strategy: type-guard
Validate before calling
await handle.waitFor({ state: 'visible' });
const box = await handle.boundingBox();
if (!box || box.width <= 0 || box.height <= 0) throw new Error('element not renderable');
await handle.screenshot({ path: 'e.png' }); Type guard
const isRenderableElement = async (h) => {
const b = await h.boundingBox();
return !!b && b.width > 0 && b.height > 0;
}; Try / catch
try {
await handle.screenshot({ path: 'e.png' });
} catch (e) {
if (/not visible or not an HTMLElement/i.test(String(e))) {
// element hidden/detached: open container or re-query before retrying
await page.locator(sel).waitFor({ state: 'visible' });
await page.locator(sel).screenshot({ path: 'e.png' });
} else throw e;
} Prevention
- Open collapsed containers (accordions, tabs) before capturing their content.
- Re-locate elements right before screenshots in SPAs that re-render.
- Wait for animations to complete so the box model is final.
When it happens
Trigger: Screenshotting a hidden element (display:none/visibility:hidden ancestor), an element detached between scroll and measurement, an SVG-in-shadow-DOM edge case, or a handle to a non-element node.
Common situations: SPA re-render detaching the node mid-call; elements inside collapsed accordions/tabs not opened; content in an iframe whose layout changed; screenshotting immediately after style changes with transitions pending.
Related errors
- getting original viewport size
- node has 0 height
- node has 0 width
- scrolling element into view
- capturing screenshot
AI-assisted analysis of grafana/k6@3fcf5388d7 (2026-08-18).
Data as JSON: /api/errors/15eb956e02cfd718.
Report an issue: GitHub.
Appendix: source
Thrown at internal/js/modules/k6/browser/common/screenshotter.go:304
func (s *screenshotter) screenshotElement(
h *ElementHandle, opts *ElementHandleScreenshotOptions,
) (buf []byte, err error) {
format := opts.Format
viewportSize, err := s.originalViewportSize(h.frame.page)
if err != nil {
return nil, fmt.Errorf("getting original viewport size: %w", err)
}
originalSize := h.frame.page.emulatedSize
err = h.waitAndScrollIntoViewIfNeeded(h.ctx, false, true, opts.Timeout)
if err != nil {
return nil, fmt.Errorf("scrolling element into view: %w", err)
}
bbox, err := h.boundingBox()
if err != nil {
return nil, fmt.Errorf("node is either not visible or not an HTMLElement: %w", err)
}
if bbox.Width <= 0 {
return nil, fmt.Errorf("node has 0 width")
}
if bbox.Height <= 0 {
return nil, fmt.Errorf("node has 0 height")
}
fitsViewport := bbox.Width <= viewportSize.Width && bbox.Height <= viewportSize.Height
if !fitsViewport { //nolint:nestif
overriddenViewportSize := Size{
Width: math.Max(viewportSize.Width, bbox.Width),
Height: math.Max(viewportSize.Height, bbox.Height),
}.enclosingIntSize()
defer func() {
if restoreErr := s.restoreViewport(h.frame.page, originalSize); restoreErr != nil {
err = errors.Join(err, fmt.Errorf("restoring viewport: %w", restoreErr))
}View on GitHub (pinned to 3fcf5388d7)