@validate.qa/runner 1.0.89 → 1.0.91
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +210 -20
- package/dist/cli.mjs +210 -20
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -80738,7 +80738,7 @@ Return ONLY one valid JSON object matching the schema below. Do NOT include <thi
|
|
|
80738
80738
|
2. **One purpose per scenario** - each scenario tests ONE thing (happy path, validation, journey, or bug)
|
|
80739
80739
|
3. **Happy paths first** - then pair edge cases to the same form/flow
|
|
80740
80740
|
4. **Multi-screen journeys** - only when screen transitions were actually observed. When an observed target element sits below the fold, include an explicit swipe step to scroll it into view before the tap (mobile elements off-screen are not hittable until revealed)
|
|
80741
|
-
5. **Bug reports** - for observed
|
|
80741
|
+
5. **Bug reports** - for observed *product* defects (wrong row deleted, validation bypassed, count wrong). Do NOT plan scenarios whose expected outcome is system ANR / "isn't responding" freezes \u2014 those are environment noise
|
|
80742
80742
|
6. **Steps are human-readable** - "Type into the email field", not "driver.findElement(AppiumBy.accessibilityId('email')).sendKeys()"
|
|
80743
80743
|
7. **Expected outcomes are plain-English success criteria** - describe the user-visible product result, not the controls touched along the way. Steps already cover buttons, inputs, tabs, sheets, and menus. Expected outcomes should say what the user can now do or rely on (record saved, message sent, search results filtered, validation explained, home screen reached). Name the target screen or exact copy only when it is the real product outcome; do NOT list "heading visible", "button visible", or similar element inventory unless that exact control is the behavior under test.
|
|
80744
80744
|
8. **Auth-aware** - mark scenarios that need a signed-in session. Credentials are filled ON-DEVICE from the stored TEST_USER_EMAIL / TEST_USER_PASSWORD keys (key-based injection at run time) \u2014 say "type the test account email", never a literal value and never a code idiom like process.env
|
|
@@ -81150,7 +81150,7 @@ ${context.userNotes.trim()}`);
|
|
|
81150
81150
|
const requiredBlock = (0, scenario_taxonomy_js_1.formatRequiredScenarioKindsPrompt)(signals);
|
|
81151
81151
|
if (requiredBlock) {
|
|
81152
81152
|
sections.push(requiredBlock);
|
|
81153
|
-
sections.push('## Scenario kind coverage rules\n1. Prefer scenarios whose expectedOutcomes prove durable product results (state deltas, validation errors, persistence), not only "control X is visible".\n2. For negative_validation: empty required field
|
|
81153
|
+
sections.push('## Scenario kind coverage rules\n1. Prefer scenarios whose expectedOutcomes prove durable product results (state deltas, validation errors, persistence), not only "control X is visible".\n2. For negative_validation: empty required field AND/OR malformed email-like input; expected outcome must be blocked submit or visible validation error \u2014 never success Home after invalid submit.\n3. For crud_identity: capture entity identity before delete/edit, then assert that identity is gone/changed (state delta) \u2014 never only that the list screen is still visible.\n4. For count_delta: capture before count, mutate, assert count changed as expected.\n5. For double_submit: assert count +1 not +2 (or single confirmation), not merely that a button remains visible.\n6. Never plan expectedOutcomes that are ANR / System UI freezes.\n7. If a required kind is impossible given evidence, say so in "reasoning" \u2014 do not invent unobserved screens.');
|
|
81154
81154
|
}
|
|
81155
81155
|
}
|
|
81156
81156
|
const orderedScreens = plannableTargetAppScreens(graph, context.appId).sort((a, b) => a.firstSeenIteration - b.firstSeenIteration || a.screenId.localeCompare(b.screenId));
|
|
@@ -82026,6 +82026,123 @@ var require_discovery_lint_flags = __commonJS({
|
|
|
82026
82026
|
}
|
|
82027
82027
|
});
|
|
82028
82028
|
|
|
82029
|
+
// ../runner-core/dist/services/mobile/anr-env.js
|
|
82030
|
+
var require_anr_env = __commonJS({
|
|
82031
|
+
"../runner-core/dist/services/mobile/anr-env.js"(exports2) {
|
|
82032
|
+
"use strict";
|
|
82033
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
82034
|
+
exports2.textLooksLikeAnrOrSystemUi = textLooksLikeAnrOrSystemUi;
|
|
82035
|
+
exports2.isAnrOrSystemUiEnvironmentFailure = isAnrOrSystemUiEnvironmentFailure;
|
|
82036
|
+
exports2.stepLooksLikeAnrChromeAssert = stepLooksLikeAnrChromeAssert;
|
|
82037
|
+
var ANR_TEXT_RE = /system\s*ui\s*isn['’]?t\s*responding|app\s*isn['’]?t\s*responding|application\s*not\s*responding|\banr\b|close\s*app|wait\s*and\s*see|isn['’]?t\s*responding/i;
|
|
82038
|
+
function textLooksLikeAnrOrSystemUi(text) {
|
|
82039
|
+
if (!text || typeof text !== "string")
|
|
82040
|
+
return false;
|
|
82041
|
+
return ANR_TEXT_RE.test(text);
|
|
82042
|
+
}
|
|
82043
|
+
function isAnrOrSystemUiEnvironmentFailure(error2, logs2) {
|
|
82044
|
+
const blob = `${error2 ?? ""}
|
|
82045
|
+
${logs2 ?? ""}`;
|
|
82046
|
+
if (textLooksLikeAnrOrSystemUi(blob))
|
|
82047
|
+
return true;
|
|
82048
|
+
if (/\bcold-boot\b/i.test(blob) && /\b(froze|hang|anr|unresponsive)\b/i.test(blob))
|
|
82049
|
+
return true;
|
|
82050
|
+
if (/\bno actionable elements\b/i.test(blob) && /\b(system|anr|responding)\b/i.test(blob)) {
|
|
82051
|
+
return true;
|
|
82052
|
+
}
|
|
82053
|
+
return false;
|
|
82054
|
+
}
|
|
82055
|
+
function stepLooksLikeAnrChromeAssert(step) {
|
|
82056
|
+
if (step.action !== "assertVisible" && step.action !== "assertText")
|
|
82057
|
+
return false;
|
|
82058
|
+
return textLooksLikeAnrOrSystemUi([step.description, step.assertion, step.value].filter(Boolean).join(" "));
|
|
82059
|
+
}
|
|
82060
|
+
}
|
|
82061
|
+
});
|
|
82062
|
+
|
|
82063
|
+
// ../runner-core/dist/services/mobile/oracle-quality.js
|
|
82064
|
+
var require_oracle_quality = __commonJS({
|
|
82065
|
+
"../runner-core/dist/services/mobile/oracle-quality.js"(exports2) {
|
|
82066
|
+
"use strict";
|
|
82067
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
82068
|
+
exports2.assessStrongOracleQuality = assessStrongOracleQuality;
|
|
82069
|
+
var anr_env_js_1 = require_anr_env();
|
|
82070
|
+
var CHROME_ONLY_RE = /\b(tab|home tab|catalog tab|forms tab|account tab|back|menu|navigation|nav bar|bottom nav|status bar|query|search field)\b/i;
|
|
82071
|
+
var PRODUCT_OUTCOME_TEXT_RE = /\b(error|invalid|required|must|cannot|failed|denied|blocked|empty|missing|deleted|removed|saved|success|created|updated|signed?\s*out|logged?\s*out|wrong|incorrect)\b/i;
|
|
82072
|
+
function stepBlob(step) {
|
|
82073
|
+
return [step.description, step.assertion, step.value, step.target?.primary].filter(Boolean).join(" ");
|
|
82074
|
+
}
|
|
82075
|
+
function isChromeOnlyVisible(step) {
|
|
82076
|
+
if (step.action !== "assertVisible")
|
|
82077
|
+
return false;
|
|
82078
|
+
const blob = stepBlob(step);
|
|
82079
|
+
if (PRODUCT_OUTCOME_TEXT_RE.test(blob))
|
|
82080
|
+
return false;
|
|
82081
|
+
return CHROME_ONLY_RE.test(blob) || blob.length < 40;
|
|
82082
|
+
}
|
|
82083
|
+
function assessStrongOracleQuality(steps) {
|
|
82084
|
+
const productSteps = steps.filter((s) => s.action !== "launchApp");
|
|
82085
|
+
const anrAsserts = productSteps.filter(anr_env_js_1.stepLooksLikeAnrChromeAssert);
|
|
82086
|
+
const nonAnrAsserts = productSteps.filter((s) => (s.action === "assertVisible" || s.action === "assertText" || s.action === "assertStateDelta") && !(0, anr_env_js_1.stepLooksLikeAnrChromeAssert)(s));
|
|
82087
|
+
if (productSteps.length > 0 && anrAsserts.length > 0 && nonAnrAsserts.length === 0) {
|
|
82088
|
+
return {
|
|
82089
|
+
strong: false,
|
|
82090
|
+
unpairedCaptures: 0,
|
|
82091
|
+
hasStateDelta: false,
|
|
82092
|
+
hasStrongAssertText: false,
|
|
82093
|
+
hasStrongValidationVisible: false,
|
|
82094
|
+
anrOnly: true,
|
|
82095
|
+
reason: "only asserts system ANR / freeze chrome \u2014 not a product test"
|
|
82096
|
+
};
|
|
82097
|
+
}
|
|
82098
|
+
const captures = steps.filter((s) => s.action === "captureState").length;
|
|
82099
|
+
const deltas = steps.filter((s) => s.action === "assertStateDelta").length;
|
|
82100
|
+
const unpairedCaptures = Math.max(0, captures - deltas);
|
|
82101
|
+
const hasStateDelta = deltas > 0;
|
|
82102
|
+
const hasStrongAssertText = steps.some((s) => {
|
|
82103
|
+
if (s.action !== "assertText")
|
|
82104
|
+
return false;
|
|
82105
|
+
if ((0, anr_env_js_1.stepLooksLikeAnrChromeAssert)(s))
|
|
82106
|
+
return false;
|
|
82107
|
+
const blob = stepBlob(s);
|
|
82108
|
+
if (/^(email|password|username|name|query|search)$/i.test((s.assertion ?? "").trim())) {
|
|
82109
|
+
return false;
|
|
82110
|
+
}
|
|
82111
|
+
return blob.length >= 3;
|
|
82112
|
+
});
|
|
82113
|
+
const hasStrongValidationVisible = steps.some((s) => {
|
|
82114
|
+
if (s.action !== "assertVisible")
|
|
82115
|
+
return false;
|
|
82116
|
+
if ((0, anr_env_js_1.stepLooksLikeAnrChromeAssert)(s) || isChromeOnlyVisible(s))
|
|
82117
|
+
return false;
|
|
82118
|
+
return PRODUCT_OUTCOME_TEXT_RE.test(stepBlob(s));
|
|
82119
|
+
});
|
|
82120
|
+
const strong = hasStateDelta || hasStrongAssertText || hasStrongValidationVisible;
|
|
82121
|
+
let reason;
|
|
82122
|
+
if (strong && hasStateDelta) {
|
|
82123
|
+
reason = unpairedCaptures > 0 ? `has assertStateDelta but ${unpairedCaptures} unpaired captureState (prefer 1:1 pairing)` : "has assertStateDelta durable state proof";
|
|
82124
|
+
} else if (strong && hasStrongAssertText) {
|
|
82125
|
+
reason = "has product assertText outcome";
|
|
82126
|
+
} else if (strong) {
|
|
82127
|
+
reason = "has validation/error assertVisible outcome";
|
|
82128
|
+
} else if (unpairedCaptures > 0) {
|
|
82129
|
+
reason = `captureState without assertStateDelta (${unpairedCaptures}) \u2014 not strong`;
|
|
82130
|
+
} else {
|
|
82131
|
+
reason = "no durable state/text/validation oracle \u2014 smoke/nav only";
|
|
82132
|
+
}
|
|
82133
|
+
return {
|
|
82134
|
+
strong,
|
|
82135
|
+
unpairedCaptures,
|
|
82136
|
+
hasStateDelta,
|
|
82137
|
+
hasStrongAssertText,
|
|
82138
|
+
hasStrongValidationVisible,
|
|
82139
|
+
anrOnly: false,
|
|
82140
|
+
reason
|
|
82141
|
+
};
|
|
82142
|
+
}
|
|
82143
|
+
}
|
|
82144
|
+
});
|
|
82145
|
+
|
|
82029
82146
|
// ../runner-core/dist/services/mobile/mobile-generator.js
|
|
82030
82147
|
var require_mobile_generator = __commonJS({
|
|
82031
82148
|
"../runner-core/dist/services/mobile/mobile-generator.js"(exports2) {
|
|
@@ -82052,6 +82169,8 @@ var require_mobile_generator = __commonJS({
|
|
|
82052
82169
|
var mobile_session_keepalive_js_1 = require_mobile_session_keepalive();
|
|
82053
82170
|
var discovery_lint_flags_js_1 = require_discovery_lint_flags();
|
|
82054
82171
|
var scenario_taxonomy_js_1 = require_scenario_taxonomy();
|
|
82172
|
+
var anr_env_js_1 = require_anr_env();
|
|
82173
|
+
var oracle_quality_js_1 = require_oracle_quality();
|
|
82055
82174
|
var mobile_source_parser_js_1 = require_mobile_source_parser();
|
|
82056
82175
|
var mobile_source_parser_js_2 = require_mobile_source_parser();
|
|
82057
82176
|
var mobile_observation_js_1 = require_mobile_observation();
|
|
@@ -82197,7 +82316,7 @@ var require_mobile_generator = __commonJS({
|
|
|
82197
82316
|
type: "function",
|
|
82198
82317
|
function: {
|
|
82199
82318
|
name: "mobile_capture_state",
|
|
82200
|
-
description:
|
|
82319
|
+
description: "Capture list labels or numeric badge counts into a named state key BEFORE a mutating action (delete/create/toggle). MUST pair with mobile_assert_state_delta afterward \u2014 capture alone is not a strong oracle and will not tag the test @strong-oracle.",
|
|
82201
82320
|
parameters: {
|
|
82202
82321
|
type: "object",
|
|
82203
82322
|
properties: {
|
|
@@ -82559,9 +82678,11 @@ The scenario name + goal + steps describe ONE product behavior from survey/disco
|
|
|
82559
82678
|
- Navigation scenarios: you MUST land on the destination and assert something unique to it.
|
|
82560
82679
|
4. PROVE the outcome with an assert tool that matches the scenario \u2014 not ambient chrome:
|
|
82561
82680
|
- mobile_assert_visible({ref}) / mobile_assert_text({ref, expected})
|
|
82562
|
-
- For list delete/create or count changes: mobile_capture_state({stateKey, kind}) BEFORE the mutation, then mobile_assert_state_delta({stateKey, expect}) AFTER \u2014
|
|
82681
|
+
- For list delete/create or count changes: mobile_capture_state({stateKey, kind}) BEFORE the mutation, then mobile_assert_state_delta({stateKey, expect}) AFTER \u2014 BOTH are required. captureState alone does NOT prove the outcome.
|
|
82682
|
+
- Negative validation: leave a required field EMPTY (or type clearly invalid email-like input), submit, assert the app BLOCKS success \u2014 error message / still on form / no navigation to Home. Do NOT fill a valid password-only path and call it "empty email".
|
|
82563
82683
|
- After sign-in \u2192 signed-in product signal; after like \u2192 liked/unlike control or durable like state; after open Settings \u2192 a settings-specific control.
|
|
82564
82684
|
- Do NOT assert bottom-nav tab labels alone, notification badge counts ("N unread"), or one-time empty-state CTAs as the scenario outcome.
|
|
82685
|
+
- NEVER assert system freeze chrome ("System UI isn't responding", ANR dialogs, Wait/Close app). If the app freezes, finish_generate with verified:false (environment) \u2014 do NOT write a test whose only proof is the ANR dialog.
|
|
82565
82686
|
5. Call finish_generate with verified:true ONLY if you completed the goal AND asserted a matching durable outcome. If the app cannot complete the goal, finish with verified:false and explain \u2014 never invent a green path from unrelated screens.
|
|
82566
82687
|
|
|
82567
82688
|
## Rules
|
|
@@ -82570,7 +82691,7 @@ The scenario name + goal + steps describe ONE product behavior from survey/disco
|
|
|
82570
82691
|
- Assert only DURABLE elements \u2014 screen chrome that exists on every visit (tab labels, headers, nav bars, the entity you just created). NEVER assert one-time / data-dependent UI: empty-state CTAs, first-run or onboarding prompts, or counters \u2014 they vanish as soon as the account has data, and the test then fails forever on a healthy app.
|
|
82571
82692
|
- Do NOT trigger irreversible or destructive side effects \u2014 real payments; sending messages to others; and ESPECIALLY account/data deletion, deactivation, or any "type DELETE to confirm" gate (the runner hard-blocks destructive taps and confirmation keystrokes). Note them and assert what you safely can.
|
|
82572
82693
|
- STATE OWNERSHIP for mutation scenarios (edit / rename / archive / remove): CREATE the record first in this same recording, then act on the row YOU created \u2014 never mutate pre-existing data (it may not exist on a clean install, so the replay fails forever, and another test may depend on it). If the app offers no way to create one, do NOT act on someone else's data: assert what you can safely prove and finish with verified:false, explaining why in the description.
|
|
82573
|
-
- If the APP itself misbehaves (
|
|
82694
|
+
- If the APP itself misbehaves (product error screen, broken validation, wrong entity deleted), assert the product failure and call finish_generate with suspectedBug:true and a concrete bugDescription. If the device shows ANR / "System UI isn't responding" / freeze dialogs, that is ENVIRONMENT \u2014 finish with verified:false and do NOT assert the freeze dialog as the test outcome.
|
|
82574
82695
|
- Stay inside this app.${targetLine} Do not open or use app stores, browsers, email apps, settings, or any other app. If an action leaves the target app, the runner will relaunch the target app and reject that action.
|
|
82575
82696
|
- NEVER tap the phone's home screen / launcher (app icons, "Predicted app" chips, search bars, widgets). Those are NOT part of the app under test \u2014 tapping them is junk that poisons the test. If the app ever backgrounds or you find yourself on the launcher, use mobile_relaunch to cold-start the app; do NOT tap a launcher icon or app-prediction to get back in.
|
|
82576
82697
|
- Do NOT re-do a sequence you already completed. If you filled a form then navigated away, do NOT come back and fill it again \u2014 the scenario needs ONE clean pass. Filling the login form, going back, and re-filling it is exactly the kind of wandering that produces a bloated, broken test.
|
|
@@ -84162,6 +84283,9 @@ ${statePacket}`, redactionCredentials) },
|
|
|
84162
84283
|
function weakAssertionTargetReason(element) {
|
|
84163
84284
|
if (isAlwaysPresentContainer(element))
|
|
84164
84285
|
return "always-present app/system container";
|
|
84286
|
+
if ((0, anr_env_js_1.textLooksLikeAnrOrSystemUi)([element.text, element.label, element.accessibilityId, element.resourceId].filter(Boolean).join(" "))) {
|
|
84287
|
+
return "system ANR / freeze dialog chrome (environment, not product)";
|
|
84288
|
+
}
|
|
84165
84289
|
const hasIdentitySignal = Boolean(nonEmpty3(element.accessibilityId) || nonEmpty3(element.resourceId) || nonEmpty3(element.text) || nonEmpty3(element.label));
|
|
84166
84290
|
if (!hasIdentitySignal)
|
|
84167
84291
|
return "no identity locator (accessibility id / resource id / text) for the assert gate";
|
|
@@ -85023,6 +85147,30 @@ ${statePacket}`, redactionCredentials) },
|
|
|
85023
85147
|
return [action.selector, action.value, action.innerText, action.metadata?.targetLabel].filter((v) => typeof v === "string" && v.trim().length > 0).join(" ");
|
|
85024
85148
|
}
|
|
85025
85149
|
function evaluateMobileAssertionStrength(actions) {
|
|
85150
|
+
const anrAsserts = actions.filter((a) => {
|
|
85151
|
+
if (a.type !== "ASSERT_VISIBLE" && a.type !== "ASSERT_TEXT")
|
|
85152
|
+
return false;
|
|
85153
|
+
return (0, anr_env_js_1.textLooksLikeAnrOrSystemUi)([a.value, a.innerText, a.metadata?.targetLabel, a.selector].filter(Boolean).join(" "));
|
|
85154
|
+
});
|
|
85155
|
+
if (anrAsserts.length > 0) {
|
|
85156
|
+
const otherAsserts = actions.filter((a) => (a.type === "ASSERT_VISIBLE" || a.type === "ASSERT_TEXT" || a.type === "ASSERT_STATE_DELTA") && !anrAsserts.includes(a));
|
|
85157
|
+
if (otherAsserts.length === 0) {
|
|
85158
|
+
return {
|
|
85159
|
+
ok: false,
|
|
85160
|
+
severity: "block",
|
|
85161
|
+
reason: `only asserts system ANR / "isn't responding" chrome \u2014 not a product test (discard or re-record on a healthy session)`,
|
|
85162
|
+
signals: {
|
|
85163
|
+
apiTest: false,
|
|
85164
|
+
interacted: actions.some((a) => a.type === "TAP" || a.type === "TYPE"),
|
|
85165
|
+
mutated: false,
|
|
85166
|
+
navOnly: false,
|
|
85167
|
+
hasOutcomeProof: false,
|
|
85168
|
+
errorBanner: false,
|
|
85169
|
+
terminal: null
|
|
85170
|
+
}
|
|
85171
|
+
};
|
|
85172
|
+
}
|
|
85173
|
+
}
|
|
85026
85174
|
if (actions.some((a) => a.type === "ASSERT_STATE_DELTA")) {
|
|
85027
85175
|
return {
|
|
85028
85176
|
ok: true,
|
|
@@ -85169,9 +85317,10 @@ ${statePacket}`, redactionCredentials) },
|
|
|
85169
85317
|
}
|
|
85170
85318
|
const baseStrengthVerdict = evaluateMobileAssertionStrength(actions);
|
|
85171
85319
|
const bugDescription = recording.finish?.bugDescription?.trim() ?? "";
|
|
85172
|
-
const
|
|
85320
|
+
const bugLooksLikeAnr = (0, anr_env_js_1.textLooksLikeAnrOrSystemUi)(bugDescription);
|
|
85321
|
+
const bugWitnessHonored = process.env.DISCOVERY_BUG_WITNESS_ENABLED === "true" && recording.finish?.suspectedBug === true && bugDescription.length >= 12 && recording.assertCount > 0 && !bugLooksLikeAnr;
|
|
85173
85322
|
if (recording.finish?.suspectedBug === true && !bugWitnessHonored) {
|
|
85174
|
-
log2(" suspectedBug declared but NOT honored: " + (process.env.DISCOVERY_BUG_WITNESS_ENABLED !== "true" ? "bug-witness channel disabled (DISCOVERY_BUG_WITNESS_ENABLED)." : bugDescription.length < 12 ? "bugDescription too short." : "no honored assert backs the observation."));
|
|
85323
|
+
log2(" suspectedBug declared but NOT honored: " + (bugLooksLikeAnr ? "ANR/System UI freeze is environment noise, not a product bug-witness." : process.env.DISCOVERY_BUG_WITNESS_ENABLED !== "true" ? "bug-witness channel disabled (DISCOVERY_BUG_WITNESS_ENABLED)." : bugDescription.length < 12 ? "bugDescription too short." : "no honored assert backs the observation."));
|
|
85175
85324
|
}
|
|
85176
85325
|
const strengthVerdict = bugWitnessHonored ? {
|
|
85177
85326
|
...baseStrengthVerdict,
|
|
@@ -85210,12 +85359,22 @@ ${statePacket}`, redactionCredentials) },
|
|
|
85210
85359
|
const strengthBlocked = !strengthVerdict.ok || strengthVerdict.severity === "block" || strengthVerdict.severity === "route-bug" || !verified || coldResetUnavailable;
|
|
85211
85360
|
{
|
|
85212
85361
|
const kind = (0, scenario_taxonomy_js_1.inferScenarioKind)(scenario);
|
|
85213
|
-
const
|
|
85214
|
-
|
|
85362
|
+
const oracleQuality = (0, oracle_quality_js_1.assessStrongOracleQuality)(compiled.steps);
|
|
85363
|
+
if (oracleQuality.anrOnly) {
|
|
85364
|
+
log2(` discarding ANR-chrome-only recording: ${oracleQuality.reason}`);
|
|
85365
|
+
return null;
|
|
85366
|
+
}
|
|
85367
|
+
if (oracleQuality.unpairedCaptures > 0) {
|
|
85368
|
+
log2(` oracle quality: ${oracleQuality.reason}`);
|
|
85369
|
+
}
|
|
85370
|
+
const strong = verified && !strengthBlocked && strengthVerdict.severity === "none" && oracleQuality.strong && ((0, scenario_taxonomy_js_1.isStrongOracleKind)(kind) || oracleQuality.hasStateDelta || oracleQuality.hasStrongAssertText);
|
|
85215
85371
|
if (strong)
|
|
85216
85372
|
tags.push("@strong-oracle");
|
|
85217
|
-
else if (!tags.includes("@smoke")
|
|
85373
|
+
else if (!tags.includes("@smoke")) {
|
|
85218
85374
|
tags.push("@smoke");
|
|
85375
|
+
if (!oracleQuality.strong) {
|
|
85376
|
+
log2(` tagged @smoke (not strong-oracle): ${oracleQuality.reason}`);
|
|
85377
|
+
}
|
|
85219
85378
|
}
|
|
85220
85379
|
}
|
|
85221
85380
|
const strengthReason = coldResetUnavailable ? `Cold app-data reset unavailable${coldResetReason ? ` (${coldResetReason})` : ""} \u2014 generation may not match cold-boot replay. ${strengthVerdict.reason}` : !verified && recording.assertCount === 0 ? "Mobile scenario finished unverified \u2014 the agent never proved the scenario outcome live, so the recording may not demonstrate the goal (e.g. a happy path that never completed). Review before promotion." : strengthVerdict.reason;
|
|
@@ -85475,6 +85634,7 @@ var require_failure_triage = __commonJS({
|
|
|
85475
85634
|
exports2.listStrongOracleKinds = listStrongOracleKinds;
|
|
85476
85635
|
exports2.triageMobileFailure = triageMobileFailure;
|
|
85477
85636
|
exports2.triageToHealTerminal = triageToHealTerminal;
|
|
85637
|
+
var anr_env_js_1 = require_anr_env();
|
|
85478
85638
|
var STRONG_ACTIONS = /* @__PURE__ */ new Set(["assertStateDelta", "assertText", "captureState"]);
|
|
85479
85639
|
var CHROME_VISIBLE_RE = /\b(tab|home|settings|back|menu|navigation|nav bar|bottom nav|status bar)\b/i;
|
|
85480
85640
|
function hasStrongMobileOracle(steps) {
|
|
@@ -85564,6 +85724,15 @@ ${logs2 ?? ""}`.trim();
|
|
|
85564
85724
|
failureCount,
|
|
85565
85725
|
errorPrefix: prefix
|
|
85566
85726
|
};
|
|
85727
|
+
if ((0, anr_env_js_1.isAnrOrSystemUiEnvironmentFailure)(input.error, input.logs)) {
|
|
85728
|
+
return {
|
|
85729
|
+
verdict: "config_issue",
|
|
85730
|
+
reason: "ANR / System UI freeze (device or app hung) \u2014 treat as environment, not product bug",
|
|
85731
|
+
shouldHeal: false,
|
|
85732
|
+
healVerdict: "config_issue",
|
|
85733
|
+
evidence
|
|
85734
|
+
};
|
|
85735
|
+
}
|
|
85567
85736
|
if (prefix === "CONFIG_ISSUE" || prefix === "RATE_LIMIT_BLOCKED") {
|
|
85568
85737
|
return {
|
|
85569
85738
|
verdict: "config_issue",
|
|
@@ -85812,6 +85981,7 @@ var require_mobile_heal_postprocess = __commonJS({
|
|
|
85812
85981
|
exports2.detectMobileStepSecretLeak = detectMobileStepSecretLeak;
|
|
85813
85982
|
exports2.computeMobileHealOutcome = computeMobileHealOutcome2;
|
|
85814
85983
|
var oracle_protection_js_1 = require_oracle_protection();
|
|
85984
|
+
var anr_env_js_1 = require_anr_env();
|
|
85815
85985
|
var REDACT_KEY_RE = /email|username|user|phone|password|secret|token|api[_-]?key|access[_-]?key|\bpass\b|credential|bearer|\bpat\b|session|cookie|jwt|otp|pin/i;
|
|
85816
85986
|
var DETECT_KEY_RE = /password|secret|token|api[_]?key|\bpass\b|credential|bearer|\bpat\b/i;
|
|
85817
85987
|
var MIN_REDACT_LEN = 4;
|
|
@@ -85882,21 +86052,26 @@ var require_mobile_heal_postprocess = __commonJS({
|
|
|
85882
86052
|
const oracleWeakening = isVerifiedFix && args.originalSteps ? (0, oracle_protection_js_1.rejectOracleWeakeningPatch)(args.originalSteps, heal.healedSteps ?? []) : null;
|
|
85883
86053
|
if (oracleWeakening)
|
|
85884
86054
|
isVerifiedFix = false;
|
|
85885
|
-
const
|
|
85886
|
-
const
|
|
86055
|
+
const anrEnv = (0, anr_env_js_1.isAnrOrSystemUiEnvironmentFailure)(heal.healReason ?? args.finalError ?? args.reproduceError, typeof args.finalError === "string" ? args.finalError : void 0);
|
|
86056
|
+
const isConfigIssue = heal.verdict === "config_issue" || heal.verdict === "app_bug" && anrEnv;
|
|
86057
|
+
const isAppBug = !anrEnv && (heal.verdict === "app_bug" || Boolean(oracleWeakening && heal.passed));
|
|
85887
86058
|
const extraTags = new Set((heal.extraTags ?? []).filter((t) => typeof t === "string" && t.length > 0));
|
|
85888
86059
|
if (secretLeak)
|
|
85889
86060
|
extraTags.add("@needs-review");
|
|
85890
|
-
if (oracleWeakening) {
|
|
86061
|
+
if (oracleWeakening && !anrEnv) {
|
|
85891
86062
|
extraTags.add("@needs-review");
|
|
85892
86063
|
extraTags.add("@bug-found");
|
|
85893
86064
|
}
|
|
86065
|
+
if (anrEnv) {
|
|
86066
|
+
extraTags.delete("@bug-found");
|
|
86067
|
+
extraTags.add("@needs-review");
|
|
86068
|
+
}
|
|
85894
86069
|
if (!heal.passed && NEEDS_REVIEW_VERDICTS.has(heal.verdict))
|
|
85895
86070
|
extraTags.add("@needs-review");
|
|
85896
86071
|
let status;
|
|
85897
86072
|
if (secretLeak)
|
|
85898
86073
|
status = "FAILED";
|
|
85899
|
-
else if (oracleWeakening)
|
|
86074
|
+
else if (oracleWeakening && !anrEnv)
|
|
85900
86075
|
status = "FAILED";
|
|
85901
86076
|
else if (isVerifiedFix)
|
|
85902
86077
|
status = "PASSED";
|
|
@@ -85909,10 +86084,11 @@ var require_mobile_heal_postprocess = __commonJS({
|
|
|
85909
86084
|
error2 = void 0;
|
|
85910
86085
|
} else if (secretLeak) {
|
|
85911
86086
|
error2 = `HEAL_SECRET_LEAK: healed steps contained a literal secret (${secretLeak}); patch dropped, tagged @needs-review`;
|
|
85912
|
-
} else if (oracleWeakening) {
|
|
86087
|
+
} else if (oracleWeakening && !anrEnv) {
|
|
85913
86088
|
error2 = `APP_BUG: heal attempted to drop strong oracle (${oracleWeakening}); patch refused`;
|
|
85914
86089
|
} else if (isConfigIssue) {
|
|
85915
|
-
|
|
86090
|
+
const reason = heal.healReason ?? args.finalError ?? args.reproduceError ?? "environment/rate-limit blocker";
|
|
86091
|
+
error2 = anrEnv ? `CONFIG_ISSUE: ANR / System UI freeze (not a product assertion failure) \u2014 ${reason}` : `CONFIG_ISSUE: ${reason}`;
|
|
85916
86092
|
} else if (isAppBug) {
|
|
85917
86093
|
error2 = `APP_BUG: ${heal.healReason ?? args.finalError ?? args.reproduceError ?? "application defect found during heal"}`;
|
|
85918
86094
|
} else if (heal.passed && heal.verdict === "fix" && !hasFix) {
|
|
@@ -86355,6 +86531,7 @@ var require_mobile_heal_loop = __commonJS({
|
|
|
86355
86531
|
var failure_triage_js_1 = require_failure_triage();
|
|
86356
86532
|
var oracle_protection_js_1 = require_oracle_protection();
|
|
86357
86533
|
var network_oracle_js_1 = require_network_oracle();
|
|
86534
|
+
var anr_env_js_1 = require_anr_env();
|
|
86358
86535
|
var mobile_observation_js_1 = require_mobile_observation();
|
|
86359
86536
|
var selector_registry_context_js_1 = require_selector_registry_context();
|
|
86360
86537
|
var mobile_session_keepalive_js_1 = require_mobile_session_keepalive();
|
|
@@ -86366,12 +86543,13 @@ Reason over steps + error + tree + registry and return working healed steps. Do
|
|
|
86366
86543
|
2. STEP RESULTS: which steps passed before the failure.
|
|
86367
86544
|
3. TREE: does the current screen match what the failed step expects?
|
|
86368
86545
|
3b. STALE EMPTY-STATE ASSERT: a failing assert on one-time / data-dependent UI (empty-state CTAs, first-run prompts, onboarding banners) is NOT a locator problem \u2014 the element is gone because the account now has data. REPLACE the assertion with a durable landing-screen anchor (tab label, header, nav bar) that proves the same outcome (verdict fix).
|
|
86546
|
+
3c. ANR / "System UI isn't responding" / freeze dialogs: this is ENVIRONMENT (config_issue), NOT app_bug. Do not quarantine product bugs for device freezes.
|
|
86369
86547
|
4. EXTERNAL AUTH HANDOFF: if the failure says the foreground app changed outside the target app after a Login / Sign In tap, and the steps never enter email/password credentials, do NOT heal by choosing Google/Apple/Facebook/SSO or another external app. If the test later expects Logout/session state, return test_invalid or prereq_unmet: the recorded test lacks a real credential setup.
|
|
86370
86548
|
5. ENTRY-SCREEN DRIFT ("unexpected screen"/"screen drift"): the app started on a DIFFERENT screen than recorded; the fix is usually a MISSING NAVIGATION step (e.g. tap the onboarding/welcome CTA present in the tree before the recorded first step). Author it from the tree if you can (verdict fix); if a single static tree is insufficient, return needs_device.
|
|
86371
86549
|
## Response \u2014 STRICT JSON, exactly one verdict:
|
|
86372
86550
|
{"verdict":"fix","steps":[<FULL healed MobileTestStep array, all steps, your changes applied, order/metadata preserved>],"reason":"what changed and why","confidence":0.0-1.0}
|
|
86373
|
-
{"verdict":"app_bug","reason":"
|
|
86374
|
-
{"verdict":"config_issue","reason":"429/rate limit/lockout/quota"}
|
|
86551
|
+
{"verdict":"app_bug","reason":"product defect with durable oracle evidence (wrong entity, validation bypass, wrong count) \u2014 NOT ANR/System UI"}
|
|
86552
|
+
{"verdict":"config_issue","reason":"429/rate limit/lockout/quota OR ANR/System UI freeze/device unresponsive"}
|
|
86375
86553
|
{"verdict":"needs_device","reason":"cannot fix from a static tree; need live re-observation"}
|
|
86376
86554
|
{"verdict":"prereq_unmet","reason":"required entity/state absent for the account"}
|
|
86377
86555
|
{"verdict":"state_stale","reason":"leftover data blocks a baseline assertion"}
|
|
@@ -86382,7 +86560,7 @@ For a fix, return the COMPLETE steps array (not a diff).
|
|
|
86382
86560
|
- NEVER remove or downgrade assertStateDelta, captureState, or assertText outcome proof to go green.
|
|
86383
86561
|
- NEVER replace a durable outcome assert with mere assertVisible of a tab/header.
|
|
86384
86562
|
- You MAY fix locators, waits, and navigation around those oracles.
|
|
86385
|
-
- If the app truly violates the oracle, return verdict "app_bug" instead of stripping the assert.`;
|
|
86563
|
+
- If the app truly violates the oracle on a healthy UI, return verdict "app_bug" instead of stripping the assert.`;
|
|
86386
86564
|
var DEFAULT_SCRIPT_MAX_ATTEMPTS = 6;
|
|
86387
86565
|
var RECOVERABLE_MAX = 3;
|
|
86388
86566
|
var MAX_CONSECUTIVE_INVALID_VERDICTS = 3;
|
|
@@ -87350,6 +87528,18 @@ ${args.directive}`);
|
|
|
87350
87528
|
providerUnavailable: true
|
|
87351
87529
|
};
|
|
87352
87530
|
}
|
|
87531
|
+
if (script.verdict === "app_bug" && (0, anr_env_js_1.isAnrOrSystemUiEnvironmentFailure)(script.healReason ?? ctx.error, ctx.logs)) {
|
|
87532
|
+
return {
|
|
87533
|
+
passed: false,
|
|
87534
|
+
verdict: "config_issue",
|
|
87535
|
+
healedSteps: script.healedSteps,
|
|
87536
|
+
healedAppiumCode: script.healedAppiumCode,
|
|
87537
|
+
healReason: `ANR / System UI freeze (environment) \u2014 ${script.healReason ?? ctx.error ?? "device unresponsive"}`,
|
|
87538
|
+
confidence: script.confidence,
|
|
87539
|
+
extraTags: script.extraTags,
|
|
87540
|
+
removeTags: [...script.removeTags ?? [], "@bug-found"].filter((t, i, a) => a.indexOf(t) === i)
|
|
87541
|
+
};
|
|
87542
|
+
}
|
|
87353
87543
|
const shouldEscalate = script.verdict === "needs_device" || script.verdict === "fix" && !script.passed;
|
|
87354
87544
|
if (shouldEscalate && options.runLiveHeal) {
|
|
87355
87545
|
try {
|
|
@@ -355106,7 +355296,7 @@ var require_package4 = __commonJS({
|
|
|
355106
355296
|
"package.json"(exports2, module2) {
|
|
355107
355297
|
module2.exports = {
|
|
355108
355298
|
name: "@validate.qa/runner",
|
|
355109
|
-
version: "1.0.
|
|
355299
|
+
version: "1.0.91",
|
|
355110
355300
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
355111
355301
|
bin: {
|
|
355112
355302
|
"validate-runner": "dist/cli.js"
|
package/dist/cli.mjs
CHANGED
|
@@ -80743,7 +80743,7 @@ Return ONLY one valid JSON object matching the schema below. Do NOT include <thi
|
|
|
80743
80743
|
2. **One purpose per scenario** - each scenario tests ONE thing (happy path, validation, journey, or bug)
|
|
80744
80744
|
3. **Happy paths first** - then pair edge cases to the same form/flow
|
|
80745
80745
|
4. **Multi-screen journeys** - only when screen transitions were actually observed. When an observed target element sits below the fold, include an explicit swipe step to scroll it into view before the tap (mobile elements off-screen are not hittable until revealed)
|
|
80746
|
-
5. **Bug reports** - for observed
|
|
80746
|
+
5. **Bug reports** - for observed *product* defects (wrong row deleted, validation bypassed, count wrong). Do NOT plan scenarios whose expected outcome is system ANR / "isn't responding" freezes \u2014 those are environment noise
|
|
80747
80747
|
6. **Steps are human-readable** - "Type into the email field", not "driver.findElement(AppiumBy.accessibilityId('email')).sendKeys()"
|
|
80748
80748
|
7. **Expected outcomes are plain-English success criteria** - describe the user-visible product result, not the controls touched along the way. Steps already cover buttons, inputs, tabs, sheets, and menus. Expected outcomes should say what the user can now do or rely on (record saved, message sent, search results filtered, validation explained, home screen reached). Name the target screen or exact copy only when it is the real product outcome; do NOT list "heading visible", "button visible", or similar element inventory unless that exact control is the behavior under test.
|
|
80749
80749
|
8. **Auth-aware** - mark scenarios that need a signed-in session. Credentials are filled ON-DEVICE from the stored TEST_USER_EMAIL / TEST_USER_PASSWORD keys (key-based injection at run time) \u2014 say "type the test account email", never a literal value and never a code idiom like process.env
|
|
@@ -81155,7 +81155,7 @@ ${context.userNotes.trim()}`);
|
|
|
81155
81155
|
const requiredBlock = (0, scenario_taxonomy_js_1.formatRequiredScenarioKindsPrompt)(signals);
|
|
81156
81156
|
if (requiredBlock) {
|
|
81157
81157
|
sections.push(requiredBlock);
|
|
81158
|
-
sections.push('## Scenario kind coverage rules\n1. Prefer scenarios whose expectedOutcomes prove durable product results (state deltas, validation errors, persistence), not only "control X is visible".\n2. For negative_validation: empty required field
|
|
81158
|
+
sections.push('## Scenario kind coverage rules\n1. Prefer scenarios whose expectedOutcomes prove durable product results (state deltas, validation errors, persistence), not only "control X is visible".\n2. For negative_validation: empty required field AND/OR malformed email-like input; expected outcome must be blocked submit or visible validation error \u2014 never success Home after invalid submit.\n3. For crud_identity: capture entity identity before delete/edit, then assert that identity is gone/changed (state delta) \u2014 never only that the list screen is still visible.\n4. For count_delta: capture before count, mutate, assert count changed as expected.\n5. For double_submit: assert count +1 not +2 (or single confirmation), not merely that a button remains visible.\n6. Never plan expectedOutcomes that are ANR / System UI freezes.\n7. If a required kind is impossible given evidence, say so in "reasoning" \u2014 do not invent unobserved screens.');
|
|
81159
81159
|
}
|
|
81160
81160
|
}
|
|
81161
81161
|
const orderedScreens = plannableTargetAppScreens(graph, context.appId).sort((a, b) => a.firstSeenIteration - b.firstSeenIteration || a.screenId.localeCompare(b.screenId));
|
|
@@ -82031,6 +82031,123 @@ var require_discovery_lint_flags = __commonJS({
|
|
|
82031
82031
|
}
|
|
82032
82032
|
});
|
|
82033
82033
|
|
|
82034
|
+
// ../runner-core/dist/services/mobile/anr-env.js
|
|
82035
|
+
var require_anr_env = __commonJS({
|
|
82036
|
+
"../runner-core/dist/services/mobile/anr-env.js"(exports) {
|
|
82037
|
+
"use strict";
|
|
82038
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
82039
|
+
exports.textLooksLikeAnrOrSystemUi = textLooksLikeAnrOrSystemUi;
|
|
82040
|
+
exports.isAnrOrSystemUiEnvironmentFailure = isAnrOrSystemUiEnvironmentFailure;
|
|
82041
|
+
exports.stepLooksLikeAnrChromeAssert = stepLooksLikeAnrChromeAssert;
|
|
82042
|
+
var ANR_TEXT_RE = /system\s*ui\s*isn['’]?t\s*responding|app\s*isn['’]?t\s*responding|application\s*not\s*responding|\banr\b|close\s*app|wait\s*and\s*see|isn['’]?t\s*responding/i;
|
|
82043
|
+
function textLooksLikeAnrOrSystemUi(text) {
|
|
82044
|
+
if (!text || typeof text !== "string")
|
|
82045
|
+
return false;
|
|
82046
|
+
return ANR_TEXT_RE.test(text);
|
|
82047
|
+
}
|
|
82048
|
+
function isAnrOrSystemUiEnvironmentFailure(error2, logs2) {
|
|
82049
|
+
const blob = `${error2 ?? ""}
|
|
82050
|
+
${logs2 ?? ""}`;
|
|
82051
|
+
if (textLooksLikeAnrOrSystemUi(blob))
|
|
82052
|
+
return true;
|
|
82053
|
+
if (/\bcold-boot\b/i.test(blob) && /\b(froze|hang|anr|unresponsive)\b/i.test(blob))
|
|
82054
|
+
return true;
|
|
82055
|
+
if (/\bno actionable elements\b/i.test(blob) && /\b(system|anr|responding)\b/i.test(blob)) {
|
|
82056
|
+
return true;
|
|
82057
|
+
}
|
|
82058
|
+
return false;
|
|
82059
|
+
}
|
|
82060
|
+
function stepLooksLikeAnrChromeAssert(step) {
|
|
82061
|
+
if (step.action !== "assertVisible" && step.action !== "assertText")
|
|
82062
|
+
return false;
|
|
82063
|
+
return textLooksLikeAnrOrSystemUi([step.description, step.assertion, step.value].filter(Boolean).join(" "));
|
|
82064
|
+
}
|
|
82065
|
+
}
|
|
82066
|
+
});
|
|
82067
|
+
|
|
82068
|
+
// ../runner-core/dist/services/mobile/oracle-quality.js
|
|
82069
|
+
var require_oracle_quality = __commonJS({
|
|
82070
|
+
"../runner-core/dist/services/mobile/oracle-quality.js"(exports) {
|
|
82071
|
+
"use strict";
|
|
82072
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
82073
|
+
exports.assessStrongOracleQuality = assessStrongOracleQuality;
|
|
82074
|
+
var anr_env_js_1 = require_anr_env();
|
|
82075
|
+
var CHROME_ONLY_RE = /\b(tab|home tab|catalog tab|forms tab|account tab|back|menu|navigation|nav bar|bottom nav|status bar|query|search field)\b/i;
|
|
82076
|
+
var PRODUCT_OUTCOME_TEXT_RE = /\b(error|invalid|required|must|cannot|failed|denied|blocked|empty|missing|deleted|removed|saved|success|created|updated|signed?\s*out|logged?\s*out|wrong|incorrect)\b/i;
|
|
82077
|
+
function stepBlob(step) {
|
|
82078
|
+
return [step.description, step.assertion, step.value, step.target?.primary].filter(Boolean).join(" ");
|
|
82079
|
+
}
|
|
82080
|
+
function isChromeOnlyVisible(step) {
|
|
82081
|
+
if (step.action !== "assertVisible")
|
|
82082
|
+
return false;
|
|
82083
|
+
const blob = stepBlob(step);
|
|
82084
|
+
if (PRODUCT_OUTCOME_TEXT_RE.test(blob))
|
|
82085
|
+
return false;
|
|
82086
|
+
return CHROME_ONLY_RE.test(blob) || blob.length < 40;
|
|
82087
|
+
}
|
|
82088
|
+
function assessStrongOracleQuality(steps) {
|
|
82089
|
+
const productSteps = steps.filter((s) => s.action !== "launchApp");
|
|
82090
|
+
const anrAsserts = productSteps.filter(anr_env_js_1.stepLooksLikeAnrChromeAssert);
|
|
82091
|
+
const nonAnrAsserts = productSteps.filter((s) => (s.action === "assertVisible" || s.action === "assertText" || s.action === "assertStateDelta") && !(0, anr_env_js_1.stepLooksLikeAnrChromeAssert)(s));
|
|
82092
|
+
if (productSteps.length > 0 && anrAsserts.length > 0 && nonAnrAsserts.length === 0) {
|
|
82093
|
+
return {
|
|
82094
|
+
strong: false,
|
|
82095
|
+
unpairedCaptures: 0,
|
|
82096
|
+
hasStateDelta: false,
|
|
82097
|
+
hasStrongAssertText: false,
|
|
82098
|
+
hasStrongValidationVisible: false,
|
|
82099
|
+
anrOnly: true,
|
|
82100
|
+
reason: "only asserts system ANR / freeze chrome \u2014 not a product test"
|
|
82101
|
+
};
|
|
82102
|
+
}
|
|
82103
|
+
const captures = steps.filter((s) => s.action === "captureState").length;
|
|
82104
|
+
const deltas = steps.filter((s) => s.action === "assertStateDelta").length;
|
|
82105
|
+
const unpairedCaptures = Math.max(0, captures - deltas);
|
|
82106
|
+
const hasStateDelta = deltas > 0;
|
|
82107
|
+
const hasStrongAssertText = steps.some((s) => {
|
|
82108
|
+
if (s.action !== "assertText")
|
|
82109
|
+
return false;
|
|
82110
|
+
if ((0, anr_env_js_1.stepLooksLikeAnrChromeAssert)(s))
|
|
82111
|
+
return false;
|
|
82112
|
+
const blob = stepBlob(s);
|
|
82113
|
+
if (/^(email|password|username|name|query|search)$/i.test((s.assertion ?? "").trim())) {
|
|
82114
|
+
return false;
|
|
82115
|
+
}
|
|
82116
|
+
return blob.length >= 3;
|
|
82117
|
+
});
|
|
82118
|
+
const hasStrongValidationVisible = steps.some((s) => {
|
|
82119
|
+
if (s.action !== "assertVisible")
|
|
82120
|
+
return false;
|
|
82121
|
+
if ((0, anr_env_js_1.stepLooksLikeAnrChromeAssert)(s) || isChromeOnlyVisible(s))
|
|
82122
|
+
return false;
|
|
82123
|
+
return PRODUCT_OUTCOME_TEXT_RE.test(stepBlob(s));
|
|
82124
|
+
});
|
|
82125
|
+
const strong = hasStateDelta || hasStrongAssertText || hasStrongValidationVisible;
|
|
82126
|
+
let reason;
|
|
82127
|
+
if (strong && hasStateDelta) {
|
|
82128
|
+
reason = unpairedCaptures > 0 ? `has assertStateDelta but ${unpairedCaptures} unpaired captureState (prefer 1:1 pairing)` : "has assertStateDelta durable state proof";
|
|
82129
|
+
} else if (strong && hasStrongAssertText) {
|
|
82130
|
+
reason = "has product assertText outcome";
|
|
82131
|
+
} else if (strong) {
|
|
82132
|
+
reason = "has validation/error assertVisible outcome";
|
|
82133
|
+
} else if (unpairedCaptures > 0) {
|
|
82134
|
+
reason = `captureState without assertStateDelta (${unpairedCaptures}) \u2014 not strong`;
|
|
82135
|
+
} else {
|
|
82136
|
+
reason = "no durable state/text/validation oracle \u2014 smoke/nav only";
|
|
82137
|
+
}
|
|
82138
|
+
return {
|
|
82139
|
+
strong,
|
|
82140
|
+
unpairedCaptures,
|
|
82141
|
+
hasStateDelta,
|
|
82142
|
+
hasStrongAssertText,
|
|
82143
|
+
hasStrongValidationVisible,
|
|
82144
|
+
anrOnly: false,
|
|
82145
|
+
reason
|
|
82146
|
+
};
|
|
82147
|
+
}
|
|
82148
|
+
}
|
|
82149
|
+
});
|
|
82150
|
+
|
|
82034
82151
|
// ../runner-core/dist/services/mobile/mobile-generator.js
|
|
82035
82152
|
var require_mobile_generator = __commonJS({
|
|
82036
82153
|
"../runner-core/dist/services/mobile/mobile-generator.js"(exports) {
|
|
@@ -82057,6 +82174,8 @@ var require_mobile_generator = __commonJS({
|
|
|
82057
82174
|
var mobile_session_keepalive_js_1 = require_mobile_session_keepalive();
|
|
82058
82175
|
var discovery_lint_flags_js_1 = require_discovery_lint_flags();
|
|
82059
82176
|
var scenario_taxonomy_js_1 = require_scenario_taxonomy();
|
|
82177
|
+
var anr_env_js_1 = require_anr_env();
|
|
82178
|
+
var oracle_quality_js_1 = require_oracle_quality();
|
|
82060
82179
|
var mobile_source_parser_js_1 = require_mobile_source_parser();
|
|
82061
82180
|
var mobile_source_parser_js_2 = require_mobile_source_parser();
|
|
82062
82181
|
var mobile_observation_js_1 = require_mobile_observation();
|
|
@@ -82202,7 +82321,7 @@ var require_mobile_generator = __commonJS({
|
|
|
82202
82321
|
type: "function",
|
|
82203
82322
|
function: {
|
|
82204
82323
|
name: "mobile_capture_state",
|
|
82205
|
-
description:
|
|
82324
|
+
description: "Capture list labels or numeric badge counts into a named state key BEFORE a mutating action (delete/create/toggle). MUST pair with mobile_assert_state_delta afterward \u2014 capture alone is not a strong oracle and will not tag the test @strong-oracle.",
|
|
82206
82325
|
parameters: {
|
|
82207
82326
|
type: "object",
|
|
82208
82327
|
properties: {
|
|
@@ -82564,9 +82683,11 @@ The scenario name + goal + steps describe ONE product behavior from survey/disco
|
|
|
82564
82683
|
- Navigation scenarios: you MUST land on the destination and assert something unique to it.
|
|
82565
82684
|
4. PROVE the outcome with an assert tool that matches the scenario \u2014 not ambient chrome:
|
|
82566
82685
|
- mobile_assert_visible({ref}) / mobile_assert_text({ref, expected})
|
|
82567
|
-
- For list delete/create or count changes: mobile_capture_state({stateKey, kind}) BEFORE the mutation, then mobile_assert_state_delta({stateKey, expect}) AFTER \u2014
|
|
82686
|
+
- For list delete/create or count changes: mobile_capture_state({stateKey, kind}) BEFORE the mutation, then mobile_assert_state_delta({stateKey, expect}) AFTER \u2014 BOTH are required. captureState alone does NOT prove the outcome.
|
|
82687
|
+
- Negative validation: leave a required field EMPTY (or type clearly invalid email-like input), submit, assert the app BLOCKS success \u2014 error message / still on form / no navigation to Home. Do NOT fill a valid password-only path and call it "empty email".
|
|
82568
82688
|
- After sign-in \u2192 signed-in product signal; after like \u2192 liked/unlike control or durable like state; after open Settings \u2192 a settings-specific control.
|
|
82569
82689
|
- Do NOT assert bottom-nav tab labels alone, notification badge counts ("N unread"), or one-time empty-state CTAs as the scenario outcome.
|
|
82690
|
+
- NEVER assert system freeze chrome ("System UI isn't responding", ANR dialogs, Wait/Close app). If the app freezes, finish_generate with verified:false (environment) \u2014 do NOT write a test whose only proof is the ANR dialog.
|
|
82570
82691
|
5. Call finish_generate with verified:true ONLY if you completed the goal AND asserted a matching durable outcome. If the app cannot complete the goal, finish with verified:false and explain \u2014 never invent a green path from unrelated screens.
|
|
82571
82692
|
|
|
82572
82693
|
## Rules
|
|
@@ -82575,7 +82696,7 @@ The scenario name + goal + steps describe ONE product behavior from survey/disco
|
|
|
82575
82696
|
- Assert only DURABLE elements \u2014 screen chrome that exists on every visit (tab labels, headers, nav bars, the entity you just created). NEVER assert one-time / data-dependent UI: empty-state CTAs, first-run or onboarding prompts, or counters \u2014 they vanish as soon as the account has data, and the test then fails forever on a healthy app.
|
|
82576
82697
|
- Do NOT trigger irreversible or destructive side effects \u2014 real payments; sending messages to others; and ESPECIALLY account/data deletion, deactivation, or any "type DELETE to confirm" gate (the runner hard-blocks destructive taps and confirmation keystrokes). Note them and assert what you safely can.
|
|
82577
82698
|
- STATE OWNERSHIP for mutation scenarios (edit / rename / archive / remove): CREATE the record first in this same recording, then act on the row YOU created \u2014 never mutate pre-existing data (it may not exist on a clean install, so the replay fails forever, and another test may depend on it). If the app offers no way to create one, do NOT act on someone else's data: assert what you can safely prove and finish with verified:false, explaining why in the description.
|
|
82578
|
-
- If the APP itself misbehaves (
|
|
82699
|
+
- If the APP itself misbehaves (product error screen, broken validation, wrong entity deleted), assert the product failure and call finish_generate with suspectedBug:true and a concrete bugDescription. If the device shows ANR / "System UI isn't responding" / freeze dialogs, that is ENVIRONMENT \u2014 finish with verified:false and do NOT assert the freeze dialog as the test outcome.
|
|
82579
82700
|
- Stay inside this app.${targetLine} Do not open or use app stores, browsers, email apps, settings, or any other app. If an action leaves the target app, the runner will relaunch the target app and reject that action.
|
|
82580
82701
|
- NEVER tap the phone's home screen / launcher (app icons, "Predicted app" chips, search bars, widgets). Those are NOT part of the app under test \u2014 tapping them is junk that poisons the test. If the app ever backgrounds or you find yourself on the launcher, use mobile_relaunch to cold-start the app; do NOT tap a launcher icon or app-prediction to get back in.
|
|
82581
82702
|
- Do NOT re-do a sequence you already completed. If you filled a form then navigated away, do NOT come back and fill it again \u2014 the scenario needs ONE clean pass. Filling the login form, going back, and re-filling it is exactly the kind of wandering that produces a bloated, broken test.
|
|
@@ -84167,6 +84288,9 @@ ${statePacket}`, redactionCredentials) },
|
|
|
84167
84288
|
function weakAssertionTargetReason(element) {
|
|
84168
84289
|
if (isAlwaysPresentContainer(element))
|
|
84169
84290
|
return "always-present app/system container";
|
|
84291
|
+
if ((0, anr_env_js_1.textLooksLikeAnrOrSystemUi)([element.text, element.label, element.accessibilityId, element.resourceId].filter(Boolean).join(" "))) {
|
|
84292
|
+
return "system ANR / freeze dialog chrome (environment, not product)";
|
|
84293
|
+
}
|
|
84170
84294
|
const hasIdentitySignal = Boolean(nonEmpty3(element.accessibilityId) || nonEmpty3(element.resourceId) || nonEmpty3(element.text) || nonEmpty3(element.label));
|
|
84171
84295
|
if (!hasIdentitySignal)
|
|
84172
84296
|
return "no identity locator (accessibility id / resource id / text) for the assert gate";
|
|
@@ -85028,6 +85152,30 @@ ${statePacket}`, redactionCredentials) },
|
|
|
85028
85152
|
return [action.selector, action.value, action.innerText, action.metadata?.targetLabel].filter((v) => typeof v === "string" && v.trim().length > 0).join(" ");
|
|
85029
85153
|
}
|
|
85030
85154
|
function evaluateMobileAssertionStrength(actions) {
|
|
85155
|
+
const anrAsserts = actions.filter((a) => {
|
|
85156
|
+
if (a.type !== "ASSERT_VISIBLE" && a.type !== "ASSERT_TEXT")
|
|
85157
|
+
return false;
|
|
85158
|
+
return (0, anr_env_js_1.textLooksLikeAnrOrSystemUi)([a.value, a.innerText, a.metadata?.targetLabel, a.selector].filter(Boolean).join(" "));
|
|
85159
|
+
});
|
|
85160
|
+
if (anrAsserts.length > 0) {
|
|
85161
|
+
const otherAsserts = actions.filter((a) => (a.type === "ASSERT_VISIBLE" || a.type === "ASSERT_TEXT" || a.type === "ASSERT_STATE_DELTA") && !anrAsserts.includes(a));
|
|
85162
|
+
if (otherAsserts.length === 0) {
|
|
85163
|
+
return {
|
|
85164
|
+
ok: false,
|
|
85165
|
+
severity: "block",
|
|
85166
|
+
reason: `only asserts system ANR / "isn't responding" chrome \u2014 not a product test (discard or re-record on a healthy session)`,
|
|
85167
|
+
signals: {
|
|
85168
|
+
apiTest: false,
|
|
85169
|
+
interacted: actions.some((a) => a.type === "TAP" || a.type === "TYPE"),
|
|
85170
|
+
mutated: false,
|
|
85171
|
+
navOnly: false,
|
|
85172
|
+
hasOutcomeProof: false,
|
|
85173
|
+
errorBanner: false,
|
|
85174
|
+
terminal: null
|
|
85175
|
+
}
|
|
85176
|
+
};
|
|
85177
|
+
}
|
|
85178
|
+
}
|
|
85031
85179
|
if (actions.some((a) => a.type === "ASSERT_STATE_DELTA")) {
|
|
85032
85180
|
return {
|
|
85033
85181
|
ok: true,
|
|
@@ -85174,9 +85322,10 @@ ${statePacket}`, redactionCredentials) },
|
|
|
85174
85322
|
}
|
|
85175
85323
|
const baseStrengthVerdict = evaluateMobileAssertionStrength(actions);
|
|
85176
85324
|
const bugDescription = recording.finish?.bugDescription?.trim() ?? "";
|
|
85177
|
-
const
|
|
85325
|
+
const bugLooksLikeAnr = (0, anr_env_js_1.textLooksLikeAnrOrSystemUi)(bugDescription);
|
|
85326
|
+
const bugWitnessHonored = process.env.DISCOVERY_BUG_WITNESS_ENABLED === "true" && recording.finish?.suspectedBug === true && bugDescription.length >= 12 && recording.assertCount > 0 && !bugLooksLikeAnr;
|
|
85178
85327
|
if (recording.finish?.suspectedBug === true && !bugWitnessHonored) {
|
|
85179
|
-
log2(" suspectedBug declared but NOT honored: " + (process.env.DISCOVERY_BUG_WITNESS_ENABLED !== "true" ? "bug-witness channel disabled (DISCOVERY_BUG_WITNESS_ENABLED)." : bugDescription.length < 12 ? "bugDescription too short." : "no honored assert backs the observation."));
|
|
85328
|
+
log2(" suspectedBug declared but NOT honored: " + (bugLooksLikeAnr ? "ANR/System UI freeze is environment noise, not a product bug-witness." : process.env.DISCOVERY_BUG_WITNESS_ENABLED !== "true" ? "bug-witness channel disabled (DISCOVERY_BUG_WITNESS_ENABLED)." : bugDescription.length < 12 ? "bugDescription too short." : "no honored assert backs the observation."));
|
|
85180
85329
|
}
|
|
85181
85330
|
const strengthVerdict = bugWitnessHonored ? {
|
|
85182
85331
|
...baseStrengthVerdict,
|
|
@@ -85215,12 +85364,22 @@ ${statePacket}`, redactionCredentials) },
|
|
|
85215
85364
|
const strengthBlocked = !strengthVerdict.ok || strengthVerdict.severity === "block" || strengthVerdict.severity === "route-bug" || !verified || coldResetUnavailable;
|
|
85216
85365
|
{
|
|
85217
85366
|
const kind = (0, scenario_taxonomy_js_1.inferScenarioKind)(scenario);
|
|
85218
|
-
const
|
|
85219
|
-
|
|
85367
|
+
const oracleQuality = (0, oracle_quality_js_1.assessStrongOracleQuality)(compiled.steps);
|
|
85368
|
+
if (oracleQuality.anrOnly) {
|
|
85369
|
+
log2(` discarding ANR-chrome-only recording: ${oracleQuality.reason}`);
|
|
85370
|
+
return null;
|
|
85371
|
+
}
|
|
85372
|
+
if (oracleQuality.unpairedCaptures > 0) {
|
|
85373
|
+
log2(` oracle quality: ${oracleQuality.reason}`);
|
|
85374
|
+
}
|
|
85375
|
+
const strong = verified && !strengthBlocked && strengthVerdict.severity === "none" && oracleQuality.strong && ((0, scenario_taxonomy_js_1.isStrongOracleKind)(kind) || oracleQuality.hasStateDelta || oracleQuality.hasStrongAssertText);
|
|
85220
85376
|
if (strong)
|
|
85221
85377
|
tags.push("@strong-oracle");
|
|
85222
|
-
else if (!tags.includes("@smoke")
|
|
85378
|
+
else if (!tags.includes("@smoke")) {
|
|
85223
85379
|
tags.push("@smoke");
|
|
85380
|
+
if (!oracleQuality.strong) {
|
|
85381
|
+
log2(` tagged @smoke (not strong-oracle): ${oracleQuality.reason}`);
|
|
85382
|
+
}
|
|
85224
85383
|
}
|
|
85225
85384
|
}
|
|
85226
85385
|
const strengthReason = coldResetUnavailable ? `Cold app-data reset unavailable${coldResetReason ? ` (${coldResetReason})` : ""} \u2014 generation may not match cold-boot replay. ${strengthVerdict.reason}` : !verified && recording.assertCount === 0 ? "Mobile scenario finished unverified \u2014 the agent never proved the scenario outcome live, so the recording may not demonstrate the goal (e.g. a happy path that never completed). Review before promotion." : strengthVerdict.reason;
|
|
@@ -85480,6 +85639,7 @@ var require_failure_triage = __commonJS({
|
|
|
85480
85639
|
exports.listStrongOracleKinds = listStrongOracleKinds;
|
|
85481
85640
|
exports.triageMobileFailure = triageMobileFailure;
|
|
85482
85641
|
exports.triageToHealTerminal = triageToHealTerminal;
|
|
85642
|
+
var anr_env_js_1 = require_anr_env();
|
|
85483
85643
|
var STRONG_ACTIONS = /* @__PURE__ */ new Set(["assertStateDelta", "assertText", "captureState"]);
|
|
85484
85644
|
var CHROME_VISIBLE_RE = /\b(tab|home|settings|back|menu|navigation|nav bar|bottom nav|status bar)\b/i;
|
|
85485
85645
|
function hasStrongMobileOracle(steps) {
|
|
@@ -85569,6 +85729,15 @@ ${logs2 ?? ""}`.trim();
|
|
|
85569
85729
|
failureCount,
|
|
85570
85730
|
errorPrefix: prefix
|
|
85571
85731
|
};
|
|
85732
|
+
if ((0, anr_env_js_1.isAnrOrSystemUiEnvironmentFailure)(input.error, input.logs)) {
|
|
85733
|
+
return {
|
|
85734
|
+
verdict: "config_issue",
|
|
85735
|
+
reason: "ANR / System UI freeze (device or app hung) \u2014 treat as environment, not product bug",
|
|
85736
|
+
shouldHeal: false,
|
|
85737
|
+
healVerdict: "config_issue",
|
|
85738
|
+
evidence
|
|
85739
|
+
};
|
|
85740
|
+
}
|
|
85572
85741
|
if (prefix === "CONFIG_ISSUE" || prefix === "RATE_LIMIT_BLOCKED") {
|
|
85573
85742
|
return {
|
|
85574
85743
|
verdict: "config_issue",
|
|
@@ -85817,6 +85986,7 @@ var require_mobile_heal_postprocess = __commonJS({
|
|
|
85817
85986
|
exports.detectMobileStepSecretLeak = detectMobileStepSecretLeak;
|
|
85818
85987
|
exports.computeMobileHealOutcome = computeMobileHealOutcome2;
|
|
85819
85988
|
var oracle_protection_js_1 = require_oracle_protection();
|
|
85989
|
+
var anr_env_js_1 = require_anr_env();
|
|
85820
85990
|
var REDACT_KEY_RE = /email|username|user|phone|password|secret|token|api[_-]?key|access[_-]?key|\bpass\b|credential|bearer|\bpat\b|session|cookie|jwt|otp|pin/i;
|
|
85821
85991
|
var DETECT_KEY_RE = /password|secret|token|api[_]?key|\bpass\b|credential|bearer|\bpat\b/i;
|
|
85822
85992
|
var MIN_REDACT_LEN = 4;
|
|
@@ -85887,21 +86057,26 @@ var require_mobile_heal_postprocess = __commonJS({
|
|
|
85887
86057
|
const oracleWeakening = isVerifiedFix && args.originalSteps ? (0, oracle_protection_js_1.rejectOracleWeakeningPatch)(args.originalSteps, heal.healedSteps ?? []) : null;
|
|
85888
86058
|
if (oracleWeakening)
|
|
85889
86059
|
isVerifiedFix = false;
|
|
85890
|
-
const
|
|
85891
|
-
const
|
|
86060
|
+
const anrEnv = (0, anr_env_js_1.isAnrOrSystemUiEnvironmentFailure)(heal.healReason ?? args.finalError ?? args.reproduceError, typeof args.finalError === "string" ? args.finalError : void 0);
|
|
86061
|
+
const isConfigIssue = heal.verdict === "config_issue" || heal.verdict === "app_bug" && anrEnv;
|
|
86062
|
+
const isAppBug = !anrEnv && (heal.verdict === "app_bug" || Boolean(oracleWeakening && heal.passed));
|
|
85892
86063
|
const extraTags = new Set((heal.extraTags ?? []).filter((t) => typeof t === "string" && t.length > 0));
|
|
85893
86064
|
if (secretLeak)
|
|
85894
86065
|
extraTags.add("@needs-review");
|
|
85895
|
-
if (oracleWeakening) {
|
|
86066
|
+
if (oracleWeakening && !anrEnv) {
|
|
85896
86067
|
extraTags.add("@needs-review");
|
|
85897
86068
|
extraTags.add("@bug-found");
|
|
85898
86069
|
}
|
|
86070
|
+
if (anrEnv) {
|
|
86071
|
+
extraTags.delete("@bug-found");
|
|
86072
|
+
extraTags.add("@needs-review");
|
|
86073
|
+
}
|
|
85899
86074
|
if (!heal.passed && NEEDS_REVIEW_VERDICTS.has(heal.verdict))
|
|
85900
86075
|
extraTags.add("@needs-review");
|
|
85901
86076
|
let status;
|
|
85902
86077
|
if (secretLeak)
|
|
85903
86078
|
status = "FAILED";
|
|
85904
|
-
else if (oracleWeakening)
|
|
86079
|
+
else if (oracleWeakening && !anrEnv)
|
|
85905
86080
|
status = "FAILED";
|
|
85906
86081
|
else if (isVerifiedFix)
|
|
85907
86082
|
status = "PASSED";
|
|
@@ -85914,10 +86089,11 @@ var require_mobile_heal_postprocess = __commonJS({
|
|
|
85914
86089
|
error2 = void 0;
|
|
85915
86090
|
} else if (secretLeak) {
|
|
85916
86091
|
error2 = `HEAL_SECRET_LEAK: healed steps contained a literal secret (${secretLeak}); patch dropped, tagged @needs-review`;
|
|
85917
|
-
} else if (oracleWeakening) {
|
|
86092
|
+
} else if (oracleWeakening && !anrEnv) {
|
|
85918
86093
|
error2 = `APP_BUG: heal attempted to drop strong oracle (${oracleWeakening}); patch refused`;
|
|
85919
86094
|
} else if (isConfigIssue) {
|
|
85920
|
-
|
|
86095
|
+
const reason = heal.healReason ?? args.finalError ?? args.reproduceError ?? "environment/rate-limit blocker";
|
|
86096
|
+
error2 = anrEnv ? `CONFIG_ISSUE: ANR / System UI freeze (not a product assertion failure) \u2014 ${reason}` : `CONFIG_ISSUE: ${reason}`;
|
|
85921
86097
|
} else if (isAppBug) {
|
|
85922
86098
|
error2 = `APP_BUG: ${heal.healReason ?? args.finalError ?? args.reproduceError ?? "application defect found during heal"}`;
|
|
85923
86099
|
} else if (heal.passed && heal.verdict === "fix" && !hasFix) {
|
|
@@ -86360,6 +86536,7 @@ var require_mobile_heal_loop = __commonJS({
|
|
|
86360
86536
|
var failure_triage_js_1 = require_failure_triage();
|
|
86361
86537
|
var oracle_protection_js_1 = require_oracle_protection();
|
|
86362
86538
|
var network_oracle_js_1 = require_network_oracle();
|
|
86539
|
+
var anr_env_js_1 = require_anr_env();
|
|
86363
86540
|
var mobile_observation_js_1 = require_mobile_observation();
|
|
86364
86541
|
var selector_registry_context_js_1 = require_selector_registry_context();
|
|
86365
86542
|
var mobile_session_keepalive_js_1 = require_mobile_session_keepalive();
|
|
@@ -86371,12 +86548,13 @@ Reason over steps + error + tree + registry and return working healed steps. Do
|
|
|
86371
86548
|
2. STEP RESULTS: which steps passed before the failure.
|
|
86372
86549
|
3. TREE: does the current screen match what the failed step expects?
|
|
86373
86550
|
3b. STALE EMPTY-STATE ASSERT: a failing assert on one-time / data-dependent UI (empty-state CTAs, first-run prompts, onboarding banners) is NOT a locator problem \u2014 the element is gone because the account now has data. REPLACE the assertion with a durable landing-screen anchor (tab label, header, nav bar) that proves the same outcome (verdict fix).
|
|
86551
|
+
3c. ANR / "System UI isn't responding" / freeze dialogs: this is ENVIRONMENT (config_issue), NOT app_bug. Do not quarantine product bugs for device freezes.
|
|
86374
86552
|
4. EXTERNAL AUTH HANDOFF: if the failure says the foreground app changed outside the target app after a Login / Sign In tap, and the steps never enter email/password credentials, do NOT heal by choosing Google/Apple/Facebook/SSO or another external app. If the test later expects Logout/session state, return test_invalid or prereq_unmet: the recorded test lacks a real credential setup.
|
|
86375
86553
|
5. ENTRY-SCREEN DRIFT ("unexpected screen"/"screen drift"): the app started on a DIFFERENT screen than recorded; the fix is usually a MISSING NAVIGATION step (e.g. tap the onboarding/welcome CTA present in the tree before the recorded first step). Author it from the tree if you can (verdict fix); if a single static tree is insufficient, return needs_device.
|
|
86376
86554
|
## Response \u2014 STRICT JSON, exactly one verdict:
|
|
86377
86555
|
{"verdict":"fix","steps":[<FULL healed MobileTestStep array, all steps, your changes applied, order/metadata preserved>],"reason":"what changed and why","confidence":0.0-1.0}
|
|
86378
|
-
{"verdict":"app_bug","reason":"
|
|
86379
|
-
{"verdict":"config_issue","reason":"429/rate limit/lockout/quota"}
|
|
86556
|
+
{"verdict":"app_bug","reason":"product defect with durable oracle evidence (wrong entity, validation bypass, wrong count) \u2014 NOT ANR/System UI"}
|
|
86557
|
+
{"verdict":"config_issue","reason":"429/rate limit/lockout/quota OR ANR/System UI freeze/device unresponsive"}
|
|
86380
86558
|
{"verdict":"needs_device","reason":"cannot fix from a static tree; need live re-observation"}
|
|
86381
86559
|
{"verdict":"prereq_unmet","reason":"required entity/state absent for the account"}
|
|
86382
86560
|
{"verdict":"state_stale","reason":"leftover data blocks a baseline assertion"}
|
|
@@ -86387,7 +86565,7 @@ For a fix, return the COMPLETE steps array (not a diff).
|
|
|
86387
86565
|
- NEVER remove or downgrade assertStateDelta, captureState, or assertText outcome proof to go green.
|
|
86388
86566
|
- NEVER replace a durable outcome assert with mere assertVisible of a tab/header.
|
|
86389
86567
|
- You MAY fix locators, waits, and navigation around those oracles.
|
|
86390
|
-
- If the app truly violates the oracle, return verdict "app_bug" instead of stripping the assert.`;
|
|
86568
|
+
- If the app truly violates the oracle on a healthy UI, return verdict "app_bug" instead of stripping the assert.`;
|
|
86391
86569
|
var DEFAULT_SCRIPT_MAX_ATTEMPTS = 6;
|
|
86392
86570
|
var RECOVERABLE_MAX = 3;
|
|
86393
86571
|
var MAX_CONSECUTIVE_INVALID_VERDICTS = 3;
|
|
@@ -87355,6 +87533,18 @@ ${args.directive}`);
|
|
|
87355
87533
|
providerUnavailable: true
|
|
87356
87534
|
};
|
|
87357
87535
|
}
|
|
87536
|
+
if (script.verdict === "app_bug" && (0, anr_env_js_1.isAnrOrSystemUiEnvironmentFailure)(script.healReason ?? ctx.error, ctx.logs)) {
|
|
87537
|
+
return {
|
|
87538
|
+
passed: false,
|
|
87539
|
+
verdict: "config_issue",
|
|
87540
|
+
healedSteps: script.healedSteps,
|
|
87541
|
+
healedAppiumCode: script.healedAppiumCode,
|
|
87542
|
+
healReason: `ANR / System UI freeze (environment) \u2014 ${script.healReason ?? ctx.error ?? "device unresponsive"}`,
|
|
87543
|
+
confidence: script.confidence,
|
|
87544
|
+
extraTags: script.extraTags,
|
|
87545
|
+
removeTags: [...script.removeTags ?? [], "@bug-found"].filter((t, i, a) => a.indexOf(t) === i)
|
|
87546
|
+
};
|
|
87547
|
+
}
|
|
87358
87548
|
const shouldEscalate = script.verdict === "needs_device" || script.verdict === "fix" && !script.passed;
|
|
87359
87549
|
if (shouldEscalate && options.runLiveHeal) {
|
|
87360
87550
|
try {
|
|
@@ -355111,7 +355301,7 @@ var require_package4 = __commonJS({
|
|
|
355111
355301
|
"package.json"(exports, module) {
|
|
355112
355302
|
module.exports = {
|
|
355113
355303
|
name: "@validate.qa/runner",
|
|
355114
|
-
version: "1.0.
|
|
355304
|
+
version: "1.0.91",
|
|
355115
355305
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
355116
355306
|
bin: {
|
|
355117
355307
|
"validate-runner": "dist/cli.js"
|
package/package.json
CHANGED