grafana/k6 · error

predicate must be a function

Error message

predicate must be a function

What it means

Thrown by parsePageWaitForEventOptions when the 'predicate' option of page.waitForEvent is present but sobek.AssertFunction fails — the value is not callable. k6 requires an actual function reference; strings of source code, bound-object property names, or plain objects are rejected.

Solutions

  1. Pass a real function reference: { predicate: r => r.ok() }.
  2. If building predicates from config data, construct a closure: const p = makePredicate(cfg); then { predicate: p }.
  3. Do not call the function when passing it (no trailing parentheses).
  4. Use an object-to-predicate adapter when config is data-only.

Example fix

// before
await page.waitForEvent('response', { predicate: cfg.predicateExpr }); // string from config

// after
const pred = new Function('r', 'return ' + cfg.predicateExpr);
await page.waitForEvent('response', { predicate: pred });
Defensive patterns

Strategy: type-guard

Validate before calling

if (opts.predicate !== undefined && typeof opts.predicate !== 'function') {
  throw new TypeError('opts.predicate must be a function');
}
await page.waitForEvent('response', opts);

Type guard

const hasCallablePredicate = (o) => o?.predicate === undefined || typeof o.predicate === 'function';

Prevention

When it happens

Trigger: page.waitForEvent('response', { predicate: 'r => r.ok()' }) (function as string); { predicate: { url: '/x' } } (object filter, unsupported in k6); passing result of calling the function instead of the function itself: { predicate: myPred() }.

Common situations: Translating Playwright's urlOrPredicate shorthand into a string; passing config-driven filter objects; accidentally invoking the function when passing it; JSON-serializing handlers from external config files.

Related errors


AI-assisted analysis of grafana/k6@8d06114777 (2026-08-18). Data as JSON: /api/errors/1aa4936d2fcfba07. Report an issue: GitHub.

Appendix: source

Thrown at internal/js/modules/k6/browser/browser/page_mapping.go:1176

	if k6common.IsNullish(opts) {
		return ropts, nil, nil
	}

	if fn, ok := sobek.AssertFunction(opts); ok {
		return ropts, fn, nil
	}

	obj := opts.ToObject(k6ext.Runtime(ctx))

	var pred sobek.Callable
	for _, k := range obj.Keys() {
		switch k {
		case "timeout":
			ropts.Timeout = time.Duration(obj.Get(k).ToInteger()) * time.Millisecond
		case "predicate":
			fn, ok := sobek.AssertFunction(obj.Get(k))
			if !ok {
				return nil, nil, fmt.Errorf("predicate must be a function")
			}
			pred = fn
		default:
			return nil, nil, fmt.Errorf("waitForEvent does not support option: '%s'", k)
		}
	}

	return ropts, pred, nil
}

// parseSize parses the size options from a Sobek value.
func parseSize(rt *sobek.Runtime, opts sobek.Value) (*common.Size, error) {
	size := &common.Size{}
	if k6common.IsNullish(opts) {
		return size, nil
	}

	obj := opts.ToObject(rt)

View on GitHub (pinned to 8d06114777)