阅读之前你需要知道的知识包括
Git常用命令nodejs基础知识随着项目不断的迭代,新的功能在增加,旧的功能也逐渐被更新。这导致E2E测试的体量在不断的增大。 但是,我们的每次修改不一定会涉及所有的E2E测试。所以,我们是否可以做到只运行本次commit影响的E2E?
我将问题拆分为下面几个子问题:
commit和当前分支刚被创建出时的commit,这是由于,我们的分支一般会从最新的master分支获取,而master分支中的E2E在大部分情况下,都是已经被验证过的。commit做对比以排查特定更改,引起的bug时。这里特定的commitID,需要我们在commit message中加入特定的内容,进行标记。nodejs的child_process模块调度Git命令,并使用正则的方式获取需要的内容。nodejs的child_process模块调度Cypress命令,并将输出使用Steam进行实时输出typescriptconst getBranchFirstCommitID = () => { return new Promise<string>((res, rej) => { // compare with master exec('git cherry -v master', (err, data) => { if (err) rej(err); const dataArr = data.split('\n') as string[]; const commitContent = dataArr[0].split(' '); res(commitContent[1]); }); }); };
typescriptconst getCurrentCommit = () => { return new Promise<string>((res, rej) => exec(`git log -1`, (err, stdout, stderr) => { if (err) { rej(stderr); } res(stdout); }) ); }; const getCompareCommitID = async () => { const commitContent = await getCurrentCommit(); const regx = /\[Compare: (.+?)\]/; if (!regx.test(commitContent)) { return await getBranchFirstCommitID(); } const resultArray = commitContent.match(regx) as RegExpMatchArray; return resultArray[1]; };
这里如果运行getCompareCommitID后,得到返回值为'ALL'的时候,就会运行所有的E2E测试了
这里我们约定,所有的E2E文件的路径中都会携带cypress这个字符
typescriptconst getDiffFlies = (baseCommitID: string, commitID: string) => { return new Promise<string>((res, rej) => exec(`git diff --name-only ${baseCommitID} ${commitID}`, (err, stdout, stderr) => { if (err) { rej(stderr); } res(stdout); }) ); }; const getCommitID = (index: number) => { return new Promise<string>((res, rej) => exec(`git show HEAD~${index} --pretty=format:"%h" --no-patch`, (err, stdout) => { if (err) { rej(err); } res(stdout); }) ); }; //一些特殊的项目结构下,项目中可能存在多个子系统,所以这里需要匹配特定的路径 const getProjectDiffFiles = async () => { try { const compareCommitID = await getCompareCommitID(); const currentCommitID = await getCommitID(0); if (compareCommitID === 'ALL') { // it will run all tests return []; } const diffFiles = (await getDiffFlies(currentCommitID, compareCommitID)).split('\n'); const currentProjectDiffFile = diffFiles .filter((file) => { const {dir} = parse(file); const currentDirArr = __dirname.split(sep); const currentDir = currentDirArr[currentDirArr.length - 2] as string; if (dir.includes(currentDir) && dir.includes('cypress')) { return true; } return false; }) .map((file) => { return join(...file.split(sep).slice(2)) as string; }); return currentProjectDiffFile; } catch (error) { console.log(error); // eslint-disable-line throw new Error('get commit id is fail'); } };
nodejs的child_process模块提供了多种方式运行命令,一般情况下使用exec,但由于exec的输出将会在命令完全运行后才输出,并且一般的CI中,都会设置每一步的响应时间。如果使用exec,有可能会由于E2E时间过长,导致超时。 所以,这里我使用spawn,它将会实时的输出命令的运行日志。
typescriptconst testRunner = (specPath: string[]) => { console.log('It will run test', specPath.join(',')); // eslint-disable-line const runner = spawn('cypress', ['run', '--headless', '--spec', specPath.join(',')]); runner.stderr.on('data', (data) => { if (data) console.log(data.toString()); // eslint-disable-line }); runner.stdout.on('data', (data) => { console.log(data.toString()); // eslint-disable-line }); }; const main = async () => { const testPath = await getProjectDiffFiles(); testRunner(testPath); };
至此就完成了增量的运行E2E测试。