This repository has been archived on 2025-04-22. You can view files and clone it, but cannot push or open issues or pull requests.
hlk_autotest/tests/utils/utils.ts

218 lines
7.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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; // 将错误抛出以供调用者处理
}
};