// 解析二维码 const decodeImage = require('jimp').read; const { readFile, unlinkSync } = require('fs'); const qrcodeReader = require('qrcode-reader'); const sharp = require('sharp'); const Tesseract = require('tesseract.js'); /** * 解析二维码 * @param {*} pathSrc 图片路径 * @returns Promise 二维码内容 */ export const decodeQR = function(pathSrc) { // var filePath = path.resolve(__dirname, pathSrc); return new Promise((resolve, reject) => { readFile(pathSrc, function(err, fileBuffer) { if (err) { reject(err); return; } decodeImage(fileBuffer, function(err, image) { if (err) { reject(err); return; } let decodeQR = new qrcodeReader(); decodeQR.callback = function(errorWhenDecodeQR, result) { if (errorWhenDecodeQR) { reject(errorWhenDecodeQR); unlinkSync(pathSrc); return; } if (!result) { console.log('gone with wind'); resolve(''); unlinkSync(pathSrc); } else { resolve(result.result); console.log(result.result); //结果 unlinkSync(pathSrc); } }; decodeQR.decode(image.bitmap); }); }); }); }; /** * - ¥1500 -> { method: '', amount: 1500 } * - 银联¥1500.0 -> { method: 银联, amount: 1500 } * @param {string} amountText ¥1500 * @returns {{ method: string, amount: number }} */ export const convertAmountText = function(amountText) { let method = ''; let amount = 0; const text = amountText.replace('\n', ''); const amountTextArray = text.match(/(\s)¥([0-9.]+)/); if (amountTextArray) { method = amountTextArray[1]; amount = Number(amountTextArray[2]); } return { method: method, amount: amount }; }; /** * 返回列表中目标元素的index * @param {import('@playwright/test').Locator} targetElement * @param elementList */ export const getListIndexForTargetElement = async (targetElement, elementList) => { return targetElement.evaluate((el, list) => { return Array.from(list).indexOf(el); }, await elementList.elementHandles()); }; /** * 保留数字(清空非数字) * @param {*} str 仅保留数字 * @returns */ export function KeepOnlyNumbers(str) { return str.replace(/\D/g, ''); } /** * 保留中文和数字(清空符号) * @param {*} str 仅保留中文和数字 * @returns */ export function CleanPunctuation(str) { return str.replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, ''); } /** * 等待指定接口加载完成 * @param {import('@playwright/test').Page} page * @param {string[]} apiArray 接口名称数组 * @returns Promise */ export const waitSpecifyApiLoad = (page, apiArray) => { if (apiArray === undefined || apiArray.length === 0) { return Promise.resolve([]); } return Promise.all( apiArray.map(api => page.waitForResponse(res => res.url().includes(api) && res.status() === 200)), ); }; /** * 等待Locator元素内的文本值不再变化 * @param {import('@playwright/test').Locator} locator * @param {boolean} [waitForAnimations = false] 等待动画结束 * @param {number} [sameTextCount = 20] 重复次数 * @returns */ export const waitStable = async function(locator, waitForAnimations = false, sameTextCount = 20) { await locator.evaluate( async (element, { waitForAnimations, sameTextCount }) => { const progressIsStable = async function(element, lastText = null, sameTextCounter = 0, iteration = 0) { // 递归次数超过500次退出 if (iteration > 500) { throw new Error('超出了最大的递归次数'); } // 等待动画结束 if (waitForAnimations) { await Promise.all(element.getAnimations().map(animation => animation.finished)); } // 间隔15ms检测一次元素变化 await new Promise(resolve => setTimeout(resolve, 15)); // 获取当前元素文本 const text = element.innerText; // 和上次元素的文本进行对比 const sameText = text === lastText; if (sameText) { // 和上次一致,则+1 ++sameTextCounter; } else { // 和上次不一致,则归0 sameTextCounter = 0; } const isStable = sameTextCounter >= sameTextCount; if (isStable) { // 重复次数超过20次则推出循环 return true; } else { // 否则继续递归 return progressIsStable(element, text, sameTextCounter, ++iteration); } }; // 进行递归 return progressIsStable(element); }, { waitForAnimations, sameTextCount }, ); // 返回通过校验的locator return locator; }; /** * 处理图像并进行验证码识别 * @param {string} inputImagePath - 输入图像文件名(位于 .images 文件夹内) * @param {string} outputImagePath - 输出处理后图像的文件名(位于 .images 文件夹内) * @returns {Promise} - 返回识别的文本结果 */ export const processAndRecognizeCaptcha = async (inputImagePath, outputImagePath) => { try { // 图像处理 await sharp(inputImagePath) // .sharpen() .modulate({ brightness: 1.2, // 增加亮度 contrast: 1.5, // 增强对比度 }) .resize(800) // 调整图像宽度为800像素,保持纵横比 .grayscale() // 转换为灰度图 .threshold(128) // 二值化,阈值设定为128 .toFile(outputImagePath); console.log('图像处理完成:', outputImagePath); // 图像识别 const { data: { text }, } = await Tesseract.recognize(outputImagePath, 'eng', { langPath: './tessdata', }); console.log('识别结果:', text.trim()); return text.replace(/\s+/g, '').trim(); // 返回识别的文本结果 } catch (err) { console.error('处理或识别出错:', err); throw err; // 将错误抛出以供调用者处理 } };