@validate.qa/runner 1.0.37 → 1.0.38
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 +497 -28
- package/dist/cli.mjs +497 -28
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -241,6 +241,9 @@ var require_ai_provider = __commonJS({
|
|
|
241
241
|
exports2.getAvailableModels = getAvailableModels;
|
|
242
242
|
exports2.getAvailableOrchestratorModels = getAvailableOrchestratorModels;
|
|
243
243
|
exports2.isAvailableModelId = isAvailableModelId;
|
|
244
|
+
exports2.resolveWhisperProvider = resolveWhisperProvider;
|
|
245
|
+
exports2.isWhisperConfigured = isWhisperConfigured;
|
|
246
|
+
exports2.getWhisperModel = getWhisperModel;
|
|
244
247
|
exports2.getOpenAIWhisperClient = getOpenAIWhisperClient;
|
|
245
248
|
exports2.resetAIClient = resetAIClient;
|
|
246
249
|
exports2.getServerProxiedAIClient = getServerProxiedAIClient;
|
|
@@ -570,20 +573,70 @@ var require_ai_provider = __commonJS({
|
|
|
570
573
|
function isAvailableModelId(modelId) {
|
|
571
574
|
return getAvailableModels().some((model) => model.id === modelId);
|
|
572
575
|
}
|
|
576
|
+
var GROQ_API_BASE_DEFAULT = "https://api.groq.com/openai/v1";
|
|
577
|
+
var GROQ_WHISPER_DEFAULT = "whisper-large-v3-turbo";
|
|
578
|
+
var OPENAI_WHISPER_DEFAULT = "whisper-1";
|
|
579
|
+
function isUsableApiKey(value) {
|
|
580
|
+
if (!value)
|
|
581
|
+
return false;
|
|
582
|
+
const trimmed = value.trim();
|
|
583
|
+
if (!trimmed)
|
|
584
|
+
return false;
|
|
585
|
+
if (/your[-_]?.*(key|api|secret)/i.test(trimmed))
|
|
586
|
+
return false;
|
|
587
|
+
if (trimmed === "sk-..." || trimmed === "gsk_...")
|
|
588
|
+
return false;
|
|
589
|
+
return true;
|
|
590
|
+
}
|
|
591
|
+
function resolveWhisperProvider() {
|
|
592
|
+
const forced = (process.env.STT_PROVIDER ?? process.env.WHISPER_PROVIDER ?? "").trim().toLowerCase();
|
|
593
|
+
const hasGroq = isUsableApiKey(process.env.GROQ_API_KEY);
|
|
594
|
+
const hasOpenAI = isUsableApiKey(process.env.OPENAI_API_KEY);
|
|
595
|
+
if (forced === "groq")
|
|
596
|
+
return hasGroq ? "groq" : null;
|
|
597
|
+
if (forced === "openai")
|
|
598
|
+
return hasOpenAI ? "openai" : null;
|
|
599
|
+
if (hasGroq)
|
|
600
|
+
return "groq";
|
|
601
|
+
if (hasOpenAI)
|
|
602
|
+
return "openai";
|
|
603
|
+
return null;
|
|
604
|
+
}
|
|
605
|
+
function isWhisperConfigured() {
|
|
606
|
+
return resolveWhisperProvider() !== null;
|
|
607
|
+
}
|
|
608
|
+
function getWhisperModel(provider) {
|
|
609
|
+
const p = provider ?? resolveWhisperProvider();
|
|
610
|
+
if (p === "groq") {
|
|
611
|
+
return process.env.GROQ_WHISPER_MODEL?.trim() || process.env.OPENAI_WHISPER_MODEL?.trim() || GROQ_WHISPER_DEFAULT;
|
|
612
|
+
}
|
|
613
|
+
return process.env.OPENAI_WHISPER_MODEL?.trim() || OPENAI_WHISPER_DEFAULT;
|
|
614
|
+
}
|
|
573
615
|
var _whisperClient = null;
|
|
616
|
+
var _whisperClientProvider = null;
|
|
574
617
|
function getOpenAIWhisperClient() {
|
|
575
|
-
|
|
618
|
+
const provider = resolveWhisperProvider();
|
|
619
|
+
if (!provider) {
|
|
620
|
+
throw new Error("Speech transcription is not configured (set GROQ_API_KEY or OPENAI_API_KEY)");
|
|
621
|
+
}
|
|
622
|
+
if (_whisperClient && _whisperClientProvider === provider) {
|
|
576
623
|
return _whisperClient;
|
|
577
|
-
|
|
578
|
-
if (
|
|
579
|
-
|
|
580
|
-
|
|
624
|
+
}
|
|
625
|
+
if (provider === "groq") {
|
|
626
|
+
const apiKey = process.env.GROQ_API_KEY;
|
|
627
|
+
const baseURL = process.env.GROQ_API_BASE?.trim() || GROQ_API_BASE_DEFAULT;
|
|
628
|
+
_whisperClient = new openai_1.default({ apiKey, baseURL });
|
|
629
|
+
} else {
|
|
630
|
+
_whisperClient = new openai_1.default({ apiKey: process.env.OPENAI_API_KEY });
|
|
631
|
+
}
|
|
632
|
+
_whisperClientProvider = provider;
|
|
581
633
|
return _whisperClient;
|
|
582
634
|
}
|
|
583
635
|
var _serverProxiedClient = null;
|
|
584
636
|
function resetAIClient() {
|
|
585
637
|
_clientCache.clear();
|
|
586
638
|
_whisperClient = null;
|
|
639
|
+
_whisperClientProvider = null;
|
|
587
640
|
_serverProxiedClient = null;
|
|
588
641
|
}
|
|
589
642
|
function buildServerProxiedClient() {
|
|
@@ -1682,6 +1735,7 @@ var init_constants = __esm({
|
|
|
1682
1735
|
"DATABASE_URL",
|
|
1683
1736
|
"JWT_SECRET",
|
|
1684
1737
|
"OPENAI_API_KEY",
|
|
1738
|
+
"GROQ_API_KEY",
|
|
1685
1739
|
"GEMINI_API_KEY",
|
|
1686
1740
|
"XAI_API_KEY",
|
|
1687
1741
|
"GROK_API_KEY",
|
|
@@ -3248,8 +3302,10 @@ var init_audit_pricing = __esm({
|
|
|
3248
3302
|
"MiniMax-M2.7-highspeed": { input: 0.6, output: 2.4 },
|
|
3249
3303
|
"MiniMax-M2": { input: 0.3, output: 1.2 },
|
|
3250
3304
|
"deepseek-v4-flash": { input: 0.14, output: 0.28 },
|
|
3251
|
-
// Whisper bills per-minute, not per-token
|
|
3305
|
+
// Whisper bills per-minute, not per-token (OpenAI + Groq)
|
|
3252
3306
|
"whisper-1": null,
|
|
3307
|
+
"whisper-large-v3": null,
|
|
3308
|
+
"whisper-large-v3-turbo": null,
|
|
3253
3309
|
// Historical / deprecated (kept so old audit rows still show a cost).
|
|
3254
3310
|
// Deprecated Grok variants priced at the legacy authoritative Grok rate
|
|
3255
3311
|
// ($0.20/$0.80) those rows were generated under.
|
|
@@ -3860,6 +3916,43 @@ function assertedText(stmt) {
|
|
|
3860
3916
|
}
|
|
3861
3917
|
return out.join(" ").toLowerCase();
|
|
3862
3918
|
}
|
|
3919
|
+
function clickedAdditiveAffordance(lines) {
|
|
3920
|
+
const nameRe = /name\s*:\s*(?:['"`]([^'"`]+)['"`]|\/([^/]+)\/)/g;
|
|
3921
|
+
for (const ln of lines) {
|
|
3922
|
+
if (!/\.click\s*\(/.test(ln))
|
|
3923
|
+
continue;
|
|
3924
|
+
nameRe.lastIndex = 0;
|
|
3925
|
+
let m;
|
|
3926
|
+
while (m = nameRe.exec(ln)) {
|
|
3927
|
+
const name = (m[1] ?? m[2]).toLowerCase();
|
|
3928
|
+
if (EXCLUDE_AFFORDANCE.test(name))
|
|
3929
|
+
continue;
|
|
3930
|
+
if (ADDITIVE_AFFORDANCE.test(name))
|
|
3931
|
+
return true;
|
|
3932
|
+
}
|
|
3933
|
+
}
|
|
3934
|
+
return false;
|
|
3935
|
+
}
|
|
3936
|
+
function matcherText(stmt) {
|
|
3937
|
+
const out = [];
|
|
3938
|
+
const re = /\.(?:toContainText|toHaveText)\s*\(\s*(?:['"`]([^'"`]+)['"`]|\/([^/]+)\/)/g;
|
|
3939
|
+
let m;
|
|
3940
|
+
while (m = re.exec(stmt))
|
|
3941
|
+
out.push(m[1] ?? m[2]);
|
|
3942
|
+
return out.join(" ").toLowerCase();
|
|
3943
|
+
}
|
|
3944
|
+
function isEmptyStateProof(stmt) {
|
|
3945
|
+
if (NEGATED_ASSERT.test(stmt))
|
|
3946
|
+
return false;
|
|
3947
|
+
if (/\.toHaveCount\s*\(\s*0\s*\)/.test(stmt))
|
|
3948
|
+
return true;
|
|
3949
|
+
if (/\.toBe\s*\(\s*0\s*\)/.test(stmt) && !isChromeTarget(stmt))
|
|
3950
|
+
return true;
|
|
3951
|
+
if (VISIBILITY_MATCHER.test(stmt) || TEXT_CONTENT_MATCHER.test(stmt)) {
|
|
3952
|
+
return EMPTY_STATE_TEXT.test(`${assertedText(stmt)} ${matcherText(stmt)}`);
|
|
3953
|
+
}
|
|
3954
|
+
return false;
|
|
3955
|
+
}
|
|
3863
3956
|
function hasAttributeTransition(stmts) {
|
|
3864
3957
|
const byAttr = /* @__PURE__ */ new Map();
|
|
3865
3958
|
for (const { stmt } of stmts) {
|
|
@@ -3966,6 +4059,21 @@ function validateAssertionStrength(code, opts = {}) {
|
|
|
3966
4059
|
const interacted = mutated || navClicks > 0;
|
|
3967
4060
|
navOnly = !mutated && navClicks > 0;
|
|
3968
4061
|
const reloaded = RELOAD.test(body);
|
|
4062
|
+
if (opts.flagContradictedAdditions && mutated && clickedAdditiveAffordance(interactionStmts)) {
|
|
4063
|
+
const empties = stmts.filter(({ stmt }) => isEmptyStateProof(stmt));
|
|
4064
|
+
if (empties.length > 0) {
|
|
4065
|
+
const others = stmts.filter(({ stmt }) => !isEmptyStateProof(stmt));
|
|
4066
|
+
const provesSomethingElse = detectOutcomeProof(others, mutated, false, clickedControlNames(interactionStmts), typedValues(interactionStmts));
|
|
4067
|
+
if (!provesSomethingElse) {
|
|
4068
|
+
return {
|
|
4069
|
+
ok: false,
|
|
4070
|
+
severity: "block",
|
|
4071
|
+
reason: "additive action (add/create/save/favourite) proves no positive outcome \u2014 its only asserted result is that the target collection is still empty; hold @needs-review, do not ship as a passing regression",
|
|
4072
|
+
signals: base2({ interacted, mutated, terminal })
|
|
4073
|
+
};
|
|
4074
|
+
}
|
|
4075
|
+
}
|
|
4076
|
+
}
|
|
3969
4077
|
const hasOutcomeProof = detectOutcomeProof(stmts, mutated, reloaded, clickedControlNames(interactionStmts), typedValues(interactionStmts));
|
|
3970
4078
|
const signals = base2({ interacted, mutated, navOnly, hasOutcomeProof, terminal });
|
|
3971
4079
|
if (hasOutcomeProof) {
|
|
@@ -4020,7 +4128,7 @@ function validateAssertionStrength(code, opts = {}) {
|
|
|
4020
4128
|
signals
|
|
4021
4129
|
};
|
|
4022
4130
|
}
|
|
4023
|
-
var ERROR_BANNER, OUTCOME_ALWAYS, FORM_INPUT, ANY_CLICK, NAV_LOCATOR, URL_PROOF, RELOAD, EXPECT_STMT, BLOCK_SCAFFOLD, LOCATOR_BINDING, STATE_MATCHER, CHROME_LOCATOR, CONTENT_ROLE, GET_BY_TEXT, HAS_TEXT_FILTER, OUTCOME_TEXT, MESSAGE_SURFACE, TEXT_CONTENT_MATCHER, VISIBILITY_MATCHER, AUTH_START, AUTH_END, LOGIN_CALL_LINE, NEGATED_ASSERT, TYPED_INPUT, TITLE_LINE, ROLE_NAME_RE, INTERACT_VERB, ROLE_SIG_RE, TRANSIENT_CONTROL_ROLES;
|
|
4131
|
+
var ERROR_BANNER, OUTCOME_ALWAYS, FORM_INPUT, ANY_CLICK, NAV_LOCATOR, URL_PROOF, RELOAD, EXPECT_STMT, BLOCK_SCAFFOLD, LOCATOR_BINDING, STATE_MATCHER, CHROME_LOCATOR, CONTENT_ROLE, GET_BY_TEXT, HAS_TEXT_FILTER, OUTCOME_TEXT, MESSAGE_SURFACE, TEXT_CONTENT_MATCHER, VISIBILITY_MATCHER, ADDITIVE_AFFORDANCE, EXCLUDE_AFFORDANCE, EMPTY_STATE_TEXT, AUTH_START, AUTH_END, LOGIN_CALL_LINE, NEGATED_ASSERT, TYPED_INPUT, TITLE_LINE, ROLE_NAME_RE, INTERACT_VERB, ROLE_SIG_RE, TRANSIENT_CONTROL_ROLES;
|
|
4024
4132
|
var init_assertion_strength = __esm({
|
|
4025
4133
|
"../shared/dist/assertion-strength.js"() {
|
|
4026
4134
|
"use strict";
|
|
@@ -4064,6 +4172,9 @@ var init_assertion_strength = __esm({
|
|
|
4064
4172
|
MESSAGE_SURFACE = /getByText\s*\(|\.filter\s*\(\s*\{\s*hasText\s*:|getByRole\s*\(\s*['"`](?:alert|alertdialog|status|heading|dialog)['"`]/i;
|
|
4065
4173
|
TEXT_CONTENT_MATCHER = /\.(toHaveText|toContainText)\s*\(/;
|
|
4066
4174
|
VISIBILITY_MATCHER = /\.toBeVisible\s*\(/;
|
|
4175
|
+
ADDITIVE_AFFORDANCE = /\b(add|adds|added|create|creates|save|saves|favou?rite|favou?rites|invite|invites|upload|uploads|insert|append|bookmark|follow|subscribe|post|posts|new)\b/i;
|
|
4176
|
+
EXCLUDE_AFFORDANCE = /\b(filter|filters|search|searches|sort|view|views|tab|tabs|window|windows|toggle|expand|collapse|column|columns|zoom|layout)\b/i;
|
|
4177
|
+
EMPTY_STATE_TEXT = /\bno\b[\s\S]{0,24}?\b(yet|found|results?|items?|favou?rites?|entries|records?|matches|posts?|comments?)\b|nothing (?:here|yet|to show|found)|\bempty\b|\(\s*0\s*\)|\b0\s+(?:favou?rites?|results?|items?|entries|records?|matches|posts?|comments?)\b/i;
|
|
4067
4178
|
AUTH_START = /^\s*\/\/\s*>>>\s*VALIDATE_QA_AUTH/;
|
|
4068
4179
|
AUTH_END = /^\s*\/\/\s*<<<\s*VALIDATE_QA_AUTH/;
|
|
4069
4180
|
LOGIN_CALL_LINE = /^\s*await\s+__vqaLogin\s*\(\s*page\s*\)\s*;?\s*$/;
|
|
@@ -4086,6 +4197,24 @@ var init_assertion_strength = __esm({
|
|
|
4086
4197
|
});
|
|
4087
4198
|
|
|
4088
4199
|
// ../shared/dist/run-classification.js
|
|
4200
|
+
function pickLatestHealthRun(runs) {
|
|
4201
|
+
for (const run2 of runs) {
|
|
4202
|
+
if (run2.mode === "auth-setup")
|
|
4203
|
+
continue;
|
|
4204
|
+
if (run2.status === "PASSED" || run2.status === "FAILED" || run2.status === "ERROR") {
|
|
4205
|
+
return run2;
|
|
4206
|
+
}
|
|
4207
|
+
}
|
|
4208
|
+
return null;
|
|
4209
|
+
}
|
|
4210
|
+
function pickDisplayRun(runs) {
|
|
4211
|
+
if (runs.length === 0)
|
|
4212
|
+
return null;
|
|
4213
|
+
const latest = runs[0];
|
|
4214
|
+
if (latest.status === "PENDING" || latest.status === "RUNNING")
|
|
4215
|
+
return latest;
|
|
4216
|
+
return pickLatestHealthRun(runs) ?? latest;
|
|
4217
|
+
}
|
|
4089
4218
|
function isEnvironmentalError(run2) {
|
|
4090
4219
|
if (!run2)
|
|
4091
4220
|
return false;
|
|
@@ -4547,6 +4676,262 @@ var init_origin_domain = __esm({
|
|
|
4547
4676
|
}
|
|
4548
4677
|
});
|
|
4549
4678
|
|
|
4679
|
+
// ../shared/dist/voice-config.js
|
|
4680
|
+
function normalizeTtsVoiceId(voiceId) {
|
|
4681
|
+
if (!voiceId)
|
|
4682
|
+
return null;
|
|
4683
|
+
const trimmed = voiceId.trim();
|
|
4684
|
+
if (!trimmed)
|
|
4685
|
+
return null;
|
|
4686
|
+
return LEGACY_TTS_VOICE_ID_MAP[trimmed] ?? trimmed;
|
|
4687
|
+
}
|
|
4688
|
+
function sttModelsForProvider(provider) {
|
|
4689
|
+
if (provider === "openai") {
|
|
4690
|
+
return STT_MODEL_OPTIONS.filter((m) => m.provider === "openai");
|
|
4691
|
+
}
|
|
4692
|
+
if (provider === "groq") {
|
|
4693
|
+
return STT_MODEL_OPTIONS.filter((m) => m.provider === "groq");
|
|
4694
|
+
}
|
|
4695
|
+
return STT_MODEL_OPTIONS;
|
|
4696
|
+
}
|
|
4697
|
+
function isValidSttProvider(value) {
|
|
4698
|
+
return value === "groq" || value === "openai";
|
|
4699
|
+
}
|
|
4700
|
+
function isSttModelForProvider(modelId, provider) {
|
|
4701
|
+
if (!modelId)
|
|
4702
|
+
return false;
|
|
4703
|
+
return STT_MODEL_OPTIONS.some((m) => m.id === modelId && m.provider === provider);
|
|
4704
|
+
}
|
|
4705
|
+
function resolveSttModelForProvider(provider, configuredModel) {
|
|
4706
|
+
if (configuredModel && isSttModelForProvider(configuredModel, provider)) {
|
|
4707
|
+
return configuredModel;
|
|
4708
|
+
}
|
|
4709
|
+
return DEFAULT_STT_MODEL_BY_PROVIDER[provider];
|
|
4710
|
+
}
|
|
4711
|
+
function sanitizeSttErrorMessage(raw) {
|
|
4712
|
+
if (!raw)
|
|
4713
|
+
return "Speech transcription failed.";
|
|
4714
|
+
let msg = String(raw).replace(/\b(sk-proj-|sk-|gsk_|sk-svcacct-)[A-Za-z0-9_-]{8,}/gi, "$1***").replace(/Incorrect API key provided:\s*\S+/gi, "Incorrect API key provided").replace(/You can find your API key at https?:\/\/\S+/gi, "").replace(/\s+/g, " ").trim();
|
|
4715
|
+
if (/401|incorrect api key|invalid.?api.?key|authentication|unauthorized/i.test(msg)) {
|
|
4716
|
+
return "Speech transcription failed: the speech API key was rejected. Prefer GROQ_API_KEY (same as Copilot voice) or a valid OPENAI_API_KEY on the org server.";
|
|
4717
|
+
}
|
|
4718
|
+
if (/429|rate.?limit|too many requests/i.test(msg)) {
|
|
4719
|
+
return "Speech transcription failed: the speech provider rate-limited the request. Try again in a moment.";
|
|
4720
|
+
}
|
|
4721
|
+
if (/not configured|GROQ_API_KEY or OPENAI_API_KEY/i.test(msg)) {
|
|
4722
|
+
return "Speech transcription is not configured. Set GROQ_API_KEY (preferred) or OPENAI_API_KEY on the org server.";
|
|
4723
|
+
}
|
|
4724
|
+
if (msg.length > 280)
|
|
4725
|
+
msg = `${msg.slice(0, 277)}\u2026`;
|
|
4726
|
+
return msg || "Speech transcription failed.";
|
|
4727
|
+
}
|
|
4728
|
+
function isValidTtsVoiceId(value) {
|
|
4729
|
+
const normalized = normalizeTtsVoiceId(value);
|
|
4730
|
+
if (!normalized)
|
|
4731
|
+
return false;
|
|
4732
|
+
return TTS_VOICE_OPTIONS.some((v) => v.id === normalized);
|
|
4733
|
+
}
|
|
4734
|
+
function isValidTtsModel(value) {
|
|
4735
|
+
if (!value)
|
|
4736
|
+
return false;
|
|
4737
|
+
return TTS_MODEL_OPTIONS.some((m) => m.id === value);
|
|
4738
|
+
}
|
|
4739
|
+
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;
|
|
4740
|
+
var init_voice_config = __esm({
|
|
4741
|
+
"../shared/dist/voice-config.js"() {
|
|
4742
|
+
"use strict";
|
|
4743
|
+
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.";
|
|
4744
|
+
DEFAULT_TTS_MODEL = "speech-2.6-hd";
|
|
4745
|
+
DEFAULT_TTS_VOICE_ID = "English_Graceful_Lady";
|
|
4746
|
+
DEFAULT_TTS_SPEED = 1;
|
|
4747
|
+
LEGACY_TTS_VOICE_ID_MAP = {
|
|
4748
|
+
English_LivelyGirl: "English_PlayfulGirl",
|
|
4749
|
+
English_Insightful_Man: "English_Insightful_Speaker",
|
|
4750
|
+
English_Deep_Voice_Man: "English_ManWithDeepVoice",
|
|
4751
|
+
English_CasualMan: "English_Jovialman"
|
|
4752
|
+
};
|
|
4753
|
+
DEFAULT_STT_MODEL_BY_PROVIDER = {
|
|
4754
|
+
groq: "whisper-large-v3-turbo",
|
|
4755
|
+
openai: "whisper-1"
|
|
4756
|
+
};
|
|
4757
|
+
STT_PROVIDER_OPTIONS = [
|
|
4758
|
+
{
|
|
4759
|
+
id: "auto",
|
|
4760
|
+
label: "Auto (prefer Groq)",
|
|
4761
|
+
description: "Use Groq when GROQ_API_KEY is set, otherwise OpenAI Whisper."
|
|
4762
|
+
},
|
|
4763
|
+
{
|
|
4764
|
+
id: "groq",
|
|
4765
|
+
label: "Groq Whisper",
|
|
4766
|
+
description: "Free-tier OpenAI-compatible Whisper via Groq. Requires GROQ_API_KEY on org servers."
|
|
4767
|
+
},
|
|
4768
|
+
{
|
|
4769
|
+
id: "openai",
|
|
4770
|
+
label: "OpenAI Whisper",
|
|
4771
|
+
description: "OpenAI whisper-1 API. Requires OPENAI_API_KEY on org servers."
|
|
4772
|
+
}
|
|
4773
|
+
];
|
|
4774
|
+
STT_MODEL_OPTIONS = [
|
|
4775
|
+
{
|
|
4776
|
+
id: "whisper-large-v3-turbo",
|
|
4777
|
+
label: "Whisper Large V3 Turbo",
|
|
4778
|
+
provider: "groq",
|
|
4779
|
+
description: "Fast, accurate default for Groq (recommended)."
|
|
4780
|
+
},
|
|
4781
|
+
{
|
|
4782
|
+
id: "whisper-large-v3",
|
|
4783
|
+
label: "Whisper Large V3",
|
|
4784
|
+
provider: "groq",
|
|
4785
|
+
description: "Full-size Groq Whisper model \u2014 higher quality, slower."
|
|
4786
|
+
},
|
|
4787
|
+
{
|
|
4788
|
+
id: "distil-whisper-large-v3-en",
|
|
4789
|
+
label: "Distil Whisper Large V3 (EN)",
|
|
4790
|
+
provider: "groq",
|
|
4791
|
+
description: "English-optimized distilled model \u2014 fastest Groq option."
|
|
4792
|
+
},
|
|
4793
|
+
{
|
|
4794
|
+
id: "whisper-1",
|
|
4795
|
+
label: "Whisper 1",
|
|
4796
|
+
provider: "openai",
|
|
4797
|
+
description: "OpenAI hosted Whisper model."
|
|
4798
|
+
}
|
|
4799
|
+
];
|
|
4800
|
+
TTS_MODEL_OPTIONS = [
|
|
4801
|
+
{
|
|
4802
|
+
id: "speech-2.8-hd",
|
|
4803
|
+
label: "Speech 2.8 HD",
|
|
4804
|
+
provider: "minimax",
|
|
4805
|
+
description: "Latest high-definition model with interjection tags (recommended)."
|
|
4806
|
+
},
|
|
4807
|
+
{
|
|
4808
|
+
id: "speech-2.8-turbo",
|
|
4809
|
+
label: "Speech 2.8 Turbo",
|
|
4810
|
+
provider: "minimax",
|
|
4811
|
+
description: "Latest low-latency model with interjection tags."
|
|
4812
|
+
},
|
|
4813
|
+
{
|
|
4814
|
+
id: "speech-2.6-hd",
|
|
4815
|
+
label: "Speech 2.6 HD",
|
|
4816
|
+
provider: "minimax",
|
|
4817
|
+
description: "High-definition MiniMax voice model."
|
|
4818
|
+
},
|
|
4819
|
+
{
|
|
4820
|
+
id: "speech-2.6-turbo",
|
|
4821
|
+
label: "Speech 2.6 Turbo",
|
|
4822
|
+
provider: "minimax",
|
|
4823
|
+
description: "Lower-latency Speech 2.6 variant."
|
|
4824
|
+
},
|
|
4825
|
+
{
|
|
4826
|
+
id: "speech-02-hd",
|
|
4827
|
+
label: "Speech 02 HD",
|
|
4828
|
+
provider: "minimax",
|
|
4829
|
+
description: "Previous-generation HD model."
|
|
4830
|
+
},
|
|
4831
|
+
{
|
|
4832
|
+
id: "speech-02-turbo",
|
|
4833
|
+
label: "Speech 02 Turbo",
|
|
4834
|
+
provider: "minimax",
|
|
4835
|
+
description: "Lower-latency Speech 02 variant."
|
|
4836
|
+
},
|
|
4837
|
+
{
|
|
4838
|
+
id: "speech-01-hd",
|
|
4839
|
+
label: "Speech 01 HD",
|
|
4840
|
+
provider: "minimax",
|
|
4841
|
+
description: "Legacy HD model."
|
|
4842
|
+
},
|
|
4843
|
+
{
|
|
4844
|
+
id: "speech-01-turbo",
|
|
4845
|
+
label: "Speech 01 Turbo",
|
|
4846
|
+
provider: "minimax",
|
|
4847
|
+
description: "Legacy fast model."
|
|
4848
|
+
}
|
|
4849
|
+
];
|
|
4850
|
+
TTS_VOICE_OPTIONS = [
|
|
4851
|
+
{
|
|
4852
|
+
id: "English_Graceful_Lady",
|
|
4853
|
+
label: "Graceful Lady",
|
|
4854
|
+
description: "Warm, polished professional tone.",
|
|
4855
|
+
gender: "female",
|
|
4856
|
+
accent: "English",
|
|
4857
|
+
sampleText: VOICE_PREVIEW_SAMPLE
|
|
4858
|
+
},
|
|
4859
|
+
{
|
|
4860
|
+
id: "English_ConfidentWoman",
|
|
4861
|
+
label: "Confident Woman",
|
|
4862
|
+
description: "Assured and direct \u2014 good for technical guidance.",
|
|
4863
|
+
gender: "female",
|
|
4864
|
+
accent: "English",
|
|
4865
|
+
sampleText: VOICE_PREVIEW_SAMPLE
|
|
4866
|
+
},
|
|
4867
|
+
{
|
|
4868
|
+
id: "English_SereneWoman",
|
|
4869
|
+
label: "Serene Woman",
|
|
4870
|
+
description: "Calm and measured delivery.",
|
|
4871
|
+
gender: "female",
|
|
4872
|
+
accent: "English",
|
|
4873
|
+
sampleText: VOICE_PREVIEW_SAMPLE
|
|
4874
|
+
},
|
|
4875
|
+
{
|
|
4876
|
+
id: "English_PlayfulGirl",
|
|
4877
|
+
label: "Playful Girl",
|
|
4878
|
+
description: "Upbeat and energetic.",
|
|
4879
|
+
gender: "female",
|
|
4880
|
+
accent: "English",
|
|
4881
|
+
sampleText: VOICE_PREVIEW_SAMPLE
|
|
4882
|
+
},
|
|
4883
|
+
{
|
|
4884
|
+
id: "English_CalmWoman",
|
|
4885
|
+
label: "Calm Woman",
|
|
4886
|
+
description: "Soft, reassuring cadence.",
|
|
4887
|
+
gender: "female",
|
|
4888
|
+
accent: "English",
|
|
4889
|
+
sampleText: VOICE_PREVIEW_SAMPLE
|
|
4890
|
+
},
|
|
4891
|
+
{
|
|
4892
|
+
id: "English_Insightful_Speaker",
|
|
4893
|
+
label: "Insightful Speaker",
|
|
4894
|
+
description: "Clear analyst-style narration.",
|
|
4895
|
+
gender: "male",
|
|
4896
|
+
accent: "English",
|
|
4897
|
+
sampleText: VOICE_PREVIEW_SAMPLE
|
|
4898
|
+
},
|
|
4899
|
+
{
|
|
4900
|
+
id: "English_ManWithDeepVoice",
|
|
4901
|
+
label: "Deep Voice Man",
|
|
4902
|
+
description: "Rich, authoritative baritone.",
|
|
4903
|
+
gender: "male",
|
|
4904
|
+
accent: "English",
|
|
4905
|
+
sampleText: VOICE_PREVIEW_SAMPLE
|
|
4906
|
+
},
|
|
4907
|
+
{
|
|
4908
|
+
id: "English_Jovialman",
|
|
4909
|
+
label: "Jovial Man",
|
|
4910
|
+
description: "Relaxed conversational style.",
|
|
4911
|
+
gender: "male",
|
|
4912
|
+
accent: "English",
|
|
4913
|
+
sampleText: VOICE_PREVIEW_SAMPLE
|
|
4914
|
+
},
|
|
4915
|
+
{
|
|
4916
|
+
id: "English_PatientMan",
|
|
4917
|
+
label: "Patient Man",
|
|
4918
|
+
description: "Steady, tutorial-friendly pacing.",
|
|
4919
|
+
gender: "male",
|
|
4920
|
+
accent: "English",
|
|
4921
|
+
sampleText: VOICE_PREVIEW_SAMPLE
|
|
4922
|
+
},
|
|
4923
|
+
{
|
|
4924
|
+
id: "English_FriendlyPerson",
|
|
4925
|
+
label: "Friendly Person",
|
|
4926
|
+
description: "Neutral, approachable assistant voice.",
|
|
4927
|
+
gender: "neutral",
|
|
4928
|
+
accent: "English",
|
|
4929
|
+
sampleText: VOICE_PREVIEW_SAMPLE
|
|
4930
|
+
}
|
|
4931
|
+
];
|
|
4932
|
+
}
|
|
4933
|
+
});
|
|
4934
|
+
|
|
4550
4935
|
// ../shared/dist/index.js
|
|
4551
4936
|
var dist_exports = {};
|
|
4552
4937
|
__export(dist_exports, {
|
|
@@ -4561,8 +4946,13 @@ __export(dist_exports, {
|
|
|
4561
4946
|
CREDIT_PRICES_CENTS: () => CREDIT_PRICES_CENTS,
|
|
4562
4947
|
DANGEROUS_DEEPLINK_SCHEMES: () => DANGEROUS_DEEPLINK_SCHEMES,
|
|
4563
4948
|
DB_PRICING_PLAN_MAP: () => DB_PRICING_PLAN_MAP,
|
|
4949
|
+
DEFAULT_STT_MODEL_BY_PROVIDER: () => DEFAULT_STT_MODEL_BY_PROVIDER,
|
|
4950
|
+
DEFAULT_TTS_MODEL: () => DEFAULT_TTS_MODEL,
|
|
4951
|
+
DEFAULT_TTS_SPEED: () => DEFAULT_TTS_SPEED,
|
|
4952
|
+
DEFAULT_TTS_VOICE_ID: () => DEFAULT_TTS_VOICE_ID,
|
|
4564
4953
|
DEFAULT_UI_VARIANT: () => DEFAULT_UI_VARIANT,
|
|
4565
4954
|
ENVIRONMENTAL_ERROR_PREFIXES: () => ENVIRONMENTAL_ERROR_PREFIXES,
|
|
4955
|
+
LEGACY_TTS_VOICE_ID_MAP: () => LEGACY_TTS_VOICE_ID_MAP,
|
|
4566
4956
|
MAX_CREDIT_TOPUP_CENTS: () => MAX_CREDIT_TOPUP_CENTS,
|
|
4567
4957
|
MAX_DEEPLINK_LENGTH: () => MAX_DEEPLINK_LENGTH,
|
|
4568
4958
|
MIN_CREDIT_TOPUP_CENTS: () => MIN_CREDIT_TOPUP_CENTS,
|
|
@@ -4584,10 +4974,15 @@ __export(dist_exports, {
|
|
|
4584
4974
|
PLATFORM_ADMIN_EMAIL: () => PLATFORM_ADMIN_EMAIL,
|
|
4585
4975
|
SELECTOR_PRIORITIES: () => SELECTOR_PRIORITIES,
|
|
4586
4976
|
SHARED_PRICING_PLAN_MAP: () => SHARED_PRICING_PLAN_MAP,
|
|
4977
|
+
STT_MODEL_OPTIONS: () => STT_MODEL_OPTIONS,
|
|
4978
|
+
STT_PROVIDER_OPTIONS: () => STT_PROVIDER_OPTIONS,
|
|
4587
4979
|
SUPPORT_EMAIL: () => SUPPORT_EMAIL,
|
|
4588
4980
|
TRANSIENT_CONTROL_ROLES: () => TRANSIENT_CONTROL_ROLES,
|
|
4589
4981
|
TRIAL_DURATION_DAYS: () => TRIAL_DURATION_DAYS,
|
|
4982
|
+
TTS_MODEL_OPTIONS: () => TTS_MODEL_OPTIONS,
|
|
4983
|
+
TTS_VOICE_OPTIONS: () => TTS_VOICE_OPTIONS,
|
|
4590
4984
|
UI_VARIANTS: () => UI_VARIANTS,
|
|
4985
|
+
VOICE_PREVIEW_SAMPLE: () => VOICE_PREVIEW_SAMPLE,
|
|
4591
4986
|
accumulateStatements: () => accumulateStatements,
|
|
4592
4987
|
aiCostToCreditCents: () => aiCostToCreditCents,
|
|
4593
4988
|
analyzeMobileSelectorQuality: () => analyzeMobileSelectorQuality,
|
|
@@ -4650,16 +5045,23 @@ __export(dist_exports, {
|
|
|
4650
5045
|
isRunnableStep: () => isRunnableStep,
|
|
4651
5046
|
isSafeDeepLink: () => isSafeDeepLink,
|
|
4652
5047
|
isSameRegistrableDomainHost: () => isSameRegistrableDomainHost,
|
|
5048
|
+
isSttModelForProvider: () => isSttModelForProvider,
|
|
4653
5049
|
isTestReadyForPlatform: () => isTestReadyForPlatform,
|
|
4654
5050
|
isTrialExpired: () => isTrialExpired,
|
|
4655
5051
|
isUIVariant: () => isUIVariant,
|
|
4656
5052
|
isValidMobileAppId: () => isValidMobileAppId,
|
|
5053
|
+
isValidSttProvider: () => isValidSttProvider,
|
|
5054
|
+
isValidTtsModel: () => isValidTtsModel,
|
|
5055
|
+
isValidTtsVoiceId: () => isValidTtsVoiceId,
|
|
4657
5056
|
mediumForMobilePlatform: () => mediumForMobilePlatform,
|
|
4658
5057
|
mobileCredentialPlaceholder: () => mobileCredentialPlaceholder,
|
|
4659
5058
|
mobilePlatformFromMedium: () => mobilePlatformFromMedium,
|
|
4660
5059
|
normalizeControlName: () => normalizeControlName,
|
|
5060
|
+
normalizeTtsVoiceId: () => normalizeTtsVoiceId,
|
|
4661
5061
|
otherMobilePlatform: () => otherMobilePlatform,
|
|
4662
5062
|
parenDepthDelta: () => parenDepthDelta,
|
|
5063
|
+
pickDisplayRun: () => pickDisplayRun,
|
|
5064
|
+
pickLatestHealthRun: () => pickLatestHealthRun,
|
|
4663
5065
|
placeholderizeRecordedMobileAction: () => placeholderizeRecordedMobileAction,
|
|
4664
5066
|
placeholderizeRecordedTypeValue: () => placeholderizeRecordedTypeValue,
|
|
4665
5067
|
planHasBillingPortal: () => planHasBillingPortal,
|
|
@@ -4669,7 +5071,9 @@ __export(dist_exports, {
|
|
|
4669
5071
|
resolveCrossOriginConfigOverride: () => resolveCrossOriginConfigOverride,
|
|
4670
5072
|
resolveMobileStepValuePlaceholders: () => resolveMobileStepValuePlaceholders,
|
|
4671
5073
|
resolvePlatformLocatorBundle: () => resolvePlatformLocatorBundle,
|
|
5074
|
+
resolveSttModelForProvider: () => resolveSttModelForProvider,
|
|
4672
5075
|
safeOrigin: () => safeOrigin,
|
|
5076
|
+
sanitizeSttErrorMessage: () => sanitizeSttErrorMessage,
|
|
4673
5077
|
sanitizeTestVariables: () => sanitizeTestVariables,
|
|
4674
5078
|
selectorCandidatesForStep: () => selectorCandidatesForStep,
|
|
4675
5079
|
selectorForValue: () => selectorForValue,
|
|
@@ -4679,6 +5083,7 @@ __export(dist_exports, {
|
|
|
4679
5083
|
stepsNeedingReviewForPlatform: () => stepsNeedingReviewForPlatform,
|
|
4680
5084
|
stripEnvironmentalPrefix: () => stripEnvironmentalPrefix,
|
|
4681
5085
|
stripLoginScaffolding: () => stripLoginScaffolding,
|
|
5086
|
+
sttModelsForProvider: () => sttModelsForProvider,
|
|
4682
5087
|
summarizeHealth: () => summarizeHealth,
|
|
4683
5088
|
summarizeMobileReadiness: () => summarizeMobileReadiness,
|
|
4684
5089
|
summarizeRunHealth: () => summarizeRunHealth,
|
|
@@ -4703,6 +5108,7 @@ var init_dist = __esm({
|
|
|
4703
5108
|
init_agent_env();
|
|
4704
5109
|
init_test_counting();
|
|
4705
5110
|
init_origin_domain();
|
|
5111
|
+
init_voice_config();
|
|
4706
5112
|
}
|
|
4707
5113
|
});
|
|
4708
5114
|
|
|
@@ -79521,7 +79927,7 @@ var require_agentic_discovery_prompts = __commonJS({
|
|
|
79521
79927
|
"## Test-quality bar",
|
|
79522
79928
|
"",
|
|
79523
79929
|
"- ONE `test.describe(<featureName>)` block per file with ONE `test(...)` call inside.",
|
|
79524
|
-
"- Use `await page.goto(<relativePath>)` \u2014 baseURL is set in the runner config; never hardcode the host.",
|
|
79930
|
+
"- Use `await page.goto(<relativePath>)` \u2014 baseURL is set in the runner config; never hardcode the host. If the base URL includes a path (e.g. `https://host/app`), every goto path MUST start with that path: Playwright only prefixes relative paths, so `page.goto('/')` lands on the ORIGIN ROOT outside the app \u2014 the app home is `page.goto('/app')`.",
|
|
79525
79931
|
"- Every test must contain at least one `expect(...)` assertion.",
|
|
79526
79932
|
"- **The LAST statement inside the test body MUST be `await expect(...)` that PROVES THE OUTCOME of the test's primary action.** A test ending in `.click()`, `.fill()`, `.press()`, or a `console.log` is INVALID. For an INTERACTION test, re-asserting that the control you just clicked is visible or `aria-selected=true` proves NOTHING (the toggle could be broken and the test would still pass) and is rejected by the assertion-strength gate. Instead verify the post-action state: a before\u2192after count/value change, new content/value that appeared (`getByText` of the revealed value, `toHaveText`/`toContainText`), a created item present or a removed item gone (`toHaveCount`/`not.toBeVisible`), a field value (`toHaveValue`), or a control now disabled (`toBeDisabled`). EXEMPT: a pure navigation/routing test may end on `toHaveURL` + one landmark `toBeVisible`, and the mandatory post-login wait ends on a landmark `toBeVisible` \u2014 those are correct and required. The parent validator REJECTS tests whose last non-trivial line is anything other than `await expect(...)`.",
|
|
79527
79933
|
" Prefer: `expect(locator).toBeVisible()`, `expect(page).toHaveURL(/regex/)`, `expect(locator).toHaveText(...)`.",
|
|
@@ -81757,7 +82163,7 @@ var require_agentic_discovery_prompts = __commonJS({
|
|
|
81757
82163
|
lines.push("## Test-quality bar");
|
|
81758
82164
|
lines.push("");
|
|
81759
82165
|
lines.push("- ONE `test.describe(<featureName>)` block per file with ONE `test(...)` inside.");
|
|
81760
|
-
lines.push("- Use `await page.goto(<relativePath>)` \u2014 never hardcode the host.");
|
|
82166
|
+
lines.push("- Use `await page.goto(<relativePath>)` \u2014 never hardcode the host. If the base URL includes a path (e.g. `https://host/app`), every goto path MUST start with that path (`page.goto('/')` escapes to the origin root \u2014 the app home is `page.goto('/app')`).");
|
|
81761
82167
|
lines.push("- Every test MUST include the `@generated` tag.");
|
|
81762
82168
|
lines.push("- Each test creates its OWN run-scoped data; never depend on pre-seeded data.");
|
|
81763
82169
|
lines.push("");
|
|
@@ -82311,6 +82717,21 @@ var require_agentic_discovery_prompts = __commonJS({
|
|
|
82311
82717
|
lines.push(`Use \`browser_navigate\` with the full URL (prepend ${baseUrl}). In the`);
|
|
82312
82718
|
lines.push("emitted test code keep URLs RELATIVE (`page.goto('/path')`) \u2014 the Playwright");
|
|
82313
82719
|
lines.push("config sets `baseURL`.");
|
|
82720
|
+
{
|
|
82721
|
+
const gotoBasePath = (() => {
|
|
82722
|
+
try {
|
|
82723
|
+
const p = new URL(baseUrl).pathname.replace(/\/+$/, "");
|
|
82724
|
+
return p && p !== "/" ? p : void 0;
|
|
82725
|
+
} catch {
|
|
82726
|
+
return void 0;
|
|
82727
|
+
}
|
|
82728
|
+
})();
|
|
82729
|
+
if (gotoBasePath) {
|
|
82730
|
+
lines.push(`IMPORTANT: the base URL has a PATH (\`${gotoBasePath}\`) and a leading-slash path escapes`);
|
|
82731
|
+
lines.push(`it to the origin root. Every \`page.goto\` path MUST start with \`${gotoBasePath}\` \u2014 the`);
|
|
82732
|
+
lines.push(`app home is \`page.goto('${gotoBasePath}')\`, NOT \`page.goto('/')\`.`);
|
|
82733
|
+
}
|
|
82734
|
+
}
|
|
82314
82735
|
lines.push("");
|
|
82315
82736
|
lines.push("## The scenario");
|
|
82316
82737
|
lines.push("");
|
|
@@ -82994,12 +83415,14 @@ var require_discover_generator = __commonJS({
|
|
|
82994
83415
|
exports2.detectScenarioSignals = detectScenarioSignals;
|
|
82995
83416
|
exports2.scenarioStepsCoverAuth = scenarioStepsCoverAuth;
|
|
82996
83417
|
exports2.getLoginCommands = getLoginCommands;
|
|
83418
|
+
exports2.hasConfiguredLoginCredentials = hasConfiguredLoginCredentials;
|
|
82997
83419
|
exports2.computeSkeletonTags = computeSkeletonTags;
|
|
82998
83420
|
exports2.shouldReplayAuthRecipe = shouldReplayAuthRecipe;
|
|
82999
83421
|
exports2.buildPageContext = buildPageContext;
|
|
83000
83422
|
exports2.stripTypescriptSyntax = stripTypescriptSyntax;
|
|
83001
83423
|
exports2.stripImportDeclarations = stripImportDeclarations;
|
|
83002
83424
|
exports2.stripStringsAndComments = stripStringsAndComments;
|
|
83425
|
+
exports2.gotoBasePathFromBaseUrl = gotoBasePathFromBaseUrl;
|
|
83003
83426
|
exports2.sanitizeCode = sanitizeCode;
|
|
83004
83427
|
exports2.validateTests = validateTests;
|
|
83005
83428
|
exports2.formatEvidenceForLiveGenerate = formatEvidenceForLiveGenerate;
|
|
@@ -83586,11 +84009,15 @@ var require_discover_generator = __commonJS({
|
|
|
83586
84009
|
const replayPlan = (0, auth_recipe_js_1.buildReplayPlan)(login);
|
|
83587
84010
|
return { ...login, replayPlan };
|
|
83588
84011
|
}
|
|
83589
|
-
function
|
|
84012
|
+
function hasConfiguredLoginCredentials(credentials) {
|
|
84013
|
+
return ["TEST_USER_EMAIL", "TEST_USER_PASSWORD"].some((k) => typeof credentials?.[k] === "string" && credentials[k].trim().length > 0);
|
|
84014
|
+
}
|
|
84015
|
+
function computeSkeletonTags(targetPages, pages, opts) {
|
|
83590
84016
|
const isAuthPage = (p) => p.pageType === "AUTH" || /\/(auth|login|sign-?in)\b/i.test(p.route);
|
|
84017
|
+
const siteHasAuthEvidence = pages.some(isAuthPage) || opts?.extraAuthEvidence === true;
|
|
83591
84018
|
const targetPagesInSiteMap = pages.filter((p) => targetPages.includes(p.route));
|
|
83592
84019
|
const hasAuthPage = targetPagesInSiteMap.some(isAuthPage);
|
|
83593
|
-
const hasAuthRequiredPage = targetPagesInSiteMap.some((p) => p.requiresAuth && !isAuthPage(p));
|
|
84020
|
+
const hasAuthRequiredPage = siteHasAuthEvidence && targetPagesInSiteMap.some((p) => p.requiresAuth && !isAuthPage(p));
|
|
83594
84021
|
if (hasAuthPage && !hasAuthRequiredPage)
|
|
83595
84022
|
return ["@auth", "@discovered"];
|
|
83596
84023
|
if (hasAuthRequiredPage)
|
|
@@ -83598,7 +84025,7 @@ var require_discover_generator = __commonJS({
|
|
|
83598
84025
|
return ["@discovered", "@regression"];
|
|
83599
84026
|
}
|
|
83600
84027
|
function shouldReplayAuthRecipe(targetPages, siteMapPages, recipe) {
|
|
83601
|
-
return !!recipe && computeSkeletonTags(targetPages, siteMapPages).includes("@needs-auth");
|
|
84028
|
+
return !!recipe && computeSkeletonTags(targetPages, siteMapPages, { extraAuthEvidence: true }).includes("@needs-auth");
|
|
83602
84029
|
}
|
|
83603
84030
|
function isLowConfidenceSnapshotKind(kind) {
|
|
83604
84031
|
return kind === "missing" || kind === "error" || kind === "timeout" || kind === "empty" || kind === "ai-capture_fallback";
|
|
@@ -84044,6 +84471,16 @@ ${elementContext}${formContext}${observationContext}${interactionContext}${lowCo
|
|
|
84044
84471
|
function findSensitiveLiteralLeaks(code, testVariables) {
|
|
84045
84472
|
return buildSensitiveEnvReplacements(testVariables).filter((replacement) => code.includes(replacement.value)).map((replacement) => replacement.key);
|
|
84046
84473
|
}
|
|
84474
|
+
function gotoBasePathFromBaseUrl(baseUrl) {
|
|
84475
|
+
if (!baseUrl)
|
|
84476
|
+
return void 0;
|
|
84477
|
+
try {
|
|
84478
|
+
const path = new URL(baseUrl).pathname.replace(/\/+$/, "");
|
|
84479
|
+
return path && path !== "/" ? path : void 0;
|
|
84480
|
+
} catch {
|
|
84481
|
+
return void 0;
|
|
84482
|
+
}
|
|
84483
|
+
}
|
|
84047
84484
|
function sanitizeCode(code, options) {
|
|
84048
84485
|
let result = code;
|
|
84049
84486
|
const isNetworkIdleWaitLine = (line) => /^\s*await page\.waitForLoadState\((['"])networkidle\1\);\s*$/.test(line);
|
|
@@ -84066,12 +84503,24 @@ ${elementContext}${formContext}${observationContext}${interactionContext}${lowCo
|
|
|
84066
84503
|
result = result.replace(/\s*await page\.waitForTimeout\(\d+\);\s*/g, "\n");
|
|
84067
84504
|
result = result.replace(/(await page\.goto\((?:[^()()]|\([^()]*\))*\);)\s*\n\s*await expect\(page\)\.toHaveURL\((?:[^()()]|\([^()]*\))*\);/g, (_match, gotoLine) => gotoLine);
|
|
84068
84505
|
result = result.replace(/const baseUrl\s*=\s*process\.env\.BASE_URL[^;]*;\s*\n/g, "");
|
|
84069
|
-
result = result.replace(
|
|
84506
|
+
result = result.replace(
|
|
84507
|
+
/page\.goto\(`\$\{baseUrl\}([^`]*)`/g,
|
|
84508
|
+
// Empty template path (goto(`${baseUrl}`)) means the app home → '/', which the
|
|
84509
|
+
// path-scoped basePath pass below re-joins to the real base (e.g. '/wing').
|
|
84510
|
+
(_match, path) => `page.goto('${path || "/"}'`
|
|
84511
|
+
);
|
|
84070
84512
|
result = result.replace(/test\s*\(\s*(['"`].*?['"`])\s*,\s*\{\s*tags\s*:\s*\[/g, (match) => match.replace("tags", "tag"));
|
|
84071
84513
|
result = result.replace(/page\.goto\(\s*['"]https?:\/\/[^/'"]+(\/[^'"]*)?['"]/g, (_match, path) => `page.goto('${path || "/"}'`);
|
|
84072
84514
|
result = result.replace(/waitForURL\(\s*['"]https?:\/\/[^/'"]+(\/.+?)['"]\s*\)/g, (_match, path) => `waitForURL(/${path.replace(/\//g, "\\/")}/)`);
|
|
84073
84515
|
result = result.replace(/toHaveURL\(\s*['"]https?:\/\/[^/'"]+(\/.+?)['"]\s*\)/g, (_match, path) => `toHaveURL(/${path.replace(/\//g, "\\/")}/)`);
|
|
84074
84516
|
result = result.replace(/(waitForURL|toHaveURL)\(\/(.*?)\\\/\/\)/g, (_match, fn, inner) => `${fn}(/${inner}/)`);
|
|
84517
|
+
const basePath = options?.basePath && options.basePath !== "/" ? options.basePath.replace(/\/+$/, "") : void 0;
|
|
84518
|
+
if (basePath) {
|
|
84519
|
+
result = result.replace(/\b(goto|waitForURL|toHaveURL)\(\s*(['"])(\/(?!\/)[^'"]*)\2/g, (_match, fn, _quote, path) => {
|
|
84520
|
+
const joined = path === basePath || path.startsWith(basePath + "/") ? path : basePath + (path === "/" ? "" : path);
|
|
84521
|
+
return `${fn}('${joined}'`;
|
|
84522
|
+
});
|
|
84523
|
+
}
|
|
84075
84524
|
result = result.replace(/\)\)\.(first\(\)|nth\(\d+\)|last\(\))\./g, (match, method) => `).${method}).`);
|
|
84076
84525
|
result = result.replace(/^(\s+)expect\(page\b/gm, "$1await expect(page");
|
|
84077
84526
|
result = result.replace(/^(\s+)page\.waitForURL\(/gm, "$1await page.waitForURL(");
|
|
@@ -84138,7 +84587,8 @@ ${testOpen}`;
|
|
|
84138
84587
|
} : void 0;
|
|
84139
84588
|
const playwrightCode = sanitizeCode(String(test.playwrightCode || ""), {
|
|
84140
84589
|
sensitiveEnvMap: options?.sensitiveEnvMap,
|
|
84141
|
-
authStamp
|
|
84590
|
+
authStamp,
|
|
84591
|
+
basePath: options?.basePath
|
|
84142
84592
|
});
|
|
84143
84593
|
const leakedSensitiveKeys = findSensitiveLiteralLeaks(playwrightCode, options?.sensitiveEnvMap);
|
|
84144
84594
|
const authGuardStamped = !!options?.authRecipe && rawTags.includes("@needs-auth") && !rawTags.includes("@auth") && process.env.SHARED_AUTH_INJECT === "true";
|
|
@@ -85540,7 +85990,11 @@ ${outcomesText}
|
|
|
85540
85990
|
${hasVerifiedRecipe ? "The browser is ALREADY authenticated \u2014 `__vqaLogin(page)` ran first; cookies/session are populated. Do NOT log in again; begin from the authenticated app." : "The browser starts on about:blank with NO cookies, NO session, NO localStorage. You are NOT logged in."}
|
|
85541
85991
|
The base URL is: ${baseUrl}
|
|
85542
85992
|
When navigating, use \`browser_navigate\` with the full URL (prepend ${baseUrl}).
|
|
85543
|
-
In your generated test code, keep URLs RELATIVE (e.g. \`page.goto('/path')\`) \u2014 Playwright config has baseURL set
|
|
85993
|
+
In your generated test code, keep URLs RELATIVE (e.g. \`page.goto('/path')\`) \u2014 Playwright config has baseURL set.${(() => {
|
|
85994
|
+
const basePath = gotoBasePathFromBaseUrl(baseUrl);
|
|
85995
|
+
return basePath ? `
|
|
85996
|
+
IMPORTANT: the base URL has a PATH (\`${basePath}\`), and Playwright's baseURL only prefixes relative paths \u2014 a leading-slash path escapes to the origin root. Every \`page.goto\` path MUST start with \`${basePath}\` (the app home is \`page.goto('${basePath}')\`, NOT \`page.goto('/')\`).` : "";
|
|
85997
|
+
})()}
|
|
85544
85998
|
|
|
85545
85999
|
${evidenceBlock}${credentialNote}
|
|
85546
86000
|
## ASSERTION VERIFICATION WORKFLOW
|
|
@@ -85748,7 +86202,9 @@ ${tailBlock}`;
|
|
|
85748
86202
|
}
|
|
85749
86203
|
}
|
|
85750
86204
|
});
|
|
85751
|
-
const tags = computeSkeletonTags(scenario.targetPages, siteMap.pages
|
|
86205
|
+
const tags = computeSkeletonTags(scenario.targetPages, siteMap.pages, {
|
|
86206
|
+
extraAuthEvidence: !!opts?.recipe || hasConfiguredLoginCredentials(credentials)
|
|
86207
|
+
});
|
|
85752
86208
|
const baseSkeletonHadRegression = tags.includes("@regression");
|
|
85753
86209
|
const hasVerifiedRecipe = !!opts?.recipe && tags.includes("@needs-auth");
|
|
85754
86210
|
const systemPrompt = buildScenarioSystemPrompt(scenario, baseUrl, credentials, tags, siteMap.pages, { appBrief: opts?.appBrief, availableVariableKeys: opts?.availableVariableKeys, hasVerifiedRecipe, knowledgeContext: opts?.knowledgeContext });
|
|
@@ -85957,7 +86413,7 @@ Do NOT add steps you did not execute. Do NOT skip steps from the recording.${led
|
|
|
85957
86413
|
const suspectedBugDeclared = toolArgs.suspectedBug === true;
|
|
85958
86414
|
const bugDescription = String(toolArgs.bugDescription || "").slice(0, 500).replace(/[\u0000-\u001F\u007F]+/g, " ").replace(/\s+/g, " ").trim();
|
|
85959
86415
|
const testObj = { name, description, playwrightCode: code, tags };
|
|
85960
|
-
const validated = validateTests([testObj], logs2, { sensitiveEnvMap: credentials, authRecipe: opts?.recipe ?? null });
|
|
86416
|
+
const validated = validateTests([testObj], logs2, { sensitiveEnvMap: credentials, authRecipe: opts?.recipe ?? null, basePath: gotoBasePathFromBaseUrl(baseUrl) });
|
|
85961
86417
|
if (validated.length > 0) {
|
|
85962
86418
|
if (actionLedger.length > lastRenderedLedgerEntryCount && actionLedger.length > 0) {
|
|
85963
86419
|
const renderedLedger = renderSessionRecording(actionLedger, credentials);
|
|
@@ -86076,7 +86532,12 @@ Do NOT add steps you did not execute. Do NOT skip steps from the recording.${led
|
|
|
86076
86532
|
const creditedControlSig = revealVerdict === "revealed" ? assertedSigs.find((s) => revealedControls.has(s)) ?? null : null;
|
|
86077
86533
|
let strengthVerdict = null;
|
|
86078
86534
|
try {
|
|
86079
|
-
strengthVerdict = (0, shared_1.validateAssertionStrength)(validated[0].playwrightCode, {
|
|
86535
|
+
strengthVerdict = (0, shared_1.validateAssertionStrength)(validated[0].playwrightCode, {
|
|
86536
|
+
revealedControls,
|
|
86537
|
+
// Opt-in additive-contradiction gate (see assertion-strength.ts). On Fargate this
|
|
86538
|
+
// env must be dispatcher-forwarded to take effect here; off → byte-identical.
|
|
86539
|
+
flagContradictedAdditions: process.env.STRENGTH_ADDITIVE_CONTRADICTION_GATE === "true"
|
|
86540
|
+
});
|
|
86080
86541
|
} catch (err) {
|
|
86081
86542
|
logs2.push(` Strength gate errored \u2014 skipping (fail-open): ${String(err)}`);
|
|
86082
86543
|
}
|
|
@@ -301288,7 +301749,8 @@ ${currentError.slice(0, 2e3)}`
|
|
|
301288
301749
|
}
|
|
301289
301750
|
lastHealedCode = (0, discover_generator_js_1.sanitizeCode)(stripCodeFences(fixedCode), {
|
|
301290
301751
|
sensitiveEnvMap: testVariables,
|
|
301291
|
-
authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(playwrightCode, lastFailureError, logs2)
|
|
301752
|
+
authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(playwrightCode, lastFailureError, logs2),
|
|
301753
|
+
basePath: (0, discover_generator_js_1.gotoBasePathFromBaseUrl)(baseUrl)
|
|
301292
301754
|
});
|
|
301293
301755
|
logs2.push("\u{1F680} Running fixed code natively...");
|
|
301294
301756
|
let verifyResult;
|
|
@@ -302742,7 +303204,8 @@ ${errorContent}`;
|
|
|
302742
303204
|
}
|
|
302743
303205
|
const candidateCode = (0, discover_generator_js_1.sanitizeCode)(stripCodeFences(fixedCode), {
|
|
302744
303206
|
sensitiveEnvMap: testVariables,
|
|
302745
|
-
authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(playwrightCode, options?.lastFailureError, logs2)
|
|
303207
|
+
authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(playwrightCode, options?.lastFailureError, logs2),
|
|
303208
|
+
basePath: (0, discover_generator_js_1.gotoBasePathFromBaseUrl)(baseUrl)
|
|
302746
303209
|
});
|
|
302747
303210
|
{
|
|
302748
303211
|
const syntaxCheckCode = (0, discover_generator_js_1.stripTypescriptSyntax)((0, discover_generator_js_1.stripImportDeclarations)(candidateCode));
|
|
@@ -304489,7 +304952,8 @@ var require_grok_per_test_healer = __commonJS({
|
|
|
304489
304952
|
}
|
|
304490
304953
|
const healedCode = (0, discover_generator_js_1.sanitizeCode)(payload.playwrightCode, {
|
|
304491
304954
|
sensitiveEnvMap: secrets,
|
|
304492
|
-
authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(playwrightCode, options.lastFailureError, logs2)
|
|
304955
|
+
authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(playwrightCode, options.lastFailureError, logs2),
|
|
304956
|
+
basePath: (0, discover_generator_js_1.gotoBasePathFromBaseUrl)(baseUrl)
|
|
304493
304957
|
});
|
|
304494
304958
|
const leakedKey = findLeakedCredential(healedCode, secrets);
|
|
304495
304959
|
if (leakedKey) {
|
|
@@ -305662,7 +306126,8 @@ var require_grok_batch_healer = __commonJS({
|
|
|
305662
306126
|
}
|
|
305663
306127
|
const healedCode = (0, discover_generator_js_1.sanitizeCode)(verdict.playwrightCode, {
|
|
305664
306128
|
sensitiveEnvMap: secrets,
|
|
305665
|
-
authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(test.playwrightCode, test.lastFailureError, logs2)
|
|
306129
|
+
authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(test.playwrightCode, test.lastFailureError, logs2),
|
|
306130
|
+
basePath: (0, discover_generator_js_1.gotoBasePathFromBaseUrl)(baseUrl)
|
|
305666
306131
|
});
|
|
305667
306132
|
const leakedKey = findLeakedCredential(healedCode, secrets);
|
|
305668
306133
|
if (leakedKey) {
|
|
@@ -310674,7 +311139,7 @@ var require_discover_explorer = __commonJS({
|
|
|
310674
311139
|
},
|
|
310675
311140
|
requiresAuth: {
|
|
310676
311141
|
type: "boolean",
|
|
310677
|
-
description: "
|
|
311142
|
+
description: "OBSERVED evidence only: true if reaching this page logged-out redirected to a login page, returned 401/403, or showed an explicit sign-in wall. Never infer from content looking account-ish \u2014 a page you read without logging in does NOT require auth."
|
|
310678
311143
|
},
|
|
310679
311144
|
observations: {
|
|
310680
311145
|
type: "array",
|
|
@@ -310903,7 +311368,7 @@ You do NOT have a \`record_journey\` tool - your job is mapping, not testing. Do
|
|
|
310903
311368
|
2. Action buttons: submit, save, delete, create, edit - include label, triggersSubmit
|
|
310904
311369
|
3. Navigation links to OTHER pages within the app - include label, triggersNavigation
|
|
310905
311370
|
4. Skip repeated navigation chrome you already cataloged (sidebar/header links appear on every page)
|
|
310906
|
-
- **requiresAuth**:
|
|
311371
|
+
- **requiresAuth**: OBSERVED evidence only \u2014 you were redirected to a login page, got 401/403, or hit a sign-in wall. If you are reading the page without having logged in, it does NOT require auth (account-ish CONTENT is not evidence)
|
|
310907
311372
|
- **observations**: empty array (you are mapping, not interacting)
|
|
310908
311373
|
|
|
310909
311374
|
## Loading and readiness
|
|
@@ -314601,9 +315066,13 @@ var require_agentic_survey_prompts = __commonJS({
|
|
|
314601
315066
|
' route: string; // path-only, e.g. "/dashboard/profile"',
|
|
314602
315067
|
' type: string; // "AUTH" | "DASHBOARD" | "FORM" | "LIST" | ...',
|
|
314603
315068
|
" summary: string; // 1-line of key elements/affordances",
|
|
314604
|
-
" requiresAuth?: boolean; //
|
|
314605
|
-
" //
|
|
314606
|
-
" //
|
|
315069
|
+
" requiresAuth?: boolean; // OBSERVED evidence ONLY: you were redirected",
|
|
315070
|
+
" // to a login page, got 401/403, or hit an",
|
|
315071
|
+
" // explicit sign-in wall. NEVER infer it from",
|
|
315072
|
+
" // content merely LOOKING account-ish \u2014 if you",
|
|
315073
|
+
" // read the page without logging in, it does",
|
|
315074
|
+
" // NOT require auth. A site with no login at",
|
|
315075
|
+
" // all has requiresAuth=false everywhere.",
|
|
314607
315076
|
" }>;",
|
|
314608
315077
|
' flows?: string[]; // ["/login \u2192 /dashboard (submit form)"]',
|
|
314609
315078
|
" observations?: string[]; // things YOU noticed in the loop",
|
|
@@ -336171,7 +336640,7 @@ var require_package4 = __commonJS({
|
|
|
336171
336640
|
"package.json"(exports2, module2) {
|
|
336172
336641
|
module2.exports = {
|
|
336173
336642
|
name: "@validate.qa/runner",
|
|
336174
|
-
version: "1.0.
|
|
336643
|
+
version: "1.0.38",
|
|
336175
336644
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
336176
336645
|
bin: {
|
|
336177
336646
|
"validate-runner": "dist/cli.js"
|