@validate.qa/runner 1.0.72 → 1.0.73

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.
Files changed (3) hide show
  1. package/dist/cli.js +156 -45
  2. package/dist/cli.mjs +156 -45
  3. 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
- if (!value)
5927
+ const normalized = normalizeTtsModel(value);
5928
+ if (!normalized)
5905
5929
  return false;
5906
- return TTS_MODEL_OPTIONS.some((m) => m.id === value);
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.6-hd";
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: "Latest high-definition model with interjection tags (recommended)."
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: "Latest low-latency model with interjection tags."
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.72",
353017
+ version: "1.0.73",
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,27 @@ 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 sel = 'button, a, [role="tab"], [role="button"], [onclick], .stream-tab, .ux-interactive, input[type="submit"], input[type="button"]';
354685
+ var norm = function (e) { return (e.textContent || e.value || '').trim().replace(/\\s+/g, ' '); };
354686
+ var vis = function (e) { var r = e.getBoundingClientRect(); return r.width > 0 && r.height > 0; };
354687
+ var all = Array.prototype.slice.call(document.querySelectorAll(sel));
354688
+ var match = all.filter(function (e) { return norm(e) === t && vis(e); });
354689
+ if (match.length !== 1) { return match.length; }
354690
+ match[0].click();
354691
+ return 1;
354692
+ `;
354693
+ const result = await appiumRequest(`${sessionPath}/execute/sync`, {
354694
+ method: "POST",
354695
+ body: JSON.stringify({ script, args: [text] })
354696
+ });
354697
+ const count = readValue(result);
354698
+ if (count !== 1) {
354699
+ throw new Error(`WEBVIEW text click found ${count === 0 || count === void 0 ? "no" : String(count)} actionable match(es) for "${text}"`);
354700
+ }
354701
+ }
354674
354702
  async function typeCssInCurrentContext(sessionPath, css, text) {
354675
354703
  const elResult = await appiumRequest(`${sessionPath}/element`, {
354676
354704
  method: "POST",
@@ -357029,6 +357057,48 @@ function stepWebViewContext(step) {
357029
357057
  const meta = step.metadata && typeof step.metadata === "object" ? step.metadata : null;
357030
357058
  return typeof meta?.webviewContext === "string" && meta.webviewContext.trim() ? meta.webviewContext.trim() : null;
357031
357059
  }
357060
+ var DOM_ID_RE = /^[A-Za-z_][\w-]*$/;
357061
+ var SYSTEM_RESOURCE_ID_RE = /^(?:android|com\.android\.|com\.google\.android\.)/i;
357062
+ function webViewCssFallbackFromNativeStep(step) {
357063
+ if (stepWebViewCss(step)) return null;
357064
+ const target = step.target;
357065
+ if (!target) return null;
357066
+ const bundle = target.locatorBundle;
357067
+ const resourceId = typeof bundle?.resourceId === "string" ? bundle.resourceId.trim() : "";
357068
+ const accessibilityId = typeof bundle?.accessibilityId === "string" ? bundle.accessibilityId.trim() : "";
357069
+ const primary = typeof target.primary === "string" ? target.primary.trim() : "";
357070
+ const fromResourceId = (raw) => {
357071
+ if (!raw) return null;
357072
+ if (raw.includes(":id/")) {
357073
+ if (SYSTEM_RESOURCE_ID_RE.test(raw)) return null;
357074
+ const local = raw.slice(raw.indexOf(":id/") + 4);
357075
+ return DOM_ID_RE.test(local) ? local : null;
357076
+ }
357077
+ return DOM_ID_RE.test(raw) ? raw : null;
357078
+ };
357079
+ const domId = fromResourceId(resourceId) ?? fromResourceId(accessibilityId) ?? (primary && !/[=~/[\].\s]/.test(primary) && DOM_ID_RE.test(primary) ? primary : null);
357080
+ return domId ? `#${domId}` : null;
357081
+ }
357082
+ function webViewTextFallbackFromNativeStep(step) {
357083
+ if (stepWebViewCss(step)) return null;
357084
+ if (step.action !== "tap") return null;
357085
+ const target = step.target;
357086
+ if (!target) return null;
357087
+ const bundle = target.locatorBundle;
357088
+ 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() : "";
357089
+ const normalized = text.replace(/\s+/g, " ");
357090
+ if (!normalized || normalized.length > 40 || /[\n\r]/.test(text)) return null;
357091
+ if (SYSTEM_PERMISSION_BUTTON_RE.test(text)) return null;
357092
+ return normalized;
357093
+ }
357094
+ async function hasLiveWebViewContext(sessionId) {
357095
+ try {
357096
+ const contexts = await listAppContexts(`/session/${sessionId}`);
357097
+ return contexts.some((c) => /^WEBVIEW/i.test(c));
357098
+ } catch {
357099
+ return false;
357100
+ }
357101
+ }
357032
357102
  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
