This commit is contained in:
dengqichen 2025-11-19 15:42:49 +08:00
parent 6d70376a26
commit beca781934
2 changed files with 123 additions and 12 deletions

View File

@ -17,29 +17,96 @@ class BaseAction {
}
/**
* 替换配置中的变量
* @param {string} value - 包含变量的字符串 ( "{{account.email}}")
* 替换配置中的变量增强版
*
* 支持特性
* - 多数据源{{account.email}}, {{site.url}}, {{config.timeout}}
* - 默认值{{var|default}}, {{user.name|Guest}}
* - 变量不存在时警告
*
* @param {string} value - 包含变量的字符串
* @returns {string} - 替换后的值
*/
replaceVariables(value) {
if (typeof value !== 'string') return value;
return value.replace(/\{\{(.+?)\}\}/g, (match, path) => {
const keys = path.trim().split('.');
let result = this.context.data;
return value.replace(/\{\{(.+?)\}\}/g, (match, expression) => {
// 解析默认值:{{var|default}}
const [path, defaultValue] = expression.split('|').map(s => s.trim());
for (const key of keys) {
if (result && typeof result === 'object') {
result = result[key];
} else {
return match; // 无法解析,返回原始值
}
// 获取变量值
const result = this.resolveVariablePath(path);
// 如果找到值,返回
if (result !== undefined && result !== null) {
return result;
}
return result !== undefined ? result : match;
// 如果有默认值,使用默认值
if (defaultValue !== undefined) {
this.log('debug', `变量 "${path}" 不存在,使用默认值: "${defaultValue}"`);
return defaultValue;
}
// 变量不存在且无默认值,发出警告
this.log('warn', `⚠️ 变量 "${path}" 不存在,返回原始值: ${match}`);
return match;
});
}
/**
* 解析变量路径支持多个数据源
* @param {string} path - 变量路径 "account.email" "site.url"
* @returns {*} - 解析后的值
*/
resolveVariablePath(path) {
const keys = path.split('.');
const rootKey = keys[0];
// 确定数据源
let dataSource;
let startIndex = 1; // 从第二个key开始
switch (rootKey) {
case 'site':
// {{site.url}} -> context.siteConfig.url
dataSource = this.context.siteConfig;
break;
case 'config':
// {{config.timeout}} -> context.config
dataSource = this.context.config;
break;
case 'env':
// {{env.API_KEY}} -> process.env
dataSource = process.env;
break;
default:
// 默认从 context.data 读取
// {{account.email}} -> context.data.account.email
dataSource = this.context.data;
startIndex = 0; // 从第一个key开始
}
if (!dataSource) {
return undefined;
}
// 遍历路径获取值
let result = dataSource;
for (let i = startIndex; i < keys.length; i++) {
if (result && typeof result === 'object') {
result = result[keys[i]];
} else {
return undefined;
}
}
return result;
}
/**
* 记录日志
*/

View File

@ -106,6 +106,9 @@ class ConfigValidator {
}
});
// 类型检查
errors.push(...this.validateFieldTypes(step, stepLabel));
// 检查未知字段(警告)
const allFields = [...schema.required, ...schema.optional, 'action'];
Object.keys(step).forEach(field => {
@ -135,6 +138,47 @@ class ConfigValidator {
return errors;
}
/**
* 类型检查
*/
validateFieldTypes(step, stepLabel) {
const errors = [];
// fillForm 的 fields 应该是对象
if (step.action === 'fillForm' && step.fields) {
if (typeof step.fields !== 'object' || Array.isArray(step.fields)) {
errors.push(`${stepLabel}: fields 必须是对象类型`);
}
}
// 数字类型字段检查
const numberFields = ['timeout', 'maxRetries', 'retryDelay', 'duration', 'totalTimeout', 'pollInterval'];
numberFields.forEach(field => {
if (step[field] !== undefined && typeof step[field] !== 'number') {
errors.push(`${stepLabel}: ${field} 必须是数字类型,当前为 ${typeof step[field]}`);
}
});
// 布尔类型字段检查
const booleanFields = ['humanLike', 'optional', 'waitForEnabled'];
booleanFields.forEach(field => {
if (step[field] !== undefined && typeof step[field] !== 'boolean') {
errors.push(`${stepLabel}: ${field} 必须是布尔类型,当前为 ${typeof step[field]}`);
}
});
// 数组类型字段检查
if (step.steps && !Array.isArray(step.steps)) {
errors.push(`${stepLabel}: steps 必须是数组类型`);
}
if (step.verifyElements && !Array.isArray(step.verifyElements)) {
errors.push(`${stepLabel}: verifyElements 必须是数组类型`);
}
return errors;
}
/**
* 验证并打印结果
*/