@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.mjs
CHANGED
|
@@ -246,6 +246,9 @@ var require_ai_provider = __commonJS({
|
|
|
246
246
|
exports.getAvailableModels = getAvailableModels;
|
|
247
247
|
exports.getAvailableOrchestratorModels = getAvailableOrchestratorModels;
|
|
248
248
|
exports.isAvailableModelId = isAvailableModelId;
|
|
249
|
+
exports.resolveWhisperProvider = resolveWhisperProvider;
|
|
250
|
+
exports.isWhisperConfigured = isWhisperConfigured;
|
|
251
|
+
exports.getWhisperModel = getWhisperModel;
|
|
249
252
|
exports.getOpenAIWhisperClient = getOpenAIWhisperClient;
|
|
250
253
|
exports.resetAIClient = resetAIClient;
|
|
251
254
|
exports.getServerProxiedAIClient = getServerProxiedAIClient;
|
|
@@ -575,20 +578,70 @@ var require_ai_provider = __commonJS({
|
|
|
575
578
|
function isAvailableModelId(modelId) {
|
|
576
579
|
return getAvailableModels().some((model) => model.id === modelId);
|
|
577
580
|
}
|
|
581
|
+
var GROQ_API_BASE_DEFAULT = "https://api.groq.com/openai/v1";
|
|
582
|
+
var GROQ_WHISPER_DEFAULT = "whisper-large-v3-turbo";
|
|
583
|
+
var OPENAI_WHISPER_DEFAULT = "whisper-1";
|
|
584
|
+
function isUsableApiKey(value) {
|
|
585
|
+
if (!value)
|
|
586
|
+
return false;
|
|
587
|
+
const trimmed = value.trim();
|
|
588
|
+
if (!trimmed)
|
|
589
|
+
return false;
|
|
590
|
+
if (/your[-_]?.*(key|api|secret)/i.test(trimmed))
|
|
591
|
+
return false;
|
|
592
|
+
if (trimmed === "sk-..." || trimmed === "gsk_...")
|
|
593
|
+
return false;
|
|
594
|
+
return true;
|
|
595
|
+
}
|
|
596
|
+
function resolveWhisperProvider() {
|
|
597
|
+
const forced = (process.env.STT_PROVIDER ?? process.env.WHISPER_PROVIDER ?? "").trim().toLowerCase();
|
|
598
|
+
const hasGroq = isUsableApiKey(process.env.GROQ_API_KEY);
|
|
599
|
+
const hasOpenAI = isUsableApiKey(process.env.OPENAI_API_KEY);
|
|
600
|
+
if (forced === "groq")
|
|
601
|
+
return hasGroq ? "groq" : null;
|
|
602
|
+
if (forced === "openai")
|
|
603
|
+
return hasOpenAI ? "openai" : null;
|
|
604
|
+
if (hasGroq)
|
|
605
|
+
return "groq";
|
|
606
|
+
if (hasOpenAI)
|
|
607
|
+
return "openai";
|
|
608
|
+
return null;
|
|
609
|
+
}
|
|
610
|
+
function isWhisperConfigured() {
|
|
611
|
+
return resolveWhisperProvider() !== null;
|
|
612
|
+
}
|
|
613
|
+
function getWhisperModel(provider) {
|
|
614
|
+
const p = provider ?? resolveWhisperProvider();
|
|
615
|
+
if (p === "groq") {
|
|
616
|
+
return process.env.GROQ_WHISPER_MODEL?.trim() || process.env.OPENAI_WHISPER_MODEL?.trim() || GROQ_WHISPER_DEFAULT;
|
|
617
|
+
}
|
|
618
|
+
return process.env.OPENAI_WHISPER_MODEL?.trim() || OPENAI_WHISPER_DEFAULT;
|
|
619
|
+
}
|
|
578
620
|
var _whisperClient = null;
|
|
621
|
+
var _whisperClientProvider = null;
|
|
579
622
|
function getOpenAIWhisperClient() {
|
|
580
|
-
|
|
623
|
+
const provider = resolveWhisperProvider();
|
|
624
|
+
if (!provider) {
|
|
625
|
+
throw new Error("Speech transcription is not configured (set GROQ_API_KEY or OPENAI_API_KEY)");
|
|
626
|
+
}
|
|
627
|
+
if (_whisperClient && _whisperClientProvider === provider) {
|
|
581
628
|
return _whisperClient;
|
|
582
|
-
|
|
583
|
-
if (
|
|
584
|
-
|
|
585
|
-
|
|
629
|
+
}
|
|
630
|
+
if (provider === "groq") {
|
|
631
|
+
const apiKey = process.env.GROQ_API_KEY;
|
|
632
|
+
const baseURL = process.env.GROQ_API_BASE?.trim() || GROQ_API_BASE_DEFAULT;
|
|
633
|
+
_whisperClient = new openai_1.default({ apiKey, baseURL });
|
|
634
|
+
} else {
|
|
635
|
+
_whisperClient = new openai_1.default({ apiKey: process.env.OPENAI_API_KEY });
|
|
636
|
+
}
|
|
637
|
+
_whisperClientProvider = provider;
|
|
586
638
|
return _whisperClient;
|
|
587
639
|
}
|
|
588
640
|
var _serverProxiedClient = null;
|
|
589
641
|
function resetAIClient() {
|
|
590
642
|
_clientCache.clear();
|
|
591
643
|
_whisperClient = null;
|
|
644
|
+
_whisperClientProvider = null;
|
|
592
645
|
_serverProxiedClient = null;
|
|
593
646
|
}
|
|
594
647
|
function buildServerProxiedClient() {
|
|
@@ -1687,6 +1740,7 @@ var init_constants = __esm({
|
|
|
1687
1740
|
"DATABASE_URL",
|
|
1688
1741
|
"JWT_SECRET",
|
|
1689
1742
|
"OPENAI_API_KEY",
|
|
1743
|
+
"GROQ_API_KEY",
|
|
1690
1744
|
"GEMINI_API_KEY",
|
|
1691
1745
|
"XAI_API_KEY",
|
|
1692
1746
|
"GROK_API_KEY",
|
|
@@ -3253,8 +3307,10 @@ var init_audit_pricing = __esm({
|
|
|
3253
3307
|
"MiniMax-M2.7-highspeed": { input: 0.6, output: 2.4 },
|
|
3254
3308
|
"MiniMax-M2": { input: 0.3, output: 1.2 },
|
|
3255
3309
|
"deepseek-v4-flash": { input: 0.14, output: 0.28 },
|
|
3256
|
-
// Whisper bills per-minute, not per-token
|
|
3310
|
+
// Whisper bills per-minute, not per-token (OpenAI + Groq)
|
|
3257
3311
|
"whisper-1": null,
|
|
3312
|
+
"whisper-large-v3": null,
|
|
3313
|
+
"whisper-large-v3-turbo": null,
|
|
3258
3314
|
// Historical / deprecated (kept so old audit rows still show a cost).
|
|
3259
3315
|
// Deprecated Grok variants priced at the legacy authoritative Grok rate
|
|
3260
3316
|
// ($0.20/$0.80) those rows were generated under.
|
|
@@ -3865,6 +3921,43 @@ function assertedText(stmt) {
|
|
|
3865
3921
|
}
|
|
3866
3922
|
return out.join(" ").toLowerCase();
|
|
3867
3923
|
}
|
|
3924
|
+
function clickedAdditiveAffordance(lines) {
|
|
3925
|
+
const nameRe = /name\s*:\s*(?:['"`]([^'"`]+)['"`]|\/([^/]+)\/)/g;
|
|
3926
|
+
for (const ln of lines) {
|
|
3927
|
+
if (!/\.click\s*\(/.test(ln))
|
|
3928
|
+
continue;
|
|
3929
|
+
nameRe.lastIndex = 0;
|
|
3930
|
+
let m;
|
|
3931
|
+
while (m = nameRe.exec(ln)) {
|
|
3932
|
+
const name = (m[1] ?? m[2]).toLowerCase();
|
|
3933
|
+
if (EXCLUDE_AFFORDANCE.test(name))
|
|
3934
|
+
continue;
|
|
3935
|
+
if (ADDITIVE_AFFORDANCE.test(name))
|
|
3936
|
+
return true;
|
|
3937
|
+
}
|
|
3938
|
+
}
|
|
3939
|
+
return false;
|
|
3940
|
+
}
|
|
3941
|
+
function matcherText(stmt) {
|
|
3942
|
+
const out = [];
|
|
3943
|
+
const re = /\.(?:toContainText|toHaveText)\s*\(\s*(?:['"`]([^'"`]+)['"`]|\/([^/]+)\/)/g;
|
|
3944
|
+
let m;
|
|
3945
|
+
while (m = re.exec(stmt))
|
|
3946
|
+
out.push(m[1] ?? m[2]);
|
|
3947
|
+
return out.join(" ").toLowerCase();
|
|
3948
|
+
}
|
|
3949
|
+
function isEmptyStateProof(stmt) {
|
|
3950
|
+
if (NEGATED_ASSERT.test(stmt))
|
|
3951
|
+
return false;
|
|
3952
|
+
if (/\.toHaveCount\s*\(\s*0\s*\)/.test(stmt))
|
|
3953
|
+
return true;
|
|
3954
|
+
if (/\.toBe\s*\(\s*0\s*\)/.test(stmt) && !isChromeTarget(stmt))
|
|
3955
|
+
return true;
|
|
3956
|
+
if (VISIBILITY_MATCHER.test(stmt) || TEXT_CONTENT_MATCHER.test(stmt)) {
|
|
3957
|
+
return EMPTY_STATE_TEXT.test(`${assertedText(stmt)} ${matcherText(stmt)}`);
|
|
3958
|
+
}
|
|
3959
|
+
return false;
|
|
3960
|
+
}
|
|
3868
3961
|
function hasAttributeTransition(stmts) {
|
|
3869
3962
|
const byAttr = /* @__PURE__ */ new Map();
|
|
3870
3963
|
for (const { stmt } of stmts) {
|
|
@@ -3971,6 +4064,21 @@ function validateAssertionStrength(code, opts = {}) {
|
|
|
3971
4064
|
const interacted = mutated || navClicks > 0;
|
|
3972
4065
|
navOnly = !mutated && navClicks > 0;
|
|
3973
4066
|
const reloaded = RELOAD.test(body);
|
|
4067
|
+
if (opts.flagContradictedAdditions && mutated && clickedAdditiveAffordance(interactionStmts)) {
|
|
4068
|
+
const empties = stmts.filter(({ stmt }) => isEmptyStateProof(stmt));
|
|
4069
|
+
if (empties.length > 0) {
|
|
4070
|
+
const others = stmts.filter(({ stmt }) => !isEmptyStateProof(stmt));
|
|
4071
|
+
const provesSomethingElse = detectOutcomeProof(others, mutated, false, clickedControlNames(interactionStmts), typedValues(interactionStmts));
|
|
4072
|
+
if (!provesSomethingElse) {
|
|
4073
|
+
return {
|
|
4074
|
+
ok: false,
|
|
4075
|
+
severity: "block",
|
|
4076
|
+
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",
|
|
4077
|
+
signals: base2({ interacted, mutated, terminal })
|
|
4078
|
+
};
|
|
4079
|
+
}
|
|
4080
|
+
}
|
|
4081
|
+
}
|
|
3974
4082
|
const hasOutcomeProof = detectOutcomeProof(stmts, mutated, reloaded, clickedControlNames(interactionStmts), typedValues(interactionStmts));
|
|
3975
4083
|
const signals = base2({ interacted, mutated, navOnly, hasOutcomeProof, terminal });
|
|
3976
4084
|
if (hasOutcomeProof) {
|
|
@@ -4025,7 +4133,7 @@ function validateAssertionStrength(code, opts = {}) {
|
|
|
4025
4133
|
signals
|
|
4026
4134
|
};
|
|
4027
4135
|
}
|
|
4028
|
-
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;
|
|
4136
|
+
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;
|
|
4029
4137
|
var init_assertion_strength = __esm({
|
|
4030
4138
|
"../shared/dist/assertion-strength.js"() {
|
|
4031
4139
|
"use strict";
|
|
@@ -4069,6 +4177,9 @@ var init_assertion_strength = __esm({
|
|
|
4069
4177
|
MESSAGE_SURFACE = /getByText\s*\(|\.filter\s*\(\s*\{\s*hasText\s*:|getByRole\s*\(\s*['"`](?:alert|alertdialog|status|heading|dialog)['"`]/i;
|
|
4070
4178
|
TEXT_CONTENT_MATCHER = /\.(toHaveText|toContainText)\s*\(/;
|
|
4071
4179
|
VISIBILITY_MATCHER = /\.toBeVisible\s*\(/;
|
|
4180
|
+
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;
|
|
4181
|
+
EXCLUDE_AFFORDANCE = /\b(filter|filters|search|searches|sort|view|views|tab|tabs|window|windows|toggle|expand|collapse|column|columns|zoom|layout)\b/i;
|
|
4182
|
+
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;
|
|
4072
4183
|
AUTH_START = /^\s*\/\/\s*>>>\s*VALIDATE_QA_AUTH/;
|
|
4073
4184
|
AUTH_END = /^\s*\/\/\s*<<<\s*VALIDATE_QA_AUTH/;
|
|
4074
4185
|
LOGIN_CALL_LINE = /^\s*await\s+__vqaLogin\s*\(\s*page\s*\)\s*;?\s*$/;
|
|
@@ -4091,6 +4202,24 @@ var init_assertion_strength = __esm({
|
|
|
4091
4202
|
});
|
|
4092
4203
|
|
|
4093
4204
|
// ../shared/dist/run-classification.js
|
|
4205
|
+
function pickLatestHealthRun(runs) {
|
|
4206
|
+
for (const run2 of runs) {
|
|
4207
|
+
if (run2.mode === "auth-setup")
|
|
4208
|
+
continue;
|
|
4209
|
+
if (run2.status === "PASSED" || run2.status === "FAILED" || run2.status === "ERROR") {
|
|
4210
|
+
return run2;
|
|
4211
|
+
}
|
|
4212
|
+
}
|
|
4213
|
+
return null;
|
|
4214
|
+
}
|
|
4215
|
+
function pickDisplayRun(runs) {
|
|
4216
|
+
if (runs.length === 0)
|
|
4217
|
+
return null;
|
|
4218
|
+
const latest = runs[0];
|
|
4219
|
+
if (latest.status === "PENDING" || latest.status === "RUNNING")
|
|
4220
|
+
return latest;
|
|
4221
|
+
return pickLatestHealthRun(runs) ?? latest;
|
|
4222
|
+
}
|
|
4094
4223
|
function isEnvironmentalError(run2) {
|
|
4095
4224
|
if (!run2)
|
|
4096
4225
|
return false;
|
|
@@ -4552,6 +4681,262 @@ var init_origin_domain = __esm({
|
|
|
4552
4681
|
}
|
|
4553
4682
|
});
|
|
4554
4683
|
|
|
4684
|
+
// ../shared/dist/voice-config.js
|
|
4685
|
+
function normalizeTtsVoiceId(voiceId) {
|
|
4686
|
+
if (!voiceId)
|
|
4687
|
+
return null;
|
|
4688
|
+
const trimmed = voiceId.trim();
|
|
4689
|
+
if (!trimmed)
|
|
4690
|
+
return null;
|
|
4691
|
+
return LEGACY_TTS_VOICE_ID_MAP[trimmed] ?? trimmed;
|
|
4692
|
+
}
|
|
4693
|
+
function sttModelsForProvider(provider) {
|
|
4694
|
+
if (provider === "openai") {
|
|
4695
|
+
return STT_MODEL_OPTIONS.filter((m) => m.provider === "openai");
|
|
4696
|
+
}
|
|
4697
|
+
if (provider === "groq") {
|
|
4698
|
+
return STT_MODEL_OPTIONS.filter((m) => m.provider === "groq");
|
|
4699
|
+
}
|
|
4700
|
+
return STT_MODEL_OPTIONS;
|
|
4701
|
+
}
|
|
4702
|
+
function isValidSttProvider(value) {
|
|
4703
|
+
return value === "groq" || value === "openai";
|
|
4704
|
+
}
|
|
4705
|
+
function isSttModelForProvider(modelId, provider) {
|
|
4706
|
+
if (!modelId)
|
|
4707
|
+
return false;
|
|
4708
|
+
return STT_MODEL_OPTIONS.some((m) => m.id === modelId && m.provider === provider);
|
|
4709
|
+
}
|
|
4710
|
+
function resolveSttModelForProvider(provider, configuredModel) {
|
|
4711
|
+
if (configuredModel && isSttModelForProvider(configuredModel, provider)) {
|
|
4712
|
+
return configuredModel;
|
|
4713
|
+
}
|
|
4714
|
+
return DEFAULT_STT_MODEL_BY_PROVIDER[provider];
|
|
4715
|
+
}
|
|
4716
|
+
function sanitizeSttErrorMessage(raw) {
|
|
4717
|
+
if (!raw)
|
|
4718
|
+
return "Speech transcription failed.";
|
|
4719
|
+
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();
|
|
4720
|
+
if (/401|incorrect api key|invalid.?api.?key|authentication|unauthorized/i.test(msg)) {
|
|
4721
|
+
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.";
|
|
4722
|
+
}
|
|
4723
|
+
if (/429|rate.?limit|too many requests/i.test(msg)) {
|
|
4724
|
+
return "Speech transcription failed: the speech provider rate-limited the request. Try again in a moment.";
|
|
4725
|
+
}
|
|
4726
|
+
if (/not configured|GROQ_API_KEY or OPENAI_API_KEY/i.test(msg)) {
|
|
4727
|
+
return "Speech transcription is not configured. Set GROQ_API_KEY (preferred) or OPENAI_API_KEY on the org server.";
|
|
4728
|
+
}
|
|
4729
|
+
if (msg.length > 280)
|
|
4730
|
+
msg = `${msg.slice(0, 277)}\u2026`;
|
|
4731
|
+
return msg || "Speech transcription failed.";
|
|
4732
|
+
}
|
|
4733
|
+
function isValidTtsVoiceId(value) {
|
|
4734
|
+
const normalized = normalizeTtsVoiceId(value);
|
|
4735
|
+
if (!normalized)
|
|
4736
|
+
return false;
|
|
4737
|
+
return TTS_VOICE_OPTIONS.some((v) => v.id === normalized);
|
|
4738
|
+
}
|
|
4739
|
+
function isValidTtsModel(value) {
|
|
4740
|
+
if (!value)
|
|
4741
|
+
return false;
|
|
4742
|
+
return TTS_MODEL_OPTIONS.some((m) => m.id === value);
|
|
4743
|
+
}
|
|
4744
|
+
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;
|
|
4745
|
+
var init_voice_config = __esm({
|
|
4746
|
+
"../shared/dist/voice-config.js"() {
|
|
4747
|
+
"use strict";
|
|
4748
|
+
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.";
|
|
4749
|
+
DEFAULT_TTS_MODEL = "speech-2.6-hd";
|
|
4750
|
+
DEFAULT_TTS_VOICE_ID = "English_Graceful_Lady";
|
|
4751
|
+
DEFAULT_TTS_SPEED = 1;
|
|
4752
|
+
LEGACY_TTS_VOICE_ID_MAP = {
|
|
4753
|
+
English_LivelyGirl: "English_PlayfulGirl",
|
|
4754
|
+
English_Insightful_Man: "English_Insightful_Speaker",
|
|
4755
|
+
English_Deep_Voice_Man: "English_ManWithDeepVoice",
|
|
4756
|
+
English_CasualMan: "English_Jovialman"
|
|
4757
|
+
};
|
|
4758
|
+
DEFAULT_STT_MODEL_BY_PROVIDER = {
|
|
4759
|
+
groq: "whisper-large-v3-turbo",
|
|
4760
|
+
openai: "whisper-1"
|
|
4761
|
+
};
|
|
4762
|
+
STT_PROVIDER_OPTIONS = [
|
|
4763
|
+
{
|
|
4764
|
+
id: "auto",
|
|
4765
|
+
label: "Auto (prefer Groq)",
|
|
4766
|
+
description: "Use Groq when GROQ_API_KEY is set, otherwise OpenAI Whisper."
|
|
4767
|
+
},
|
|
4768
|
+
{
|
|
4769
|
+
id: "groq",
|
|
4770
|
+
label: "Groq Whisper",
|
|
4771
|
+
description: "Free-tier OpenAI-compatible Whisper via Groq. Requires GROQ_API_KEY on org servers."
|
|
4772
|
+
},
|
|
4773
|
+
{
|
|
4774
|
+
id: "openai",
|
|
4775
|
+
label: "OpenAI Whisper",
|
|
4776
|
+
description: "OpenAI whisper-1 API. Requires OPENAI_API_KEY on org servers."
|
|
4777
|
+
}
|
|
4778
|
+
];
|
|
4779
|
+
STT_MODEL_OPTIONS = [
|
|
4780
|
+
{
|
|
4781
|
+
id: "whisper-large-v3-turbo",
|
|
4782
|
+
label: "Whisper Large V3 Turbo",
|
|
4783
|
+
provider: "groq",
|
|
4784
|
+
description: "Fast, accurate default for Groq (recommended)."
|
|
4785
|
+
},
|
|
4786
|
+
{
|
|
4787
|
+
id: "whisper-large-v3",
|
|
4788
|
+
label: "Whisper Large V3",
|
|
4789
|
+
provider: "groq",
|
|
4790
|
+
description: "Full-size Groq Whisper model \u2014 higher quality, slower."
|
|
4791
|
+
},
|
|
4792
|
+
{
|
|
4793
|
+
id: "distil-whisper-large-v3-en",
|
|
4794
|
+
label: "Distil Whisper Large V3 (EN)",
|
|
4795
|
+
provider: "groq",
|
|
4796
|
+
description: "English-optimized distilled model \u2014 fastest Groq option."
|
|
4797
|
+
},
|
|
4798
|
+
{
|
|
4799
|
+
id: "whisper-1",
|
|
4800
|
+
label: "Whisper 1",
|
|
4801
|
+
provider: "openai",
|
|
4802
|
+
description: "OpenAI hosted Whisper model."
|
|
4803
|
+
}
|
|
4804
|
+
];
|
|
4805
|
+
TTS_MODEL_OPTIONS = [
|
|
4806
|
+
{
|
|
4807
|
+
id: "speech-2.8-hd",
|
|
4808
|
+
label: "Speech 2.8 HD",
|
|
4809
|
+
provider: "minimax",
|
|
4810
|
+
description: "Latest high-definition model with interjection tags (recommended)."
|
|
4811
|
+
},
|
|
4812
|
+
{
|
|
4813
|
+
id: "speech-2.8-turbo",
|
|
4814
|
+
label: "Speech 2.8 Turbo",
|
|
4815
|
+
provider: "minimax",
|
|
4816
|
+
description: "Latest low-latency model with interjection tags."
|
|
4817
|
+
},
|
|
4818
|
+
{
|
|
4819
|
+
id: "speech-2.6-hd",
|
|
4820
|
+
label: "Speech 2.6 HD",
|
|
4821
|
+
provider: "minimax",
|
|
4822
|
+
description: "High-definition MiniMax voice model."
|
|
4823
|
+
},
|
|
4824
|
+
{
|
|
4825
|
+
id: "speech-2.6-turbo",
|
|
4826
|
+
label: "Speech 2.6 Turbo",
|
|
4827
|
+
provider: "minimax",
|
|
4828
|
+
description: "Lower-latency Speech 2.6 variant."
|
|
4829
|
+
},
|
|
4830
|
+
{
|
|
4831
|
+
id: "speech-02-hd",
|
|
4832
|
+
label: "Speech 02 HD",
|
|
4833
|
+
provider: "minimax",
|
|
4834
|
+
description: "Previous-generation HD model."
|
|
4835
|
+
},
|
|
4836
|
+
{
|
|
4837
|
+
id: "speech-02-turbo",
|
|
4838
|
+
label: "Speech 02 Turbo",
|
|
4839
|
+
provider: "minimax",
|
|
4840
|
+
description: "Lower-latency Speech 02 variant."
|
|
4841
|
+
},
|
|
4842
|
+
{
|
|
4843
|
+
id: "speech-01-hd",
|
|
4844
|
+
label: "Speech 01 HD",
|
|
4845
|
+
provider: "minimax",
|
|
4846
|
+
description: "Legacy HD model."
|
|
4847
|
+
},
|
|
4848
|
+
{
|
|
4849
|
+
id: "speech-01-turbo",
|
|
4850
|
+
label: "Speech 01 Turbo",
|
|
4851
|
+
provider: "minimax",
|
|
4852
|
+
description: "Legacy fast model."
|
|
4853
|
+
}
|
|
4854
|
+
];
|
|
4855
|
+
TTS_VOICE_OPTIONS = [
|
|
4856
|
+
{
|
|
4857
|
+
id: "English_Graceful_Lady",
|
|
4858
|
+
label: "Graceful Lady",
|
|
4859
|
+
description: "Warm, polished professional tone.",
|
|
4860
|
+
gender: "female",
|
|
4861
|
+
accent: "English",
|
|
4862
|
+
sampleText: VOICE_PREVIEW_SAMPLE
|
|
4863
|
+
},
|
|
4864
|
+
{
|
|
4865
|
+
id: "English_ConfidentWoman",
|
|
4866
|
+
label: "Confident Woman",
|
|
4867
|
+
description: "Assured and direct \u2014 good for technical guidance.",
|
|
4868
|
+
gender: "female",
|
|
4869
|
+
accent: "English",
|
|
4870
|
+
sampleText: VOICE_PREVIEW_SAMPLE
|
|
4871
|
+
},
|
|
4872
|
+
{
|
|
4873
|
+
id: "English_SereneWoman",
|
|
4874
|
+
label: "Serene Woman",
|
|
4875
|
+
description: "Calm and measured delivery.",
|
|
4876
|
+
gender: "female",
|
|
4877
|
+
accent: "English",
|
|
4878
|
+
sampleText: VOICE_PREVIEW_SAMPLE
|
|
4879
|
+
},
|
|
4880
|
+
{
|
|
4881
|
+
id: "English_PlayfulGirl",
|
|
4882
|
+
label: "Playful Girl",
|
|
4883
|
+
description: "Upbeat and energetic.",
|
|
4884
|
+
gender: "female",
|
|
4885
|
+
accent: "English",
|
|
4886
|
+
sampleText: VOICE_PREVIEW_SAMPLE
|
|
4887
|
+
},
|
|
4888
|
+
{
|
|
4889
|
+
id: "English_CalmWoman",
|
|
4890
|
+
label: "Calm Woman",
|
|
4891
|
+
description: "Soft, reassuring cadence.",
|
|
4892
|
+
gender: "female",
|
|
4893
|
+
accent: "English",
|
|
4894
|
+
sampleText: VOICE_PREVIEW_SAMPLE
|
|
4895
|
+
},
|
|
4896
|
+
{
|
|
4897
|
+
id: "English_Insightful_Speaker",
|
|
4898
|
+
label: "Insightful Speaker",
|
|
4899
|
+
description: "Clear analyst-style narration.",
|
|
4900
|
+
gender: "male",
|
|
4901
|
+
accent: "English",
|
|
4902
|
+
sampleText: VOICE_PREVIEW_SAMPLE
|
|
4903
|
+
},
|
|
4904
|
+
{
|
|
4905
|
+
id: "English_ManWithDeepVoice",
|
|
4906
|
+
label: "Deep Voice Man",
|
|
4907
|
+
description: "Rich, authoritative baritone.",
|
|
4908
|
+
gender: "male",
|
|
4909
|
+
accent: "English",
|
|
4910
|
+
sampleText: VOICE_PREVIEW_SAMPLE
|
|
4911
|
+
},
|
|
4912
|
+
{
|
|
4913
|
+
id: "English_Jovialman",
|
|
4914
|
+
label: "Jovial Man",
|
|
4915
|
+
description: "Relaxed conversational style.",
|
|
4916
|
+
gender: "male",
|
|
4917
|
+
accent: "English",
|
|
4918
|
+
sampleText: VOICE_PREVIEW_SAMPLE
|
|
4919
|
+
},
|
|
4920
|
+
{
|
|
4921
|
+
id: "English_PatientMan",
|
|
4922
|
+
label: "Patient Man",
|
|
4923
|
+
description: "Steady, tutorial-friendly pacing.",
|
|
4924
|
+
gender: "male",
|
|
4925
|
+
accent: "English",
|
|
4926
|
+
sampleText: VOICE_PREVIEW_SAMPLE
|
|
4927
|
+
},
|
|
4928
|
+
{
|
|
4929
|
+
id: "English_FriendlyPerson",
|
|
4930
|
+
label: "Friendly Person",
|
|
4931
|
+
description: "Neutral, approachable assistant voice.",
|
|
4932
|
+
gender: "neutral",
|
|
4933
|
+
accent: "English",
|
|
4934
|
+
sampleText: VOICE_PREVIEW_SAMPLE
|
|
4935
|
+
}
|
|
4936
|
+
];
|
|
4937
|
+
}
|
|
4938
|
+
});
|
|
4939
|
+
|
|
4555
4940
|
// ../shared/dist/index.js
|
|
4556
4941
|
var dist_exports = {};
|
|
4557
4942
|
__export(dist_exports, {
|
|
@@ -4566,8 +4951,13 @@ __export(dist_exports, {
|
|
|
4566
4951
|
CREDIT_PRICES_CENTS: () => CREDIT_PRICES_CENTS,
|
|
4567
4952
|
DANGEROUS_DEEPLINK_SCHEMES: () => DANGEROUS_DEEPLINK_SCHEMES,
|
|
4568
4953
|
DB_PRICING_PLAN_MAP: () => DB_PRICING_PLAN_MAP,
|
|
4954
|
+
DEFAULT_STT_MODEL_BY_PROVIDER: () => DEFAULT_STT_MODEL_BY_PROVIDER,
|
|
4955
|
+
DEFAULT_TTS_MODEL: () => DEFAULT_TTS_MODEL,
|
|
4956
|
+
DEFAULT_TTS_SPEED: () => DEFAULT_TTS_SPEED,
|
|
4957
|
+
DEFAULT_TTS_VOICE_ID: () => DEFAULT_TTS_VOICE_ID,
|
|
4569
4958
|
DEFAULT_UI_VARIANT: () => DEFAULT_UI_VARIANT,
|
|
4570
4959
|
ENVIRONMENTAL_ERROR_PREFIXES: () => ENVIRONMENTAL_ERROR_PREFIXES,
|
|
4960
|
+
LEGACY_TTS_VOICE_ID_MAP: () => LEGACY_TTS_VOICE_ID_MAP,
|
|
4571
4961
|
MAX_CREDIT_TOPUP_CENTS: () => MAX_CREDIT_TOPUP_CENTS,
|
|
4572
4962
|
MAX_DEEPLINK_LENGTH: () => MAX_DEEPLINK_LENGTH,
|
|
4573
4963
|
MIN_CREDIT_TOPUP_CENTS: () => MIN_CREDIT_TOPUP_CENTS,
|
|
@@ -4589,10 +4979,15 @@ __export(dist_exports, {
|
|
|
4589
4979
|
PLATFORM_ADMIN_EMAIL: () => PLATFORM_ADMIN_EMAIL,
|
|
4590
4980
|
SELECTOR_PRIORITIES: () => SELECTOR_PRIORITIES,
|
|
4591
4981
|
SHARED_PRICING_PLAN_MAP: () => SHARED_PRICING_PLAN_MAP,
|
|
4982
|
+
STT_MODEL_OPTIONS: () => STT_MODEL_OPTIONS,
|
|
4983
|
+
STT_PROVIDER_OPTIONS: () => STT_PROVIDER_OPTIONS,
|
|
4592
4984
|
SUPPORT_EMAIL: () => SUPPORT_EMAIL,
|
|
4593
4985
|
TRANSIENT_CONTROL_ROLES: () => TRANSIENT_CONTROL_ROLES,
|
|
4594
4986
|
TRIAL_DURATION_DAYS: () => TRIAL_DURATION_DAYS,
|
|
4987
|
+
TTS_MODEL_OPTIONS: () => TTS_MODEL_OPTIONS,
|
|
4988
|
+
TTS_VOICE_OPTIONS: () => TTS_VOICE_OPTIONS,
|
|
4595
4989
|
UI_VARIANTS: () => UI_VARIANTS,
|
|
4990
|
+
VOICE_PREVIEW_SAMPLE: () => VOICE_PREVIEW_SAMPLE,
|
|
4596
4991
|
accumulateStatements: () => accumulateStatements,
|
|
4597
4992
|
aiCostToCreditCents: () => aiCostToCreditCents,
|
|
4598
4993
|
analyzeMobileSelectorQuality: () => analyzeMobileSelectorQuality,
|
|
@@ -4655,16 +5050,23 @@ __export(dist_exports, {
|
|
|
4655
5050
|
isRunnableStep: () => isRunnableStep,
|
|
4656
5051
|
isSafeDeepLink: () => isSafeDeepLink,
|
|
4657
5052
|
isSameRegistrableDomainHost: () => isSameRegistrableDomainHost,
|
|
5053
|
+
isSttModelForProvider: () => isSttModelForProvider,
|
|
4658
5054
|
isTestReadyForPlatform: () => isTestReadyForPlatform,
|
|
4659
5055
|
isTrialExpired: () => isTrialExpired,
|
|
4660
5056
|
isUIVariant: () => isUIVariant,
|
|
4661
5057
|
isValidMobileAppId: () => isValidMobileAppId,
|
|
5058
|
+
isValidSttProvider: () => isValidSttProvider,
|
|
5059
|
+
isValidTtsModel: () => isValidTtsModel,
|
|
5060
|
+
isValidTtsVoiceId: () => isValidTtsVoiceId,
|
|
4662
5061
|
mediumForMobilePlatform: () => mediumForMobilePlatform,
|
|
4663
5062
|
mobileCredentialPlaceholder: () => mobileCredentialPlaceholder,
|
|
4664
5063
|
mobilePlatformFromMedium: () => mobilePlatformFromMedium,
|
|
4665
5064
|
normalizeControlName: () => normalizeControlName,
|
|
5065
|
+
normalizeTtsVoiceId: () => normalizeTtsVoiceId,
|
|
4666
5066
|
otherMobilePlatform: () => otherMobilePlatform,
|
|
4667
5067
|
parenDepthDelta: () => parenDepthDelta,
|
|
5068
|
+
pickDisplayRun: () => pickDisplayRun,
|
|
5069
|
+
pickLatestHealthRun: () => pickLatestHealthRun,
|
|
4668
5070
|
placeholderizeRecordedMobileAction: () => placeholderizeRecordedMobileAction,
|
|
4669
5071
|
placeholderizeRecordedTypeValue: () => placeholderizeRecordedTypeValue,
|
|
4670
5072
|
planHasBillingPortal: () => planHasBillingPortal,
|
|
@@ -4674,7 +5076,9 @@ __export(dist_exports, {
|
|
|
4674
5076
|
resolveCrossOriginConfigOverride: () => resolveCrossOriginConfigOverride,
|
|
4675
5077
|
resolveMobileStepValuePlaceholders: () => resolveMobileStepValuePlaceholders,
|
|
4676
5078
|
resolvePlatformLocatorBundle: () => resolvePlatformLocatorBundle,
|
|
5079
|
+
resolveSttModelForProvider: () => resolveSttModelForProvider,
|
|
4677
5080
|
safeOrigin: () => safeOrigin,
|
|
5081
|
+
sanitizeSttErrorMessage: () => sanitizeSttErrorMessage,
|
|
4678
5082
|
sanitizeTestVariables: () => sanitizeTestVariables,
|
|
4679
5083
|
selectorCandidatesForStep: () => selectorCandidatesForStep,
|
|
4680
5084
|
selectorForValue: () => selectorForValue,
|
|
@@ -4684,6 +5088,7 @@ __export(dist_exports, {
|
|
|
4684
5088
|
stepsNeedingReviewForPlatform: () => stepsNeedingReviewForPlatform,
|
|
4685
5089
|
stripEnvironmentalPrefix: () => stripEnvironmentalPrefix,
|
|
4686
5090
|
stripLoginScaffolding: () => stripLoginScaffolding,
|
|
5091
|
+
sttModelsForProvider: () => sttModelsForProvider,
|
|
4687
5092
|
summarizeHealth: () => summarizeHealth,
|
|
4688
5093
|
summarizeMobileReadiness: () => summarizeMobileReadiness,
|
|
4689
5094
|
summarizeRunHealth: () => summarizeRunHealth,
|
|
@@ -4708,6 +5113,7 @@ var init_dist = __esm({
|
|
|
4708
5113
|
init_agent_env();
|
|
4709
5114
|
init_test_counting();
|
|
4710
5115
|
init_origin_domain();
|
|
5116
|
+
init_voice_config();
|
|
4711
5117
|
}
|
|
4712
5118
|
});
|
|
4713
5119
|
|
|
@@ -79526,7 +79932,7 @@ var require_agentic_discovery_prompts = __commonJS({
|
|
|
79526
79932
|
"## Test-quality bar",
|
|
79527
79933
|
"",
|
|
79528
79934
|
"- ONE `test.describe(<featureName>)` block per file with ONE `test(...)` call inside.",
|
|
79529
|
-
"- Use `await page.goto(<relativePath>)` \u2014 baseURL is set in the runner config; never hardcode the host.",
|
|
79935
|
+
"- 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')`.",
|
|
79530
79936
|
"- Every test must contain at least one `expect(...)` assertion.",
|
|
79531
79937
|
"- **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(...)`.",
|
|
79532
79938
|
" Prefer: `expect(locator).toBeVisible()`, `expect(page).toHaveURL(/regex/)`, `expect(locator).toHaveText(...)`.",
|
|
@@ -81762,7 +82168,7 @@ var require_agentic_discovery_prompts = __commonJS({
|
|
|
81762
82168
|
lines.push("## Test-quality bar");
|
|
81763
82169
|
lines.push("");
|
|
81764
82170
|
lines.push("- ONE `test.describe(<featureName>)` block per file with ONE `test(...)` inside.");
|
|
81765
|
-
lines.push("- Use `await page.goto(<relativePath>)` \u2014 never hardcode the host.");
|
|
82171
|
+
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')`).");
|
|
81766
82172
|
lines.push("- Every test MUST include the `@generated` tag.");
|
|
81767
82173
|
lines.push("- Each test creates its OWN run-scoped data; never depend on pre-seeded data.");
|
|
81768
82174
|
lines.push("");
|
|
@@ -82316,6 +82722,21 @@ var require_agentic_discovery_prompts = __commonJS({
|
|
|
82316
82722
|
lines.push(`Use \`browser_navigate\` with the full URL (prepend ${baseUrl}). In the`);
|
|
82317
82723
|
lines.push("emitted test code keep URLs RELATIVE (`page.goto('/path')`) \u2014 the Playwright");
|
|
82318
82724
|
lines.push("config sets `baseURL`.");
|
|
82725
|
+
{
|
|
82726
|
+
const gotoBasePath = (() => {
|
|
82727
|
+
try {
|
|
82728
|
+
const p = new URL(baseUrl).pathname.replace(/\/+$/, "");
|
|
82729
|
+
return p && p !== "/" ? p : void 0;
|
|
82730
|
+
} catch {
|
|
82731
|
+
return void 0;
|
|
82732
|
+
}
|
|
82733
|
+
})();
|
|
82734
|
+
if (gotoBasePath) {
|
|
82735
|
+
lines.push(`IMPORTANT: the base URL has a PATH (\`${gotoBasePath}\`) and a leading-slash path escapes`);
|
|
82736
|
+
lines.push(`it to the origin root. Every \`page.goto\` path MUST start with \`${gotoBasePath}\` \u2014 the`);
|
|
82737
|
+
lines.push(`app home is \`page.goto('${gotoBasePath}')\`, NOT \`page.goto('/')\`.`);
|
|
82738
|
+
}
|
|
82739
|
+
}
|
|
82319
82740
|
lines.push("");
|
|
82320
82741
|
lines.push("## The scenario");
|
|
82321
82742
|
lines.push("");
|
|
@@ -82999,12 +83420,14 @@ var require_discover_generator = __commonJS({
|
|
|
82999
83420
|
exports.detectScenarioSignals = detectScenarioSignals;
|
|
83000
83421
|
exports.scenarioStepsCoverAuth = scenarioStepsCoverAuth;
|
|
83001
83422
|
exports.getLoginCommands = getLoginCommands;
|
|
83423
|
+
exports.hasConfiguredLoginCredentials = hasConfiguredLoginCredentials;
|
|
83002
83424
|
exports.computeSkeletonTags = computeSkeletonTags;
|
|
83003
83425
|
exports.shouldReplayAuthRecipe = shouldReplayAuthRecipe;
|
|
83004
83426
|
exports.buildPageContext = buildPageContext;
|
|
83005
83427
|
exports.stripTypescriptSyntax = stripTypescriptSyntax;
|
|
83006
83428
|
exports.stripImportDeclarations = stripImportDeclarations;
|
|
83007
83429
|
exports.stripStringsAndComments = stripStringsAndComments;
|
|
83430
|
+
exports.gotoBasePathFromBaseUrl = gotoBasePathFromBaseUrl;
|
|
83008
83431
|
exports.sanitizeCode = sanitizeCode;
|
|
83009
83432
|
exports.validateTests = validateTests;
|
|
83010
83433
|
exports.formatEvidenceForLiveGenerate = formatEvidenceForLiveGenerate;
|
|
@@ -83591,11 +84014,15 @@ var require_discover_generator = __commonJS({
|
|
|
83591
84014
|
const replayPlan = (0, auth_recipe_js_1.buildReplayPlan)(login);
|
|
83592
84015
|
return { ...login, replayPlan };
|
|
83593
84016
|
}
|
|
83594
|
-
function
|
|
84017
|
+
function hasConfiguredLoginCredentials(credentials) {
|
|
84018
|
+
return ["TEST_USER_EMAIL", "TEST_USER_PASSWORD"].some((k) => typeof credentials?.[k] === "string" && credentials[k].trim().length > 0);
|
|
84019
|
+
}
|
|
84020
|
+
function computeSkeletonTags(targetPages, pages, opts) {
|
|
83595
84021
|
const isAuthPage = (p) => p.pageType === "AUTH" || /\/(auth|login|sign-?in)\b/i.test(p.route);
|
|
84022
|
+
const siteHasAuthEvidence = pages.some(isAuthPage) || opts?.extraAuthEvidence === true;
|
|
83596
84023
|
const targetPagesInSiteMap = pages.filter((p) => targetPages.includes(p.route));
|
|
83597
84024
|
const hasAuthPage = targetPagesInSiteMap.some(isAuthPage);
|
|
83598
|
-
const hasAuthRequiredPage = targetPagesInSiteMap.some((p) => p.requiresAuth && !isAuthPage(p));
|
|
84025
|
+
const hasAuthRequiredPage = siteHasAuthEvidence && targetPagesInSiteMap.some((p) => p.requiresAuth && !isAuthPage(p));
|
|
83599
84026
|
if (hasAuthPage && !hasAuthRequiredPage)
|
|
83600
84027
|
return ["@auth", "@discovered"];
|
|
83601
84028
|
if (hasAuthRequiredPage)
|
|
@@ -83603,7 +84030,7 @@ var require_discover_generator = __commonJS({
|
|
|
83603
84030
|
return ["@discovered", "@regression"];
|
|
83604
84031
|
}
|
|
83605
84032
|
function shouldReplayAuthRecipe(targetPages, siteMapPages, recipe) {
|
|
83606
|
-
return !!recipe && computeSkeletonTags(targetPages, siteMapPages).includes("@needs-auth");
|
|
84033
|
+
return !!recipe && computeSkeletonTags(targetPages, siteMapPages, { extraAuthEvidence: true }).includes("@needs-auth");
|
|
83607
84034
|
}
|
|
83608
84035
|
function isLowConfidenceSnapshotKind(kind) {
|
|
83609
84036
|
return kind === "missing" || kind === "error" || kind === "timeout" || kind === "empty" || kind === "ai-capture_fallback";
|
|
@@ -84049,6 +84476,16 @@ ${elementContext}${formContext}${observationContext}${interactionContext}${lowCo
|
|
|
84049
84476
|
function findSensitiveLiteralLeaks(code, testVariables) {
|
|
84050
84477
|
return buildSensitiveEnvReplacements(testVariables).filter((replacement) => code.includes(replacement.value)).map((replacement) => replacement.key);
|
|
84051
84478
|
}
|
|
84479
|
+
function gotoBasePathFromBaseUrl(baseUrl) {
|
|
84480
|
+
if (!baseUrl)
|
|
84481
|
+
return void 0;
|
|
84482
|
+
try {
|
|
84483
|
+
const path = new URL(baseUrl).pathname.replace(/\/+$/, "");
|
|
84484
|
+
return path && path !== "/" ? path : void 0;
|
|
84485
|
+
} catch {
|
|
84486
|
+
return void 0;
|
|
84487
|
+
}
|
|
84488
|
+
}
|
|
84052
84489
|
function sanitizeCode(code, options) {
|
|
84053
84490
|
let result = code;
|
|
84054
84491
|
const isNetworkIdleWaitLine = (line) => /^\s*await page\.waitForLoadState\((['"])networkidle\1\);\s*$/.test(line);
|
|
@@ -84071,12 +84508,24 @@ ${elementContext}${formContext}${observationContext}${interactionContext}${lowCo
|
|
|
84071
84508
|
result = result.replace(/\s*await page\.waitForTimeout\(\d+\);\s*/g, "\n");
|
|
84072
84509
|
result = result.replace(/(await page\.goto\((?:[^()()]|\([^()]*\))*\);)\s*\n\s*await expect\(page\)\.toHaveURL\((?:[^()()]|\([^()]*\))*\);/g, (_match, gotoLine) => gotoLine);
|
|
84073
84510
|
result = result.replace(/const baseUrl\s*=\s*process\.env\.BASE_URL[^;]*;\s*\n/g, "");
|
|
84074
|
-
result = result.replace(
|
|
84511
|
+
result = result.replace(
|
|
84512
|
+
/page\.goto\(`\$\{baseUrl\}([^`]*)`/g,
|
|
84513
|
+
// Empty template path (goto(`${baseUrl}`)) means the app home → '/', which the
|
|
84514
|
+
// path-scoped basePath pass below re-joins to the real base (e.g. '/wing').
|
|
84515
|
+
(_match, path) => `page.goto('${path || "/"}'`
|
|
84516
|
+
);
|
|
84075
84517
|
result = result.replace(/test\s*\(\s*(['"`].*?['"`])\s*,\s*\{\s*tags\s*:\s*\[/g, (match) => match.replace("tags", "tag"));
|
|
84076
84518
|
result = result.replace(/page\.goto\(\s*['"]https?:\/\/[^/'"]+(\/[^'"]*)?['"]/g, (_match, path) => `page.goto('${path || "/"}'`);
|
|
84077
84519
|
result = result.replace(/waitForURL\(\s*['"]https?:\/\/[^/'"]+(\/.+?)['"]\s*\)/g, (_match, path) => `waitForURL(/${path.replace(/\//g, "\\/")}/)`);
|
|
84078
84520
|
result = result.replace(/toHaveURL\(\s*['"]https?:\/\/[^/'"]+(\/.+?)['"]\s*\)/g, (_match, path) => `toHaveURL(/${path.replace(/\//g, "\\/")}/)`);
|
|
84079
84521
|
result = result.replace(/(waitForURL|toHaveURL)\(\/(.*?)\\\/\/\)/g, (_match, fn, inner) => `${fn}(/${inner}/)`);
|
|
84522
|
+
const basePath = options?.basePath && options.basePath !== "/" ? options.basePath.replace(/\/+$/, "") : void 0;
|
|
84523
|
+
if (basePath) {
|
|
84524
|
+
result = result.replace(/\b(goto|waitForURL|toHaveURL)\(\s*(['"])(\/(?!\/)[^'"]*)\2/g, (_match, fn, _quote, path) => {
|
|
84525
|
+
const joined = path === basePath || path.startsWith(basePath + "/") ? path : basePath + (path === "/" ? "" : path);
|
|
84526
|
+
return `${fn}('${joined}'`;
|
|
84527
|
+
});
|
|
84528
|
+
}
|
|
84080
84529
|
result = result.replace(/\)\)\.(first\(\)|nth\(\d+\)|last\(\))\./g, (match, method) => `).${method}).`);
|
|
84081
84530
|
result = result.replace(/^(\s+)expect\(page\b/gm, "$1await expect(page");
|
|
84082
84531
|
result = result.replace(/^(\s+)page\.waitForURL\(/gm, "$1await page.waitForURL(");
|
|
@@ -84143,7 +84592,8 @@ ${testOpen}`;
|
|
|
84143
84592
|
} : void 0;
|
|
84144
84593
|
const playwrightCode = sanitizeCode(String(test.playwrightCode || ""), {
|
|
84145
84594
|
sensitiveEnvMap: options?.sensitiveEnvMap,
|
|
84146
|
-
authStamp
|
|
84595
|
+
authStamp,
|
|
84596
|
+
basePath: options?.basePath
|
|
84147
84597
|
});
|
|
84148
84598
|
const leakedSensitiveKeys = findSensitiveLiteralLeaks(playwrightCode, options?.sensitiveEnvMap);
|
|
84149
84599
|
const authGuardStamped = !!options?.authRecipe && rawTags.includes("@needs-auth") && !rawTags.includes("@auth") && process.env.SHARED_AUTH_INJECT === "true";
|
|
@@ -85545,7 +85995,11 @@ ${outcomesText}
|
|
|
85545
85995
|
${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."}
|
|
85546
85996
|
The base URL is: ${baseUrl}
|
|
85547
85997
|
When navigating, use \`browser_navigate\` with the full URL (prepend ${baseUrl}).
|
|
85548
|
-
In your generated test code, keep URLs RELATIVE (e.g. \`page.goto('/path')\`) \u2014 Playwright config has baseURL set
|
|
85998
|
+
In your generated test code, keep URLs RELATIVE (e.g. \`page.goto('/path')\`) \u2014 Playwright config has baseURL set.${(() => {
|
|
85999
|
+
const basePath = gotoBasePathFromBaseUrl(baseUrl);
|
|
86000
|
+
return basePath ? `
|
|
86001
|
+
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('/')\`).` : "";
|
|
86002
|
+
})()}
|
|
85549
86003
|
|
|
85550
86004
|
${evidenceBlock}${credentialNote}
|
|
85551
86005
|
## ASSERTION VERIFICATION WORKFLOW
|
|
@@ -85753,7 +86207,9 @@ ${tailBlock}`;
|
|
|
85753
86207
|
}
|
|
85754
86208
|
}
|
|
85755
86209
|
});
|
|
85756
|
-
const tags = computeSkeletonTags(scenario.targetPages, siteMap.pages
|
|
86210
|
+
const tags = computeSkeletonTags(scenario.targetPages, siteMap.pages, {
|
|
86211
|
+
extraAuthEvidence: !!opts?.recipe || hasConfiguredLoginCredentials(credentials)
|
|
86212
|
+
});
|
|
85757
86213
|
const baseSkeletonHadRegression = tags.includes("@regression");
|
|
85758
86214
|
const hasVerifiedRecipe = !!opts?.recipe && tags.includes("@needs-auth");
|
|
85759
86215
|
const systemPrompt = buildScenarioSystemPrompt(scenario, baseUrl, credentials, tags, siteMap.pages, { appBrief: opts?.appBrief, availableVariableKeys: opts?.availableVariableKeys, hasVerifiedRecipe, knowledgeContext: opts?.knowledgeContext });
|
|
@@ -85962,7 +86418,7 @@ Do NOT add steps you did not execute. Do NOT skip steps from the recording.${led
|
|
|
85962
86418
|
const suspectedBugDeclared = toolArgs.suspectedBug === true;
|
|
85963
86419
|
const bugDescription = String(toolArgs.bugDescription || "").slice(0, 500).replace(/[\u0000-\u001F\u007F]+/g, " ").replace(/\s+/g, " ").trim();
|
|
85964
86420
|
const testObj = { name, description, playwrightCode: code, tags };
|
|
85965
|
-
const validated = validateTests([testObj], logs2, { sensitiveEnvMap: credentials, authRecipe: opts?.recipe ?? null });
|
|
86421
|
+
const validated = validateTests([testObj], logs2, { sensitiveEnvMap: credentials, authRecipe: opts?.recipe ?? null, basePath: gotoBasePathFromBaseUrl(baseUrl) });
|
|
85966
86422
|
if (validated.length > 0) {
|
|
85967
86423
|
if (actionLedger.length > lastRenderedLedgerEntryCount && actionLedger.length > 0) {
|
|
85968
86424
|
const renderedLedger = renderSessionRecording(actionLedger, credentials);
|
|
@@ -86081,7 +86537,12 @@ Do NOT add steps you did not execute. Do NOT skip steps from the recording.${led
|
|
|
86081
86537
|
const creditedControlSig = revealVerdict === "revealed" ? assertedSigs.find((s) => revealedControls.has(s)) ?? null : null;
|
|
86082
86538
|
let strengthVerdict = null;
|
|
86083
86539
|
try {
|
|
86084
|
-
strengthVerdict = (0, shared_1.validateAssertionStrength)(validated[0].playwrightCode, {
|
|
86540
|
+
strengthVerdict = (0, shared_1.validateAssertionStrength)(validated[0].playwrightCode, {
|
|
86541
|
+
revealedControls,
|
|
86542
|
+
// Opt-in additive-contradiction gate (see assertion-strength.ts). On Fargate this
|
|
86543
|
+
// env must be dispatcher-forwarded to take effect here; off → byte-identical.
|
|
86544
|
+
flagContradictedAdditions: process.env.STRENGTH_ADDITIVE_CONTRADICTION_GATE === "true"
|
|
86545
|
+
});
|
|
86085
86546
|
} catch (err) {
|
|
86086
86547
|
logs2.push(` Strength gate errored \u2014 skipping (fail-open): ${String(err)}`);
|
|
86087
86548
|
}
|
|
@@ -301293,7 +301754,8 @@ ${currentError.slice(0, 2e3)}`
|
|
|
301293
301754
|
}
|
|
301294
301755
|
lastHealedCode = (0, discover_generator_js_1.sanitizeCode)(stripCodeFences(fixedCode), {
|
|
301295
301756
|
sensitiveEnvMap: testVariables,
|
|
301296
|
-
authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(playwrightCode, lastFailureError, logs2)
|
|
301757
|
+
authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(playwrightCode, lastFailureError, logs2),
|
|
301758
|
+
basePath: (0, discover_generator_js_1.gotoBasePathFromBaseUrl)(baseUrl)
|
|
301297
301759
|
});
|
|
301298
301760
|
logs2.push("\u{1F680} Running fixed code natively...");
|
|
301299
301761
|
let verifyResult;
|
|
@@ -302747,7 +303209,8 @@ ${errorContent}`;
|
|
|
302747
303209
|
}
|
|
302748
303210
|
const candidateCode = (0, discover_generator_js_1.sanitizeCode)(stripCodeFences(fixedCode), {
|
|
302749
303211
|
sensitiveEnvMap: testVariables,
|
|
302750
|
-
authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(playwrightCode, options?.lastFailureError, logs2)
|
|
303212
|
+
authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(playwrightCode, options?.lastFailureError, logs2),
|
|
303213
|
+
basePath: (0, discover_generator_js_1.gotoBasePathFromBaseUrl)(baseUrl)
|
|
302751
303214
|
});
|
|
302752
303215
|
{
|
|
302753
303216
|
const syntaxCheckCode = (0, discover_generator_js_1.stripTypescriptSyntax)((0, discover_generator_js_1.stripImportDeclarations)(candidateCode));
|
|
@@ -304494,7 +304957,8 @@ var require_grok_per_test_healer = __commonJS({
|
|
|
304494
304957
|
}
|
|
304495
304958
|
const healedCode = (0, discover_generator_js_1.sanitizeCode)(payload.playwrightCode, {
|
|
304496
304959
|
sensitiveEnvMap: secrets,
|
|
304497
|
-
authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(playwrightCode, options.lastFailureError, logs2)
|
|
304960
|
+
authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(playwrightCode, options.lastFailureError, logs2),
|
|
304961
|
+
basePath: (0, discover_generator_js_1.gotoBasePathFromBaseUrl)(baseUrl)
|
|
304498
304962
|
});
|
|
304499
304963
|
const leakedKey = findLeakedCredential(healedCode, secrets);
|
|
304500
304964
|
if (leakedKey) {
|
|
@@ -305667,7 +306131,8 @@ var require_grok_batch_healer = __commonJS({
|
|
|
305667
306131
|
}
|
|
305668
306132
|
const healedCode = (0, discover_generator_js_1.sanitizeCode)(verdict.playwrightCode, {
|
|
305669
306133
|
sensitiveEnvMap: secrets,
|
|
305670
|
-
authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(test.playwrightCode, test.lastFailureError, logs2)
|
|
306134
|
+
authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(test.playwrightCode, test.lastFailureError, logs2),
|
|
306135
|
+
basePath: (0, discover_generator_js_1.gotoBasePathFromBaseUrl)(baseUrl)
|
|
305671
306136
|
});
|
|
305672
306137
|
const leakedKey = findLeakedCredential(healedCode, secrets);
|
|
305673
306138
|
if (leakedKey) {
|
|
@@ -310679,7 +311144,7 @@ var require_discover_explorer = __commonJS({
|
|
|
310679
311144
|
},
|
|
310680
311145
|
requiresAuth: {
|
|
310681
311146
|
type: "boolean",
|
|
310682
|
-
description: "
|
|
311147
|
+
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."
|
|
310683
311148
|
},
|
|
310684
311149
|
observations: {
|
|
310685
311150
|
type: "array",
|
|
@@ -310908,7 +311373,7 @@ You do NOT have a \`record_journey\` tool - your job is mapping, not testing. Do
|
|
|
310908
311373
|
2. Action buttons: submit, save, delete, create, edit - include label, triggersSubmit
|
|
310909
311374
|
3. Navigation links to OTHER pages within the app - include label, triggersNavigation
|
|
310910
311375
|
4. Skip repeated navigation chrome you already cataloged (sidebar/header links appear on every page)
|
|
310911
|
-
- **requiresAuth**:
|
|
311376
|
+
- **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)
|
|
310912
311377
|
- **observations**: empty array (you are mapping, not interacting)
|
|
310913
311378
|
|
|
310914
311379
|
## Loading and readiness
|
|
@@ -314606,9 +315071,13 @@ var require_agentic_survey_prompts = __commonJS({
|
|
|
314606
315071
|
' route: string; // path-only, e.g. "/dashboard/profile"',
|
|
314607
315072
|
' type: string; // "AUTH" | "DASHBOARD" | "FORM" | "LIST" | ...',
|
|
314608
315073
|
" summary: string; // 1-line of key elements/affordances",
|
|
314609
|
-
" requiresAuth?: boolean; //
|
|
314610
|
-
" //
|
|
314611
|
-
" //
|
|
315074
|
+
" requiresAuth?: boolean; // OBSERVED evidence ONLY: you were redirected",
|
|
315075
|
+
" // to a login page, got 401/403, or hit an",
|
|
315076
|
+
" // explicit sign-in wall. NEVER infer it from",
|
|
315077
|
+
" // content merely LOOKING account-ish \u2014 if you",
|
|
315078
|
+
" // read the page without logging in, it does",
|
|
315079
|
+
" // NOT require auth. A site with no login at",
|
|
315080
|
+
" // all has requiresAuth=false everywhere.",
|
|
314612
315081
|
" }>;",
|
|
314613
315082
|
' flows?: string[]; // ["/login \u2192 /dashboard (submit form)"]',
|
|
314614
315083
|
" observations?: string[]; // things YOU noticed in the loop",
|
|
@@ -336176,7 +336645,7 @@ var require_package4 = __commonJS({
|
|
|
336176
336645
|
"package.json"(exports, module) {
|
|
336177
336646
|
module.exports = {
|
|
336178
336647
|
name: "@validate.qa/runner",
|
|
336179
|
-
version: "1.0.
|
|
336648
|
+
version: "1.0.38",
|
|
336180
336649
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
336181
336650
|
bin: {
|
|
336182
336651
|
"validate-runner": "dist/cli.js"
|