/**
 * @vitest-environment node
 *
 * 文件上传工具函数单元测试
 *
 * 测试策略：
 * - 使用 vi.fn() 模拟 Playwright Page 对象和 Locator
 * - 测试 resolveFixturePath 函数的路径解析逻辑（核心测试重点）
 * - 验证错误处理和 E2ETestError 上下文完整性
 * - 验证安全防护机制（路径遍历攻击防护）
 * - 注意：单元测试无法替代真实 E2E 集成测试（见 Story 3.3）
 *
 * Epic 2 经验教训：
 * - 单元测试覆盖率目标 80%，但无法发现真实 DOM 问题
 * - 真实 E2E 测试是必需的，不是可选项
 * - 本测试重点：路径解析逻辑、错误处理、安全防护
 */

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import type { Page } from '@playwright/test';
import * as fs from 'node:fs';
// 从主导出点导入，验证 index.ts 导出配置正确
import {
  uploadFileToField,
  E2ETestError,
  DEFAULT_TIMEOUTS,
  type FileUploadOptions
} from '@d8d/e2e-test-utils';

// Mock fs 模块
vi.mock('node:fs', async () => {
  const actual = await vi.importActual('node:fs');
  return {
    ...actual,
    existsSync: vi.fn(),
  };
});

describe('uploadFileToField - 文件上传工具', () => {
  let mockPage: Page;
  let mockLocator: any;
  let mockExistsSync: ReturnType<typeof vi.mocked<typeof fs.existsSync>>;
  let consoleDebugSpy: ReturnType<typeof vi.spyOn>;

  beforeEach(() => {
    // 重置所有 mocks
    vi.clearAllMocks();

    // Mock console.debug 减少测试输出噪音
    consoleDebugSpy = vi.spyOn(console, 'debug').mockImplementation(() => {});

    // 创建 mock locator
    mockLocator = {
      setInputFiles: vi.fn().mockResolvedValue(undefined),
    };

    // 创建 mock page
    mockPage = {
      locator: vi.fn().mockReturnValue(mockLocator),
      waitForTimeout: vi.fn().mockResolvedValue(undefined),
    } as unknown as Page;

    // 获取 mock 的 existsSync
    mockExistsSync = vi.mocked(fs.existsSync);

    // 默认行为：文件存在
    mockExistsSync.mockReturnValue(true);
  });

  afterEach(() => {
    // 恢复 console.debug 原始实现
    consoleDebugSpy.mockRestore();
  });

  describe('Task 2: 成功上传场景测试', () => {
    describe('Subtask 2.1: 默认 fixtures 目录上传', () => {
      it('应该成功上传文件（使用默认 fixtures 目录）', async () => {
        // Arrange
        const fileName = 'test-sample.jpg';
        const selector = 'photo-upload';

        // Act
        await uploadFileToField(mockPage, selector, fileName);

        // Assert
        expect(mockPage.locator).toHaveBeenCalledWith(selector);
        expect(mockLocator.setInputFiles).toHaveBeenCalledWith(
          expect.stringContaining(fileName),
          { timeout: DEFAULT_TIMEOUTS.static }
        );
        // 默认 waitForUpload: true，应该调用 waitForTimeout
        expect(mockPage.waitForTimeout).toHaveBeenCalledWith(200);
      });

      it('应该使用默认 fixtures 目录 "web/tests/fixtures"', async () => {
        // Arrange
        const fileName = 'sample.jpg';
        const selector = 'input-file';

        // Act
        await uploadFileToField(mockPage, selector, fileName);

        // Assert
        const filePathArg = mockLocator.setInputFiles.mock.calls[0][0] as string;
        expect(filePathArg).toContain('web/tests/fixtures');
        expect(filePathArg).toContain(fileName);
      });
    });

    describe('Subtask 2.2: 自定义 fixtures 目录上传', () => {
      it('应该使用自定义 fixtures 目录上传文件', async () => {
        // Arrange
        const customFixturesDir = 'custom/fixtures/path';
        const fileName = 'test-sample.jpg';
        const selector = 'file-upload';

        // Act
        await uploadFileToField(mockPage, selector, fileName, {
          fixturesDir: customFixturesDir
        });

        // Assert
        const filePathArg = mockLocator.setInputFiles.mock.calls[0][0] as string;
        expect(filePathArg).toContain(customFixturesDir);
        expect(filePathArg).toContain(fileName);
      });

      it('应该使用自定义 fixtures 目录解析绝对路径', async () => {
        // Arrange
        const customFixturesDir = 'tests/fixtures';
        const fileName = 'documents/test-sample.pdf';
        const selector = 'document-upload';

        // Act
        await uploadFileToField(mockPage, selector, fileName, {
          fixturesDir: customFixturesDir
        });

        // Assert
        const filePathArg = mockLocator.setInputFiles.mock.calls[0][0] as string;
        expect(filePathArg).toContain(customFixturesDir);
        expect(filePathArg).toContain(fileName);
      });
    });

    describe('Subtask 2.3: 子目录文件上传', () => {
      it('应该支持子目录文件上传（如 images/sample.jpg）', async () => {
        // Arrange
        const fileName = 'images/sample-id-card.jpg';
        const selector = 'photo-upload';

        // Act
        await uploadFileToField(mockPage, selector, fileName);

        // Assert
        const filePathArg = mockLocator.setInputFiles.mock.calls[0][0] as string;
        expect(filePathArg).toContain('images');
        expect(filePathArg).toContain('sample-id-card.jpg');
      });

      it('应该支持多级子目录文件上传', async () => {
        // Arrange
        const fileName = 'documents/2024/01/test-sample.pdf';
        const selector = 'document-upload';

        // Act
        await uploadFileToField(mockPage, selector, fileName);

        // Assert
        const filePathArg = mockLocator.setInputFiles.mock.calls[0][0] as string;
        expect(filePathArg).toContain('documents/2024/01');
        expect(filePathArg).toContain('test-sample.pdf');
      });
    });

    describe('Subtask 2.4: 验证 setInputFiles API 调用', () => {
      it('应该使用正确的参数调用 setInputFiles', async () => {
        // Arrange
        const fileName = 'test.jpg';
        const selector = 'file-input';

        // Act
        await uploadFileToField(mockPage, selector, fileName);

        // Assert
        expect(mockLocator.setInputFiles).toHaveBeenCalledTimes(1);
        const callArgs = mockLocator.setInputFiles.mock.calls[0];
        expect(callArgs[0]).toEqual(expect.any(String)); // 文件路径
        expect(callArgs[1]).toEqual({ timeout: DEFAULT_TIMEOUTS.static });
      });

      it('应该先调用 locator 再调用 setInputFiles', async () => {
        // Arrange
        const fileName = 'test.jpg';
        const selector = 'my-input';

        // Act
        await uploadFileToField(mockPage, selector, fileName);

        // Assert - 验证调用顺序
        expect(mockPage.locator).toHaveBeenCalledWith(selector);
        expect(mockLocator.setInputFiles).toHaveBeenCalled();
      });
    });
  });

  describe('Task 3: 错误场景测试', () => {
    describe('Subtask 3.1: 文件不存在错误', () => {
      it('应该在文件不存在时抛出 E2ETestError', async () => {
        // Arrange
        const nonExistentFile = 'non-existent-file.jpg';
        mockExistsSync.mockReturnValue(false); // 文件不存在

        // Act & Assert
        await expect(
          uploadFileToField(mockPage, 'photo-upload', nonExistentFile)
        ).rejects.toThrow(E2ETestError);
      });

      it('文件不存在错误应该包含正确的上下文信息', async () => {
        // Arrange
        const nonExistentFile = 'missing-file.jpg';
        mockExistsSync.mockReturnValue(false);

        // Act & Assert
        try {
          await uploadFileToField(mockPage, 'photo-upload', nonExistentFile);
          expect.fail('应该抛出错误');
        } catch (error) {
          expect(error).toBeInstanceOf(E2ETestError);
          const e2eError = error as E2ETestError;
          expect(e2eError.context.operation).toBe('uploadFileToField');
          expect(e2eError.context.target).toContain(nonExistentFile);
          expect(e2eError.message).toContain('💡');
        }
      });
    });

    describe('Subtask 3.2: 选择器无效错误', () => {
      it('应该在选择器无效时抛出 E2ETestError', async () => {
        // Arrange
        mockLocator.setInputFiles.mockRejectedValue(
          new Error('Element not found')
        );
        const invalidSelector = 'invalid-file-input';

        // Act & Assert
        await expect(
          uploadFileToField(mockPage, invalidSelector, 'test.jpg')
        ).rejects.toThrow(E2ETestError);
      });

      it('选择器错误应该包含选择器上下文', async () => {
        // Arrange
        const invalidSelector = 'missing-input';
        mockLocator.setInputFiles.mockRejectedValue(
          new Error('Timeout waiting for element')
        );

        // Act & Assert
        try {
          await uploadFileToField(mockPage, invalidSelector, 'test.jpg');
          expect.fail('应该抛出错误');
        } catch (error) {
          expect(error).toBeInstanceOf(E2ETestError);
          const e2eError = error as E2ETestError;
          expect(e2eError.context.operation).toBe('uploadFileToField');
          expect(e2eError.context.target).toContain(invalidSelector);
        }
      });
    });

    describe('Subtask 3.3: 验证错误消息包含正确的上下文信息', () => {
      it('文件不存在错误应该包含建议', async () => {
        // Arrange
        mockExistsSync.mockReturnValue(false);

        // Act & Assert
        try {
          await uploadFileToField(mockPage, 'input', 'missing.jpg');
          expect.fail('应该抛出错误');
        } catch (error) {
          expect(error).toBeInstanceOf(E2ETestError);
          const e2eError = error as E2ETestError;
          expect(e2eError.context.suggestion).toBeDefined();
          expect(e2eError.context.suggestion).toContain('fixtures');
        }
      });

      it('选择器错误应该包含详细建议', async () => {
        // Arrange
        mockLocator.setInputFiles.mockRejectedValue(
          new Error('Element not found')
        );

        // Act & Assert
        try {
          await uploadFileToField(mockPage, 'bad-selector', 'test.jpg');
          expect.fail('应该抛出错误');
        } catch (error) {
          expect(error).toBeInstanceOf(E2ETestError);
          const e2eError = error as E2ETestError;
          expect(e2eError.context.suggestion).toBeDefined();
          expect(e2eError.context.suggestion).toContain('data-testid');
        }
      });
    });
  });

  describe('Task 4: 边界条件和安全测试', () => {
    describe('Subtask 4.1: 路径遍历攻击防护（../路径被拒绝）', () => {
      it('应该拒绝包含 ".." 的路径（路径遍历攻击防护）', async () => {
        // Arrange & Act & Assert
        await expect(
          uploadFileToField(mockPage, 'photo-upload', '../../../etc/passwd')
        ).rejects.toThrow(E2ETestError);
      });

      it('应该拒绝包含 ".." 的子目录路径', async () => {
        // Arrange & Act & Assert
        await expect(
          uploadFileToField(mockPage, 'photo-upload', 'images/../../etc/passwd')
        ).rejects.toThrow(E2ETestError);
      });

      it('应该拒绝包含 ".." 的相对路径', async () => {
        // Arrange & Act & Assert
        await expect(
          uploadFileToField(mockPage, 'photo-upload', '../test.jpg')
        ).rejects.toThrow(E2ETestError);
      });
    });

    describe('Subtask 4.2: 绝对路径被拒绝', () => {
      it('应该拒绝绝对路径（Linux）', async () => {
        // Arrange & Act & Assert
        await expect(
          uploadFileToField(mockPage, 'photo-upload', '/etc/passwd')
        ).rejects.toThrow(E2ETestError);
      });

      it('应该拒绝绝对路径（Windows）', { skip: process.platform !== 'win32' }, async () => {
        // 注意：此测试仅在 Windows 平台上运行
        // 在 Linux 上，Node.js 的 path.isAbsolute() 不识别 Windows 路径格式
        // Arrange & Act & Assert
        await expect(
          uploadFileToField(mockPage, 'photo-upload', 'C:\\Windows\\System32\\config')
        ).rejects.toThrow(E2ETestError);
      });

      it('路径遍历错误应该包含安全建议', async () => {
        // Arrange & Act & Assert
        try {
          await uploadFileToField(mockPage, 'photo-upload', '/etc/passwd');
          expect.fail('应该抛出错误');
        } catch (error) {
          expect(error).toBeInstanceOf(E2ETestError);
          const e2eError = error as E2ETestError;
          expect(e2eError.context.suggestion).toBeDefined();
          expect(e2eError.context.suggestion).toContain('fixtures');
        }
      });
    });

    describe('Subtask 4.3: 路径遍历验证（解析后的路径在 fixtures 目录内）', () => {
      it('应该验证解析后的路径在 fixtures 目录内（防止路径遍历）', async () => {
        // 此测试验证 resolveFixturePath 的安全检查
        // 路径如 "images/../../../etc/passwd" 会被拒绝
        // 即使经过 path.normalize 处理后

        // Arrange & Act & Assert
        await expect(
          uploadFileToField(mockPage, 'photo-upload', 'images/../../../etc/passwd')
        ).rejects.toThrow(E2ETestError);
      });

      it('应该拒绝路径遍历绕过尝试', async () => {
        // 尝试使用 ./ 绕过
        await expect(
          uploadFileToField(mockPage, 'photo-upload', './../../../etc/passwd')
        ).rejects.toThrow(E2ETestError);
      });
    });

    describe('Subtask 4.4: 超时配置生效', () => {
      it('应该使用自定义超时配置', async () => {
        // Arrange
        const customTimeout = 10000;

        // Act
        await uploadFileToField(mockPage, 'photo-upload', 'test.jpg', {
          timeout: customTimeout
        });

        // Assert
        expect(mockLocator.setInputFiles).toHaveBeenCalledWith(
          expect.anything(),
          { timeout: customTimeout }
        );
      });

      it('应该使用默认超时配置（DEFAULT_TIMEOUTS.static）', async () => {
        // Arrange & Act
        await uploadFileToField(mockPage, 'photo-upload', 'test.jpg');

        // Assert
        expect(mockLocator.setInputFiles).toHaveBeenCalledWith(
          expect.anything(),
          { timeout: DEFAULT_TIMEOUTS.static }
        );
      });
    });
  });

  describe('Task 4+: 边界条件和额外安全测试', () => {
    describe('Subtask 4.5: 边界条件测试', () => {
      it('应该拒绝空文件名（文件不存在）', async () => {
        // 空文件名会导致无效路径，文件不存在检查应该失败
        mockExistsSync.mockReturnValue(false);
        await expect(
          uploadFileToField(mockPage, 'photo-upload', '')
        ).rejects.toThrow(E2ETestError);
      });

      it('应该拒绝只包含空白的文件名', async () => {
        // 只包含空白的文件名会被 path.normalize 处理，但文件不存在
        mockExistsSync.mockReturnValue(false);
        await expect(
          uploadFileToField(mockPage, 'photo-upload', '   ')
        ).rejects.toThrow(E2ETestError);
      });

      it('应该处理带特殊字符的文件名（如果文件存在则接受）', async () => {
        // 函数不验证文件名模式，只检查文件是否存在
        // 如果文件存在，则接受该文件名
        const fileNameWithSpecialChars = 'test@#$file.jpg';
        mockExistsSync.mockReturnValue(true);
        await uploadFileToField(mockPage, 'photo-upload', fileNameWithSpecialChars);
        expect(mockLocator.setInputFiles).toHaveBeenCalled();
      });

      it('应该处理超长文件名（如果文件存在则接受）', async () => {
        // 函数不验证文件名长度，只检查文件是否存在
        // 实际的文件系统限制会在创建文件时生效
        const longFileName = 'a'.repeat(200) + '.jpg';
        mockExistsSync.mockReturnValue(true);
        await uploadFileToField(mockPage, 'photo-upload', longFileName);
        expect(mockLocator.setInputFiles).toHaveBeenCalled();
      });

      it('应该拒绝超长路径（文件不存在）', async () => {
        // 超长路径可能导致文件不存在
        const veryLongFileName = 'a'.repeat(300) + '.jpg';
        mockExistsSync.mockReturnValue(false);
        await expect(
          uploadFileToField(mockPage, 'photo-upload', veryLongFileName)
        ).rejects.toThrow(E2ETestError);
      });
    });

    describe('Subtask 4.6: 路径遍历安全验证测试', () => {
      it('应该拒绝路径遍历：sub/../../../etc/passwd', async () => {
        // path.normalize("sub/../../../etc/passwd") = "../../etc/passwd"
        // 规范化后以 ".." 开头，应该被拒绝
        await expect(
          uploadFileToField(mockPage, 'photo-upload', 'sub/../../../etc/passwd')
        ).rejects.toThrow(E2ETestError);
      });

      it('应该拒绝路径遍历：a/b/../../../../../etc/passwd', async () => {
        // path.normalize 规范化为 "../../../etc/passwd"
        await expect(
          uploadFileToField(mockPage, 'photo-upload', 'a/b/../../../../../etc/passwd')
        ).rejects.toThrow(E2ETestError);
      });

      it('应该拒绝路径遍历：./../../../etc/passwd', async () => {
        // path.normalize 规范化为 "../../../etc/passwd"
        await expect(
          uploadFileToField(mockPage, 'photo-upload', './../../../etc/passwd')
        ).rejects.toThrow(E2ETestError);
      });

      it('应该接受有效的子目录路径', async () => {
        // 验证正常子目录路径仍然有效
        // 这个测试确保我们没有过度限制合法的文件路径
        mockExistsSync.mockReturnValue(true);
        await uploadFileToField(mockPage, 'photo-upload', 'images/sample.jpg');
        expect(mockLocator.setInputFiles).toHaveBeenCalled();
      });
    });
  });

  describe('其他配置选项测试', () => {
    it('应该支持 waitForUpload: false（不等待上传完成）', async () => {
      // Arrange
      const fileName = 'test.jpg';
      const selector = 'file-upload';

      // Act
      await uploadFileToField(mockPage, selector, fileName, {
        waitForUpload: false
      });

      // Assert
      expect(mockPage.waitForTimeout).not.toHaveBeenCalled();
      expect(mockLocator.setInputFiles).toHaveBeenCalled();
    });

    it('应该同时支持多个自定义选项', async () => {
      // Arrange
      const customTimeout = 8000;
      const customFixturesDir = 'custom/fixtures';

      // Act
      await uploadFileToField(mockPage, 'file-input', 'test.pdf', {
        timeout: customTimeout,
        fixturesDir: customFixturesDir,
        waitForUpload: false
      });

      // Assert
      expect(mockLocator.setInputFiles).toHaveBeenCalledWith(
        expect.stringContaining(customFixturesDir),
        { timeout: customTimeout }
      );
      expect(mockPage.waitForTimeout).not.toHaveBeenCalled();
    });
  });

  describe('Task 5: 多文件上传测试', () => {
    describe('Subtask 5.1: 单文件上传（向后兼容）', () => {
      it('应该保持向后兼容（单文件上传仍然工作）', async () => {
        // Arrange
        const fileName = 'sample-id-card.jpg';
        const selector = 'photo-upload';

        // Act
        await uploadFileToField(mockPage, selector, fileName);

        // Assert
        expect(mockPage.locator).toHaveBeenCalledWith(selector);
        expect(mockLocator.setInputFiles).toHaveBeenCalledWith(
          expect.stringContaining(fileName),
          { timeout: DEFAULT_TIMEOUTS.static }
        );
      });

      it('单文件上传应该传递字符串参数给 setInputFiles', async () => {
        // Arrange
        const fileName = 'test.jpg';

        // Act
        await uploadFileToField(mockPage, 'input', fileName);

        // Assert
        const filePathArg = mockLocator.setInputFiles.mock.calls[0][0];
        expect(typeof filePathArg).toBe('string');
        expect(filePathArg).toContain(fileName);
      });
    });

    describe('Subtask 5.2: 多文件上传（2-3 个文件）', () => {
      it('应该成功上传多个文件（3个文件）', async () => {
        // Arrange
        const fileNames = [
          'images/sample-id-card.jpg',
          'images/sample-disability-card.jpg',
          'images/sample-photo.jpg'
        ];
        const selector = 'photo-upload';

        // Act
        await uploadFileToField(mockPage, selector, fileNames);

        // Assert
        expect(mockPage.locator).toHaveBeenCalledWith(selector);
        expect(mockLocator.setInputFiles).toHaveBeenCalledWith(
          expect.any(Array),
          { timeout: DEFAULT_TIMEOUTS.static }
        );

        // 验证传入的是文件路径数组
        const filePathsArg = mockLocator.setInputFiles.mock.calls[0][0];
        expect(Array.isArray(filePathsArg)).toBe(true);
        expect(filePathsArg).toHaveLength(3);
      });

      it('应该为每个文件解析正确的路径', async () => {
        // Arrange
        const fileNames = ['test1.jpg', 'test2.jpg'];

        // Act
        await uploadFileToField(mockPage, 'input', fileNames);

        // Assert
        const filePathsArg = mockLocator.setInputFiles.mock.calls[0][0];
        expect(filePathsArg[0]).toContain('test1.jpg');
        expect(filePathsArg[1]).toContain('test2.jpg');
      });

      it('应该支持两个文件的上传', async () => {
        // Arrange
        const fileNames = ['front.jpg', 'back.jpg'];

        // Act
        await uploadFileToField(mockPage, 'input', fileNames);

        // Assert
        const filePathsArg = mockLocator.setInputFiles.mock.calls[0][0];
        expect(filePathsArg).toHaveLength(2);
      });
    });

    describe('Subtask 5.3: 文件不存在错误（包含路径列表）', () => {
      it('应该在多文件中有文件不存在时抛出错误', async () => {
        // Arrange
        mockExistsSync.mockImplementation((path: string) => {
          // 只有第一个文件存在
          return path.includes('exist.jpg');
        });
        const fileNames = ['exist.jpg', 'missing.jpg', 'also-missing.jpg'];

        // Act & Assert
        await expect(
          uploadFileToField(mockPage, 'input', fileNames)
        ).rejects.toThrow(E2ETestError);
      });

      it('多文件错误消息应该列出所有缺失文件', async () => {
        // Arrange
        mockExistsSync.mockImplementation((path: string) => {
          // 只有 exist.jpg 存在
          return path.includes('exist.jpg');
        });
        const fileNames = ['exist.jpg', 'missing1.jpg', 'missing2.jpg'];

        // Act & Assert
        try {
          await uploadFileToField(mockPage, 'input', fileNames);
          expect.fail('应该抛出错误');
        } catch (error) {
          expect(error).toBeInstanceOf(E2ETestError);
          const e2eError = error as E2ETestError;
          expect(e2eError.context.actual).toContain('missing1.jpg');
          expect(e2eError.context.actual).toContain('missing2.jpg');
          // 错误消息应该包含可用文件
          expect(e2eError.context.suggestion).toContain('exist.jpg');
        }
      });

      it('应该在所有文件都不存在时列出所有缺失文件', async () => {
        // Arrange
        mockExistsSync.mockReturnValue(false);
        const fileNames = ['missing1.jpg', 'missing2.jpg', 'missing3.jpg'];

        // Act & Assert
        try {
          await uploadFileToField(mockPage, 'input', fileNames);
          expect.fail('应该抛出错误');
        } catch (error) {
          expect(error).toBeInstanceOf(E2ETestError);
          const e2eError = error as E2ETestError;
          expect(e2eError.context.actual).toContain('missing1.jpg');
          expect(e2eError.context.actual).toContain('missing2.jpg');
          expect(e2eError.context.actual).toContain('missing3.jpg');
        }
      });
    });

    describe('Subtask 5.4: 空数组错误处理', () => {
      it('应该在空数组时抛出错误', async () => {
        // Arrange
        const emptyFileNames: string[] = [];

        // Act & Assert
        await expect(
          uploadFileToField(mockPage, 'input', emptyFileNames)
        ).rejects.toThrow(E2ETestError);
      });

      it('空数组错误消息应该包含清晰的提示', async () => {
        // Arrange
        const emptyFileNames: string[] = [];

        // Act & Assert
        try {
          await uploadFileToField(mockPage, 'input', emptyFileNames);
          expect.fail('应该抛出错误');
        } catch (error) {
          expect(error).toBeInstanceOf(E2ETestError);
          const e2eError = error as E2ETestError;
          expect(e2eError.context.operation).toBe('uploadFileToField');
          expect(e2eError.context.suggestion).toContain('不能为空');
        }
      });
    });

    describe('多文件上传配置选项', () => {
      it('多文件上传应该支持自定义 fixtures 目录', async () => {
        // Arrange
        const customFixturesDir = 'custom/fixtures';
        const fileNames = ['test1.jpg', 'test2.jpg'];

        // Act
        await uploadFileToField(mockPage, 'input', fileNames, {
          fixturesDir: customFixturesDir
        });

        // Assert
        const filePathsArg = mockLocator.setInputFiles.mock.calls[0][0];
        expect(filePathsArg[0]).toContain(customFixturesDir);
        expect(filePathsArg[1]).toContain(customFixturesDir);
      });

      it('多文件上传应该支持自定义超时', async () => {
        // Arrange
        const customTimeout = 8000;
        const fileNames = ['test1.jpg', 'test2.jpg'];

        // Act
        await uploadFileToField(mockPage, 'input', fileNames, {
          timeout: customTimeout
        });

        // Assert
        expect(mockLocator.setInputFiles).toHaveBeenCalledWith(
          expect.any(Array),
          { timeout: customTimeout }
        );
      });

      it('多文件上传应该支持 waitForUpload: false', async () => {
        // Arrange
        const fileNames = ['test1.jpg', 'test2.jpg'];

        // Act
        await uploadFileToField(mockPage, 'input', fileNames, {
          waitForUpload: false
        });

        // Assert
        expect(mockPage.waitForTimeout).not.toHaveBeenCalled();
      });
    });

    describe('多文件上传错误处理增强', () => {
      it('选择器错误应该包含多文件支持提示', async () => {
        // Arrange
        mockLocator.setInputFiles.mockRejectedValue(
          new Error('Element not found')
        );
        const fileNames = ['test1.jpg', 'test2.jpg'];

        // Act & Assert
        try {
          await uploadFileToField(mockPage, 'bad-selector', fileNames);
          expect.fail('应该抛出错误');
        } catch (error) {
          expect(error).toBeInstanceOf(E2ETestError);
          const e2eError = error as E2ETestError;
          expect(e2eError.context.suggestion).toContain('multiple');
        }
      });

      it('多文件路径遍历攻击应该被拒绝（第一个文件）', async () => {
        // Arrange & Act & Assert
        await expect(
          uploadFileToField(mockPage, 'input', ['../../../etc/passwd', 'test.jpg'])
        ).rejects.toThrow(E2ETestError);
      });

      it('多文件路径遍历攻击应该被拒绝（第二个文件）', async () => {
        // Arrange & Act & Assert
        await expect(
          uploadFileToField(mockPage, 'input', ['test.jpg', '../../../etc/passwd'])
        ).rejects.toThrow(E2ETestError);
      });
    });
  });

  describe('主导出验证 (index.ts)', () => {
    it('应该正确导出 uploadFileToField 函数', () => {
      expect(uploadFileToField).toBeDefined();
      expect(typeof uploadFileToField).toBe('function');
    });

    it('应该正确导出 FileUploadOptions 类型', () => {
      const options: FileUploadOptions = {
        timeout: 10000,
        fixturesDir: 'custom/fixtures',
        waitForUpload: false
      };
      expect(options).toBeDefined();
    });
  });
});
