@dev-blinq/bvt-playwright-js 1.0.0-dev.4.latest.158.1 → 1.0.0-dev.4.latest.177.1

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/index.d.mts CHANGED
@@ -277,6 +277,7 @@ declare const elementAssertionSchema: z.ZodDiscriminatedUnion<[
277
277
  type: z.ZodLiteral<"toHaveProperty">;
278
278
  name: z.ZodString;
279
279
  operator: z.ZodOptional<z.ZodEnum<{
280
+ regex: "regex";
280
281
  equals: "equals";
281
282
  notEquals: "notEquals";
282
283
  contains: "contains";
@@ -2021,6 +2022,7 @@ declare const CommandSchema: z.ZodDiscriminatedUnion<[
2021
2022
  type: z.ZodLiteral<"toHaveProperty">;
2022
2023
  name: z.ZodString;
2023
2024
  operator: z.ZodOptional<z.ZodEnum<{
2025
+ regex: "regex";
2024
2026
  equals: "equals";
2025
2027
  notEquals: "notEquals";
2026
2028
  contains: "contains";
@@ -5031,6 +5033,7 @@ declare const StepDefinitionSchema: z.ZodObject<{
5031
5033
  type: z.ZodLiteral<"toHaveProperty">;
5032
5034
  name: z.ZodString;
5033
5035
  operator: z.ZodOptional<z.ZodEnum<{
5036
+ regex: "regex";
5034
5037
  equals: "equals";
5035
5038
  notEquals: "notEquals";
5036
5039
  contains: "contains";
@@ -9305,6 +9308,19 @@ type TesterSession = {
9305
9308
  */
9306
9309
  screenshots?: {
9307
9310
  enabled: boolean;
9311
+ /**
9312
+ * "upload" (default): Tester requests presigned URLs and uploads the
9313
+ * captured JPEGs to S3 itself at the end of the testcase.
9314
+ * "local": Tester captures JPEGs to `outputDir` and leaves them on disk
9315
+ * for an out-of-band uploader (mirrors the local-trace mode used by the
9316
+ * execution worker, which has no presigned-URL API client).
9317
+ */
9318
+ mode?: "upload" | "local";
9319
+ /**
9320
+ * Local-mode capture directory. When omitted a per-testcase /tmp dir is
9321
+ * used. Ignored in "upload" mode.
9322
+ */
9323
+ outputDir?: string;
9308
9324
  };
9309
9325
  deterministicRecoveryMode?: "new-tour-maintenance";
9310
9326
  };
@@ -9323,6 +9339,7 @@ type ExecuteStepsOptions = {
9323
9339
  onFinish?: () => Promise<void>;
9324
9340
  onError?: (error: Error) => Promise<void>;
9325
9341
  abortController?: AbortController;
9342
+ recoveryAbortController?: AbortController;
9326
9343
  };
9327
9344
  type GetAPIClient = (sessionToken: string) => TesterApiClient;
9328
9345
  declare class Tester {
package/index.mjs CHANGED
@@ -5170,7 +5170,8 @@ const ASSERTION_OPERATORS = [
5170
5170
  "contains",
5171
5171
  "lessThan",
5172
5172
  "greaterThan",
5173
- "exists"
5173
+ "exists",
5174
+ "regex"
5174
5175
  ];
5175
5176
 
5176
5177
  //#endregion
@@ -10936,6 +10937,7 @@ const AiChatDataExecutionStatusPartSchema = object({
10936
10937
  * from the call sites in `apps/ai-server/src/mastra/agents/`:
10937
10938
  * - `observe-act-loop` (observe-act-completion happy path)
10938
10939
  * - `observe-act-loop-cap-reached` (loop hit iteration cap mid-run)
10940
+ * - `form-fill-batch` (on-mode form-fill fast-path success)
10939
10941
  * - `segment-followup` (apps/ai-server/src/trpc/router.ts segment-followup path)
10940
10942
  * Plus the AiStepDraftMode values used by client-side fixtures that did not
10941
10943
  * originate from the observe-act loop (legacy: `new-ai-step`, `edit-ai-step`).
@@ -10946,6 +10948,7 @@ const AiChatDataExecutionStatusPartSchema = object({
10946
10948
  const AiChatDataCompleteModeSchema = _enum([
10947
10949
  "observe-act-loop",
10948
10950
  "observe-act-loop-cap-reached",
10951
+ "form-fill-batch",
10949
10952
  "segment-followup",
10950
10953
  "new-ai-step",
10951
10954
  "edit-ai-step"
@@ -26320,6 +26323,9 @@ const NEW_TOUR_MAINTENANCE_RECOVERY_ARTIFACT = {
26320
26323
  aiLog: "BlinqIO matched the original gender-selection intent to the new radio button UI and rewrote the stale command.",
26321
26324
  description: "The repaired step selects the gender radio option, then continues the registration flow successfully."
26322
26325
  };
26326
+ function isAbortError(error) {
26327
+ return error instanceof Error && error.name === "AbortError";
26328
+ }
26323
26329
  const unconfiguredGetAPIClient = () => {
26324
26330
  throw new Error("Tester was created without an API client.");
26325
26331
  };
@@ -26454,7 +26460,7 @@ var Tester = class {
26454
26460
  return `${this.dataContext?.projectId ?? ""}/${session.reportId}/testcases/${session.testCaseId}/resources/screenshots/${stepId}-${commandId}-${phase}.jpg`;
26455
26461
  }
26456
26462
  getScreenshotTempDir(session) {
26457
- if (!this.tempPathForAssets) this.tempPathForAssets = join("/tmp", `blinq-screenshots-${session.reportId}-${session.testCaseId}`);
26463
+ if (!this.tempPathForAssets) this.tempPathForAssets = session.screenshots?.outputDir ?? join("/tmp", `blinq-screenshots-${session.reportId}-${session.testCaseId}`);
26458
26464
  return this.tempPathForAssets;
26459
26465
  }
26460
26466
  /**
@@ -27044,6 +27050,16 @@ var Tester = class {
27044
27050
  });
27045
27051
  return false;
27046
27052
  }
27053
+ if (session.screenshots?.mode === "local") {
27054
+ this.obs.logger.log("Screenshots captured in local mode; leaving files for out-of-band upload", {
27055
+ reportId: session.reportId,
27056
+ testCaseId: session.testCaseId,
27057
+ capturedCount,
27058
+ dir: this.tempPathForAssets
27059
+ });
27060
+ this.capturedScreenshots = [];
27061
+ return capturedCount > 0;
27062
+ }
27047
27063
  if (!session.token) {
27048
27064
  this.obs.metrics.count("bvt_agent.screenshots.upload.skipped.total", 1, { reason: "no-session-token" });
27049
27065
  this.obs.logger.warn(`Screenshot upload skipped because no session token was provided for reportId: ${session.reportId}, testCaseId: ${session.testCaseId}.`);
@@ -27275,7 +27291,21 @@ var Tester = class {
27275
27291
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
27276
27292
  };
27277
27293
  const failureContext = this.buildFailureContext(command, recorderStep, stepDefinitionId, commandIndex, commandContext, executionError);
27278
- const recoveryResult = await this.recoveryController.analyzeAndPropose(failureContext);
27294
+ const recoveryContext = {
27295
+ ...failureContext,
27296
+ abortSignal: options.recoveryAbortController?.signal ?? failureContext.abortSignal
27297
+ };
27298
+ let recoveryResult;
27299
+ try {
27300
+ recoveryResult = await this.recoveryController.analyzeAndPropose(recoveryContext);
27301
+ } catch (recoveryError) {
27302
+ if (isAbortError(recoveryError)) {
27303
+ this.obs.logger.log("AI recovery cancelled; preserving original command failure", { commandId: command._id });
27304
+ yield await this.onCommandFail(command, stepDefinitionId, executionError, session);
27305
+ throw executionError;
27306
+ }
27307
+ throw recoveryError;
27308
+ }
27279
27309
  const { decision } = recoveryResult;
27280
27310
  let artifact = recoveryResult.artifact;
27281
27311
  if (this.shouldAttemptAiRecovery(session)) {
@@ -27344,7 +27374,7 @@ var Tester = class {
27344
27374
  if (!persistResult.success) throw new Error(persistResult.error);
27345
27375
  artifact = await this.generateRecoveryArtifactForOutcome({
27346
27376
  decision,
27347
- failureContext,
27377
+ failureContext: recoveryContext,
27348
27378
  recoverySucceeded: true,
27349
27379
  stoppedReason: "accept-repair",
27350
27380
  attemptLog: this.buildStepRepairAttemptLog(decision, "succeeded")
@@ -27403,7 +27433,7 @@ var Tester = class {
27403
27433
  });
27404
27434
  artifact = await this.generateRecoveryArtifactForOutcome({
27405
27435
  decision,
27406
- failureContext,
27436
+ failureContext: recoveryContext,
27407
27437
  recoverySucceeded: false,
27408
27438
  stoppedReason: "retry_failed",
27409
27439
  attemptLog: this.buildStepRepairAttemptLog(decision, "failed")
@@ -27424,7 +27454,7 @@ var Tester = class {
27424
27454
  terminalExecutionError = /* @__PURE__ */ new Error(`${decision.userFacingMessages.failed}. Repair application failure: ${appliedRepair.error}. Original failure: ${executionError.message}`);
27425
27455
  artifact = await this.generateRecoveryArtifactForOutcome({
27426
27456
  decision,
27427
- failureContext,
27457
+ failureContext: recoveryContext,
27428
27458
  recoverySucceeded: false,
27429
27459
  stoppedReason: "repair_application_failed",
27430
27460
  attemptLog: this.buildStepRepairAttemptLog(decision, "failed")
@@ -27485,7 +27515,7 @@ var Tester = class {
27485
27515
  if (!persistResult.success) throw new Error(persistResult.error);
27486
27516
  artifact = await this.generateRecoveryArtifactForOutcome({
27487
27517
  decision,
27488
- failureContext,
27518
+ failureContext: recoveryContext,
27489
27519
  recoverySucceeded: true,
27490
27520
  stoppedReason: "accept-repair",
27491
27521
  attemptLog: this.buildStepRepairAttemptLog(decision, "succeeded")
@@ -27545,7 +27575,7 @@ var Tester = class {
27545
27575
  });
27546
27576
  artifact = await this.generateRecoveryArtifactForOutcome({
27547
27577
  decision,
27548
- failureContext,
27578
+ failureContext: recoveryContext,
27549
27579
  recoverySucceeded: false,
27550
27580
  stoppedReason: "retry_failed",
27551
27581
  attemptLog: this.buildStepRepairAttemptLog(decision, "failed")
@@ -27565,7 +27595,7 @@ var Tester = class {
27565
27595
  else {
27566
27596
  artifact = await this.generateRecoveryArtifactForOutcome({
27567
27597
  decision,
27568
- failureContext,
27598
+ failureContext: recoveryContext,
27569
27599
  recoverySucceeded: false,
27570
27600
  stoppedReason: "repair_application_failed",
27571
27601
  attemptLog: this.buildStepRepairAttemptLog(decision, "failed")
@@ -27978,6 +28008,33 @@ var Tester = class {
27978
28008
  if (!(actual > threshold)) throw new Error(`Property "${assertion.name}" expected to be greater than ${threshold}, but got ${actual}`);
27979
28009
  }
27980
28010
  break;
28011
+ case "regex":
28012
+ if (assertion.value !== void 0) {
28013
+ const raw = getValueFromPrimitiveOrRegex(assertion.value);
28014
+ const pattern = raw instanceof RegExp ? raw : new RegExp(String(raw));
28015
+ this.obs.logger.info(`Expected property "${assertion.name}" to match pattern: ${pattern}`);
28016
+ const propName = assertion.name;
28017
+ const isTextProp = propName === "textContent" || propName === "innerText";
28018
+ const isHtmlAttr = [
28019
+ "href",
28020
+ "src",
28021
+ "class",
28022
+ "id",
28023
+ "placeholder",
28024
+ "title",
28025
+ "alt",
28026
+ "name",
28027
+ "type",
28028
+ "action",
28029
+ "target",
28030
+ "rel",
28031
+ "value"
28032
+ ].includes(propName) || propName.startsWith("data-") || propName.startsWith("aria-");
28033
+ if (isTextProp) await expect(locator).toHaveText(pattern, assertion.options);
28034
+ else if (isHtmlAttr) await expect(locator).toHaveAttribute(propName, pattern, assertion.options);
28035
+ else await expect.poll(async () => locator.evaluate((el, name) => String(el[name] ?? ""), propName), { timeout: assertion.options?.timeout }).toMatch(pattern);
28036
+ }
28037
+ break;
27981
28038
  case "exists": {
27982
28039
  const exists = await locator.evaluate((el, name) => el[name] !== void 0, assertion.name);
27983
28040
  this.obs.logger.info(`Expected property "${assertion.name}" to exist: ${exists}`);
@@ -28033,7 +28090,9 @@ var Tester = class {
28033
28090
  const frame = await this.getFrameObjectFromFrameScope(frameScope);
28034
28091
  this.obs.logger.info(`Checking for absence of element with text ${value} using "${locator.first()?._selector}" `);
28035
28092
  this.obs.logger.info(`Waiting for page to load and stabilize before checking for absence of text to reduce flakiness...`);
28036
- await frame.waitForLoadState("networkidle", { timeout: 3e4 });
28093
+ await frame.waitForLoadState("networkidle", { timeout: 3e4 }).catch((error) => {
28094
+ this.obs.logger.error(`Frame load timeout, the page may not be in the desired state yet`, error);
28095
+ });
28037
28096
  this.obs.logger.info(`frame load detected. Waiting an additional 500ms for stabilization...`);
28038
28097
  await frame.waitForTimeout(500);
28039
28098
  this.obs.logger.info(`Checking for element with text "${value}" after page load and stabilization delay...`);