{"id":"8360e71660121b10","repo":"vitest-dev/vitest","slug":"runner-must-implement-importfile-method","errorCode":null,"errorMessage":"Runner must implement \"importFile\" method.","messagePattern":"Runner must implement \"importFile\" method\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/vitest/src/runtime/runners/index.ts","lineNumber":49,"sourceCode":"  moduleRunner: TestModuleRunner,\n  traces: Traces,\n): Promise<VitestRunner> {\n  const TestRunner = await getTestRunnerConstructor(config, moduleRunner)\n  const testRunner = new TestRunner(config)\n\n  // inject private executor to every runner\n  Object.defineProperty(testRunner, 'moduleRunner', {\n    value: moduleRunner,\n    enumerable: false,\n    configurable: false,\n  })\n\n  if (!testRunner.config) {\n    testRunner.config = config\n  }\n\n  if (!testRunner.importFile) {\n    throw new Error('Runner must implement \"importFile\" method.')\n  }\n\n  if ('__setTraces' in testRunner) {\n    (testRunner.__setTraces as any)(traces)\n  }\n\n  const [diffOptions] = await Promise.all([\n    loadDiffConfig(config, moduleRunner),\n    loadSnapshotSerializers(config, moduleRunner),\n  ])\n  testRunner.config._diffOptions = diffOptions\n\n  // patch some methods, so custom runners don't need to call RPC\n  const originalOnTaskUpdate = testRunner.onTaskUpdate\n  testRunner.onTaskUpdate = async (task, events) => {\n    const p = rpc().onTaskUpdate(task, events)\n    await originalOnTaskUpdate?.call(testRunner, task, events)\n    return p","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/vitest-dev/vitest/blob/d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09/packages/vitest/src/runtime/runners/index.ts#L31-L67","documentation":"Thrown by resolveTestRunner after constructing the (possibly custom) runner instance: the resulting object does not have an `importFile` method. importFile is the one mandatory method of a VitestRunner because it is how Vitest loads each test file. Without it no test can run.","triggerScenarios":"A custom runner class (config.runner) that defines other lifecycle hooks (onCollected, onAfterRunTask, etc.) but forgets importFile. A runner class whose prototype chain is broken so importFile from the base class is lost. Extending the wrong base class that does not implement importFile.","commonSituations":"Implementing a custom runner and overlooking the importFile requirement. Subclassing a non-runner base. Shadowing importFile with `undefined` accidentally.","solutions":["Implement `importFile(filepath: string)` on your runner class (typically delegating to a vm/vite module loader).","Extend `VitestRunner` from `vitest/runners` so you inherit a default importFile and only override what you need.","Confirm the method name spelling is exactly `importFile`.","After build, check the compiled file exports the class with the method on its prototype."],"exampleFix":"// before\nexport default class MyRunner extends VitestRunner {\n  onCollected() { /* ... */ }\n  // missing importFile\n}\n\n// after\nexport default class MyRunner extends VitestRunner {\n  async importFile(file) {\n    return await this.moduleRunner.import(file)\n  }\n}","handlingStrategy":"type-guard","validationCode":"import MyRunner from './my-runner.js'\nconst proto = MyRunner.prototype\nif (typeof proto.importFile !== 'function') {\n  throw new Error('Custom runner is missing importFile')\n}","typeGuard":"function hasImportFile<R extends new (...a: any[]) => any>(Runner: R): boolean {\n  return typeof Runner.prototype.importFile === 'function'\n}","tryCatchPattern":null,"preventionTips":["Extend VitestRunner so you inherit importFile unless you override it.","Add a smoke test that instantiates the runner and asserts importFile exists.","When overriding, call super or re-implement importFile deliberately."],"tags":["config","runner","validation"],"analyzedSha":"d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09","analyzedAt":"2026-08-03T20:23:56.861Z","schemaVersion":2}