{"record":{"id":"d6aa88e086833d9e","repo":"grafana/k6","slug":"waiting-for-function-getting-handle-w","errorCode":null,"errorMessage":"waiting for function, getting handle: %w","messagePattern":"waiting for function, getting handle: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/browser/common/frame.go","lineNumber":2004,"sourceCode":"\tif execCtx == nil {\n\t\treturn nil, fmt.Errorf(\"waiting for function: execution context %q not found\", world)\n\t}\n\tinjected, err := execCtx.getInjectedScript(apiCtx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"getting injected script: %w\", err)\n\t}\n\n\tpageFn := `\n\t\t(injected, predicate, polling, timeout, ...args) => {\n\t\t\treturn injected.waitForPredicateFunction(predicate, polling, timeout, ...args);\n\t\t}\n\t`\n\n\t// First evaluate the predicate function itself to get its handle.\n\topts := evalOptions{forceCallable: false, returnByValue: false}\n\thandle, err := execCtx.eval(apiCtx, opts, js)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"waiting for function, getting handle: %w\", err)\n\t}\n\n\t// Then evaluate the injected function call, passing it the predicate\n\t// function handle and the rest of the arguments.\n\topts = evalOptions{forceCallable: true, returnByValue: false}\n\tresult, err := execCtx.eval(\n\t\tapiCtx, opts, pageFn, append([]any{\n\t\t\tinjected,\n\t\t\thandle,\n\t\t\tpolling,\n\t\t\ttimeout.Milliseconds(), // The JS value is in ms integers\n\t\t}, args...)...)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"waiting for function, polling: %w\", err)\n\t}\n\t// prevent passing a non-nil interface to the upper layers.\n\tif result == nil {\n\t\treturn nil, nil //nolint:nilnil","sourceCodeStart":1986,"sourceCodeEnd":2022,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/browser/common/frame.go#L1986-L2022","documentation":"waitForFunction's next step evaluates the user-supplied predicate JS itself (non-callable, returnByValue=false) to obtain a handle; failure is wrapped as 'waiting for function, getting handle: <cause>'. Causes include syntax errors in the predicate string, the context dying before evaluation, or serialization failure of the function.","triggerScenarios":"Passing a malformed predicate (e.g. waitForFunction('window.x' as a plain expression string that is not a function, unbalanced braces, or PageFunction shape the module cannot serialize); navigation destroying the context between the previous step and this eval.","commonSituations":"Building predicate strings dynamically with bad interpolation; passing an arrow function that closes over k6-side variables (not serializable); racing page transitions at poll start.","solutions":["Pass a plain function reference: waitForFunction(() => document.querySelector('.done') !== null) — no closure over external variables, no string surgery.","If using a string, make it a full function expression: waitForFunction('() => window.ready === true').","Move dynamic values into args: waitForFunction((v) => window.value === v, expectedValue).","Wait for domcontentloaded before polling to avoid the navigation race."],"exampleFix":"// before\nawait frame.waitForFunction(`window.count == ${n}`); // not a function, closure leak\n\n// after\nawait frame.waitForFunction((v) => window.count === v, n, { timeout: 30_000 });","handlingStrategy":"validation","validationCode":"// predicate must be a self-contained function; pass data via args\nconst pred = () => window.count === undefined || window.count >= 3;\nawait frame.waitForLoadState('domcontentloaded');","typeGuard":"const isFn = (f) => typeof f === 'function';\nif (!isFn(pred)) throw new TypeError('predicate must be a function');","tryCatchPattern":"try { await frame.waitForFunction(pred, arg, { timeout }); }\ncatch (e) { if (/getting handle/.test(e.message)) { /* fix predicate shape: function expr, no closures, args for data */ } throw e; }","preventionTips":["Pass function references, not interpolated strings","Never close over k6-side variables inside the predicate; use the args parameter","Ensure the string form is a full function expression like '() => ...'"],"tags":["browser","wait","function-serialization","predicate","execution-context"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}