@validate.qa/runner 1.0.72 → 1.0.74
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 +188 -47
- package/dist/cli.mjs +188 -47
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -5854,6 +5854,29 @@ function normalizeTtsVoiceId(voiceId) {
|
|
|
5854
5854
|
return null;
|
|
5855
5855
|
return LEGACY_TTS_VOICE_ID_MAP[trimmed] ?? trimmed;
|
|
5856
5856
|
}
|
|
5857
|
+
function normalizeTtsModel(model) {
|
|
5858
|
+
if (!model)
|
|
5859
|
+
return null;
|
|
5860
|
+
const trimmed = model.trim();
|
|
5861
|
+
if (!trimmed)
|
|
5862
|
+
return null;
|
|
5863
|
+
return LEGACY_TTS_MODEL_MAP[trimmed] ?? trimmed;
|
|
5864
|
+
}
|
|
5865
|
+
function ttsFallbackModel(primary) {
|
|
5866
|
+
const normalized = normalizeTtsModel(primary);
|
|
5867
|
+
if (!normalized)
|
|
5868
|
+
return null;
|
|
5869
|
+
return TTS_FALLBACK_MODEL_MAP[normalized] ?? null;
|
|
5870
|
+
}
|
|
5871
|
+
function resolveTtsModelChain(model) {
|
|
5872
|
+
const normalized = normalizeTtsModel(model);
|
|
5873
|
+
const primary = normalized && normalized in TTS_FALLBACK_MODEL_MAP ? normalized : DEFAULT_TTS_MODEL;
|
|
5874
|
+
const chain = [primary];
|
|
5875
|
+
const fallback = TTS_FALLBACK_MODEL_MAP[primary];
|
|
5876
|
+
if (fallback && fallback !== primary)
|
|
5877
|
+
chain.push(fallback);
|
|
5878
|
+
return chain;
|
|
5879
|
+
}
|
|
5857
5880
|
function sttModelsForProvider(provider) {
|
|
5858
5881
|
if (provider === "openai") {
|
|
5859
5882
|
return STT_MODEL_OPTIONS.filter((m) => m.provider === "openai");
|
|
@@ -5901,16 +5924,18 @@ function isValidTtsVoiceId(value) {
|
|
|
5901
5924
|
return TTS_VOICE_OPTIONS.some((v) => v.id === normalized);
|
|
5902
5925
|
}
|
|
5903
5926
|
function isValidTtsModel(value) {
|
|
5904
|
-
|
|
5927
|
+
const normalized = normalizeTtsModel(value);
|
|
5928
|
+
if (!normalized)
|
|
5905
5929
|
return false;
|
|
5906
|
-
return TTS_MODEL_OPTIONS.some((m) => m.id ===
|
|
5930
|
+
return TTS_MODEL_OPTIONS.some((m) => m.id === normalized);
|
|
5907
5931
|
}
|
|
5908
|
-
var VOICE_PREVIEW_SAMPLE, DEFAULT_TTS_MODEL, DEFAULT_TTS_VOICE_ID, DEFAULT_TTS_SPEED, LEGACY_TTS_VOICE_ID_MAP, DEFAULT_STT_MODEL_BY_PROVIDER, STT_PROVIDER_OPTIONS, STT_MODEL_OPTIONS, TTS_MODEL_OPTIONS, TTS_VOICE_OPTIONS;
|
|
5932
|
+
var VOICE_PREVIEW_SAMPLE, DEFAULT_TTS_MODEL, DEFAULT_TTS_TURBO_MODEL, DEFAULT_TTS_VOICE_ID, DEFAULT_TTS_SPEED, LEGACY_TTS_VOICE_ID_MAP, LEGACY_TTS_MODEL_MAP, TTS_FALLBACK_MODEL_MAP, DEFAULT_STT_MODEL_BY_PROVIDER, STT_PROVIDER_OPTIONS, STT_MODEL_OPTIONS, TTS_MODEL_OPTIONS, TTS_VOICE_OPTIONS;
|
|
5909
5933
|
var init_voice_config = __esm({
|
|
5910
5934
|
"../shared/dist/voice-config.js"() {
|
|
5911
5935
|
"use strict";
|
|
5912
5936
|
VOICE_PREVIEW_SAMPLE = "Hello! I am your Validate Copilot assistant. I can help you run tests, review failures, and generate new coverage for your project.";
|
|
5913
|
-
DEFAULT_TTS_MODEL = "speech-2.
|
|
5937
|
+
DEFAULT_TTS_MODEL = "speech-2.8-hd";
|
|
5938
|
+
DEFAULT_TTS_TURBO_MODEL = "speech-2.8-turbo";
|
|
5914
5939
|
DEFAULT_TTS_VOICE_ID = "English_Graceful_Lady";
|
|
5915
5940
|
DEFAULT_TTS_SPEED = 1;
|
|
5916
5941
|
LEGACY_TTS_VOICE_ID_MAP = {
|
|
@@ -5919,6 +5944,18 @@ var init_voice_config = __esm({
|
|
|
5919
5944
|
English_Deep_Voice_Man: "English_ManWithDeepVoice",
|
|
5920
5945
|
English_CasualMan: "English_Jovialman"
|
|
5921
5946
|
};
|
|
5947
|
+
LEGACY_TTS_MODEL_MAP = {
|
|
5948
|
+
"speech-2.6-hd": "speech-2.8-hd",
|
|
5949
|
+
"speech-2.6-turbo": "speech-2.8-turbo",
|
|
5950
|
+
"speech-02-hd": "speech-2.8-hd",
|
|
5951
|
+
"speech-02-turbo": "speech-2.8-turbo",
|
|
5952
|
+
"speech-01-hd": "speech-2.8-hd",
|
|
5953
|
+
"speech-01-turbo": "speech-2.8-turbo"
|
|
5954
|
+
};
|
|
5955
|
+
TTS_FALLBACK_MODEL_MAP = {
|
|
5956
|
+
"speech-2.8-hd": "speech-2.6-hd",
|
|
5957
|
+
"speech-2.8-turbo": "speech-2.6-turbo"
|
|
5958
|
+
};
|
|
5922
5959
|
DEFAULT_STT_MODEL_BY_PROVIDER = {
|
|
5923
5960
|
groq: "whisper-large-v3-turbo",
|
|
5924
5961
|
openai: "whisper-1"
|
|
@@ -5971,49 +6008,13 @@ var init_voice_config = __esm({
|
|
|
5971
6008
|
id: "speech-2.8-hd",
|
|
5972
6009
|
label: "Speech 2.8 HD",
|
|
5973
6010
|
provider: "minimax",
|
|
5974
|
-
description: "
|
|
6011
|
+
description: "High-definition quality (default). Auto-falls back to Speech 2.6 HD."
|
|
5975
6012
|
},
|
|
5976
6013
|
{
|
|
5977
6014
|
id: "speech-2.8-turbo",
|
|
5978
6015
|
label: "Speech 2.8 Turbo",
|
|
5979
6016
|
provider: "minimax",
|
|
5980
|
-
description: "
|
|
5981
|
-
},
|
|
5982
|
-
{
|
|
5983
|
-
id: "speech-2.6-hd",
|
|
5984
|
-
label: "Speech 2.6 HD",
|
|
5985
|
-
provider: "minimax",
|
|
5986
|
-
description: "High-definition MiniMax voice model."
|
|
5987
|
-
},
|
|
5988
|
-
{
|
|
5989
|
-
id: "speech-2.6-turbo",
|
|
5990
|
-
label: "Speech 2.6 Turbo",
|
|
5991
|
-
provider: "minimax",
|
|
5992
|
-
description: "Lower-latency Speech 2.6 variant."
|
|
5993
|
-
},
|
|
5994
|
-
{
|
|
5995
|
-
id: "speech-02-hd",
|
|
5996
|
-
label: "Speech 02 HD",
|
|
5997
|
-
provider: "minimax",
|
|
5998
|
-
description: "Previous-generation HD model."
|
|
5999
|
-
},
|
|
6000
|
-
{
|
|
6001
|
-
id: "speech-02-turbo",
|
|
6002
|
-
label: "Speech 02 Turbo",
|
|
6003
|
-
provider: "minimax",
|
|
6004
|
-
description: "Lower-latency Speech 02 variant."
|
|
6005
|
-
},
|
|
6006
|
-
{
|
|
6007
|
-
id: "speech-01-hd",
|
|
6008
|
-
label: "Speech 01 HD",
|
|
6009
|
-
provider: "minimax",
|
|
6010
|
-
description: "Legacy HD model."
|
|
6011
|
-
},
|
|
6012
|
-
{
|
|
6013
|
-
id: "speech-01-turbo",
|
|
6014
|
-
label: "Speech 01 Turbo",
|
|
6015
|
-
provider: "minimax",
|
|
6016
|
-
description: "Legacy fast model."
|
|
6017
|
+
description: "Lower latency. Auto-falls back to Speech 2.6 Turbo."
|
|
6017
6018
|
}
|
|
6018
6019
|
];
|
|
6019
6020
|
TTS_VOICE_OPTIONS = [
|
|
@@ -6297,6 +6298,7 @@ __export(dist_exports, {
|
|
|
6297
6298
|
DEFAULT_STT_MODEL_BY_PROVIDER: () => DEFAULT_STT_MODEL_BY_PROVIDER,
|
|
6298
6299
|
DEFAULT_TTS_MODEL: () => DEFAULT_TTS_MODEL,
|
|
6299
6300
|
DEFAULT_TTS_SPEED: () => DEFAULT_TTS_SPEED,
|
|
6301
|
+
DEFAULT_TTS_TURBO_MODEL: () => DEFAULT_TTS_TURBO_MODEL,
|
|
6300
6302
|
DEFAULT_TTS_VOICE_ID: () => DEFAULT_TTS_VOICE_ID,
|
|
6301
6303
|
DEFAULT_UI_VARIANT: () => DEFAULT_UI_VARIANT,
|
|
6302
6304
|
EMAIL_FROM_IDENTITIES: () => EMAIL_FROM_IDENTITIES,
|
|
@@ -6306,6 +6308,7 @@ __export(dist_exports, {
|
|
|
6306
6308
|
FLOW_SERVER_TYPES: () => FLOW_SERVER_TYPES,
|
|
6307
6309
|
GROUPABLE_CALL_TYPES: () => GROUPABLE_CALL_TYPES,
|
|
6308
6310
|
GROUP_LABELS: () => GROUP_LABELS,
|
|
6311
|
+
LEGACY_TTS_MODEL_MAP: () => LEGACY_TTS_MODEL_MAP,
|
|
6309
6312
|
LEGACY_TTS_VOICE_ID_MAP: () => LEGACY_TTS_VOICE_ID_MAP,
|
|
6310
6313
|
MAX_CREDIT_TOPUP_CENTS: () => MAX_CREDIT_TOPUP_CENTS,
|
|
6311
6314
|
MAX_DEEPLINK_LENGTH: () => MAX_DEEPLINK_LENGTH,
|
|
@@ -6339,6 +6342,7 @@ __export(dist_exports, {
|
|
|
6339
6342
|
TEST_USER_ENV_KEYS: () => TEST_USER_ENV_KEYS,
|
|
6340
6343
|
TRANSIENT_CONTROL_ROLES: () => TRANSIENT_CONTROL_ROLES,
|
|
6341
6344
|
TRIAL_DURATION_DAYS: () => TRIAL_DURATION_DAYS,
|
|
6345
|
+
TTS_FALLBACK_MODEL_MAP: () => TTS_FALLBACK_MODEL_MAP,
|
|
6342
6346
|
TTS_MODEL_OPTIONS: () => TTS_MODEL_OPTIONS,
|
|
6343
6347
|
TTS_VOICE_OPTIONS: () => TTS_VOICE_OPTIONS,
|
|
6344
6348
|
UI_VARIANTS: () => UI_VARIANTS,
|
|
@@ -6441,6 +6445,7 @@ __export(dist_exports, {
|
|
|
6441
6445
|
mobilePlatformFromMedium: () => mobilePlatformFromMedium,
|
|
6442
6446
|
mobileWanderTraceEnabled: () => mobileWanderTraceEnabled,
|
|
6443
6447
|
normalizeControlName: () => normalizeControlName,
|
|
6448
|
+
normalizeTtsModel: () => normalizeTtsModel,
|
|
6444
6449
|
normalizeTtsVoiceId: () => normalizeTtsVoiceId,
|
|
6445
6450
|
otherMobilePlatform: () => otherMobilePlatform,
|
|
6446
6451
|
parenDepthDelta: () => parenDepthDelta,
|
|
@@ -6459,6 +6464,7 @@ __export(dist_exports, {
|
|
|
6459
6464
|
resolveMobileStepValuePlaceholders: () => resolveMobileStepValuePlaceholders,
|
|
6460
6465
|
resolvePlatformLocatorBundle: () => resolvePlatformLocatorBundle,
|
|
6461
6466
|
resolveSttModelForProvider: () => resolveSttModelForProvider,
|
|
6467
|
+
resolveTtsModelChain: () => resolveTtsModelChain,
|
|
6462
6468
|
safeCurrentUrl: () => safeCurrentUrl,
|
|
6463
6469
|
safeOrigin: () => safeOrigin,
|
|
6464
6470
|
sanitizeSttErrorMessage: () => sanitizeSttErrorMessage,
|
|
@@ -6481,6 +6487,7 @@ __export(dist_exports, {
|
|
|
6481
6487
|
summarizeMobileReadiness: () => summarizeMobileReadiness,
|
|
6482
6488
|
summarizeRunHealth: () => summarizeRunHealth,
|
|
6483
6489
|
toDbPricingPlan: () => toDbPricingPlan,
|
|
6490
|
+
ttsFallbackModel: () => ttsFallbackModel,
|
|
6484
6491
|
validateAssertionStrength: () => validateAssertionStrength
|
|
6485
6492
|
});
|
|
6486
6493
|
var init_dist = __esm({
|
|
@@ -353007,7 +353014,7 @@ var require_package4 = __commonJS({
|
|
|
353007
353014
|
"package.json"(exports2, module2) {
|
|
353008
353015
|
module2.exports = {
|
|
353009
353016
|
name: "@validate.qa/runner",
|
|
353010
|
-
version: "1.0.
|
|
353017
|
+
version: "1.0.74",
|
|
353011
353018
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
353012
353019
|
bin: {
|
|
353013
353020
|
"validate-runner": "dist/cli.js"
|
|
@@ -354671,6 +354678,47 @@ async function clickCssInCurrentContext(sessionPath, css) {
|
|
|
354671
354678
|
body: "{}"
|
|
354672
354679
|
});
|
|
354673
354680
|
}
|
|
354681
|
+
async function clickByVisibleTextInCurrentContext(sessionPath, text) {
|
|
354682
|
+
const script = `
|
|
354683
|
+
var t = String(arguments[0]).trim().replace(/\\s+/g, ' ');
|
|
354684
|
+
var norm = function (e) { return (e.textContent || e.value || '').trim().replace(/\\s+/g, ' '); };
|
|
354685
|
+
var vis = function (e) { var r = e.getBoundingClientRect(); return r.width > 0 && r.height > 0; };
|
|
354686
|
+
var all = Array.prototype.slice.call(document.body.getElementsByTagName('*'));
|
|
354687
|
+
var match = all.filter(function (e) { return norm(e) === t && vis(e); });
|
|
354688
|
+
// Deepest-only: drop any match that contains another match (same text).
|
|
354689
|
+
var deepest = match.filter(function (e) {
|
|
354690
|
+
return !match.some(function (o) { return o !== e && e.contains(o); });
|
|
354691
|
+
});
|
|
354692
|
+
if (deepest.length !== 1) { return deepest.length; }
|
|
354693
|
+
var el = deepest[0];
|
|
354694
|
+
var interactive = function (e) {
|
|
354695
|
+
var tag = e.tagName;
|
|
354696
|
+
if (tag === 'BUTTON' || tag === 'A' || tag === 'INPUT' || tag === 'SELECT' || tag === 'SUMMARY') { return true; }
|
|
354697
|
+
var role = e.getAttribute && e.getAttribute('role');
|
|
354698
|
+
if (role === 'button' || role === 'tab' || role === 'link' || role === 'menuitem' || role === 'option') { return true; }
|
|
354699
|
+
if (e.hasAttribute && (e.hasAttribute('onclick') || (e.hasAttribute('tabindex') && e.getAttribute('tabindex') !== '-1'))) { return true; }
|
|
354700
|
+
try { return window.getComputedStyle(e).cursor === 'pointer'; } catch (err) { return false; }
|
|
354701
|
+
};
|
|
354702
|
+
// Prefer the outermost interactive ancestor that wraps ONLY this text.
|
|
354703
|
+
var target = el;
|
|
354704
|
+
var hop = el;
|
|
354705
|
+
for (var i = 0; i < 5 && hop && hop !== document.body; i++) {
|
|
354706
|
+
if (norm(hop) !== t) { break; }
|
|
354707
|
+
if (interactive(hop)) { target = hop; }
|
|
354708
|
+
hop = hop.parentElement;
|
|
354709
|
+
}
|
|
354710
|
+
target.click();
|
|
354711
|
+
return 1;
|
|
354712
|
+
`;
|
|
354713
|
+
const result = await appiumRequest(`${sessionPath}/execute/sync`, {
|
|
354714
|
+
method: "POST",
|
|
354715
|
+
body: JSON.stringify({ script, args: [text] })
|
|
354716
|
+
});
|
|
354717
|
+
const count = readValue(result);
|
|
354718
|
+
if (count !== 1) {
|
|
354719
|
+
throw new Error(`WEBVIEW text click found ${count === 0 || count === void 0 ? "no" : String(count)} actionable match(es) for "${text}"`);
|
|
354720
|
+
}
|
|
354721
|
+
}
|
|
354674
354722
|
async function typeCssInCurrentContext(sessionPath, css, text) {
|
|
354675
354723
|
const elResult = await appiumRequest(`${sessionPath}/element`, {
|
|
354676
354724
|
method: "POST",
|
|
@@ -357029,6 +357077,48 @@ function stepWebViewContext(step) {
|
|
|
357029
357077
|
const meta = step.metadata && typeof step.metadata === "object" ? step.metadata : null;
|
|
357030
357078
|
return typeof meta?.webviewContext === "string" && meta.webviewContext.trim() ? meta.webviewContext.trim() : null;
|
|
357031
357079
|
}
|
|
357080
|
+
var DOM_ID_RE = /^[A-Za-z_][\w-]*$/;
|
|
357081
|
+
var SYSTEM_RESOURCE_ID_RE = /^(?:android|com\.android\.|com\.google\.android\.)/i;
|
|
357082
|
+
function webViewCssFallbackFromNativeStep(step) {
|
|
357083
|
+
if (stepWebViewCss(step)) return null;
|
|
357084
|
+
const target = step.target;
|
|
357085
|
+
if (!target) return null;
|
|
357086
|
+
const bundle = target.locatorBundle;
|
|
357087
|
+
const resourceId = typeof bundle?.resourceId === "string" ? bundle.resourceId.trim() : "";
|
|
357088
|
+
const accessibilityId = typeof bundle?.accessibilityId === "string" ? bundle.accessibilityId.trim() : "";
|
|
357089
|
+
const primary = typeof target.primary === "string" ? target.primary.trim() : "";
|
|
357090
|
+
const fromResourceId = (raw) => {
|
|
357091
|
+
if (!raw) return null;
|
|
357092
|
+
if (raw.includes(":id/")) {
|
|
357093
|
+
if (SYSTEM_RESOURCE_ID_RE.test(raw)) return null;
|
|
357094
|
+
const local = raw.slice(raw.indexOf(":id/") + 4);
|
|
357095
|
+
return DOM_ID_RE.test(local) ? local : null;
|
|
357096
|
+
}
|
|
357097
|
+
return DOM_ID_RE.test(raw) ? raw : null;
|
|
357098
|
+
};
|
|
357099
|
+
const domId = fromResourceId(resourceId) ?? fromResourceId(accessibilityId) ?? (primary && !/[=~/[\].\s]/.test(primary) && DOM_ID_RE.test(primary) ? primary : null);
|
|
357100
|
+
return domId ? `#${domId}` : null;
|
|
357101
|
+
}
|
|
357102
|
+
function webViewTextFallbackFromNativeStep(step) {
|
|
357103
|
+
if (stepWebViewCss(step)) return null;
|
|
357104
|
+
if (step.action !== "tap") return null;
|
|
357105
|
+
const target = step.target;
|
|
357106
|
+
if (!target) return null;
|
|
357107
|
+
const bundle = target.locatorBundle;
|
|
357108
|
+
const text = typeof bundle?.text === "string" && bundle.text.trim() ? bundle.text.trim() : typeof bundle?.contentDesc === "string" && bundle.contentDesc.trim() ? bundle.contentDesc.trim() : typeof target.primary === "string" && target.primary.trim() && !/[=~/[\]]|:id\//.test(target.primary) ? target.primary.trim() : "";
|
|
357109
|
+
const normalized = text.replace(/\s+/g, " ");
|
|
357110
|
+
if (!normalized || normalized.length > 40 || /[\n\r]/.test(text)) return null;
|
|
357111
|
+
if (SYSTEM_PERMISSION_BUTTON_RE.test(text)) return null;
|
|
357112
|
+
return normalized;
|
|
357113
|
+
}
|
|
357114
|
+
async function hasLiveWebViewContext(sessionId) {
|
|
357115
|
+
try {
|
|
357116
|
+
const contexts = await listAppContexts(`/session/${sessionId}`);
|
|
357117
|
+
return contexts.some((c) => /^WEBVIEW/i.test(c));
|
|
357118
|
+
} catch {
|
|
357119
|
+
return false;
|
|
357120
|
+
}
|
|
357121
|
+
}
|
|
357032
357122
|
var CONTAINER_ONLY_ENTRY_ID_RE = /(?:^|[:/])id\/(webview|content|decor|decor_content_parent|action_bar_root|statusBarBackground|navigationBarBackground)$|^(?:main-content|root|app|app-root|__next|react-root|application)$/i;
|
|
357033
357123
|
function isContainerOnlyEntryTarget(target) {
|
|
357034
357124
|
const primary = target?.primary?.trim() ?? "";
|
|
@@ -357116,6 +357206,7 @@ function isWebViewHydrationTransient(err) {
|
|
|
357116
357206
|
return /no such element|NoSuchElement|element not interactable|stale element|WEBVIEW step requires|Empty page dictionary|not connectable|no WEBVIEW context|chrome not reachable/i.test(message);
|
|
357117
357207
|
}
|
|
357118
357208
|
var WEBVIEW_HYDRATION_TIMEOUT_MS = Number(process.env.VALIDATEQA_WEBVIEW_HYDRATION_TIMEOUT_MS) || 3e4;
|
|
357209
|
+
var PERMISSION_DIALOG_TIMEOUT_MS = Number(process.env.VALIDATEQA_PERMISSION_DIALOG_TIMEOUT_MS) || 3e3;
|
|
357119
357210
|
async function executeInWebViewContextWithHydrationRetry(sessionId, step, css, body, timeoutMs) {
|
|
357120
357211
|
const deadline = Date.now() + Math.max(1e3, timeoutMs);
|
|
357121
357212
|
for (; ; ) {
|
|
@@ -357128,6 +357219,17 @@ async function executeInWebViewContextWithHydrationRetry(sessionId, step, css, b
|
|
|
357128
357219
|
}
|
|
357129
357220
|
}
|
|
357130
357221
|
}
|
|
357222
|
+
var SYSTEM_PERMISSION_BUTTON_RE = /com\.android\.permissioncontroller:id\/permission_(allow|deny)|com\.android\.packageinstaller:id\/permission_|:id\/permission_allow_(foreground_only|one_time)_button|^(?:com\.apple\.springboard|SpringBoard)/i;
|
|
357223
|
+
function isSystemPermissionButtonTarget(target) {
|
|
357224
|
+
if (!target) return false;
|
|
357225
|
+
const bundle = target.locatorBundle;
|
|
357226
|
+
const candidates = [
|
|
357227
|
+
target.primary,
|
|
357228
|
+
typeof bundle?.resourceId === "string" ? bundle.resourceId : "",
|
|
357229
|
+
typeof bundle?.xpath === "string" ? bundle.xpath : ""
|
|
357230
|
+
];
|
|
357231
|
+
return candidates.some((c) => typeof c === "string" && SYSTEM_PERMISSION_BUTTON_RE.test(c));
|
|
357232
|
+
}
|
|
357131
357233
|
async function executeTap(sessionId, step, platform3, isCrossPlatform = false) {
|
|
357132
357234
|
const webCss = stepWebViewCss(step);
|
|
357133
357235
|
if (webCss) {
|
|
@@ -357140,10 +357242,11 @@ async function executeTap(sessionId, step, platform3, isCrossPlatform = false) {
|
|
|
357140
357242
|
);
|
|
357141
357243
|
return;
|
|
357142
357244
|
}
|
|
357245
|
+
const isPermissionTap = isSystemPermissionButtonTarget(step.target);
|
|
357143
357246
|
const bounds = boundsForRun(step, platform3, isCrossPlatform);
|
|
357144
357247
|
if (step.target) {
|
|
357145
357248
|
try {
|
|
357146
|
-
const elementId = await findElement(sessionId, step.target, readStepTimeout(step), {
|
|
357249
|
+
const elementId = await findElement(sessionId, step.target, isPermissionTap ? Math.min(readStepTimeout(step) ?? DEFAULT_STEP_TIMEOUT_MS, PERMISSION_DIALOG_TIMEOUT_MS) : readStepTimeout(step), {
|
|
357147
357250
|
platform: platform3,
|
|
357148
357251
|
recordedBounds: bounds
|
|
357149
357252
|
});
|
|
@@ -357153,7 +357256,23 @@ async function executeTap(sessionId, step, platform3, isCrossPlatform = false) {
|
|
|
357153
357256
|
});
|
|
357154
357257
|
return;
|
|
357155
357258
|
} catch (error2) {
|
|
357156
|
-
if (isTransportError(error2) ||
|
|
357259
|
+
if (isTransportError(error2) || error2 instanceof MobileAmbiguousMatchError) throw error2;
|
|
357260
|
+
if (isPermissionTap) return;
|
|
357261
|
+
const cssFallback = webViewCssFallbackFromNativeStep(step);
|
|
357262
|
+
const textFallback = cssFallback ? null : webViewTextFallbackFromNativeStep(step);
|
|
357263
|
+
if ((cssFallback || textFallback) && await hasLiveWebViewContext(sessionId)) {
|
|
357264
|
+
try {
|
|
357265
|
+
if (cssFallback) {
|
|
357266
|
+
await executeInWebViewContext(sessionId, step, cssFallback, (sessionPath, css) => clickCssInCurrentContext(sessionPath, css));
|
|
357267
|
+
} else if (textFallback) {
|
|
357268
|
+
await executeInWebViewContext(sessionId, step, textFallback, (sessionPath) => clickByVisibleTextInCurrentContext(sessionPath, textFallback));
|
|
357269
|
+
}
|
|
357270
|
+
return;
|
|
357271
|
+
} catch (webErr) {
|
|
357272
|
+
if (isTransportError(webErr)) throw webErr;
|
|
357273
|
+
}
|
|
357274
|
+
}
|
|
357275
|
+
if (!bounds || buildLocatorStrategies(step.target, platform3).length > 0) throw error2;
|
|
357157
357276
|
}
|
|
357158
357277
|
}
|
|
357159
357278
|
if (!bounds) {
|
|
@@ -357211,6 +357330,18 @@ async function executeType(sessionId, step, context) {
|
|
|
357211
357330
|
return;
|
|
357212
357331
|
} catch (error2) {
|
|
357213
357332
|
if (isTransportError(error2)) throw error2;
|
|
357333
|
+
if (!focusEstablished && !keyboardSubmitOnly) {
|
|
357334
|
+
const cssFallback = webViewCssFallbackFromNativeStep(step);
|
|
357335
|
+
if (cssFallback && await hasLiveWebViewContext(sessionId)) {
|
|
357336
|
+
try {
|
|
357337
|
+
context.log?.(` Native locate failed; retrying as WebView ${cssFallback} (hybrid mirror fallback)`);
|
|
357338
|
+
await executeInWebViewContext(sessionId, step, cssFallback, (sessionPath2, css) => typeCssInCurrentContext(sessionPath2, css, value));
|
|
357339
|
+
return;
|
|
357340
|
+
} catch (webErr) {
|
|
357341
|
+
if (isTransportError(webErr)) throw webErr;
|
|
357342
|
+
}
|
|
357343
|
+
}
|
|
357344
|
+
}
|
|
357214
357345
|
if (context.platform === "ANDROID" && focusEstablished) {
|
|
357215
357346
|
try {
|
|
357216
357347
|
context.log?.(" Appium active-element send failed; using Android focused-keyboard input fallback");
|
|
@@ -363045,7 +363176,11 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
363045
363176
|
surface: "mobile",
|
|
363046
363177
|
mobilePlatform: target.platform,
|
|
363047
363178
|
...assignedDeviceId ? { deviceId: assignedDeviceId } : {},
|
|
363048
|
-
...leasedDeviceName ? { deviceName: leasedDeviceName } : {}
|
|
363179
|
+
...leasedDeviceName ? { deviceName: leasedDeviceName } : {},
|
|
363180
|
+
// A mobile AI heal spends its first phase in model analysis with NO
|
|
363181
|
+
// Appium session, so the dashboard mirror is legitimately dark — say
|
|
363182
|
+
// so instead of leaving an eternal "mirror starting" placeholder.
|
|
363183
|
+
...runMode === "heal" ? { note: "Heal Phase 1 \u2014 reproducing & analyzing the failure (the device mirror attaches when the live phase starts)" } : {}
|
|
363049
363184
|
});
|
|
363050
363185
|
requestLiveBrowserHeartbeat(true);
|
|
363051
363186
|
const mobileSteps = run2.steps ?? [];
|
|
@@ -363145,7 +363280,11 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
363145
363280
|
// Surface heal liveness: a Phase-1/Phase-2 heal can legitimately run
|
|
363146
363281
|
// for many minutes of model calls with zero device traffic, which
|
|
363147
363282
|
// previously printed nothing between the start banner and the verdict.
|
|
363148
|
-
onProgress: (message) =>
|
|
363283
|
+
onProgress: (message) => {
|
|
363284
|
+
log.info(`\u{1FA7A} ${message}`);
|
|
363285
|
+
liveBrowserHandle.patch({ note: `\u{1FA7A} ${message}`.slice(0, 180) });
|
|
363286
|
+
requestLiveBrowserHeartbeat();
|
|
363287
|
+
},
|
|
363149
363288
|
selectorRegistryContext: run2.selectorRegistryContext ?? null,
|
|
363150
363289
|
runLiveHeal: async (a) => {
|
|
363151
363290
|
let liveSession = null;
|
|
@@ -363197,6 +363336,8 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
363197
363336
|
};
|
|
363198
363337
|
}
|
|
363199
363338
|
log.info("\u{1FA7A} Phase 2 (live device) \u2014 opening the live observation session...");
|
|
363339
|
+
liveBrowserHandle.patch({ note: "Heal Phase 2 \u2014 opening the live device session (mirror attaching)" });
|
|
363340
|
+
requestLiveBrowserHeartbeat(true);
|
|
363200
363341
|
try {
|
|
363201
363342
|
await openLiveSession();
|
|
363202
363343
|
const liveDriver = {
|
package/dist/cli.mjs
CHANGED
|
@@ -5859,6 +5859,29 @@ function normalizeTtsVoiceId(voiceId) {
|
|
|
5859
5859
|
return null;
|
|
5860
5860
|
return LEGACY_TTS_VOICE_ID_MAP[trimmed] ?? trimmed;
|
|
5861
5861
|
}
|
|
5862
|
+
function normalizeTtsModel(model) {
|
|
5863
|
+
if (!model)
|
|
5864
|
+
return null;
|
|
5865
|
+
const trimmed = model.trim();
|
|
5866
|
+
if (!trimmed)
|
|
5867
|
+
return null;
|
|
5868
|
+
return LEGACY_TTS_MODEL_MAP[trimmed] ?? trimmed;
|
|
5869
|
+
}
|
|
5870
|
+
function ttsFallbackModel(primary) {
|
|
5871
|
+
const normalized = normalizeTtsModel(primary);
|
|
5872
|
+
if (!normalized)
|
|
5873
|
+
return null;
|
|
5874
|
+
return TTS_FALLBACK_MODEL_MAP[normalized] ?? null;
|
|
5875
|
+
}
|
|
5876
|
+
function resolveTtsModelChain(model) {
|
|
5877
|
+
const normalized = normalizeTtsModel(model);
|
|
5878
|
+
const primary = normalized && normalized in TTS_FALLBACK_MODEL_MAP ? normalized : DEFAULT_TTS_MODEL;
|
|
5879
|
+
const chain = [primary];
|
|
5880
|
+
const fallback = TTS_FALLBACK_MODEL_MAP[primary];
|
|
5881
|
+
if (fallback && fallback !== primary)
|
|
5882
|
+
chain.push(fallback);
|
|
5883
|
+
return chain;
|
|
5884
|
+
}
|
|
5862
5885
|
function sttModelsForProvider(provider) {
|
|
5863
5886
|
if (provider === "openai") {
|
|
5864
5887
|
return STT_MODEL_OPTIONS.filter((m) => m.provider === "openai");
|
|
@@ -5906,16 +5929,18 @@ function isValidTtsVoiceId(value) {
|
|
|
5906
5929
|
return TTS_VOICE_OPTIONS.some((v) => v.id === normalized);
|
|
5907
5930
|
}
|
|
5908
5931
|
function isValidTtsModel(value) {
|
|
5909
|
-
|
|
5932
|
+
const normalized = normalizeTtsModel(value);
|
|
5933
|
+
if (!normalized)
|
|
5910
5934
|
return false;
|
|
5911
|
-
return TTS_MODEL_OPTIONS.some((m) => m.id ===
|
|
5935
|
+
return TTS_MODEL_OPTIONS.some((m) => m.id === normalized);
|
|
5912
5936
|
}
|
|
5913
|
-
var VOICE_PREVIEW_SAMPLE, DEFAULT_TTS_MODEL, DEFAULT_TTS_VOICE_ID, DEFAULT_TTS_SPEED, LEGACY_TTS_VOICE_ID_MAP, DEFAULT_STT_MODEL_BY_PROVIDER, STT_PROVIDER_OPTIONS, STT_MODEL_OPTIONS, TTS_MODEL_OPTIONS, TTS_VOICE_OPTIONS;
|
|
5937
|
+
var VOICE_PREVIEW_SAMPLE, DEFAULT_TTS_MODEL, DEFAULT_TTS_TURBO_MODEL, DEFAULT_TTS_VOICE_ID, DEFAULT_TTS_SPEED, LEGACY_TTS_VOICE_ID_MAP, LEGACY_TTS_MODEL_MAP, TTS_FALLBACK_MODEL_MAP, DEFAULT_STT_MODEL_BY_PROVIDER, STT_PROVIDER_OPTIONS, STT_MODEL_OPTIONS, TTS_MODEL_OPTIONS, TTS_VOICE_OPTIONS;
|
|
5914
5938
|
var init_voice_config = __esm({
|
|
5915
5939
|
"../shared/dist/voice-config.js"() {
|
|
5916
5940
|
"use strict";
|
|
5917
5941
|
VOICE_PREVIEW_SAMPLE = "Hello! I am your Validate Copilot assistant. I can help you run tests, review failures, and generate new coverage for your project.";
|
|
5918
|
-
DEFAULT_TTS_MODEL = "speech-2.
|
|
5942
|
+
DEFAULT_TTS_MODEL = "speech-2.8-hd";
|
|
5943
|
+
DEFAULT_TTS_TURBO_MODEL = "speech-2.8-turbo";
|
|
5919
5944
|
DEFAULT_TTS_VOICE_ID = "English_Graceful_Lady";
|
|
5920
5945
|
DEFAULT_TTS_SPEED = 1;
|
|
5921
5946
|
LEGACY_TTS_VOICE_ID_MAP = {
|
|
@@ -5924,6 +5949,18 @@ var init_voice_config = __esm({
|
|
|
5924
5949
|
English_Deep_Voice_Man: "English_ManWithDeepVoice",
|
|
5925
5950
|
English_CasualMan: "English_Jovialman"
|
|
5926
5951
|
};
|
|
5952
|
+
LEGACY_TTS_MODEL_MAP = {
|
|
5953
|
+
"speech-2.6-hd": "speech-2.8-hd",
|
|
5954
|
+
"speech-2.6-turbo": "speech-2.8-turbo",
|
|
5955
|
+
"speech-02-hd": "speech-2.8-hd",
|
|
5956
|
+
"speech-02-turbo": "speech-2.8-turbo",
|
|
5957
|
+
"speech-01-hd": "speech-2.8-hd",
|
|
5958
|
+
"speech-01-turbo": "speech-2.8-turbo"
|
|
5959
|
+
};
|
|
5960
|
+
TTS_FALLBACK_MODEL_MAP = {
|
|
5961
|
+
"speech-2.8-hd": "speech-2.6-hd",
|
|
5962
|
+
"speech-2.8-turbo": "speech-2.6-turbo"
|
|
5963
|
+
};
|
|
5927
5964
|
DEFAULT_STT_MODEL_BY_PROVIDER = {
|
|
5928
5965
|
groq: "whisper-large-v3-turbo",
|
|
5929
5966
|
openai: "whisper-1"
|
|
@@ -5976,49 +6013,13 @@ var init_voice_config = __esm({
|
|
|
5976
6013
|
id: "speech-2.8-hd",
|
|
5977
6014
|
label: "Speech 2.8 HD",
|
|
5978
6015
|
provider: "minimax",
|
|
5979
|
-
description: "
|
|
6016
|
+
description: "High-definition quality (default). Auto-falls back to Speech 2.6 HD."
|
|
5980
6017
|
},
|
|
5981
6018
|
{
|
|
5982
6019
|
id: "speech-2.8-turbo",
|
|
5983
6020
|
label: "Speech 2.8 Turbo",
|
|
5984
6021
|
provider: "minimax",
|
|
5985
|
-
description: "
|
|
5986
|
-
},
|
|
5987
|
-
{
|
|
5988
|
-
id: "speech-2.6-hd",
|
|
5989
|
-
label: "Speech 2.6 HD",
|
|
5990
|
-
provider: "minimax",
|
|
5991
|
-
description: "High-definition MiniMax voice model."
|
|
5992
|
-
},
|
|
5993
|
-
{
|
|
5994
|
-
id: "speech-2.6-turbo",
|
|
5995
|
-
label: "Speech 2.6 Turbo",
|
|
5996
|
-
provider: "minimax",
|
|
5997
|
-
description: "Lower-latency Speech 2.6 variant."
|
|
5998
|
-
},
|
|
5999
|
-
{
|
|
6000
|
-
id: "speech-02-hd",
|
|
6001
|
-
label: "Speech 02 HD",
|
|
6002
|
-
provider: "minimax",
|
|
6003
|
-
description: "Previous-generation HD model."
|
|
6004
|
-
},
|
|
6005
|
-
{
|
|
6006
|
-
id: "speech-02-turbo",
|
|
6007
|
-
label: "Speech 02 Turbo",
|
|
6008
|
-
provider: "minimax",
|
|
6009
|
-
description: "Lower-latency Speech 02 variant."
|
|
6010
|
-
},
|
|
6011
|
-
{
|
|
6012
|
-
id: "speech-01-hd",
|
|
6013
|
-
label: "Speech 01 HD",
|
|
6014
|
-
provider: "minimax",
|
|
6015
|
-
description: "Legacy HD model."
|
|
6016
|
-
},
|
|
6017
|
-
{
|
|
6018
|
-
id: "speech-01-turbo",
|
|
6019
|
-
label: "Speech 01 Turbo",
|
|
6020
|
-
provider: "minimax",
|
|
6021
|
-
description: "Legacy fast model."
|
|
6022
|
+
description: "Lower latency. Auto-falls back to Speech 2.6 Turbo."
|
|
6022
6023
|
}
|
|
6023
6024
|
];
|
|
6024
6025
|
TTS_VOICE_OPTIONS = [
|
|
@@ -6302,6 +6303,7 @@ __export(dist_exports, {
|
|
|
6302
6303
|
DEFAULT_STT_MODEL_BY_PROVIDER: () => DEFAULT_STT_MODEL_BY_PROVIDER,
|
|
6303
6304
|
DEFAULT_TTS_MODEL: () => DEFAULT_TTS_MODEL,
|
|
6304
6305
|
DEFAULT_TTS_SPEED: () => DEFAULT_TTS_SPEED,
|
|
6306
|
+
DEFAULT_TTS_TURBO_MODEL: () => DEFAULT_TTS_TURBO_MODEL,
|
|
6305
6307
|
DEFAULT_TTS_VOICE_ID: () => DEFAULT_TTS_VOICE_ID,
|
|
6306
6308
|
DEFAULT_UI_VARIANT: () => DEFAULT_UI_VARIANT,
|
|
6307
6309
|
EMAIL_FROM_IDENTITIES: () => EMAIL_FROM_IDENTITIES,
|
|
@@ -6311,6 +6313,7 @@ __export(dist_exports, {
|
|
|
6311
6313
|
FLOW_SERVER_TYPES: () => FLOW_SERVER_TYPES,
|
|
6312
6314
|
GROUPABLE_CALL_TYPES: () => GROUPABLE_CALL_TYPES,
|
|
6313
6315
|
GROUP_LABELS: () => GROUP_LABELS,
|
|
6316
|
+
LEGACY_TTS_MODEL_MAP: () => LEGACY_TTS_MODEL_MAP,
|
|
6314
6317
|
LEGACY_TTS_VOICE_ID_MAP: () => LEGACY_TTS_VOICE_ID_MAP,
|
|
6315
6318
|
MAX_CREDIT_TOPUP_CENTS: () => MAX_CREDIT_TOPUP_CENTS,
|
|
6316
6319
|
MAX_DEEPLINK_LENGTH: () => MAX_DEEPLINK_LENGTH,
|
|
@@ -6344,6 +6347,7 @@ __export(dist_exports, {
|
|
|
6344
6347
|
TEST_USER_ENV_KEYS: () => TEST_USER_ENV_KEYS,
|
|
6345
6348
|
TRANSIENT_CONTROL_ROLES: () => TRANSIENT_CONTROL_ROLES,
|
|
6346
6349
|
TRIAL_DURATION_DAYS: () => TRIAL_DURATION_DAYS,
|
|
6350
|
+
TTS_FALLBACK_MODEL_MAP: () => TTS_FALLBACK_MODEL_MAP,
|
|
6347
6351
|
TTS_MODEL_OPTIONS: () => TTS_MODEL_OPTIONS,
|
|
6348
6352
|
TTS_VOICE_OPTIONS: () => TTS_VOICE_OPTIONS,
|
|
6349
6353
|
UI_VARIANTS: () => UI_VARIANTS,
|
|
@@ -6446,6 +6450,7 @@ __export(dist_exports, {
|
|
|
6446
6450
|
mobilePlatformFromMedium: () => mobilePlatformFromMedium,
|
|
6447
6451
|
mobileWanderTraceEnabled: () => mobileWanderTraceEnabled,
|
|
6448
6452
|
normalizeControlName: () => normalizeControlName,
|
|
6453
|
+
normalizeTtsModel: () => normalizeTtsModel,
|
|
6449
6454
|
normalizeTtsVoiceId: () => normalizeTtsVoiceId,
|
|
6450
6455
|
otherMobilePlatform: () => otherMobilePlatform,
|
|
6451
6456
|
parenDepthDelta: () => parenDepthDelta,
|
|
@@ -6464,6 +6469,7 @@ __export(dist_exports, {
|
|
|
6464
6469
|
resolveMobileStepValuePlaceholders: () => resolveMobileStepValuePlaceholders,
|
|
6465
6470
|
resolvePlatformLocatorBundle: () => resolvePlatformLocatorBundle,
|
|
6466
6471
|
resolveSttModelForProvider: () => resolveSttModelForProvider,
|
|
6472
|
+
resolveTtsModelChain: () => resolveTtsModelChain,
|
|
6467
6473
|
safeCurrentUrl: () => safeCurrentUrl,
|
|
6468
6474
|
safeOrigin: () => safeOrigin,
|
|
6469
6475
|
sanitizeSttErrorMessage: () => sanitizeSttErrorMessage,
|
|
@@ -6486,6 +6492,7 @@ __export(dist_exports, {
|
|
|
6486
6492
|
summarizeMobileReadiness: () => summarizeMobileReadiness,
|
|
6487
6493
|
summarizeRunHealth: () => summarizeRunHealth,
|
|
6488
6494
|
toDbPricingPlan: () => toDbPricingPlan,
|
|
6495
|
+
ttsFallbackModel: () => ttsFallbackModel,
|
|
6489
6496
|
validateAssertionStrength: () => validateAssertionStrength
|
|
6490
6497
|
});
|
|
6491
6498
|
var init_dist = __esm({
|
|
@@ -353012,7 +353019,7 @@ var require_package4 = __commonJS({
|
|
|
353012
353019
|
"package.json"(exports, module) {
|
|
353013
353020
|
module.exports = {
|
|
353014
353021
|
name: "@validate.qa/runner",
|
|
353015
|
-
version: "1.0.
|
|
353022
|
+
version: "1.0.74",
|
|
353016
353023
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
353017
353024
|
bin: {
|
|
353018
353025
|
"validate-runner": "dist/cli.js"
|
|
@@ -354676,6 +354683,47 @@ async function clickCssInCurrentContext(sessionPath, css) {
|
|
|
354676
354683
|
body: "{}"
|
|
354677
354684
|
});
|
|
354678
354685
|
}
|
|
354686
|
+
async function clickByVisibleTextInCurrentContext(sessionPath, text) {
|
|
354687
|
+
const script = `
|
|
354688
|
+
var t = String(arguments[0]).trim().replace(/\\s+/g, ' ');
|
|
354689
|
+
var norm = function (e) { return (e.textContent || e.value || '').trim().replace(/\\s+/g, ' '); };
|
|
354690
|
+
var vis = function (e) { var r = e.getBoundingClientRect(); return r.width > 0 && r.height > 0; };
|
|
354691
|
+
var all = Array.prototype.slice.call(document.body.getElementsByTagName('*'));
|
|
354692
|
+
var match = all.filter(function (e) { return norm(e) === t && vis(e); });
|
|
354693
|
+
// Deepest-only: drop any match that contains another match (same text).
|
|
354694
|
+
var deepest = match.filter(function (e) {
|
|
354695
|
+
return !match.some(function (o) { return o !== e && e.contains(o); });
|
|
354696
|
+
});
|
|
354697
|
+
if (deepest.length !== 1) { return deepest.length; }
|
|
354698
|
+
var el = deepest[0];
|
|
354699
|
+
var interactive = function (e) {
|
|
354700
|
+
var tag = e.tagName;
|
|
354701
|
+
if (tag === 'BUTTON' || tag === 'A' || tag === 'INPUT' || tag === 'SELECT' || tag === 'SUMMARY') { return true; }
|
|
354702
|
+
var role = e.getAttribute && e.getAttribute('role');
|
|
354703
|
+
if (role === 'button' || role === 'tab' || role === 'link' || role === 'menuitem' || role === 'option') { return true; }
|
|
354704
|
+
if (e.hasAttribute && (e.hasAttribute('onclick') || (e.hasAttribute('tabindex') && e.getAttribute('tabindex') !== '-1'))) { return true; }
|
|
354705
|
+
try { return window.getComputedStyle(e).cursor === 'pointer'; } catch (err) { return false; }
|
|
354706
|
+
};
|
|
354707
|
+
// Prefer the outermost interactive ancestor that wraps ONLY this text.
|
|
354708
|
+
var target = el;
|
|
354709
|
+
var hop = el;
|
|
354710
|
+
for (var i = 0; i < 5 && hop && hop !== document.body; i++) {
|
|
354711
|
+
if (norm(hop) !== t) { break; }
|
|
354712
|
+
if (interactive(hop)) { target = hop; }
|
|
354713
|
+
hop = hop.parentElement;
|
|
354714
|
+
}
|
|
354715
|
+
target.click();
|
|
354716
|
+
return 1;
|
|
354717
|
+
`;
|
|
354718
|
+
const result = await appiumRequest(`${sessionPath}/execute/sync`, {
|
|
354719
|
+
method: "POST",
|
|
354720
|
+
body: JSON.stringify({ script, args: [text] })
|
|
354721
|
+
});
|
|
354722
|
+
const count = readValue(result);
|
|
354723
|
+
if (count !== 1) {
|
|
354724
|
+
throw new Error(`WEBVIEW text click found ${count === 0 || count === void 0 ? "no" : String(count)} actionable match(es) for "${text}"`);
|
|
354725
|
+
}
|
|
354726
|
+
}
|
|
354679
354727
|
async function typeCssInCurrentContext(sessionPath, css, text) {
|
|
354680
354728
|
const elResult = await appiumRequest(`${sessionPath}/element`, {
|
|
354681
354729
|
method: "POST",
|
|
@@ -357037,6 +357085,48 @@ function stepWebViewContext(step) {
|
|
|
357037
357085
|
const meta = step.metadata && typeof step.metadata === "object" ? step.metadata : null;
|
|
357038
357086
|
return typeof meta?.webviewContext === "string" && meta.webviewContext.trim() ? meta.webviewContext.trim() : null;
|
|
357039
357087
|
}
|
|
357088
|
+
var DOM_ID_RE = /^[A-Za-z_][\w-]*$/;
|
|
357089
|
+
var SYSTEM_RESOURCE_ID_RE = /^(?:android|com\.android\.|com\.google\.android\.)/i;
|
|
357090
|
+
function webViewCssFallbackFromNativeStep(step) {
|
|
357091
|
+
if (stepWebViewCss(step)) return null;
|
|
357092
|
+
const target = step.target;
|
|
357093
|
+
if (!target) return null;
|
|
357094
|
+
const bundle = target.locatorBundle;
|
|
357095
|
+
const resourceId = typeof bundle?.resourceId === "string" ? bundle.resourceId.trim() : "";
|
|
357096
|
+
const accessibilityId = typeof bundle?.accessibilityId === "string" ? bundle.accessibilityId.trim() : "";
|
|
357097
|
+
const primary = typeof target.primary === "string" ? target.primary.trim() : "";
|
|
357098
|
+
const fromResourceId = (raw) => {
|
|
357099
|
+
if (!raw) return null;
|
|
357100
|
+
if (raw.includes(":id/")) {
|
|
357101
|
+
if (SYSTEM_RESOURCE_ID_RE.test(raw)) return null;
|
|
357102
|
+
const local = raw.slice(raw.indexOf(":id/") + 4);
|
|
357103
|
+
return DOM_ID_RE.test(local) ? local : null;
|
|
357104
|
+
}
|
|
357105
|
+
return DOM_ID_RE.test(raw) ? raw : null;
|
|
357106
|
+
};
|
|
357107
|
+
const domId = fromResourceId(resourceId) ?? fromResourceId(accessibilityId) ?? (primary && !/[=~/[\].\s]/.test(primary) && DOM_ID_RE.test(primary) ? primary : null);
|
|
357108
|
+
return domId ? `#${domId}` : null;
|
|
357109
|
+
}
|
|
357110
|
+
function webViewTextFallbackFromNativeStep(step) {
|
|
357111
|
+
if (stepWebViewCss(step)) return null;
|
|
357112
|
+
if (step.action !== "tap") return null;
|
|
357113
|
+
const target = step.target;
|
|
357114
|
+
if (!target) return null;
|
|
357115
|
+
const bundle = target.locatorBundle;
|
|
357116
|
+
const text = typeof bundle?.text === "string" && bundle.text.trim() ? bundle.text.trim() : typeof bundle?.contentDesc === "string" && bundle.contentDesc.trim() ? bundle.contentDesc.trim() : typeof target.primary === "string" && target.primary.trim() && !/[=~/[\]]|:id\//.test(target.primary) ? target.primary.trim() : "";
|
|
357117
|
+
const normalized = text.replace(/\s+/g, " ");
|
|
357118
|
+
if (!normalized || normalized.length > 40 || /[\n\r]/.test(text)) return null;
|
|
357119
|
+
if (SYSTEM_PERMISSION_BUTTON_RE.test(text)) return null;
|
|
357120
|
+
return normalized;
|
|
357121
|
+
}
|
|
357122
|
+
async function hasLiveWebViewContext(sessionId) {
|
|
357123
|
+
try {
|
|
357124
|
+
const contexts = await listAppContexts(`/session/${sessionId}`);
|
|
357125
|
+
return contexts.some((c) => /^WEBVIEW/i.test(c));
|
|
357126
|
+
} catch {
|
|
357127
|
+
return false;
|
|
357128
|
+
}
|
|
357129
|
+
}
|
|
357040
357130
|
var CONTAINER_ONLY_ENTRY_ID_RE = /(?:^|[:/])id\/(webview|content|decor|decor_content_parent|action_bar_root|statusBarBackground|navigationBarBackground)$|^(?:main-content|root|app|app-root|__next|react-root|application)$/i;
|
|
357041
357131
|
function isContainerOnlyEntryTarget(target) {
|
|
357042
357132
|
const primary = target?.primary?.trim() ?? "";
|
|
@@ -357124,6 +357214,7 @@ function isWebViewHydrationTransient(err) {
|
|
|
357124
357214
|
return /no such element|NoSuchElement|element not interactable|stale element|WEBVIEW step requires|Empty page dictionary|not connectable|no WEBVIEW context|chrome not reachable/i.test(message);
|
|
357125
357215
|
}
|
|
357126
357216
|
var WEBVIEW_HYDRATION_TIMEOUT_MS = Number(process.env.VALIDATEQA_WEBVIEW_HYDRATION_TIMEOUT_MS) || 3e4;
|
|
357217
|
+
var PERMISSION_DIALOG_TIMEOUT_MS = Number(process.env.VALIDATEQA_PERMISSION_DIALOG_TIMEOUT_MS) || 3e3;
|
|
357127
357218
|
async function executeInWebViewContextWithHydrationRetry(sessionId, step, css, body, timeoutMs) {
|
|
357128
357219
|
const deadline = Date.now() + Math.max(1e3, timeoutMs);
|
|
357129
357220
|
for (; ; ) {
|
|
@@ -357136,6 +357227,17 @@ async function executeInWebViewContextWithHydrationRetry(sessionId, step, css, b
|
|
|
357136
357227
|
}
|
|
357137
357228
|
}
|
|
357138
357229
|
}
|
|
357230
|
+
var SYSTEM_PERMISSION_BUTTON_RE = /com\.android\.permissioncontroller:id\/permission_(allow|deny)|com\.android\.packageinstaller:id\/permission_|:id\/permission_allow_(foreground_only|one_time)_button|^(?:com\.apple\.springboard|SpringBoard)/i;
|
|
357231
|
+
function isSystemPermissionButtonTarget(target) {
|
|
357232
|
+
if (!target) return false;
|
|
357233
|
+
const bundle = target.locatorBundle;
|
|
357234
|
+
const candidates = [
|
|
357235
|
+
target.primary,
|
|
357236
|
+
typeof bundle?.resourceId === "string" ? bundle.resourceId : "",
|
|
357237
|
+
typeof bundle?.xpath === "string" ? bundle.xpath : ""
|
|
357238
|
+
];
|
|
357239
|
+
return candidates.some((c) => typeof c === "string" && SYSTEM_PERMISSION_BUTTON_RE.test(c));
|
|
357240
|
+
}
|
|
357139
357241
|
async function executeTap(sessionId, step, platform3, isCrossPlatform = false) {
|
|
357140
357242
|
const webCss = stepWebViewCss(step);
|
|
357141
357243
|
if (webCss) {
|
|
@@ -357148,10 +357250,11 @@ async function executeTap(sessionId, step, platform3, isCrossPlatform = false) {
|
|
|
357148
357250
|
);
|
|
357149
357251
|
return;
|
|
357150
357252
|
}
|
|
357253
|
+
const isPermissionTap = isSystemPermissionButtonTarget(step.target);
|
|
357151
357254
|
const bounds = boundsForRun(step, platform3, isCrossPlatform);
|
|
357152
357255
|
if (step.target) {
|
|
357153
357256
|
try {
|
|
357154
|
-
const elementId = await findElement(sessionId, step.target, readStepTimeout(step), {
|
|
357257
|
+
const elementId = await findElement(sessionId, step.target, isPermissionTap ? Math.min(readStepTimeout(step) ?? DEFAULT_STEP_TIMEOUT_MS, PERMISSION_DIALOG_TIMEOUT_MS) : readStepTimeout(step), {
|
|
357155
357258
|
platform: platform3,
|
|
357156
357259
|
recordedBounds: bounds
|
|
357157
357260
|
});
|
|
@@ -357161,7 +357264,23 @@ async function executeTap(sessionId, step, platform3, isCrossPlatform = false) {
|
|
|
357161
357264
|
});
|
|
357162
357265
|
return;
|
|
357163
357266
|
} catch (error2) {
|
|
357164
|
-
if (isTransportError(error2) ||
|
|
357267
|
+
if (isTransportError(error2) || error2 instanceof MobileAmbiguousMatchError) throw error2;
|
|
357268
|
+
if (isPermissionTap) return;
|
|
357269
|
+
const cssFallback = webViewCssFallbackFromNativeStep(step);
|
|
357270
|
+
const textFallback = cssFallback ? null : webViewTextFallbackFromNativeStep(step);
|
|
357271
|
+
if ((cssFallback || textFallback) && await hasLiveWebViewContext(sessionId)) {
|
|
357272
|
+
try {
|
|
357273
|
+
if (cssFallback) {
|
|
357274
|
+
await executeInWebViewContext(sessionId, step, cssFallback, (sessionPath, css) => clickCssInCurrentContext(sessionPath, css));
|
|
357275
|
+
} else if (textFallback) {
|
|
357276
|
+
await executeInWebViewContext(sessionId, step, textFallback, (sessionPath) => clickByVisibleTextInCurrentContext(sessionPath, textFallback));
|
|
357277
|
+
}
|
|
357278
|
+
return;
|
|
357279
|
+
} catch (webErr) {
|
|
357280
|
+
if (isTransportError(webErr)) throw webErr;
|
|
357281
|
+
}
|
|
357282
|
+
}
|
|
357283
|
+
if (!bounds || buildLocatorStrategies(step.target, platform3).length > 0) throw error2;
|
|
357165
357284
|
}
|
|
357166
357285
|
}
|
|
357167
357286
|
if (!bounds) {
|
|
@@ -357219,6 +357338,18 @@ async function executeType(sessionId, step, context) {
|
|
|
357219
357338
|
return;
|
|
357220
357339
|
} catch (error2) {
|
|
357221
357340
|
if (isTransportError(error2)) throw error2;
|
|
357341
|
+
if (!focusEstablished && !keyboardSubmitOnly) {
|
|
357342
|
+
const cssFallback = webViewCssFallbackFromNativeStep(step);
|
|
357343
|
+
if (cssFallback && await hasLiveWebViewContext(sessionId)) {
|
|
357344
|
+
try {
|
|
357345
|
+
context.log?.(` Native locate failed; retrying as WebView ${cssFallback} (hybrid mirror fallback)`);
|
|
357346
|
+
await executeInWebViewContext(sessionId, step, cssFallback, (sessionPath2, css) => typeCssInCurrentContext(sessionPath2, css, value));
|
|
357347
|
+
return;
|
|
357348
|
+
} catch (webErr) {
|
|
357349
|
+
if (isTransportError(webErr)) throw webErr;
|
|
357350
|
+
}
|
|
357351
|
+
}
|
|
357352
|
+
}
|
|
357222
357353
|
if (context.platform === "ANDROID" && focusEstablished) {
|
|
357223
357354
|
try {
|
|
357224
357355
|
context.log?.(" Appium active-element send failed; using Android focused-keyboard input fallback");
|
|
@@ -363053,7 +363184,11 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
363053
363184
|
surface: "mobile",
|
|
363054
363185
|
mobilePlatform: target.platform,
|
|
363055
363186
|
...assignedDeviceId ? { deviceId: assignedDeviceId } : {},
|
|
363056
|
-
...leasedDeviceName ? { deviceName: leasedDeviceName } : {}
|
|
363187
|
+
...leasedDeviceName ? { deviceName: leasedDeviceName } : {},
|
|
363188
|
+
// A mobile AI heal spends its first phase in model analysis with NO
|
|
363189
|
+
// Appium session, so the dashboard mirror is legitimately dark — say
|
|
363190
|
+
// so instead of leaving an eternal "mirror starting" placeholder.
|
|
363191
|
+
...runMode === "heal" ? { note: "Heal Phase 1 \u2014 reproducing & analyzing the failure (the device mirror attaches when the live phase starts)" } : {}
|
|
363057
363192
|
});
|
|
363058
363193
|
requestLiveBrowserHeartbeat(true);
|
|
363059
363194
|
const mobileSteps = run2.steps ?? [];
|
|
@@ -363153,7 +363288,11 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
363153
363288
|
// Surface heal liveness: a Phase-1/Phase-2 heal can legitimately run
|
|
363154
363289
|
// for many minutes of model calls with zero device traffic, which
|
|
363155
363290
|
// previously printed nothing between the start banner and the verdict.
|
|
363156
|
-
onProgress: (message) =>
|
|
363291
|
+
onProgress: (message) => {
|
|
363292
|
+
log.info(`\u{1FA7A} ${message}`);
|
|
363293
|
+
liveBrowserHandle.patch({ note: `\u{1FA7A} ${message}`.slice(0, 180) });
|
|
363294
|
+
requestLiveBrowserHeartbeat();
|
|
363295
|
+
},
|
|
363157
363296
|
selectorRegistryContext: run2.selectorRegistryContext ?? null,
|
|
363158
363297
|
runLiveHeal: async (a) => {
|
|
363159
363298
|
let liveSession = null;
|
|
@@ -363205,6 +363344,8 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
363205
363344
|
};
|
|
363206
363345
|
}
|
|
363207
363346
|
log.info("\u{1FA7A} Phase 2 (live device) \u2014 opening the live observation session...");
|
|
363347
|
+
liveBrowserHandle.patch({ note: "Heal Phase 2 \u2014 opening the live device session (mirror attaching)" });
|
|
363348
|
+
requestLiveBrowserHeartbeat(true);
|
|
363208
363349
|
try {
|
|
363209
363350
|
await openLiveSession();
|
|
363210
363351
|
const liveDriver = {
|
package/package.json
CHANGED