{"record":{"id":"96abb81f682c8777","repo":"grafana/k6","slug":"getting-page-title-expected-string-got-t","errorCode":null,"errorMessage":"getting page title: expected string, got %T","messagePattern":"getting page title: expected string, got %T","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/browser/common/page.go","lineNumber":1794,"sourceCode":"\n// Timeout will return the default timeout or the one set by the user.\n// It's an internal method not to be exposed as a JS API.\nfunc (p *Page) Timeout() time.Duration {\n\treturn p.defaultTimeout()\n}\n\n// Title returns the page title.\nfunc (p *Page) Title() (string, error) {\n\tp.logger.Debugf(\"Page:Title\", \"sid:%v\", p.sessionID())\n\n\tjs := `() => document.title`\n\tv, err := p.Evaluate(js)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"getting page title: %w\", err)\n\t}\n\ts, ok := v.(string)\n\tif !ok {\n\t\treturn \"\", fmt.Errorf(\"getting page title: expected string, got %T\", v)\n\t}\n\n\treturn s, nil\n}\n\n// ThrottleCPU will slow the CPU down from chrome's perspective to simulate\n// a test being run on a slower device.\nfunc (p *Page) ThrottleCPU(cpuProfile CPUProfile) error {\n\tp.logger.Debugf(\"Page:ThrottleCPU\", \"sid:%v\", p.sessionID())\n\n\tp.frameSessionsMu.RLock()\n\tdefer p.frameSessionsMu.RUnlock()\n\n\tfor _, fs := range p.frameSessions {\n\t\tif err := fs.throttleCPU(cpuProfile); err != nil {\n\t\t\treturn err\n\t\t}\n\t}","sourceCodeStart":1776,"sourceCodeEnd":1812,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/browser/common/page.go#L1776-L1812","documentation":"After successful evaluation, Page.Title type-asserts the result to string; this error means document.title evaluated to a non-string Go value (typically nil when the Sobek conversion of an undefined/null result yields untyped nil). Per spec document.title is always a string, so in practice the context returned an empty/destroyed result — an edge case around context destruction rather than page content.","triggerScenarios":"The evaluate call returns just as the execution context is destroyed, producing an undefined result instead of a string; or the page is in a non-standard state (error page, interstitial) where the evaluation short-circuits.","commonSituations":"Extremely narrow race with navigation/teardown; almost never observed in stable scripts.","solutions":["Retry page.title() once — the second call runs in the new context","Avoid calling page.title() during navigation or close","Report a k6 issue with a reproducer if it happens deterministically on a loaded, idle page"],"exampleFix":"// before\nconst t = await page.title();\n\n// after\nlet t;\nfor (let i = 0; i < 2; i++) {\n  try { t = await page.title(); break; } catch (e) { await page.waitForTimeout(100); }\n}","handlingStrategy":"type-guard","validationCode":"const t = await page.title().catch(() => null);\nif (typeof t !== 'string') { /* retry in fresh context */ }","typeGuard":"const isTitleResult = (v) => typeof v === 'string';","tryCatchPattern":"try {\n  const t = await page.title();\n} catch (e) {\n  if (/expected string/.test(e.message)) { const t = await page.title(); /* fresh context usually fixes it */ }\n  else throw e;\n}","preventionTips":["Avoid title reads while navigation swaps execution contexts","Wrap one retry; if deterministic on an idle page, file a k6 bug"],"tags":["browser","page-title","type-assertion","race-condition"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}