357103
  function isContainerOnlyEntryTarget(target) {
357034
357104
  const primary = target?.primary?.trim() ?? "";
@@ -357116,6 +357186,7 @@ function isWebViewHydrationTransient(err) {
357116
357186
  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
357187
  }
357118
357188
  var WEBVIEW_HYDRATION_TIMEOUT_MS = Number(process.env.VALIDATEQA_WEBVIEW_HYDRATION_TIMEOUT_MS) || 3e4;
357189
+ var PERMISSION_DIALOG_TIMEOUT_MS = Number(process.env.VALIDATEQA_PERMISSION_DIALOG_TIMEOUT_MS) || 3e3;
357119
357190
  async function executeInWebViewContextWithHydrationRetry(sessionId, step, css, body, timeoutMs) {
357120
357191
  const deadline = Date.now() + Math.max(1e3, timeoutMs);
357121
357192
  for (; ; ) {
@@ -357128,6 +357199,17 @@ async function executeInWebViewContextWithHydrationRetry(sessionId, step, css, b
357128
357199
  }
357129
357200
  }
357130
357201
  }
357202
+ 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;
357203
+ function isSystemPermissionButtonTarget(target) {
357204
+ if (!target) return false;
357205
+ const bundle = target.locatorBundle;
357206
+ const candidates = [
357207
+ target.primary,
357208
+ typeof bundle?.resourceId === "string" ? bundle.resourceId : "",
357209
+ typeof bundle?.xpath === "string" ? bundle.xpath : ""
357210
+ ];
357211
+ return candidates.some((c) => typeof c === "string" && SYSTEM_PERMISSION_BUTTON_RE.test(c));
357212
+ }
357131
357213
  async function executeTap(sessionId, step, platform3, isCrossPlatform = false) {
357132
357214
  const webCss = stepWebViewCss(step);
357133
357215
  if (webCss) {
@@ -357140,10 +357222,11 @@ async function executeTap(sessionId, step, platform3, isCrossPlatform = false) {
357140
357222
  );
357141
357223
  return;
357142
357224
  }
357225
+ const isPermissionTap = isSystemPermissionButtonTarget(step.target);
357143
357226
  const bounds = boundsForRun(step, platform3, isCrossPlatform);
357144
357227
  if (step.target) {
357145
357228
  try {
357146
- const elementId = await findElement(sessionId, step.target, readStepTimeout(step), {
357229
+ const elementId = await findElement(sessionId, step.target, isPermissionTap ? Math.min(readStepTimeout(step) ?? DEFAULT_STEP_TIMEOUT_MS, PERMISSION_DIALOG_TIMEOUT_MS) : readStepTimeout(step), {
357147
357230
  platform: platform3,
357148
357231
  recordedBounds: bounds
357149
357232
  });
@@ -357153,7 +357236,23 @@ async function executeTap(sessionId, step, platform3, isCrossPlatform = false) {
357153
357236
  });
357154
357237
  return;
357155
357238
  } catch (error2) {
357156
- if (isTransportError(error2) || !bounds || buildLocatorStrategies(step.target, platform3).length > 0) throw error2;
357239
+ if (isTransportError(error2) || error2 instanceof MobileAmbiguousMatchError) throw error2;
357240
+ if (isPermissionTap) return;
357241
+ const cssFallback = webViewCssFallbackFromNativeStep(step);
357242
+ const textFallback = cssFallback ? null : webViewTextFallbackFromNativeStep(step);
357243
+ if ((cssFallback || textFallback) && await hasLiveWebViewContext(sessionId)) {
357244
+ try {
357245
+ if (cssFallback) {
357246
+ await executeInWebViewContext(sessionId, step, cssFallback, (sessionPath, css) => clickCssInCurrentContext(sessionPath, css));
357247
+ } else if (textFallback) {
357248
+ await executeInWebViewContext(sessionId, step, textFallback, (sessionPath) => clickByVisibleTextInCurrentContext(sessionPath, textFallback));
357249
+ }
357250
+ return;
357251
+ } catch (webErr) {
357252
+ if (isTransportError(webErr)) throw webErr;
357253
+ }
357254
+ }
357255
+ if (!bounds || buildLocatorStrategies(step.target, platform3).length > 0) throw error2;
357157
357256
  }
357158
357257
  }
357159
357258
  if (!bounds) {
@@ -357211,6 +357310,18 @@ async function executeType(sessionId, step, context) {
357211
357310
  return;
357212
357311
  } catch (error2) {
357213
357312
  if (isTransportError(error2)) throw error2;
357313
+ if (!focusEstablished && !keyboardSubmitOnly) {
357314
+ const cssFallback = webViewCssFallbackFromNativeStep(step);
357315
+ if (cssFallback && await hasLiveWebViewContext(sessionId)) {
357316
+ try {
357317
+ context.log?.(` Native locate failed; retrying as WebView ${cssFallback} (hybrid mirror fallback)`);
357318
+ await executeInWebViewContext(sessionId, step, cssFallback, (sessionPath2, css) => typeCssInCurrentContext(sessionPath2, css, value));
357319
+ return;
357320
+ } catch (webErr) {
357321
+ if (isTransportError(webErr)) throw webErr;
357322
+ }
357323
+ }
357324
+ }
357214
357325
  if (context.platform === "ANDROID" && focusEstablished) {
357215
357326
  try {
357216
357327
  context.log?.(" Appium active-element send failed; using Android focused-keyboard input fallback");
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
- if (!value)
5932
+ const normalized = normalizeTtsModel(value);
5933
+ if (!normalized)
5910
5934
  return false;
5911
- return TTS_MODEL_OPTIONS.some((m) => m.id === value);
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.6-hd";
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: "Latest high-definition model with interjection tags (recommended)."
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: "Latest low-latency model with interjection tags."
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.72",
353022
+ version: "1.0.73",
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,27 @@ 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 sel = 'button, a, [role="tab"], [role="button"], [onclick], .stream-tab, .ux-interactive, input[type="submit"], input[type="button"]';
354690
+ var norm = function (e) { return (e.textContent || e.value || '').trim().replace(/\\s+/g, ' '); };
354691
+ var vis = function (e) { var r = e.getBoundingClientRect(); return r.width > 0 && r.height > 0; };
354692
+ var all = Array.prototype.slice.call(document.querySelectorAll(sel));
354693
+ var match = all.filter(function (e) { return norm(e) === t && vis(e); });
354694
+ if (match.length !== 1) { return match.length; }
354695
+ match[0].click();
354696
+ return 1;
354697
+ `;
354698
+ const result = await appiumRequest(`${sessionPath}/execute/sync`, {
354699
+ method: "POST",
354700
+ body: JSON.stringify({ script, args: [text] })
354701
+ });
354702
+ const count = readValue(result);
354703
+ if (count !== 1) {
354704
+ throw new Error(`WEBVIEW text click found ${count === 0 || count === void 0 ? "no" : String(count)} actionable match(es) for "${text}"`);
354705
+ }
354706
+ }
354679
354707
  async function typeCssInCurrentContext(sessionPath, css, text) {
354680
354708
  const elResult = await appiumRequest(`${sessionPath}/element`, {
354681
354709
  method: "POST",
@@ -357037,6 +357065,48 @@ function stepWebViewContext(step) {
357037
357065
  const meta = step.metadata && typeof step.metadata === "object" ? step.metadata : null;
357038
357066
  return typeof meta?.webviewContext === "string" && meta.webviewContext.trim() ? meta.webviewContext.trim() : null;
357039
357067
  }
357068
+ var DOM_ID_RE = /^[A-Za-z_][\w-]*$/;
357069
+ var SYSTEM_RESOURCE_ID_RE = /^(?:android|com\.android\.|com\.google\.android\.)/i;
357070
+ function webViewCssFallbackFromNativeStep(step) {
357071
+ if (stepWebViewCss(step)) return null;
357072
+ const target = step.target;
357073
+ if (!target) return null;
357074
+ const bundle = target.locatorBundle;
357075
+ const resourceId = typeof bundle?.resourceId === "string" ? bundle.resourceId.trim() : "";
357076
+ const accessibilityId = typeof bundle?.accessibilityId === "string" ? bundle.accessibilityId.trim() : "";
357077
+ const primary = typeof target.primary === "string" ? target.primary.trim() : "";
357078
+ const fromResourceId = (raw) => {
357079
+ if (!raw) return null;
357080
+ if (raw.includes(":id/")) {
357081
+ if (SYSTEM_RESOURCE_ID_RE.test(raw)) return null;
357082
+ const local = raw.slice(raw.indexOf(":id/") + 4);
357083
+ return DOM_ID_RE.test(local) ? local : null;
357084
+ }
357085
+ return DOM_ID_RE.test(raw) ? raw : null;
357086
+ };
357087
+ const domId = fromResourceId(resourceId) ?? fromResourceId(accessibilityId) ?? (primary && !/[=~/[\].\s]/.test(primary) && DOM_ID_RE.test(primary) ? primary : null);
357088
+ return domId ? `#${domId}` : null;
357089
+ }
357090
+ function webViewTextFallbackFromNativeStep(step) {
357091
+ if (stepWebViewCss(step)) return null;
357092
+ if (step.action !== "tap") return null;
357093
+ const target = step.target;
357094
+ if (!target) return null;
357095
+ const bundle = target.locatorBundle;
357096
+ 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() : "";
357097
+ const normalized = text.replace(/\s+/g, " ");
357098
+ if (!normalized || normalized.length > 40 || /[\n\r]/.test(text)) return null;
357099
+ if (SYSTEM_PERMISSION_BUTTON_RE.test(text)) return null;
357100
+ return normalized;
357101
+ }
357102
+ async function hasLiveWebViewContext(sessionId) {
357103
+ try {
357104
+ const contexts = await listAppContexts(`/session/${sessionId}`);
357105
+ return contexts.some((c) => /^WEBVIEW/i.test(c));
357106
+ } catch {
357107
+ return false;
357108
+ }
357109
+ }
357040
357110
  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
357111
  function isContainerOnlyEntryTarget(target) {
357042
357112
  const primary = target?.primary?.trim() ?? "";
@@ -357124,6 +357194,7 @@ function isWebViewHydrationTransient(err) {
357124
357194
  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
357195
  }
357126
357196
  var WEBVIEW_HYDRATION_TIMEOUT_MS = Number(process.env.VALIDATEQA_WEBVIEW_HYDRATION_TIMEOUT_MS) || 3e4;
357197
+ var PERMISSION_DIALOG_TIMEOUT_MS = Number(process.env.VALIDATEQA_PERMISSION_DIALOG_TIMEOUT_MS) || 3e3;
357127
357198
  async function executeInWebViewContextWithHydrationRetry(sessionId, step, css, body, timeoutMs) {
357128
357199
  const deadline = Date.now() + Math.max(1e3, timeoutMs);
357129
357200
  for (; ; ) {
@@ -357136,6 +357207,17 @@ async function executeInWebViewContextWithHydrationRetry(sessionId, step, css, b
357136
357207
  }
357137
357208
  }
357138
357209
  }
357210
+ 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;
357211
+ function isSystemPermissionButtonTarget(target) {
357212
+ if (!target) return false;
357213
+ const bundle = target.locatorBundle;
357214
+ const candidates = [
357215
+ target.primary,
357216
+ typeof bundle?.resourceId === "string" ? bundle.resourceId : "",
357217
+ typeof bundle?.xpath === "string" ? bundle.xpath : ""
357218
+ ];
357219
+ return candidates.some((c) => typeof c === "string" && SYSTEM_PERMISSION_BUTTON_RE.test(c));
357220
+ }
357139
357221
  async function executeTap(sessionId, step, platform3, isCrossPlatform = false) {
357140
357222
  const webCss = stepWebViewCss(step);
357141
357223
  if (webCss) {
@@ -357148,10 +357230,11 @@ async function executeTap(sessionId, step, platform3, isCrossPlatform = false) {
357148
357230
  );
357149
357231
  return;
357150
357232
  }
357233
+ const isPermissionTap = isSystemPermissionButtonTarget(step.target);
357151
357234
  const bounds = boundsForRun(step, platform3, isCrossPlatform);
357152
357235
  if (step.target) {
357153
357236
  try {
357154
- const elementId = await findElement(sessionId, step.target, readStepTimeout(step), {
357237
+ const elementId = await findElement(sessionId, step.target, isPermissionTap ? Math.min(readStepTimeout(step) ?? DEFAULT_STEP_TIMEOUT_MS, PERMISSION_DIALOG_TIMEOUT_MS) : readStepTimeout(step), {
357155
357238
  platform: platform3,
357156
357239
  recordedBounds: bounds
357157
357240
  });
@@ -357161,7 +357244,23 @@ async function executeTap(sessionId, step, platform3, isCrossPlatform = false) {
357161
357244
  });
357162
357245
  return;
357163
357246
  } catch (error2) {
357164
- if (isTransportError(error2) || !bounds || buildLocatorStrategies(step.target, platform3).length > 0) throw error2;
357247
+ if (isTransportError(error2) || error2 instanceof MobileAmbiguousMatchError) throw error2;
357248
+ if (isPermissionTap) return;
357249
+ const cssFallback = webViewCssFallbackFromNativeStep(step);
357250
+ const textFallback = cssFallback ? null : webViewTextFallbackFromNativeStep(step);
357251
+ if ((cssFallback || textFallback) && await hasLiveWebViewContext(sessionId)) {
357252
+ try {
357253
+ if (cssFallback) {
357254
+ await executeInWebViewContext(sessionId, step, cssFallback, (sessionPath, css) => clickCssInCurrentContext(sessionPath, css));
357255
+ } else if (textFallback) {
357256
+ await executeInWebViewContext(sessionId, step, textFallback, (sessionPath) => clickByVisibleTextInCurrentContext(sessionPath, textFallback));
357257
+ }
357258
+ return;
357259
+ } catch (webErr) {
357260
+ if (isTransportError(webErr)) throw webErr;
357261
+ }
357262
+ }
357263
+ if (!bounds || buildLocatorStrategies(step.target, platform3).length > 0) throw error2;
357165
357264
  }
357166
357265
  }
357167
357266
  if (!bounds) {
@@ -357219,6 +357318,18 @@ async function executeType(sessionId, step, context) {
357219
357318
  return;
357220
357319
  } catch (error2) {
357221
357320
  if (isTransportError(error2)) throw error2;
357321
+ if (!focusEstablished && !keyboardSubmitOnly) {
357322
+ const cssFallback = webViewCssFallbackFromNativeStep(step);
357323
+ if (cssFallback && await hasLiveWebViewContext(sessionId)) {
357324
+ try {
357325
+ context.log?.(` Native locate failed; retrying as WebView ${cssFallback} (hybrid mirror fallback)`);
357326
+ await executeInWebViewContext(sessionId, step, cssFallback, (sessionPath2, css) => typeCssInCurrentContext(sessionPath2, css, value));
357327
+ return;
357328
+ } catch (webErr) {
357329
+ if (isTransportError(webErr)) throw webErr;
357330
+ }
357331
+ }
357332
+ }
357222
357333
  if (context.platform === "ANDROID" && focusEstablished) {
357223
357334
  try {
357224
357335
  context.log?.(" Appium active-element send failed; using Android focused-keyboard input fallback");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@validate.qa/runner",
3
- "version": "1.0.72",
3
+ "version": "1.0.73",
4
4
  "description": "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
5
5
  "bin": {
6
6
  "validate-runner": "dist/cli.js"