使用Cypress TypeScript下载读取文件及cy.downloadFile报错解决
问题修复与实现方案
cy.downloadFile 是云测试平台封装的自定义命令,出现Property 'downloadFile' does not exist on type 'cy & EventEmitter'报错的核心原因有两个:一是没有给该自定义命令补充TypeScript类型声明,二是该命令仅能在对应云测试环境运行,本地原生Cypress环境没有内置这个方法,补完类型本地调用依然会出运行时错误。
下面提供两种可落地的实现方式,覆盖云环境运行、本地调试兼容的需求,同时包含PDF内容校验的完整逻辑:
方式1:仅在对应云测试环境运行的修复方案
如果你所有测试都在云平台环境执行,只需要补全类型声明即可解决报错:
- 在
cypress/support目录下新建类型声明文件cypress.d.ts,写入以下内容:
/// <reference types="cypress" /> declare namespace Cypress { interface Chainable<Subject = any> { downloadFile(fileUrl: string, fileName: string): Chainable<string> } }
- 打开项目根目录的
tsconfig.json,在include配置项中添加cypress/**/*.ts,确保TS能扫描到上述声明文件即可。
注意:该方案本地调试时downloadFile方法不存在,会直接报运行时错误,需要兼容本地运行的话用下面的通用方案。
方式2:本地+云环境通用方案(无平台依赖,推荐)
不需要依赖平台专属的自定义命令,原生Cypress能力就能实现点击下载PDF、校验内容的需求,环境兼容性更好:
第一步:安装依赖
先安装PDF解析依赖,执行命令:npm i pdf-parse -D
第二步:修改Cypress配置
打开cypress.config.ts,配置下载目录、注册PDF解析的node任务:
import { defineConfig } from "cypress"; import * as path from "path"; export default defineConfig({ e2e: { setupNodeEvents(on, config) { // 配置浏览器自动下载,不弹出确认框 on("before:browser:launch", (browser, launchOptions) => { const downloadPath = path.resolve(__dirname, "cypress", "downloads"); if (browser.family === "chromium") { launchOptions.preferences.default["download"] = { default_directory: downloadPath, prompt_for_download: false, directory_upgrade: true, }; } if (browser.family === "firefox") { launchOptions.preferences["browser.download.dir"] = downloadPath; launchOptions.preferences["browser.download.folderList"] = 2; launchOptions.preferences["browser.download.manager.showWhenStarting"] = false; launchOptions.preferences["browser.helperApps.neverAsk.saveToDisk"] = "application/pdf"; } return launchOptions; }); // 注册PDF解析任务 on("task", { async parseDownloadedPdf(fileName: string) { const pdfPath = path.resolve(__dirname, "cypress", "downloads", fileName); const pdfParse = (await import("pdf-parse")).default; const fs = await import("fs/promises"); const fileBuffer = await fs.readFile(pdfPath); const pdfContent = await pdfParse(fileBuffer); // 解析完成后删除临时文件,避免旧文件干扰后续测试 await fs.unlink(pdfPath); return pdfContent.text; }, // 直链场景直接解析buffer,不需要落盘 async parsePdfFromBuffer(buffer: Buffer) { const pdfParse = (await import("pdf-parse")).default; const pdfContent = await pdfParse(buffer); return pdfContent.text; } }); return config; }, }, });
第三步:编写测试用例
/// <reference types="cypress" /> describe("PDF下载校验", () => { beforeEach(() => { // 每次用例执行前清空下载目录,避免旧文件干扰 cy.exec("rm -rf cypress/downloads/*", { failOnNonZeroExit: false }); }); it("点击页面按钮下载PDF,内容包含预期字段", () => { // 替换为实际测试页面地址 cy.visit("/target-page-path"); // 替换为实际下载按钮的选择器,点击触发下载 cy.get("[data-testid=pdf-download-btn]").click(); // 替换为实际下载的PDF文件名,等待文件下载完成 const pdfFileName = "test-document.pdf"; cy.readFile(`cypress/downloads/${pdfFileName}`, { timeout: 20000 }).should("exist"); // 解析PDF内容做断言 cy.task("parseDownloadedPdf", pdfFileName).then((pdfText) => { // 替换为实际需要校验的预期文本 expect(pdfText).to.include("预期的文档标题"); expect(pdfText).to.include("预期的业务字段值"); }); }); // 如果能拿到PDF直链,可以跳过点击下载步骤,效率更高 it("直链场景直接解析PDF校验", () => { cy.request<Buffer>({ url: "PDF文件的实际访问地址", encoding: null }).then(res => { cy.task("parsePdfFromBuffer", res.body).then(pdfText => { expect(pdfText).to.include("预期的文档内容"); }) }) }) });
常见问题说明
- 如果下载的PDF是扫描件/图片格式,
pdf-parse无法提取文本,需要换成OCR相关依赖(比如tesseract.js)做内容识别,逻辑和上述流程一致,只需要替换解析部分的代码即可。 - 下载等待时间可以根据实际网络情况调整
cy.readFile的timeout配置,避免大文件下载超时导致用例失败。
内容的提问来源于stack exchange,提问作者Laxmi
相关产品推荐
相关产品推荐

