blastproof 0.4.0 → 0.5.0

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/README.md CHANGED
@@ -107,9 +107,9 @@ jobs:
107
107
 
108
108
  - run: npm start & # however your app boots
109
109
 
110
- - uses: hamc/blastproof@v0.4.0
110
+ - uses: hamc/blastproof@v0.5.0
111
111
  with:
112
- version: '0.4.0' # pin both when this gates merges
112
+ version: '0.5.0' # pin both when this gates merges
113
113
  api-key: ${{ secrets.ANTHROPIC_API_KEY }}
114
114
  base: ${{ github.event.pull_request.base.ref }}
115
115
  min-score: '80'
package/dist/cli.js CHANGED
@@ -256,7 +256,7 @@ function estimateMaxModelCalls(tests, maxIterationsPerStep, maxRetriesPerStep) {
256
256
  (sum, test) => sum + (test.setup?.length ?? 0) + test.steps.length,
257
257
  0
258
258
  );
259
- return totalSteps * (maxIterationsPerStep + maxRetriesPerStep);
259
+ return totalSteps * (maxIterationsPerStep + maxRetriesPerStep + Math.min(maxIterationsPerStep, maxRetriesPerStep));
260
260
  }
261
261
 
262
262
  // src/runner/env.ts
@@ -439,6 +439,19 @@ async function performAction(page, action, ctx) {
439
439
 
440
440
  // src/runner/executor.ts
441
441
  var DEFAULT_MAX_ITERATIONS_PER_STEP = 15;
442
+ var SETTLE_TIMEOUT_MS = 2e3;
443
+ async function waitForSettled(page) {
444
+ const deadline = Date.now() + SETTLE_TIMEOUT_MS;
445
+ const urlBefore = page.url();
446
+ await page.waitForLoadState("networkidle", { timeout: SETTLE_TIMEOUT_MS }).catch(() => {
447
+ });
448
+ if (page.url() === urlBefore) return;
449
+ const remaining = deadline - Date.now();
450
+ if (remaining > 0) {
451
+ await page.waitForLoadState("networkidle", { timeout: remaining }).catch(() => {
452
+ });
453
+ }
454
+ }
442
455
  async function defaultSnapshot(page, maxLines) {
443
456
  const { captureSnapshot } = await import("./snapshot-CAIB2OHX.js");
444
457
  return captureSnapshot(page, { maxLines });
@@ -496,6 +509,7 @@ async function executeTest(page, test, options) {
496
509
  if (iterations >= maxIterationsPerStep) {
497
510
  throw new StepFailure(`step exceeded ${maxIterationsPerStep} actions without completing`);
498
511
  }
512
+ await waitForSettled(page);
499
513
  const snap = await takeSnapshot(page);
500
514
  let action;
501
515
  try {
@@ -527,7 +541,12 @@ async function executeTest(page, test, options) {
527
541
  }
528
542
  if (action.action === "assert") {
529
543
  const expectation = action.expectation ?? action.reasoning;
530
- const judgment = await brain.judge(mask(expectation), mask(snap));
544
+ let judgment = await brain.judge(mask(expectation), mask(snap));
545
+ if (!judgment.pass) {
546
+ await waitForSettled(page);
547
+ const freshSnap = await takeSnapshot(page);
548
+ judgment = await brain.judge(mask(expectation), mask(freshSnap));
549
+ }
531
550
  const result = judgment.pass ? `ok: assertion passed: ${judgment.reason}` : `assertion failed: ${judgment.reason}`;
532
551
  emitAction(index, action, result);
533
552
  if (judgment.pass) {
@@ -1033,6 +1052,7 @@ Rules:
1033
1052
  - Return "done" when the current step's outcome holds \u2014 including when it already held before you acted, or was achieved by your previous action. "Already true" is done, never failure. Do not return "done" for work belonging to later steps.
1034
1053
  - Return "fail" only when the step's outcome cannot be reached: the element is still absent after retries, the page cannot support the step, or an error blocks progress. Never return "fail" because the work appears to have been done already.
1035
1054
  - If your previous action errored, re-read the fresh snapshot and choose an alternative element or approach. Do not repeat the exact same failing action.
1055
+ - \`***\` in a snapshot is a redacted secret \u2014 a password, token or key deliberately withheld from you. Seeing it is expected and is not a problem. A field showing \`***\` after you filled it from an {{env.VAR}} placeholder means the fill worked; treat that as success and move on. Never retry a fill because its value is redacted, and never report failure because a value was withheld.
1036
1056
  - Keep reasoning to one short sentence.`;
1037
1057
  }
1038
1058
  function agentUserPrompt(input) {
@@ -1053,7 +1073,9 @@ function agentUserPrompt(input) {
1053
1073
  return parts.join("\n");
1054
1074
  }
1055
1075
  function assertSystemPrompt() {
1056
- return `You are a QA judge. You receive a page accessibility snapshot and an expectation. Decide whether the snapshot satisfies the expectation. Be strict: only pass when the expectation is clearly met by the snapshot content. Answer with pass=true/false and a one-sentence reason.`;
1076
+ return `You are a QA judge. You receive a page accessibility snapshot and an expectation. Decide whether the snapshot satisfies the expectation. Be strict: only pass when the expectation is clearly met by the snapshot content. Answer with pass=true/false and a one-sentence reason.
1077
+
1078
+ \`***\` marks a secret deliberately withheld from you \u2014 a password, token or key. Seeing it is expected. A field holding \`***\` is filled, not empty, so do not fail an expectation on the grounds that a value was redacted. This applies only to the redaction itself: everything else the expectation asks for must still be visibly satisfied by the snapshot, and an expectation you genuinely cannot check against what you were shown still fails.`;
1057
1079
  }
1058
1080
  function assertUserPrompt(expectation, snapshot) {
1059
1081
  return [
@@ -2534,7 +2556,7 @@ function parsePositiveNumber(flag) {
2534
2556
  };
2535
2557
  }
2536
2558
  var program = new Command();
2537
- program.name("blastproof").description("Open-source AI testing agent: plain-English YAML tests executed agentically on a real browser.").version("0.4.0");
2559
+ program.name("blastproof").description("Open-source AI testing agent: plain-English YAML tests executed agentically on a real browser.").version("0.5.0");
2538
2560
  program.command("init").description("Scaffold .blastproof/ (config, tests, sample tests) in the current directory").action(async () => {
2539
2561
  try {
2540
2562
  const result = await initProject(process.cwd());