@hasna/testers 0.0.37 → 0.0.39
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/index.js +99 -11
- package/dist/index.js +92 -7
- package/dist/lib/quick-qa.d.ts +11 -2
- package/dist/lib/quick-qa.d.ts.map +1 -1
- package/dist/lib/runner.d.ts +2 -0
- package/dist/lib/runner.d.ts.map +1 -1
- package/dist/lib/screenshotter.d.ts +1 -1
- package/dist/lib/screenshotter.d.ts.map +1 -1
- package/dist/mcp/index.js +24 -7
- package/dist/server/index.js +24 -6
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -16697,12 +16697,19 @@ var init_personas = __esm(() => {
|
|
|
16697
16697
|
// src/lib/screenshotter.ts
|
|
16698
16698
|
import { mkdirSync as mkdirSync7, existsSync as existsSync8, writeFileSync as writeFileSync2 } from "fs";
|
|
16699
16699
|
import { join as join10 } from "path";
|
|
16700
|
-
function
|
|
16701
|
-
|
|
16700
|
+
function truncateSlug(slug, maxLength) {
|
|
16701
|
+
if (slug.length <= maxLength)
|
|
16702
|
+
return slug;
|
|
16703
|
+
const truncated = slug.slice(0, maxLength).replace(/-+$/g, "");
|
|
16704
|
+
return truncated || slug.slice(0, maxLength);
|
|
16705
|
+
}
|
|
16706
|
+
function slugify(text, maxLength) {
|
|
16707
|
+
const slug = text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
16708
|
+
return maxLength ? truncateSlug(slug, maxLength) : slug;
|
|
16702
16709
|
}
|
|
16703
16710
|
function generateFilename(stepNumber, action) {
|
|
16704
16711
|
const padded = String(stepNumber).padStart(3, "0");
|
|
16705
|
-
const slug = slugify(action);
|
|
16712
|
+
const slug = slugify(action, MAX_ACTION_SLUG_LENGTH);
|
|
16706
16713
|
return `${padded}_${slug}.png`;
|
|
16707
16714
|
}
|
|
16708
16715
|
function formatDate(date) {
|
|
@@ -16716,7 +16723,8 @@ function getScreenshotDir(baseDir, runId, scenarioSlug, projectName, timestamp)
|
|
|
16716
16723
|
const project = projectName ?? "default";
|
|
16717
16724
|
const dateDir = formatDate(now2);
|
|
16718
16725
|
const timeDir = `${formatTime(now2)}_${runId.slice(0, 8)}`;
|
|
16719
|
-
|
|
16726
|
+
const safeScenarioSlug = slugify(scenarioSlug, MAX_SCENARIO_SLUG_LENGTH) || "scenario";
|
|
16727
|
+
return join10(baseDir, project, dateDir, timeDir, safeScenarioSlug);
|
|
16720
16728
|
}
|
|
16721
16729
|
function ensureDir(dirPath) {
|
|
16722
16730
|
if (!existsSync8(dirPath)) {
|
|
@@ -16874,7 +16882,7 @@ class Screenshotter {
|
|
|
16874
16882
|
};
|
|
16875
16883
|
}
|
|
16876
16884
|
}
|
|
16877
|
-
var DEFAULT_BASE_DIR;
|
|
16885
|
+
var MAX_ACTION_SLUG_LENGTH = 80, MAX_SCENARIO_SLUG_LENGTH = 96, DEFAULT_BASE_DIR;
|
|
16878
16886
|
var init_screenshotter = __esm(() => {
|
|
16879
16887
|
init_paths();
|
|
16880
16888
|
DEFAULT_BASE_DIR = join10(getTestersDir(), "screenshots");
|
|
@@ -18137,6 +18145,7 @@ __export(exports_runner, {
|
|
|
18137
18145
|
runByFilter: () => runByFilter,
|
|
18138
18146
|
runBatch: () => runBatch,
|
|
18139
18147
|
resolveScenariosForRun: () => resolveScenariosForRun,
|
|
18148
|
+
resolveAgentMaxTurns: () => resolveAgentMaxTurns,
|
|
18140
18149
|
resolveAgentApiKeyForModel: () => resolveAgentApiKeyForModel,
|
|
18141
18150
|
onRunEvent: () => onRunEvent,
|
|
18142
18151
|
applyStructuredAssertionsToResult: () => applyStructuredAssertionsToResult
|
|
@@ -18154,6 +18163,14 @@ function emit(event) {
|
|
|
18154
18163
|
function resolveAgentApiKeyForModel(model, explicitApiKey, configuredAnthropicApiKey) {
|
|
18155
18164
|
return resolveProviderApiKeyForModel(model, explicitApiKey, configuredAnthropicApiKey);
|
|
18156
18165
|
}
|
|
18166
|
+
function resolveAgentMaxTurns(options) {
|
|
18167
|
+
if (options.maxTurns !== undefined) {
|
|
18168
|
+
const parsed = Math.floor(options.maxTurns);
|
|
18169
|
+
if (Number.isFinite(parsed) && parsed > 0)
|
|
18170
|
+
return parsed;
|
|
18171
|
+
}
|
|
18172
|
+
return options.minimal ? 10 : 30;
|
|
18173
|
+
}
|
|
18157
18174
|
function assertionDescription(result) {
|
|
18158
18175
|
return result.assertion.description || `${result.assertion.type}${result.assertion.selector ? ` ${result.assertion.selector}` : ""}`;
|
|
18159
18176
|
}
|
|
@@ -18373,7 +18390,7 @@ async function runSingleScenario(scenario, runId, options) {
|
|
|
18373
18390
|
runId,
|
|
18374
18391
|
sessionId: result.id,
|
|
18375
18392
|
baseUrl: options.url,
|
|
18376
|
-
maxTurns: effectiveOptions
|
|
18393
|
+
maxTurns: resolveAgentMaxTurns(effectiveOptions),
|
|
18377
18394
|
a11y: effectiveOptions.a11y,
|
|
18378
18395
|
persona: persona ? {
|
|
18379
18396
|
name: persona.name,
|
|
@@ -93980,7 +93997,7 @@ import chalk6 from "chalk";
|
|
|
93980
93997
|
// package.json
|
|
93981
93998
|
var package_default = {
|
|
93982
93999
|
name: "@hasna/testers",
|
|
93983
|
-
version: "0.0.
|
|
94000
|
+
version: "0.0.39",
|
|
93984
94001
|
description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
|
|
93985
94002
|
type: "module",
|
|
93986
94003
|
main: "dist/index.js",
|
|
@@ -94558,6 +94575,7 @@ function formatSmokeReport(result) {
|
|
|
94558
94575
|
|
|
94559
94576
|
// src/lib/quick-qa.ts
|
|
94560
94577
|
init_health_scan();
|
|
94578
|
+
var DEFAULT_QUICK_QA_OVERALL_TIMEOUT_MS = 120000;
|
|
94561
94579
|
var DEFAULT_QUICK_QA_SCANNERS = [
|
|
94562
94580
|
"console",
|
|
94563
94581
|
"network",
|
|
@@ -94608,7 +94626,17 @@ function resolveQuickQaSelection(options = {}) {
|
|
|
94608
94626
|
}
|
|
94609
94627
|
async function runQuickQa(options) {
|
|
94610
94628
|
const start = Date.now();
|
|
94611
|
-
const
|
|
94629
|
+
const timeoutMs = options.overallTimeoutMs ?? DEFAULT_QUICK_QA_OVERALL_TIMEOUT_MS;
|
|
94630
|
+
return withQuickQaTimeout(runQuickQaUnbounded(options, start), {
|
|
94631
|
+
url: options.url,
|
|
94632
|
+
start,
|
|
94633
|
+
timeoutMs
|
|
94634
|
+
});
|
|
94635
|
+
}
|
|
94636
|
+
async function runQuickQaUnbounded(options, start) {
|
|
94637
|
+
const healthScanner = options.healthScanner ?? runHealthScan;
|
|
94638
|
+
const smokeRunner = options.smokeRunner ?? runSmoke;
|
|
94639
|
+
const health = await healthScanner({
|
|
94612
94640
|
url: options.url,
|
|
94613
94641
|
pages: options.pages,
|
|
94614
94642
|
projectId: options.projectId,
|
|
@@ -94618,7 +94646,7 @@ async function runQuickQa(options) {
|
|
|
94618
94646
|
maxPages: options.maxPages,
|
|
94619
94647
|
wcagLevel: options.wcagLevel
|
|
94620
94648
|
});
|
|
94621
|
-
const smoke = options.includeSmoke === false ? null : await
|
|
94649
|
+
const smoke = options.includeSmoke === false ? null : await smokeRunner({
|
|
94622
94650
|
url: options.url,
|
|
94623
94651
|
model: options.model,
|
|
94624
94652
|
headed: options.headed,
|
|
@@ -94632,6 +94660,62 @@ async function runQuickQa(options) {
|
|
|
94632
94660
|
durationMs: Date.now() - start
|
|
94633
94661
|
});
|
|
94634
94662
|
}
|
|
94663
|
+
function withQuickQaTimeout(promise, options) {
|
|
94664
|
+
if (!Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0)
|
|
94665
|
+
return promise;
|
|
94666
|
+
return new Promise((resolve, reject) => {
|
|
94667
|
+
const timer = setTimeout(() => {
|
|
94668
|
+
resolve(buildQuickQaTimeoutResult({
|
|
94669
|
+
url: options.url,
|
|
94670
|
+
start: options.start,
|
|
94671
|
+
timeoutMs: options.timeoutMs
|
|
94672
|
+
}));
|
|
94673
|
+
}, options.timeoutMs);
|
|
94674
|
+
promise.then((result) => {
|
|
94675
|
+
clearTimeout(timer);
|
|
94676
|
+
resolve(result);
|
|
94677
|
+
}, (error) => {
|
|
94678
|
+
clearTimeout(timer);
|
|
94679
|
+
reject(error);
|
|
94680
|
+
});
|
|
94681
|
+
});
|
|
94682
|
+
}
|
|
94683
|
+
function buildQuickQaTimeoutResult(input) {
|
|
94684
|
+
const now2 = new Date;
|
|
94685
|
+
const durationMs = Math.max(0, Date.now() - input.start);
|
|
94686
|
+
const message = `Quick QA timed out after ${input.timeoutMs}ms before all checks finished. Increase --overall-timeout or skip slow checks.`;
|
|
94687
|
+
const health = {
|
|
94688
|
+
url: input.url,
|
|
94689
|
+
scannedAt: now2.toISOString(),
|
|
94690
|
+
durationMs,
|
|
94691
|
+
totalIssues: 1,
|
|
94692
|
+
newIssues: 1,
|
|
94693
|
+
regressedIssues: 0,
|
|
94694
|
+
existingIssues: 0,
|
|
94695
|
+
results: [{
|
|
94696
|
+
url: input.url,
|
|
94697
|
+
pages: [input.url],
|
|
94698
|
+
scannedAt: now2.toISOString(),
|
|
94699
|
+
durationMs,
|
|
94700
|
+
issues: [{
|
|
94701
|
+
type: "performance",
|
|
94702
|
+
severity: "high",
|
|
94703
|
+
pageUrl: input.url,
|
|
94704
|
+
message,
|
|
94705
|
+
detail: {
|
|
94706
|
+
check: "quick-qa",
|
|
94707
|
+
timeoutMs: input.timeoutMs
|
|
94708
|
+
}
|
|
94709
|
+
}]
|
|
94710
|
+
}]
|
|
94711
|
+
};
|
|
94712
|
+
return buildQuickQaResult({
|
|
94713
|
+
url: input.url,
|
|
94714
|
+
health,
|
|
94715
|
+
smoke: null,
|
|
94716
|
+
durationMs
|
|
94717
|
+
});
|
|
94718
|
+
}
|
|
94635
94719
|
function buildQuickQaResult(input) {
|
|
94636
94720
|
const healthActionable = input.health.newIssues + input.health.regressedIssues;
|
|
94637
94721
|
const smokeIssues = input.smoke?.issuesFound.length ?? 0;
|
|
@@ -97476,7 +97560,7 @@ program2.command("remove <id>").alias("uninstall").description("Remove a scenari
|
|
|
97476
97560
|
program2.command("run [url] [description]").alias("test").description("Run test scenarios against a URL").option("-t, --tag <tag>", "Filter by tag (repeatable)", (val, acc) => {
|
|
97477
97561
|
acc.push(val);
|
|
97478
97562
|
return acc;
|
|
97479
|
-
}, []).option("-s, --scenario <id>", "Run specific scenario ID").option("-p, --priority <level>", "Filter by priority").option("--headed", "Run browser in headed mode", false).option("-m, --model <model>", "AI model to use").option("--parallel <n>", "Number of parallel browsers", "1").option("--json", "Output results as JSON", false).option("-o, --output <filepath>", "Write JSON results to file").option("--timeout <ms>", "Timeout in milliseconds").option("--from-todos", "Import scenarios from todos before running", false).option("--project <id>", "Project ID").option("-b, --background", "Start run in background and return immediately", false).option("--browser <engine>", "Browser engine: playwright (default), lightpanda (9x faster, no screenshots), or bun (native WKWebView, 11x faster, Bun canary required)", "playwright").option("--env <name>", "Use a named environment for the URL").option("--dry-run", "Print what would run without launching browser", false).option("--retry <n>", "Retry failed scenarios up to n times", "0").option("--samples <n>", "Run each scenario N times and report flakiness (pass rate)", "1").option("--flakiness-threshold <n>", "Pass rate threshold below which a scenario is marked flaky (0-1)", "0.95").option("--a11y [level]", "Run axe-core WCAG accessibility scan after each navigation (level: A, AA, AAA \u2014 default AA)").option("--self-heal", "Enable AI-powered selector repair when elements can't be found (requires judgeModel or ANTHROPIC_API_KEY)", false).option("--verbose", "Show per-step timing and full tool results", false).option("--watch-results", "When used with --background, poll and display live results table until run completes", false).option("--failed-only", "Only show failed/error scenarios in output (passed count shown as summary)", false).option("--smoke", "Run only smoke-tagged scenarios (fast validation suite, <2 min)", false).option("--minimal", "Fastest possible run: cheapest model, max parallelism, min turns (ideal for CI)", false).option("--github-comment", "Post pass/fail summary as a GitHub PR comment (requires GITHUB_TOKEN env var)", false).option("--pr <number>", "GitHub PR number (auto-detected from GITHUB_REF if not provided)").option("--persona <id>", "Override persona for this run (comma-separated IDs for divergence testing)").option("--max-cost <dollars>", "Hard budget cap in dollars \u2014 abort if estimated cost exceeds this (e.g. 0.50 for 50 cents)").option("--cache-max-age <seconds>", "Skip scenarios that passed at the same URL within this many seconds (0 = disabled)", "0").option("--diff", "Auto-detect changed files from git diff and run only relevant scenarios", false).option("--auto-generate", "If no scenarios exist, crawl the URL and generate scenarios automatically (enabled by default when a URL is given as the first arg)").option("--no-auto-generate", "Disable automatic scenario generation when no scenarios exist").option("--overall-timeout <ms>", "Hard overall timeout for the whole run in milliseconds (default 10 minutes)").option("-y, --yes", "Skip confirmation prompts (e.g. proceed past budget warnings)", false).action(async (urlArg, description, opts) => {
|
|
97563
|
+
}, []).option("-s, --scenario <id>", "Run specific scenario ID").option("-p, --priority <level>", "Filter by priority").option("--headed", "Run browser in headed mode", false).option("-m, --model <model>", "AI model to use").option("--parallel <n>", "Number of parallel browsers", "1").option("--json", "Output results as JSON", false).option("-o, --output <filepath>", "Write JSON results to file").option("--timeout <ms>", "Timeout in milliseconds").option("--from-todos", "Import scenarios from todos before running", false).option("--project <id>", "Project ID").option("-b, --background", "Start run in background and return immediately", false).option("--browser <engine>", "Browser engine: playwright (default), lightpanda (9x faster, no screenshots), or bun (native WKWebView, 11x faster, Bun canary required)", "playwright").option("--env <name>", "Use a named environment for the URL").option("--dry-run", "Print what would run without launching browser", false).option("--retry <n>", "Retry failed scenarios up to n times", "0").option("--samples <n>", "Run each scenario N times and report flakiness (pass rate)", "1").option("--flakiness-threshold <n>", "Pass rate threshold below which a scenario is marked flaky (0-1)", "0.95").option("--a11y [level]", "Run axe-core WCAG accessibility scan after each navigation (level: A, AA, AAA \u2014 default AA)").option("--self-heal", "Enable AI-powered selector repair when elements can't be found (requires judgeModel or ANTHROPIC_API_KEY)", false).option("--verbose", "Show per-step timing and full tool results", false).option("--watch-results", "When used with --background, poll and display live results table until run completes", false).option("--failed-only", "Only show failed/error scenarios in output (passed count shown as summary)", false).option("--smoke", "Run only smoke-tagged scenarios (fast validation suite, <2 min)", false).option("--minimal", "Fastest possible run: cheapest model, max parallelism, min turns (ideal for CI)", false).option("--max-turns <n>", "Maximum AI browser-agent turns before reporting an error").option("--github-comment", "Post pass/fail summary as a GitHub PR comment (requires GITHUB_TOKEN env var)", false).option("--pr <number>", "GitHub PR number (auto-detected from GITHUB_REF if not provided)").option("--persona <id>", "Override persona for this run (comma-separated IDs for divergence testing)").option("--max-cost <dollars>", "Hard budget cap in dollars \u2014 abort if estimated cost exceeds this (e.g. 0.50 for 50 cents)").option("--cache-max-age <seconds>", "Skip scenarios that passed at the same URL within this many seconds (0 = disabled)", "0").option("--diff", "Auto-detect changed files from git diff and run only relevant scenarios", false).option("--auto-generate", "If no scenarios exist, crawl the URL and generate scenarios automatically (enabled by default when a URL is given as the first arg)").option("--no-auto-generate", "Disable automatic scenario generation when no scenarios exist").option("--overall-timeout <ms>", "Hard overall timeout for the whole run in milliseconds (default 10 minutes)").option("-y, --yes", "Skip confirmation prompts (e.g. proceed past budget warnings)", false).action(async (urlArg, description, opts) => {
|
|
97480
97564
|
try {
|
|
97481
97565
|
const projectId = resolveProject2(opts.project);
|
|
97482
97566
|
let url2 = urlArg;
|
|
@@ -97626,6 +97710,7 @@ program2.command("run [url] [description]").alias("test").description("Run test
|
|
|
97626
97710
|
headed: opts.headed,
|
|
97627
97711
|
parallel: parseInt(opts.parallel, 10),
|
|
97628
97712
|
timeout: opts.timeout ? parseInt(opts.timeout, 10) : undefined,
|
|
97713
|
+
maxTurns: opts.maxTurns ? parseInt(opts.maxTurns, 10) : undefined,
|
|
97629
97714
|
projectId,
|
|
97630
97715
|
engine: opts.browser
|
|
97631
97716
|
});
|
|
@@ -97744,6 +97829,7 @@ program2.command("run [url] [description]").alias("test").description("Run test
|
|
|
97744
97829
|
headed: opts.headed,
|
|
97745
97830
|
parallel: parseInt(opts.parallel, 10),
|
|
97746
97831
|
timeout: opts.timeout ? parseInt(opts.timeout, 10) : undefined,
|
|
97832
|
+
maxTurns: opts.maxTurns ? parseInt(opts.maxTurns, 10) : undefined,
|
|
97747
97833
|
retry: parseInt(opts.retry ?? "0", 10),
|
|
97748
97834
|
projectId,
|
|
97749
97835
|
engine: opts.browser,
|
|
@@ -97848,6 +97934,7 @@ program2.command("run [url] [description]").alias("test").description("Run test
|
|
|
97848
97934
|
headed: opts.headed,
|
|
97849
97935
|
parallel: parseInt(opts.parallel, 10),
|
|
97850
97936
|
timeout: opts.timeout ? parseInt(opts.timeout, 10) : undefined,
|
|
97937
|
+
maxTurns: opts.maxTurns ? parseInt(opts.maxTurns, 10) : undefined,
|
|
97851
97938
|
retry: parseInt(opts.retry ?? "0", 10),
|
|
97852
97939
|
projectId,
|
|
97853
97940
|
engine: opts.browser,
|
|
@@ -99145,7 +99232,7 @@ program2.command("quick-qa <url>").alias("quick-check").description("Run a fast
|
|
|
99145
99232
|
}, []).option("--max-pages <n>", "Max pages to crawl for link checks", "20").option("--skip <check>", "Skip a check: console|network|links|perf|smoke|a11y (repeatable)", (v2, acc) => {
|
|
99146
99233
|
acc.push(v2);
|
|
99147
99234
|
return acc;
|
|
99148
|
-
}, []).option("--a11y [level]", "Include WCAG accessibility scan at A, AA, or AAA (default AA)").option("--no-smoke", "Skip autonomous smoke exploration").option("-m, --model <model>", "AI model for autonomous smoke", "quick").option("--headed", "Run browser checks in headed mode", false).option("--timeout <ms>", "Navigation timeout per page in ms", "15000").option("--project <id>", "Project ID for issue tracking").option("--json", "Output results as JSON", false).option("-o, --output <file>", "Write JSON results to a file").action(async (url2, opts) => {
|
|
99235
|
+
}, []).option("--a11y [level]", "Include WCAG accessibility scan at A, AA, or AAA (default AA)").option("--no-smoke", "Skip autonomous smoke exploration").option("-m, --model <model>", "AI model for autonomous smoke", "quick").option("--headed", "Run browser checks in headed mode", false).option("--timeout <ms>", "Navigation timeout per page in ms", "15000").option("--overall-timeout <ms>", "Hard overall timeout for the quick QA run in milliseconds", String(DEFAULT_QUICK_QA_OVERALL_TIMEOUT_MS)).option("--project <id>", "Project ID for issue tracking").option("--json", "Output results as JSON", false).option("-o, --output <file>", "Write JSON results to a file").action(async (url2, opts) => {
|
|
99149
99236
|
try {
|
|
99150
99237
|
const projectId = resolveProject2(opts.project);
|
|
99151
99238
|
const includeA11y = opts.a11y !== undefined;
|
|
@@ -99166,6 +99253,7 @@ program2.command("quick-qa <url>").alias("quick-check").description("Run a fast
|
|
|
99166
99253
|
projectId,
|
|
99167
99254
|
headed: opts.headed,
|
|
99168
99255
|
timeoutMs: parseInt(opts.timeout, 10),
|
|
99256
|
+
overallTimeoutMs: parseInt(opts.overallTimeout, 10),
|
|
99169
99257
|
maxPages: parseInt(opts.maxPages, 10),
|
|
99170
99258
|
scanners: selection.scanners,
|
|
99171
99259
|
includeSmoke: selection.includeSmoke,
|
package/dist/index.js
CHANGED
|
@@ -13880,12 +13880,21 @@ init_browser_lightpanda();
|
|
|
13880
13880
|
init_paths();
|
|
13881
13881
|
import { mkdirSync as mkdirSync7, existsSync as existsSync8, writeFileSync as writeFileSync2 } from "fs";
|
|
13882
13882
|
import { join as join10 } from "path";
|
|
13883
|
-
|
|
13884
|
-
|
|
13883
|
+
var MAX_ACTION_SLUG_LENGTH = 80;
|
|
13884
|
+
var MAX_SCENARIO_SLUG_LENGTH = 96;
|
|
13885
|
+
function truncateSlug(slug, maxLength) {
|
|
13886
|
+
if (slug.length <= maxLength)
|
|
13887
|
+
return slug;
|
|
13888
|
+
const truncated = slug.slice(0, maxLength).replace(/-+$/g, "");
|
|
13889
|
+
return truncated || slug.slice(0, maxLength);
|
|
13890
|
+
}
|
|
13891
|
+
function slugify(text, maxLength) {
|
|
13892
|
+
const slug = text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
13893
|
+
return maxLength ? truncateSlug(slug, maxLength) : slug;
|
|
13885
13894
|
}
|
|
13886
13895
|
function generateFilename(stepNumber, action) {
|
|
13887
13896
|
const padded = String(stepNumber).padStart(3, "0");
|
|
13888
|
-
const slug = slugify(action);
|
|
13897
|
+
const slug = slugify(action, MAX_ACTION_SLUG_LENGTH);
|
|
13889
13898
|
return `${padded}_${slug}.png`;
|
|
13890
13899
|
}
|
|
13891
13900
|
function formatDate(date) {
|
|
@@ -13899,7 +13908,8 @@ function getScreenshotDir(baseDir, runId, scenarioSlug, projectName, timestamp)
|
|
|
13899
13908
|
const project = projectName ?? "default";
|
|
13900
13909
|
const dateDir = formatDate(now2);
|
|
13901
13910
|
const timeDir = `${formatTime(now2)}_${runId.slice(0, 8)}`;
|
|
13902
|
-
|
|
13911
|
+
const safeScenarioSlug = slugify(scenarioSlug, MAX_SCENARIO_SLUG_LENGTH) || "scenario";
|
|
13912
|
+
return join10(baseDir, project, dateDir, timeDir, safeScenarioSlug);
|
|
13903
13913
|
}
|
|
13904
13914
|
function ensureDir(dirPath) {
|
|
13905
13915
|
if (!existsSync8(dirPath)) {
|
|
@@ -16452,6 +16462,14 @@ function emit(event) {
|
|
|
16452
16462
|
function resolveAgentApiKeyForModel(model, explicitApiKey, configuredAnthropicApiKey) {
|
|
16453
16463
|
return resolveProviderApiKeyForModel(model, explicitApiKey, configuredAnthropicApiKey);
|
|
16454
16464
|
}
|
|
16465
|
+
function resolveAgentMaxTurns(options) {
|
|
16466
|
+
if (options.maxTurns !== undefined) {
|
|
16467
|
+
const parsed = Math.floor(options.maxTurns);
|
|
16468
|
+
if (Number.isFinite(parsed) && parsed > 0)
|
|
16469
|
+
return parsed;
|
|
16470
|
+
}
|
|
16471
|
+
return options.minimal ? 10 : 30;
|
|
16472
|
+
}
|
|
16455
16473
|
function assertionDescription(result) {
|
|
16456
16474
|
return result.assertion.description || `${result.assertion.type}${result.assertion.selector ? ` ${result.assertion.selector}` : ""}`;
|
|
16457
16475
|
}
|
|
@@ -16671,7 +16689,7 @@ async function runSingleScenario(scenario, runId, options) {
|
|
|
16671
16689
|
runId,
|
|
16672
16690
|
sessionId: result.id,
|
|
16673
16691
|
baseUrl: options.url,
|
|
16674
|
-
maxTurns: effectiveOptions
|
|
16692
|
+
maxTurns: resolveAgentMaxTurns(effectiveOptions),
|
|
16675
16693
|
a11y: effectiveOptions.a11y,
|
|
16676
16694
|
persona: persona ? {
|
|
16677
16695
|
name: persona.name,
|
|
@@ -19135,6 +19153,7 @@ async function notifyHealthScan(url, counts) {
|
|
|
19135
19153
|
}
|
|
19136
19154
|
|
|
19137
19155
|
// src/lib/quick-qa.ts
|
|
19156
|
+
var DEFAULT_QUICK_QA_OVERALL_TIMEOUT_MS = 120000;
|
|
19138
19157
|
var DEFAULT_QUICK_QA_SCANNERS = [
|
|
19139
19158
|
"console",
|
|
19140
19159
|
"network",
|
|
@@ -19185,7 +19204,17 @@ function resolveQuickQaSelection(options = {}) {
|
|
|
19185
19204
|
}
|
|
19186
19205
|
async function runQuickQa(options) {
|
|
19187
19206
|
const start = Date.now();
|
|
19188
|
-
const
|
|
19207
|
+
const timeoutMs = options.overallTimeoutMs ?? DEFAULT_QUICK_QA_OVERALL_TIMEOUT_MS;
|
|
19208
|
+
return withQuickQaTimeout(runQuickQaUnbounded(options, start), {
|
|
19209
|
+
url: options.url,
|
|
19210
|
+
start,
|
|
19211
|
+
timeoutMs
|
|
19212
|
+
});
|
|
19213
|
+
}
|
|
19214
|
+
async function runQuickQaUnbounded(options, start) {
|
|
19215
|
+
const healthScanner = options.healthScanner ?? runHealthScan;
|
|
19216
|
+
const smokeRunner = options.smokeRunner ?? runSmoke;
|
|
19217
|
+
const health = await healthScanner({
|
|
19189
19218
|
url: options.url,
|
|
19190
19219
|
pages: options.pages,
|
|
19191
19220
|
projectId: options.projectId,
|
|
@@ -19195,7 +19224,7 @@ async function runQuickQa(options) {
|
|
|
19195
19224
|
maxPages: options.maxPages,
|
|
19196
19225
|
wcagLevel: options.wcagLevel
|
|
19197
19226
|
});
|
|
19198
|
-
const smoke = options.includeSmoke === false ? null : await
|
|
19227
|
+
const smoke = options.includeSmoke === false ? null : await smokeRunner({
|
|
19199
19228
|
url: options.url,
|
|
19200
19229
|
model: options.model,
|
|
19201
19230
|
headed: options.headed,
|
|
@@ -19209,6 +19238,62 @@ async function runQuickQa(options) {
|
|
|
19209
19238
|
durationMs: Date.now() - start
|
|
19210
19239
|
});
|
|
19211
19240
|
}
|
|
19241
|
+
function withQuickQaTimeout(promise, options) {
|
|
19242
|
+
if (!Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0)
|
|
19243
|
+
return promise;
|
|
19244
|
+
return new Promise((resolve, reject) => {
|
|
19245
|
+
const timer = setTimeout(() => {
|
|
19246
|
+
resolve(buildQuickQaTimeoutResult({
|
|
19247
|
+
url: options.url,
|
|
19248
|
+
start: options.start,
|
|
19249
|
+
timeoutMs: options.timeoutMs
|
|
19250
|
+
}));
|
|
19251
|
+
}, options.timeoutMs);
|
|
19252
|
+
promise.then((result) => {
|
|
19253
|
+
clearTimeout(timer);
|
|
19254
|
+
resolve(result);
|
|
19255
|
+
}, (error) => {
|
|
19256
|
+
clearTimeout(timer);
|
|
19257
|
+
reject(error);
|
|
19258
|
+
});
|
|
19259
|
+
});
|
|
19260
|
+
}
|
|
19261
|
+
function buildQuickQaTimeoutResult(input) {
|
|
19262
|
+
const now2 = new Date;
|
|
19263
|
+
const durationMs = Math.max(0, Date.now() - input.start);
|
|
19264
|
+
const message = `Quick QA timed out after ${input.timeoutMs}ms before all checks finished. Increase --overall-timeout or skip slow checks.`;
|
|
19265
|
+
const health = {
|
|
19266
|
+
url: input.url,
|
|
19267
|
+
scannedAt: now2.toISOString(),
|
|
19268
|
+
durationMs,
|
|
19269
|
+
totalIssues: 1,
|
|
19270
|
+
newIssues: 1,
|
|
19271
|
+
regressedIssues: 0,
|
|
19272
|
+
existingIssues: 0,
|
|
19273
|
+
results: [{
|
|
19274
|
+
url: input.url,
|
|
19275
|
+
pages: [input.url],
|
|
19276
|
+
scannedAt: now2.toISOString(),
|
|
19277
|
+
durationMs,
|
|
19278
|
+
issues: [{
|
|
19279
|
+
type: "performance",
|
|
19280
|
+
severity: "high",
|
|
19281
|
+
pageUrl: input.url,
|
|
19282
|
+
message,
|
|
19283
|
+
detail: {
|
|
19284
|
+
check: "quick-qa",
|
|
19285
|
+
timeoutMs: input.timeoutMs
|
|
19286
|
+
}
|
|
19287
|
+
}]
|
|
19288
|
+
}]
|
|
19289
|
+
};
|
|
19290
|
+
return buildQuickQaResult({
|
|
19291
|
+
url: input.url,
|
|
19292
|
+
health,
|
|
19293
|
+
smoke: null,
|
|
19294
|
+
durationMs
|
|
19295
|
+
});
|
|
19296
|
+
}
|
|
19212
19297
|
function buildQuickQaResult(input) {
|
|
19213
19298
|
const healthActionable = input.health.newIssues + input.health.regressedIssues;
|
|
19214
19299
|
const smokeIssues = input.smoke?.issuesFound.length ?? 0;
|
package/dist/lib/quick-qa.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { type HealthScanOptions, type HealthScanSummary } from "./health-scan.js";
|
|
2
|
-
import { type SmokeResult } from "./smoke.js";
|
|
1
|
+
import { runHealthScan, type HealthScanOptions, type HealthScanSummary } from "./health-scan.js";
|
|
2
|
+
import { runSmoke, type SmokeResult } from "./smoke.js";
|
|
3
3
|
export type QuickQaScanner = NonNullable<HealthScanOptions["scanners"]>[number];
|
|
4
4
|
export type QuickQaSkipTarget = QuickQaScanner | "smoke";
|
|
5
5
|
export type QuickQaStatus = "passed" | "warn" | "failed";
|
|
6
|
+
export declare const DEFAULT_QUICK_QA_OVERALL_TIMEOUT_MS = 120000;
|
|
6
7
|
export declare const DEFAULT_QUICK_QA_SCANNERS: QuickQaScanner[];
|
|
7
8
|
export interface QuickQaSelection {
|
|
8
9
|
scanners: QuickQaScanner[];
|
|
@@ -15,11 +16,14 @@ export interface QuickQaOptions {
|
|
|
15
16
|
projectId?: string;
|
|
16
17
|
headed?: boolean;
|
|
17
18
|
timeoutMs?: number;
|
|
19
|
+
overallTimeoutMs?: number;
|
|
18
20
|
maxPages?: number;
|
|
19
21
|
scanners?: QuickQaScanner[];
|
|
20
22
|
includeSmoke?: boolean;
|
|
21
23
|
model?: string;
|
|
22
24
|
wcagLevel?: "A" | "AA" | "AAA";
|
|
25
|
+
healthScanner?: typeof runHealthScan;
|
|
26
|
+
smokeRunner?: typeof runSmoke;
|
|
23
27
|
}
|
|
24
28
|
export interface QuickQaCheckSummary {
|
|
25
29
|
name: "health" | "smoke";
|
|
@@ -50,6 +54,11 @@ export declare function resolveQuickQaSelection(options?: {
|
|
|
50
54
|
scanners?: QuickQaScanner[];
|
|
51
55
|
}): QuickQaSelection;
|
|
52
56
|
export declare function runQuickQa(options: QuickQaOptions): Promise<QuickQaResult>;
|
|
57
|
+
export declare function buildQuickQaTimeoutResult(input: {
|
|
58
|
+
url: string;
|
|
59
|
+
start: number;
|
|
60
|
+
timeoutMs: number;
|
|
61
|
+
}): QuickQaResult;
|
|
53
62
|
export declare function buildQuickQaResult(input: {
|
|
54
63
|
url: string;
|
|
55
64
|
health: HealthScanSummary;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quick-qa.d.ts","sourceRoot":"","sources":["../../src/lib/quick-qa.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"quick-qa.d.ts","sourceRoot":"","sources":["../../src/lib/quick-qa.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/runner.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export interface RunOptions {
|
|
|
23
23
|
skipBudgetCheck?: boolean;
|
|
24
24
|
cacheMaxAgeMs?: number;
|
|
25
25
|
minimal?: boolean;
|
|
26
|
+
maxTurns?: number;
|
|
26
27
|
recordVideo?: boolean;
|
|
27
28
|
}
|
|
28
29
|
export interface RunEvent {
|
|
@@ -47,6 +48,7 @@ export interface RunEvent {
|
|
|
47
48
|
export type RunEventHandler = (event: RunEvent) => void;
|
|
48
49
|
export declare function onRunEvent(handler: RunEventHandler): void;
|
|
49
50
|
export declare function resolveAgentApiKeyForModel(model: string, explicitApiKey?: string, configuredAnthropicApiKey?: string): string | undefined;
|
|
51
|
+
export declare function resolveAgentMaxTurns(options: Pick<RunOptions, "minimal" | "maxTurns">): number;
|
|
50
52
|
type AgentScenarioStatus = Extract<ResultStatus, "passed" | "failed" | "error">;
|
|
51
53
|
export interface StructuredAssertionOutcome {
|
|
52
54
|
status: AgentScenarioStatus;
|
package/dist/lib/runner.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/lib/runner.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/lib/runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Page } from "playwright";
|
|
2
|
-
export declare function slugify(text: string): string;
|
|
2
|
+
export declare function slugify(text: string, maxLength?: number): string;
|
|
3
3
|
export declare function generateFilename(stepNumber: number, action: string): string;
|
|
4
4
|
/**
|
|
5
5
|
* Build the screenshot directory for a run:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screenshotter.d.ts","sourceRoot":"","sources":["../../src/lib/screenshotter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"screenshotter.d.ts","sourceRoot":"","sources":["../../src/lib/screenshotter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAgBvC,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAMhE;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAI3E;AAUD;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,EACpB,WAAW,CAAC,EAAE,MAAM,EACpB,SAAS,CAAC,EAAE,IAAI,GACf,MAAM,CAOR;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAI/C;AAID,UAAU,oBAAoB;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,UAAU,cAAc;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAuBD,wBAAgB,YAAY,CAC1B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,GAC5G,IAAI,CAON;AAED,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACxH,IAAI,CAON;AAkCD,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,YAAY,CAAO;gBAEf,OAAO,GAAE,oBAAyB;IASxC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IAmDpE,eAAe,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IAiD5E,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;CA6CpG"}
|
package/dist/mcp/index.js
CHANGED
|
@@ -52,7 +52,7 @@ var package_default;
|
|
|
52
52
|
var init_package = __esm(() => {
|
|
53
53
|
package_default = {
|
|
54
54
|
name: "@hasna/testers",
|
|
55
|
-
version: "0.0.
|
|
55
|
+
version: "0.0.39",
|
|
56
56
|
description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
|
|
57
57
|
type: "module",
|
|
58
58
|
main: "dist/index.js",
|
|
@@ -19805,12 +19805,19 @@ var init_personas = __esm(() => {
|
|
|
19805
19805
|
// src/lib/screenshotter.ts
|
|
19806
19806
|
import { mkdirSync as mkdirSync7, existsSync as existsSync8, writeFileSync as writeFileSync2 } from "fs";
|
|
19807
19807
|
import { join as join10 } from "path";
|
|
19808
|
-
function
|
|
19809
|
-
|
|
19808
|
+
function truncateSlug(slug, maxLength) {
|
|
19809
|
+
if (slug.length <= maxLength)
|
|
19810
|
+
return slug;
|
|
19811
|
+
const truncated = slug.slice(0, maxLength).replace(/-+$/g, "");
|
|
19812
|
+
return truncated || slug.slice(0, maxLength);
|
|
19813
|
+
}
|
|
19814
|
+
function slugify(text, maxLength) {
|
|
19815
|
+
const slug = text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
19816
|
+
return maxLength ? truncateSlug(slug, maxLength) : slug;
|
|
19810
19817
|
}
|
|
19811
19818
|
function generateFilename(stepNumber, action) {
|
|
19812
19819
|
const padded = String(stepNumber).padStart(3, "0");
|
|
19813
|
-
const slug = slugify(action);
|
|
19820
|
+
const slug = slugify(action, MAX_ACTION_SLUG_LENGTH);
|
|
19814
19821
|
return `${padded}_${slug}.png`;
|
|
19815
19822
|
}
|
|
19816
19823
|
function formatDate(date) {
|
|
@@ -19824,7 +19831,8 @@ function getScreenshotDir(baseDir, runId, scenarioSlug, projectName, timestamp)
|
|
|
19824
19831
|
const project = projectName ?? "default";
|
|
19825
19832
|
const dateDir = formatDate(now2);
|
|
19826
19833
|
const timeDir = `${formatTime(now2)}_${runId.slice(0, 8)}`;
|
|
19827
|
-
|
|
19834
|
+
const safeScenarioSlug = slugify(scenarioSlug, MAX_SCENARIO_SLUG_LENGTH) || "scenario";
|
|
19835
|
+
return join10(baseDir, project, dateDir, timeDir, safeScenarioSlug);
|
|
19828
19836
|
}
|
|
19829
19837
|
function ensureDir(dirPath) {
|
|
19830
19838
|
if (!existsSync8(dirPath)) {
|
|
@@ -19982,7 +19990,7 @@ class Screenshotter {
|
|
|
19982
19990
|
};
|
|
19983
19991
|
}
|
|
19984
19992
|
}
|
|
19985
|
-
var DEFAULT_BASE_DIR;
|
|
19993
|
+
var MAX_ACTION_SLUG_LENGTH = 80, MAX_SCENARIO_SLUG_LENGTH = 96, DEFAULT_BASE_DIR;
|
|
19986
19994
|
var init_screenshotter = __esm(() => {
|
|
19987
19995
|
init_paths();
|
|
19988
19996
|
DEFAULT_BASE_DIR = join10(getTestersDir(), "screenshots");
|
|
@@ -21180,6 +21188,7 @@ __export(exports_runner, {
|
|
|
21180
21188
|
runByFilter: () => runByFilter,
|
|
21181
21189
|
runBatch: () => runBatch,
|
|
21182
21190
|
resolveScenariosForRun: () => resolveScenariosForRun,
|
|
21191
|
+
resolveAgentMaxTurns: () => resolveAgentMaxTurns,
|
|
21183
21192
|
resolveAgentApiKeyForModel: () => resolveAgentApiKeyForModel,
|
|
21184
21193
|
onRunEvent: () => onRunEvent,
|
|
21185
21194
|
applyStructuredAssertionsToResult: () => applyStructuredAssertionsToResult
|
|
@@ -21197,6 +21206,14 @@ function emit(event) {
|
|
|
21197
21206
|
function resolveAgentApiKeyForModel(model, explicitApiKey, configuredAnthropicApiKey) {
|
|
21198
21207
|
return resolveProviderApiKeyForModel(model, explicitApiKey, configuredAnthropicApiKey);
|
|
21199
21208
|
}
|
|
21209
|
+
function resolveAgentMaxTurns(options) {
|
|
21210
|
+
if (options.maxTurns !== undefined) {
|
|
21211
|
+
const parsed = Math.floor(options.maxTurns);
|
|
21212
|
+
if (Number.isFinite(parsed) && parsed > 0)
|
|
21213
|
+
return parsed;
|
|
21214
|
+
}
|
|
21215
|
+
return options.minimal ? 10 : 30;
|
|
21216
|
+
}
|
|
21200
21217
|
function assertionDescription(result) {
|
|
21201
21218
|
return result.assertion.description || `${result.assertion.type}${result.assertion.selector ? ` ${result.assertion.selector}` : ""}`;
|
|
21202
21219
|
}
|
|
@@ -21416,7 +21433,7 @@ async function runSingleScenario(scenario, runId, options) {
|
|
|
21416
21433
|
runId,
|
|
21417
21434
|
sessionId: result.id,
|
|
21418
21435
|
baseUrl: options.url,
|
|
21419
|
-
maxTurns: effectiveOptions
|
|
21436
|
+
maxTurns: resolveAgentMaxTurns(effectiveOptions),
|
|
21420
21437
|
a11y: effectiveOptions.a11y,
|
|
21421
21438
|
persona: persona ? {
|
|
21422
21439
|
name: persona.name,
|
package/dist/server/index.js
CHANGED
|
@@ -46910,7 +46910,7 @@ import { join as join14 } from "path";
|
|
|
46910
46910
|
// package.json
|
|
46911
46911
|
var package_default = {
|
|
46912
46912
|
name: "@hasna/testers",
|
|
46913
|
-
version: "0.0.
|
|
46913
|
+
version: "0.0.39",
|
|
46914
46914
|
description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
|
|
46915
46915
|
type: "module",
|
|
46916
46916
|
main: "dist/index.js",
|
|
@@ -48303,12 +48303,21 @@ init_browser();
|
|
|
48303
48303
|
init_paths();
|
|
48304
48304
|
import { mkdirSync as mkdirSync5, existsSync as existsSync7, writeFileSync as writeFileSync2 } from "fs";
|
|
48305
48305
|
import { join as join9 } from "path";
|
|
48306
|
-
|
|
48307
|
-
|
|
48306
|
+
var MAX_ACTION_SLUG_LENGTH = 80;
|
|
48307
|
+
var MAX_SCENARIO_SLUG_LENGTH = 96;
|
|
48308
|
+
function truncateSlug(slug, maxLength) {
|
|
48309
|
+
if (slug.length <= maxLength)
|
|
48310
|
+
return slug;
|
|
48311
|
+
const truncated = slug.slice(0, maxLength).replace(/-+$/g, "");
|
|
48312
|
+
return truncated || slug.slice(0, maxLength);
|
|
48313
|
+
}
|
|
48314
|
+
function slugify(text, maxLength) {
|
|
48315
|
+
const slug = text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
48316
|
+
return maxLength ? truncateSlug(slug, maxLength) : slug;
|
|
48308
48317
|
}
|
|
48309
48318
|
function generateFilename(stepNumber, action) {
|
|
48310
48319
|
const padded = String(stepNumber).padStart(3, "0");
|
|
48311
|
-
const slug = slugify(action);
|
|
48320
|
+
const slug = slugify(action, MAX_ACTION_SLUG_LENGTH);
|
|
48312
48321
|
return `${padded}_${slug}.png`;
|
|
48313
48322
|
}
|
|
48314
48323
|
function formatDate(date) {
|
|
@@ -48322,7 +48331,8 @@ function getScreenshotDir(baseDir, runId, scenarioSlug, projectName, timestamp)
|
|
|
48322
48331
|
const project = projectName ?? "default";
|
|
48323
48332
|
const dateDir = formatDate(now2);
|
|
48324
48333
|
const timeDir = `${formatTime(now2)}_${runId.slice(0, 8)}`;
|
|
48325
|
-
|
|
48334
|
+
const safeScenarioSlug = slugify(scenarioSlug, MAX_SCENARIO_SLUG_LENGTH) || "scenario";
|
|
48335
|
+
return join9(baseDir, project, dateDir, timeDir, safeScenarioSlug);
|
|
48326
48336
|
}
|
|
48327
48337
|
function ensureDir(dirPath) {
|
|
48328
48338
|
if (!existsSync7(dirPath)) {
|
|
@@ -49404,6 +49414,14 @@ function emit(event) {
|
|
|
49404
49414
|
function resolveAgentApiKeyForModel(model, explicitApiKey, configuredAnthropicApiKey) {
|
|
49405
49415
|
return resolveProviderApiKeyForModel(model, explicitApiKey, configuredAnthropicApiKey);
|
|
49406
49416
|
}
|
|
49417
|
+
function resolveAgentMaxTurns(options) {
|
|
49418
|
+
if (options.maxTurns !== undefined) {
|
|
49419
|
+
const parsed = Math.floor(options.maxTurns);
|
|
49420
|
+
if (Number.isFinite(parsed) && parsed > 0)
|
|
49421
|
+
return parsed;
|
|
49422
|
+
}
|
|
49423
|
+
return options.minimal ? 10 : 30;
|
|
49424
|
+
}
|
|
49407
49425
|
function assertionDescription(result) {
|
|
49408
49426
|
return result.assertion.description || `${result.assertion.type}${result.assertion.selector ? ` ${result.assertion.selector}` : ""}`;
|
|
49409
49427
|
}
|
|
@@ -49623,7 +49641,7 @@ async function runSingleScenario(scenario, runId, options) {
|
|
|
49623
49641
|
runId,
|
|
49624
49642
|
sessionId: result.id,
|
|
49625
49643
|
baseUrl: options.url,
|
|
49626
|
-
maxTurns: effectiveOptions
|
|
49644
|
+
maxTurns: resolveAgentMaxTurns(effectiveOptions),
|
|
49627
49645
|
a11y: effectiveOptions.a11y,
|
|
49628
49646
|
persona: persona ? {
|
|
49629
49647
|
name: persona.name,
|