{"record":{"id":"8988179baa4df9bf","repo":"microsoft/playwright","slug":"file-paths-must-be-all-files-or-a-single-directory","errorCode":null,"errorMessage":"File paths must be all files or a single directory","messagePattern":"File paths must be all files or a single directory","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/playwright-core/src/client/elementHandle.ts","lineNumber":278,"sourceCode":"  return payloads.reduce((size, item) => size + (item.buffer ? item.buffer.byteLength : 0), 0) >= fileUploadSizeLimit;\n}\n\nasync function resolvePathsAndDirectoryForInputFiles(items: string[]): Promise<[string[] | undefined, string | undefined]> {\n  let localPaths: string[] | undefined;\n  let localDirectory: string | undefined;\n  for (const item of items) {\n    const stat = await fs.promises.stat(item as string);\n    if (stat.isDirectory()) {\n      if (localDirectory)\n        throw new Error('Multiple directories are not supported');\n      localDirectory = path.resolve(item as string);\n    } else {\n      localPaths ??= [];\n      localPaths.push(path.resolve(item as string));\n    }\n  }\n  if (localPaths?.length && localDirectory)\n    throw new Error('File paths must be all files or a single directory');\n  return [localPaths, localDirectory];\n}\n\nexport async function convertInputFiles(files: string | FilePayload | string[] | FilePayload[], context: BrowserContext): Promise<SetInputFilesFiles> {\n  const items: (string | FilePayload)[] = Array.isArray(files) ? files.slice() : [files];\n\n  if (items.some(item => typeof item === 'string')) {\n    if (!items.every(item => typeof item === 'string'))\n      throw new Error('File paths cannot be mixed with buffers');\n\n    const [localPaths, localDirectory] = await resolvePathsAndDirectoryForInputFiles(items);\n\n    if (context._connection.isRemote()) {\n      const files = localDirectory ? (await fs.promises.readdir(localDirectory, { withFileTypes: true, recursive: true })).filter(f => f.isFile()).map(f => path.join(f.parentPath, f.name)) : localPaths!;\n      const { writableStreams, rootDir } = await context._wrapApiCall(async () => context._channel.createTempFiles({\n        rootDirName: localDirectory ? path.basename(localDirectory) : undefined,\n        items: await Promise.all(files.map(async file => {\n          const lastModifiedMs = (await fs.promises.stat(file)).mtimeMs;","sourceCodeStart":260,"sourceCodeEnd":296,"githubUrl":"https://github.com/microsoft/playwright/blob/c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6/packages/playwright-core/src/client/elementHandle.ts#L260-L296","documentation":"Thrown by resolvePathsAndDirectoryForInputFiles after the loop, when at least one file path (localPaths) and one directory path (localDirectory) were collected together. setInputFiles requires the list to be all files OR exactly one directory — mixing is not allowed.","triggerScenarios":"Passing an array that contains both regular files and a directory, e.g. setInputFiles(['a.txt', 'b.png', './folder']). The per-item directory check sets localDirectory, files populate localPaths, and the post-loop mix check fires.","commonSituations":"Glob results that include both files and a folder; user assembling uploads from disparate sources; expecting the directory to be uploaded alongside individual files.","solutions":["Split the call: upload files in one setInputFiles, the directory in another (or in a separate operation).","Expand the directory to explicit file paths and pass only files.","Pre-validate the list so it is either all-files or exactly-one-directory."],"exampleFix":"// before\nawait locator.setInputFiles(['a.txt', './folder']);\n\n// after — files only\nconst { readdirSync, statSync } = require('node:fs');\nconst { join } = require('node:path');\nconst expand = d => readdirSync(d).flatMap(f => { const p = join(d,f); return statSync(p).isDirectory() ? expand(p) : [p]; });\nawait locator.setInputFiles(['a.txt', ...expand('./folder')]);","handlingStrategy":"validation","validationCode":"import { statSync } from 'node:fs';\nfunction validateInputPaths(items: string[]) {\n  let files = 0, dirs = 0;\n  for (const p of items) { try { if (statSync(p).isDirectory()) dirs++; else files++; } catch {} }\n  if (files > 0 && dirs > 0) throw new Error('Pass either all files or exactly one directory; mixing is not supported.');\n  if (dirs > 1) throw new Error('At most one directory is supported.');\n  return items;\n}","typeGuard":"import { statSync } from 'node:fs';\ntype InputList = { kind: 'files'; paths: string[] } | { kind: 'directory'; path: string };\nfunction classify(items: string[]): InputList {\n  const files: string[] = [];\n  const dirs: string[] = [];\n  for (const p of items) { try { (statSync(p).isDirectory() ? dirs : files).push(p); } catch {} }\n  if (dirs.length > 1) throw new Error('Multiple directories not supported');\n  if (files.length && dirs.length) throw new Error('Files and a directory cannot be mixed');\n  return dirs.length ? { kind: 'directory', path: dirs[0] } : { kind: 'files', paths: files };\n}","tryCatchPattern":null,"preventionTips":["Classify each path (file vs dir) before building the upload list.","Expand directories to explicit file paths to keep the call uniform.","Reject mixed input early with a clear error."],"tags":["file-upload","validation","input-files"],"backgroundTag":null,"analyzedSha":"c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6","analyzedAt":"2026-08-12T07:26:36.950Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}