feat: 增强自动运行记录状态处理,确保真实失败步骤不被误解析为指导文本
This commit is contained in:
+26
-2
@@ -10448,6 +10448,28 @@ async function runAutoSequenceFromStep(startStep, context = {}) {
|
|||||||
let currentStartStep = startStep;
|
let currentStartStep = startStep;
|
||||||
let continueCurrentAttempt = continued;
|
let continueCurrentAttempt = continued;
|
||||||
const resolvedSignupMethod = await ensureResolvedSignupMethodForRun();
|
const resolvedSignupMethod = await ensureResolvedSignupMethodForRun();
|
||||||
|
const normalizePlusPaymentMethodForRun = typeof normalizePlusPaymentMethod === 'function'
|
||||||
|
? normalizePlusPaymentMethod
|
||||||
|
: (value) => (String(value || '').trim().toLowerCase() === 'gpc-helper' ? 'gpc-helper' : String(value || '').trim().toLowerCase());
|
||||||
|
const plusPaymentMethodGpcHelper = typeof PLUS_PAYMENT_METHOD_GPC_HELPER === 'string'
|
||||||
|
? PLUS_PAYMENT_METHOD_GPC_HELPER
|
||||||
|
: 'gpc-helper';
|
||||||
|
const attachFailedStep = (error, step) => {
|
||||||
|
const failedStep = Math.floor(Number(step) || 0);
|
||||||
|
if (!error || typeof error !== 'object' || failedStep <= 0) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Number.isInteger(Number(error.failedStep)) || Number(error.failedStep) <= 0) {
|
||||||
|
try {
|
||||||
|
error.failedStep = failedStep;
|
||||||
|
} catch (_err) {
|
||||||
|
// Some host errors may be non-extensible; state-based inference still covers normal paths.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return error;
|
||||||
|
};
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
||||||
@@ -10487,6 +10509,7 @@ async function runAutoSequenceFromStep(startStep, context = {}) {
|
|||||||
try {
|
try {
|
||||||
await executeStepAndWait(3, AUTO_STEP_DELAYS[3]);
|
await executeStepAndWait(3, AUTO_STEP_DELAYS[3]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
attachFailedStep(err, 3);
|
||||||
if (isStopError(err)) {
|
if (isStopError(err)) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
@@ -10533,6 +10556,7 @@ async function runAutoSequenceFromStep(startStep, context = {}) {
|
|||||||
await executeStepAndWait(step, AUTO_STEP_DELAYS[step]);
|
await executeStepAndWait(step, AUTO_STEP_DELAYS[step]);
|
||||||
step += 1;
|
step += 1;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
attachFailedStep(err, step);
|
||||||
if (isStopError(err)) {
|
if (isStopError(err)) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
@@ -10540,8 +10564,8 @@ async function runAutoSequenceFromStep(startStep, context = {}) {
|
|||||||
const stepExecutionKey = typeof getStepExecutionKeyForState === 'function'
|
const stepExecutionKey = typeof getStepExecutionKeyForState === 'function'
|
||||||
? getStepExecutionKeyForState(step, latestState)
|
? getStepExecutionKeyForState(step, latestState)
|
||||||
: '';
|
: '';
|
||||||
const isGpcCheckoutStep = normalizePlusPaymentMethod(latestState?.plusPaymentMethod) === PLUS_PAYMENT_METHOD_GPC_HELPER
|
const isGpcCheckoutStep = normalizePlusPaymentMethodForRun(latestState?.plusPaymentMethod) === plusPaymentMethodGpcHelper
|
||||||
|| String(latestState?.plusCheckoutSource || '').trim() === PLUS_PAYMENT_METHOD_GPC_HELPER;
|
|| String(latestState?.plusCheckoutSource || '').trim() === plusPaymentMethodGpcHelper;
|
||||||
if (isGpcCheckoutStep
|
if (isGpcCheckoutStep
|
||||||
&& (stepExecutionKey === 'plus-checkout-create' || stepExecutionKey === 'plus-checkout-billing')
|
&& (stepExecutionKey === 'plus-checkout-create' || stepExecutionKey === 'plus-checkout-billing')
|
||||||
&& isGpcCheckoutRestartRequiredFailure(err)) {
|
&& isGpcCheckoutRestartRequiredFailure(err)) {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractRecordStep(status = '', detail = '') {
|
function extractRecordStepFromStatus(status = '') {
|
||||||
const normalizedStatus = String(status || '').trim().toLowerCase();
|
const normalizedStatus = String(status || '').trim().toLowerCase();
|
||||||
const statusMatch = normalizedStatus.match(/^step(\d+)_(?:failed|stopped)$/);
|
const statusMatch = normalizedStatus.match(/^step(\d+)_(?:failed|stopped)$/);
|
||||||
if (statusMatch) {
|
if (statusMatch) {
|
||||||
@@ -47,6 +47,21 @@
|
|||||||
return Number.isInteger(step) && step > 0 ? step : null;
|
return Number.isInteger(step) && step > 0 ? step : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractRecordStepFromDetailPrefix(detail = '') {
|
||||||
|
const text = String(detail || '').trim();
|
||||||
|
const detailMatch = text.match(/^(?:Step\s+(\d+)|步骤\s*(\d+))\s*(?::|:|失败|停止|已|\b)/i);
|
||||||
|
if (!detailMatch) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const step = Number(detailMatch[1] || detailMatch[2]);
|
||||||
|
return Number.isInteger(step) && step > 0 ? step : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractAnyRecordStepFromDetail(detail = '') {
|
||||||
const text = String(detail || '').trim();
|
const text = String(detail || '').trim();
|
||||||
const detailMatch = text.match(/(?:Step\s+(\d+)|步骤\s*(\d+))/i);
|
const detailMatch = text.match(/(?:Step\s+(\d+)|步骤\s*(\d+))/i);
|
||||||
if (!detailMatch) {
|
if (!detailMatch) {
|
||||||
@@ -57,6 +72,76 @@
|
|||||||
return Number.isInteger(step) && step > 0 ? step : null;
|
return Number.isInteger(step) && step > 0 ? step : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractRecordStep(status = '', detail = '') {
|
||||||
|
return extractRecordStepFromStatus(status) || extractRecordStepFromDetailPrefix(detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseFailureLabelStep(label = '') {
|
||||||
|
const match = String(label || '').trim().match(/^步骤\s*(\d+)\s*(?:失败|停止)$/);
|
||||||
|
if (!match) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const step = Number(match[1]);
|
||||||
|
return Number.isInteger(step) && step > 0 ? step : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldIgnorePersistedFailedStepCandidate(candidate, failureDetail = '') {
|
||||||
|
if (!Number.isInteger(candidate) || candidate <= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = String(failureDetail || '').trim();
|
||||||
|
if (!text) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const leadingStep = extractRecordStepFromDetailPrefix(text);
|
||||||
|
if (Number.isInteger(leadingStep) && leadingStep > 0) {
|
||||||
|
return leadingStep !== candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
const incidentalStep = extractAnyRecordStepFromDetail(text);
|
||||||
|
return incidentalStep === candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveNormalizedFailedStep(record = {}, failureDetail = '') {
|
||||||
|
const explicitStatusStep = extractRecordStepFromStatus(record.finalStatus || record.status || '');
|
||||||
|
if (Number.isInteger(explicitStatusStep) && explicitStatusStep > 0) {
|
||||||
|
return explicitStatusStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
const detailStep = extractRecordStepFromDetailPrefix(failureDetail);
|
||||||
|
if (Number.isInteger(detailStep) && detailStep > 0) {
|
||||||
|
return detailStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
const failedStepCandidate = Number(record.failedStep);
|
||||||
|
if (Number.isInteger(failedStepCandidate)
|
||||||
|
&& failedStepCandidate > 0
|
||||||
|
&& !shouldIgnorePersistedFailedStepCandidate(failedStepCandidate, failureDetail)) {
|
||||||
|
return failedStepCandidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveFailureLabel(finalStatus, rawFailureLabel = '', computedFailureLabel = '', failedStep = null) {
|
||||||
|
const rawLabel = String(rawFailureLabel || '').trim();
|
||||||
|
if (finalStatus === 'stopped') {
|
||||||
|
return computedFailureLabel;
|
||||||
|
}
|
||||||
|
if (finalStatus !== 'failed') {
|
||||||
|
return rawLabel || computedFailureLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawStep = parseFailureLabelStep(rawLabel);
|
||||||
|
if (Number.isInteger(rawStep) && rawStep > 0) {
|
||||||
|
return rawStep === failedStep ? rawLabel : computedFailureLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rawLabel || computedFailureLabel;
|
||||||
|
}
|
||||||
|
|
||||||
function isPhoneVerificationFailure(detail = '') {
|
function isPhoneVerificationFailure(detail = '') {
|
||||||
const text = String(detail || '').trim();
|
const text = String(detail || '').trim();
|
||||||
if (!text) {
|
if (!text) {
|
||||||
@@ -247,10 +332,9 @@
|
|||||||
const failureDetail = finalStatus === 'failed' || finalStatus === 'stopped'
|
const failureDetail = finalStatus === 'failed' || finalStatus === 'stopped'
|
||||||
? String(record.failureDetail || record.reason || '').trim()
|
? String(record.failureDetail || record.reason || '').trim()
|
||||||
: '';
|
: '';
|
||||||
const failedStepCandidate = Number(record.failedStep);
|
const failedStep = finalStatus === 'failed' || finalStatus === 'stopped'
|
||||||
const failedStep = Number.isInteger(failedStepCandidate) && failedStepCandidate > 0
|
? resolveNormalizedFailedStep(record, failureDetail)
|
||||||
? failedStepCandidate
|
: null;
|
||||||
: extractRecordStep(record.finalStatus || record.status || '', failureDetail);
|
|
||||||
const autoRunContext = normalizeAutoRunContext(record.autoRunContext);
|
const autoRunContext = normalizeAutoRunContext(record.autoRunContext);
|
||||||
const retryCount = normalizeRetryCount(
|
const retryCount = normalizeRetryCount(
|
||||||
record.retryCount !== undefined
|
record.retryCount !== undefined
|
||||||
@@ -271,9 +355,7 @@
|
|||||||
finalStatus,
|
finalStatus,
|
||||||
finishedAt,
|
finishedAt,
|
||||||
retryCount,
|
retryCount,
|
||||||
failureLabel: finalStatus === 'stopped'
|
failureLabel: resolveFailureLabel(finalStatus, rawFailureLabel, computedFailureLabel, failedStep),
|
||||||
? computedFailureLabel
|
|
||||||
: (rawFailureLabel || computedFailureLabel),
|
|
||||||
failureDetail,
|
failureDetail,
|
||||||
failedStep: Number.isInteger(failedStep) && failedStep > 0 ? failedStep : null,
|
failedStep: Number.isInteger(failedStep) && failedStep > 0 ? failedStep : null,
|
||||||
source,
|
source,
|
||||||
|
|||||||
@@ -85,6 +85,95 @@
|
|||||||
return Math.max(0, Number(summary?.attempts || 0) - 1);
|
return Math.max(0, Number(summary?.attempts || 0) - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeRecordStep(value) {
|
||||||
|
const step = Math.floor(Number(value) || 0);
|
||||||
|
return step > 0 ? step : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractStepFromRecordStatus(status = '') {
|
||||||
|
const match = String(status || '').trim().toLowerCase().match(/^step(\d+)_(?:failed|stopped)$/);
|
||||||
|
if (!match) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return normalizeRecordStep(match[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getKnownStepIdsFromState(state = {}) {
|
||||||
|
const ids = new Set();
|
||||||
|
for (const key of Object.keys(state?.stepStatuses || {})) {
|
||||||
|
const step = normalizeRecordStep(key);
|
||||||
|
if (step) {
|
||||||
|
ids.add(step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentStep = normalizeRecordStep(state?.currentStep);
|
||||||
|
if (currentStep) {
|
||||||
|
ids.add(currentStep);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(ids).sort((left, right) => left - right);
|
||||||
|
}
|
||||||
|
|
||||||
|
function inferRecordStepFromState(state = {}, preferredStatuses = []) {
|
||||||
|
const statuses = state?.stepStatuses || {};
|
||||||
|
const preferredStatusSet = new Set(preferredStatuses.map((item) => String(item || '').trim()).filter(Boolean));
|
||||||
|
const stepIds = getKnownStepIdsFromState(state);
|
||||||
|
const currentStep = normalizeRecordStep(state?.currentStep);
|
||||||
|
|
||||||
|
if (currentStep && preferredStatusSet.has(String(statuses[currentStep] || '').trim())) {
|
||||||
|
return currentStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchingSteps = stepIds
|
||||||
|
.filter((step) => preferredStatusSet.has(String(statuses[step] || '').trim()))
|
||||||
|
.sort((left, right) => right - left);
|
||||||
|
if (matchingSteps.length) {
|
||||||
|
return matchingSteps[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentStep) {
|
||||||
|
const currentStatus = String(statuses[currentStep] || '').trim();
|
||||||
|
if (!['', 'pending', 'completed', 'manual_completed', 'skipped'].includes(currentStatus)) {
|
||||||
|
return currentStep;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function inferRecordStepFromError(errorLike = null) {
|
||||||
|
if (!errorLike || typeof errorLike !== 'object') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizeRecordStep(errorLike.failedStep)
|
||||||
|
|| normalizeRecordStep(errorLike.step)
|
||||||
|
|| normalizeRecordStep(errorLike.currentStep);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveAutoRunAccountRecordStatus(status, state = {}, errorLike = null) {
|
||||||
|
const normalizedStatus = String(status || '').trim().toLowerCase();
|
||||||
|
const explicitStep = extractStepFromRecordStatus(normalizedStatus);
|
||||||
|
if (explicitStep) {
|
||||||
|
return normalizedStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalizedStatus === 'failed') {
|
||||||
|
const failedStep = inferRecordStepFromError(errorLike)
|
||||||
|
|| inferRecordStepFromState(state, ['failed', 'running']);
|
||||||
|
return failedStep ? `step${failedStep}_failed` : status;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalizedStatus === 'stopped') {
|
||||||
|
const stoppedStep = inferRecordStepFromError(errorLike)
|
||||||
|
|| inferRecordStepFromState(state, ['stopped', 'running']);
|
||||||
|
return stoppedStep ? `step${stoppedStep}_stopped` : status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
function formatAutoRunFailureReasons(reasons = []) {
|
function formatAutoRunFailureReasons(reasons = []) {
|
||||||
if (!Array.isArray(reasons) || !reasons.length) {
|
if (!Array.isArray(reasons) || !reasons.length) {
|
||||||
return '未知错误';
|
return '未知错误';
|
||||||
@@ -456,7 +545,7 @@
|
|||||||
forceFreshTabsNextRun = false;
|
forceFreshTabsNextRun = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const appendRoundRecordIfNeeded = async (status, reason = '') => {
|
const appendRoundRecordIfNeeded = async (status, reason = '', errorLike = null) => {
|
||||||
if (roundRecordAppended) {
|
if (roundRecordAppended) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -465,7 +554,9 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const record = await appendAccountRunRecord(status, null, reason);
|
const recordState = await getState();
|
||||||
|
const recordStatus = resolveAutoRunAccountRecordStatus(status, recordState, errorLike);
|
||||||
|
const record = await appendAccountRunRecord(recordStatus, recordState, reason);
|
||||||
if (record) {
|
if (record) {
|
||||||
roundRecordAppended = true;
|
roundRecordAppended = true;
|
||||||
}
|
}
|
||||||
@@ -507,7 +598,7 @@
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (isStopError(err)) {
|
if (isStopError(err)) {
|
||||||
stoppedEarly = true;
|
stoppedEarly = true;
|
||||||
await appendRoundRecordIfNeeded('stopped', getErrorMessage(err));
|
await appendRoundRecordIfNeeded('stopped', getErrorMessage(err), err);
|
||||||
await addLog(`第 ${targetRun}/${totalRuns} 轮已被用户停止`, 'warn');
|
await addLog(`第 ${targetRun}/${totalRuns} 轮已被用户停止`, 'warn');
|
||||||
await broadcastAutoRunStatus('stopped', {
|
await broadcastAutoRunStatus('stopped', {
|
||||||
currentRun: targetRun,
|
currentRun: targetRun,
|
||||||
@@ -556,7 +647,7 @@
|
|||||||
await setState({
|
await setState({
|
||||||
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
|
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
|
||||||
});
|
});
|
||||||
await appendRoundRecordIfNeeded('failed', reason);
|
await appendRoundRecordIfNeeded('failed', reason, err);
|
||||||
cancelPendingCommands('当前轮因认证流程进入 add-phone 已终止。');
|
cancelPendingCommands('当前轮因认证流程进入 add-phone 已终止。');
|
||||||
await broadcastStopToContentScripts();
|
await broadcastStopToContentScripts();
|
||||||
if (!autoRunSkipFailures) {
|
if (!autoRunSkipFailures) {
|
||||||
@@ -591,7 +682,7 @@
|
|||||||
await setState({
|
await setState({
|
||||||
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
|
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
|
||||||
});
|
});
|
||||||
await appendRoundRecordIfNeeded('failed', reason);
|
await appendRoundRecordIfNeeded('failed', reason, err);
|
||||||
cancelPendingCommands('当前轮因接码号池暂无可用号码已终止。');
|
cancelPendingCommands('当前轮因接码号池暂无可用号码已终止。');
|
||||||
await broadcastStopToContentScripts();
|
await broadcastStopToContentScripts();
|
||||||
if (!autoRunSkipFailures) {
|
if (!autoRunSkipFailures) {
|
||||||
@@ -626,7 +717,7 @@
|
|||||||
await setState({
|
await setState({
|
||||||
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
|
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
|
||||||
});
|
});
|
||||||
await appendRoundRecordIfNeeded('failed', reason);
|
await appendRoundRecordIfNeeded('failed', reason, err);
|
||||||
cancelPendingCommands('当前轮因 Plus 免费试用资格不可用已终止。');
|
cancelPendingCommands('当前轮因 Plus 免费试用资格不可用已终止。');
|
||||||
await broadcastStopToContentScripts();
|
await broadcastStopToContentScripts();
|
||||||
if (!autoRunSkipFailures) {
|
if (!autoRunSkipFailures) {
|
||||||
@@ -661,7 +752,7 @@
|
|||||||
await setState({
|
await setState({
|
||||||
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
|
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
|
||||||
});
|
});
|
||||||
await appendRoundRecordIfNeeded('failed', reason);
|
await appendRoundRecordIfNeeded('failed', reason, err);
|
||||||
cancelPendingCommands('当前轮因 GPC 任务已结束。');
|
cancelPendingCommands('当前轮因 GPC 任务已结束。');
|
||||||
await broadcastStopToContentScripts();
|
await broadcastStopToContentScripts();
|
||||||
if (!autoRunSkipFailures) {
|
if (!autoRunSkipFailures) {
|
||||||
@@ -696,7 +787,7 @@
|
|||||||
await setState({
|
await setState({
|
||||||
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
|
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
|
||||||
});
|
});
|
||||||
await appendRoundRecordIfNeeded('failed', reason);
|
await appendRoundRecordIfNeeded('failed', reason, err);
|
||||||
cancelPendingCommands('当前轮因 user_already_exists 已终止。');
|
cancelPendingCommands('当前轮因 user_already_exists 已终止。');
|
||||||
await broadcastStopToContentScripts();
|
await broadcastStopToContentScripts();
|
||||||
if (!autoRunSkipFailures) {
|
if (!autoRunSkipFailures) {
|
||||||
@@ -731,7 +822,7 @@
|
|||||||
await setState({
|
await setState({
|
||||||
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
|
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
|
||||||
});
|
});
|
||||||
await appendRoundRecordIfNeeded('failed', reason);
|
await appendRoundRecordIfNeeded('failed', reason, err);
|
||||||
cancelPendingCommands('当前轮因步骤 4 连续 405 错误已终止。');
|
cancelPendingCommands('当前轮因步骤 4 连续 405 错误已终止。');
|
||||||
await broadcastStopToContentScripts();
|
await broadcastStopToContentScripts();
|
||||||
if (!autoRunSkipFailures) {
|
if (!autoRunSkipFailures) {
|
||||||
@@ -787,7 +878,7 @@
|
|||||||
} catch (sleepError) {
|
} catch (sleepError) {
|
||||||
if (isStopError(sleepError)) {
|
if (isStopError(sleepError)) {
|
||||||
stoppedEarly = true;
|
stoppedEarly = true;
|
||||||
await appendRoundRecordIfNeeded('stopped', getErrorMessage(sleepError));
|
await appendRoundRecordIfNeeded('stopped', getErrorMessage(sleepError), sleepError);
|
||||||
await addLog(`第 ${targetRun}/${totalRuns} 轮已被用户停止`, 'warn');
|
await addLog(`第 ${targetRun}/${totalRuns} 轮已被用户停止`, 'warn');
|
||||||
await broadcastAutoRunStatus('stopped', {
|
await broadcastAutoRunStatus('stopped', {
|
||||||
currentRun: targetRun,
|
currentRun: targetRun,
|
||||||
@@ -811,7 +902,7 @@
|
|||||||
} catch (sleepError) {
|
} catch (sleepError) {
|
||||||
if (isStopError(sleepError)) {
|
if (isStopError(sleepError)) {
|
||||||
stoppedEarly = true;
|
stoppedEarly = true;
|
||||||
await appendRoundRecordIfNeeded('stopped', getErrorMessage(sleepError));
|
await appendRoundRecordIfNeeded('stopped', getErrorMessage(sleepError), sleepError);
|
||||||
await addLog(`第 ${targetRun}/${totalRuns} 轮已被用户停止`, 'warn');
|
await addLog(`第 ${targetRun}/${totalRuns} 轮已被用户停止`, 'warn');
|
||||||
await broadcastAutoRunStatus('stopped', {
|
await broadcastAutoRunStatus('stopped', {
|
||||||
currentRun: targetRun,
|
currentRun: targetRun,
|
||||||
@@ -833,7 +924,7 @@
|
|||||||
await setState({
|
await setState({
|
||||||
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
|
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
|
||||||
});
|
});
|
||||||
await appendRoundRecordIfNeeded('failed', reason);
|
await appendRoundRecordIfNeeded('failed', reason, err);
|
||||||
if (!autoRunSkipFailures) {
|
if (!autoRunSkipFailures) {
|
||||||
cancelPendingCommands('当前轮执行失败。');
|
cancelPendingCommands('当前轮执行失败。');
|
||||||
await broadcastStopToContentScripts();
|
await broadcastStopToContentScripts();
|
||||||
@@ -948,6 +1039,7 @@
|
|||||||
handleAutoRunLoopUnhandledError,
|
handleAutoRunLoopUnhandledError,
|
||||||
logAutoRunFinalSummary,
|
logAutoRunFinalSummary,
|
||||||
normalizeAutoRunRoundSummary,
|
normalizeAutoRunRoundSummary,
|
||||||
|
resolveAutoRunAccountRecordStatus,
|
||||||
serializeAutoRunRoundSummaries,
|
serializeAutoRunRoundSummaries,
|
||||||
skipAutoRunCountdown,
|
skipAutoRunCountdown,
|
||||||
startAutoRunLoop,
|
startAutoRunLoop,
|
||||||
|
|||||||
@@ -198,6 +198,54 @@ test('account run history helper accepts phone-only records without forcing emai
|
|||||||
assert.equal(normalized.finalStatus, 'failed');
|
assert.equal(normalized.finalStatus, 'failed');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('account run history does not turn prerequisite guidance into a fake step 2 failure', () => {
|
||||||
|
const source = fs.readFileSync('background/account-run-history.js', 'utf8');
|
||||||
|
const globalScope = {};
|
||||||
|
const api = new Function('self', `${source}; return self.MultiPageBackgroundAccountRunHistory;`)(globalScope);
|
||||||
|
|
||||||
|
const helpers = api.createAccountRunHistoryHelpers({
|
||||||
|
chrome: { storage: { local: { get: async () => ({}), set: async () => {} } } },
|
||||||
|
getState: async () => ({}),
|
||||||
|
normalizeAccountRunHistoryHelperBaseUrl: (value) => String(value || '').trim(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const genericFailedRecord = helpers.buildAccountRunHistoryRecord({
|
||||||
|
email: 'late@example.com',
|
||||||
|
password: 'secret',
|
||||||
|
autoRunning: true,
|
||||||
|
autoRunCurrentRun: 1,
|
||||||
|
autoRunTotalRuns: 3,
|
||||||
|
autoRunAttemptRun: 2,
|
||||||
|
}, 'failed', '缺少登录账号:请先完成步骤 2,或在侧栏填写账号后再执行当前步骤。');
|
||||||
|
|
||||||
|
assert.equal(genericFailedRecord.failedStep, null);
|
||||||
|
assert.equal(genericFailedRecord.failureLabel, '流程失败');
|
||||||
|
|
||||||
|
const explicitFailedRecord = helpers.buildAccountRunHistoryRecord({
|
||||||
|
email: 'late@example.com',
|
||||||
|
password: 'secret',
|
||||||
|
autoRunning: true,
|
||||||
|
autoRunCurrentRun: 1,
|
||||||
|
autoRunTotalRuns: 3,
|
||||||
|
autoRunAttemptRun: 2,
|
||||||
|
}, 'step10_failed', '缺少登录账号:请先完成步骤 2,或在侧栏填写账号后再执行当前步骤。');
|
||||||
|
|
||||||
|
assert.equal(explicitFailedRecord.failedStep, 10);
|
||||||
|
assert.equal(explicitFailedRecord.failureLabel, '步骤 10 失败');
|
||||||
|
|
||||||
|
const migratedOldRecord = helpers.normalizeAccountRunHistoryRecord({
|
||||||
|
email: 'old@example.com',
|
||||||
|
password: 'secret',
|
||||||
|
finalStatus: 'failed',
|
||||||
|
failureLabel: '步骤 2 失败',
|
||||||
|
failureDetail: '缺少登录账号:请先完成步骤 2,或在侧栏填写账号后再执行当前步骤。',
|
||||||
|
failedStep: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(migratedOldRecord.failedStep, null);
|
||||||
|
assert.equal(migratedOldRecord.failureLabel, '流程失败');
|
||||||
|
});
|
||||||
|
|
||||||
test('account run history merges email and phone identities from the same run', async () => {
|
test('account run history merges email and phone identities from the same run', async () => {
|
||||||
const source = fs.readFileSync('background/account-run-history.js', 'utf8');
|
const source = fs.readFileSync('background/account-run-history.js', 'utf8');
|
||||||
const globalScope = {};
|
const globalScope = {};
|
||||||
|
|||||||
@@ -15,3 +15,31 @@ test('auto-run controller module exposes a factory', () => {
|
|||||||
|
|
||||||
assert.equal(typeof api?.createAutoRunController, 'function');
|
assert.equal(typeof api?.createAutoRunController, 'function');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('auto-run account record status preserves the real failed step instead of parsing guidance text', () => {
|
||||||
|
const source = fs.readFileSync('background/auto-run-controller.js', 'utf8');
|
||||||
|
const globalScope = {};
|
||||||
|
const api = new Function('self', `${source}; return self.MultiPageBackgroundAutoRunController;`)(globalScope);
|
||||||
|
const controller = api.createAutoRunController({});
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
currentStep: 11,
|
||||||
|
stepStatuses: {
|
||||||
|
2: 'completed',
|
||||||
|
10: 'completed',
|
||||||
|
11: 'failed',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const error = new Error('缺少登录账号:请先完成步骤 2,或在侧栏填写账号后再执行当前步骤。');
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
controller.resolveAutoRunAccountRecordStatus('failed', state, error),
|
||||||
|
'step11_failed'
|
||||||
|
);
|
||||||
|
|
||||||
|
error.failedStep = 13;
|
||||||
|
assert.equal(
|
||||||
|
controller.resolveAutoRunAccountRecordStatus('failed', state, error),
|
||||||
|
'step13_failed'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user