218 lines
7.2 KiB
TypeScript
218 lines
7.2 KiB
TypeScript
import { Page } from "playwright";
|
||
|
||
// 解析二维码
|
||
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<qrResult> 二维码内容
|
||
*/
|
||
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 {Page} page - Playwright Page 对象
|
||
* @param {string[]} apiArray - 接口名称数组
|
||
* @returns Promise<Response[]>
|
||
*/
|
||
export const waitSpecifyApiLoad = async (page: Page, apiArray: string[]): Promise<Response[]> => {
|
||
// 输入参数检查
|
||
if (!page || !Array.isArray(apiArray) || apiArray.length === 0 || !apiArray.every(item => typeof item === 'string')) {
|
||
return [];
|
||
}
|
||
|
||
// 去重
|
||
const uniqueApiArray = Array.from(new Set(apiArray));
|
||
|
||
// 精确匹配 URL
|
||
const urlMatchers = uniqueApiArray.map(api => new RegExp(`^.*${api}$`));
|
||
|
||
// 等待所有接口加载完成
|
||
const responses = await Promise.allSettled(
|
||
urlMatchers.map(matcher => page.waitForResponse(res => matcher.test(res.url()) && res.status() === 200))
|
||
);
|
||
|
||
// 处理结果
|
||
return responses
|
||
.filter(response => response.status === 'fulfilled')
|
||
.map(response => (response as unknown as PromiseFulfilledResult<Response>).value);
|
||
};
|
||
|
||
/**
|
||
* 等待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<string>} - 返回识别的文本结果
|
||
*/
|
||
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; // 将错误抛出以供调用者处理
|
||
}
|
||
};
|