{"record":{"id":"b3d4fdb4f885f6c7","repo":"grafana/k6","slug":"getting-text-content-of-q-w","errorCode":null,"errorMessage":"getting text content of %q: %w","messagePattern":"getting text content of %q: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/browser/common/frame.go","lineNumber":1848,"sourceCode":"\t\topts.Force, withRetry, opts.NoWaitAfter, opts.Timeout,\n\t)\n\n\tif _, err := call(f.ctx, act, opts.Timeout); err != nil {\n\t\treturn errorFromDOMError(err)\n\t}\n\n\treturn nil\n}\n\n// TextContent returns the textContent attribute of the first element found\n// that matches the selector. The second return value is true if the returned\n// text content is not null or empty, and false otherwise.\nfunc (f *Frame) TextContent(selector string, popts *FrameTextContentOptions) (string, bool, error) {\n\tf.log.Debugf(\"Frame:TextContent\", \"fid:%s furl:%q sel:%q\", f.ID(), f.URL(), selector)\n\n\tv, ok, err := f.textContent(selector, popts)\n\tif err != nil {\n\t\treturn \"\", false, fmt.Errorf(\"getting text content of %q: %w\", selector, err)\n\t}\n\n\treturn v, ok, nil\n}\n\nfunc (f *Frame) textContent(selector string, opts *FrameTextContentOptions) (string, bool, error) {\n\tTextContent := func(apiCtx context.Context, handle *ElementHandle) (any, error) {\n\t\treturn handle.textContent(apiCtx)\n\t}\n\tact := f.newAction(\n\t\tselector, DOMElementStateAttached, opts.Strict, TextContent,\n\t\t[]string{}, false, withRetry, true, opts.Timeout,\n\t)\n\tv, err := call(f.ctx, act, opts.Timeout)\n\tif err != nil {\n\t\treturn \"\", false, errorFromDOMError(err)\n\t}\n\tif v == nil {","sourceCodeStart":1830,"sourceCodeEnd":1866,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/browser/common/frame.go#L1830-L1866","documentation":"Frame.TextContent() wraps the internal action as 'getting text content of \"<selector>\": <cause>'. The cause is typically a wait timeout (element never reached the attached state within opts.timeout), a strict-mode ambiguity, or a frame/context failure during evaluation.","triggerScenarios":"frame.textContent(sel) where the selector never matches within the timeout (default 30s); selector matches multiple elements with strict mode; frame navigated or detached while reading; page closed.","commonSituations":"Reading text of dynamically rendered content without waiting; wrong selector after a UI update; CI slowness making the default timeout too short.","solutions":["Prefer waiting for the content: const el = await frame.waitForSelector(sel); then el.textContent().","Fix the selector to match exactly one node, or pass { strict: false } intentionally.","Increase the timeout: frame.textContent(sel, { timeout: 60_000 }).","Check page/frame state (isClosed/isDetached) if the failure is instant rather than after a timeout."],"exampleFix":"// before\nconst t = await frame.textContent('.status'); // element not rendered yet\n\n// after\nconst el = await frame.waitForSelector('.status');\nconst t = await el.textContent();","handlingStrategy":"try-catch","validationCode":"const el = await frame.waitForSelector(sel, { state: 'attached' });\nif (!el) throw new Error('element not attached');","typeGuard":null,"tryCatchPattern":"try { return await frame.textContent(sel, { timeout: 60_000 }); }\ncatch (e) { if (/getting text content of/.test(e.message)) { return null; /* treat as absent */ } throw e; }","preventionTips":["waitForSelector before reading text on dynamic content","Ensure selectors match exactly one node under strict mode","Raise timeouts on slow CI environments"],"tags":["browser","text","selector","timeout","strict-mode"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}