@midscene/web 0.3.0 → 0.3.1-beta-20240821105917.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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2021-present Midscene.js
3
+ Copyright (c) 2024-present Bytedance, Inc. and its affiliates.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/dist/es/index.js CHANGED
@@ -483,17 +483,24 @@ async function alignElements(screenshotBuffer, elements, page) {
483
483
  return item.rect.height >= sizeThreshold && item.rect.width >= sizeThreshold;
484
484
  });
485
485
  for (const item of validElements) {
486
- const { rect } = item;
486
+ const { rect, id, content, attributes, locator } = item;
487
487
  const aligned = await alignCoordByTrim(screenshotBuffer, rect);
488
+ if (aligned.width < 0)
489
+ continue;
488
490
  item.rect = aligned;
489
491
  item.center = [
490
492
  Math.round(aligned.left + aligned.width / 2),
491
493
  Math.round(aligned.top + aligned.height / 2)
492
494
  ];
493
495
  textsAligned.push(
494
- new WebElementInfo(__spreadProps(__spreadValues({}, item), {
496
+ new WebElementInfo({
497
+ rect,
498
+ locator,
499
+ id,
500
+ content,
501
+ attributes,
495
502
  page
496
- }))
503
+ })
497
504
  );
498
505
  }
499
506
  return textsAligned;
@@ -617,7 +624,7 @@ var PageTaskExecutor = class {
617
624
  };
618
625
  return taskFind;
619
626
  }
620
- if (plan2.type === "Assert") {
627
+ if (plan2.type === "Assert" || plan2.type === "AssertWithoutThrow") {
621
628
  const assertPlan = plan2;
622
629
  const taskAssert = {
623
630
  type: "Insight",
@@ -634,13 +641,16 @@ var PageTaskExecutor = class {
634
641
  assertPlan.param.assertion
635
642
  );
636
643
  if (!assertion.pass) {
637
- task.output = assertion;
638
- task.log = {
639
- dump: insightDump
640
- };
641
- throw new Error(
642
- assertion.thought || "Assertion failed without reason"
643
- );
644
+ if (plan2.type === "Assert") {
645
+ task.output = assertion;
646
+ task.log = {
647
+ dump: insightDump
648
+ };
649
+ throw new Error(
650
+ assertion.thought || "Assertion failed without reason"
651
+ );
652
+ }
653
+ task.error = assertion.thought;
644
654
  }
645
655
  return {
646
656
  output: assertion,
@@ -756,7 +766,19 @@ var PageTaskExecutor = class {
756
766
  return taskActionSleep;
757
767
  }
758
768
  if (plan2.type === "Error") {
759
- throw new Error(`Got a task plan with type Error: ${plan2.thought}`);
769
+ const taskActionError = {
770
+ type: "Action",
771
+ subType: "Error",
772
+ param: plan2.param,
773
+ executor: async (taskParam) => {
774
+ assert2(
775
+ taskParam.thought,
776
+ "An error occurred, but no thought provided"
777
+ );
778
+ throw new Error(taskParam.thought);
779
+ }
780
+ };
781
+ return taskActionError;
760
782
  }
761
783
  throw new Error(`Unknown or Unsupported task type: ${plan2.type}`);
762
784
  }).map((task) => {
@@ -766,7 +788,6 @@ var PageTaskExecutor = class {
766
788
  }
767
789
  async action(userPrompt) {
768
790
  const taskExecutor = new Executor(userPrompt);
769
- taskExecutor.description = userPrompt;
770
791
  let plans = [];
771
792
  const planningTask = {
772
793
  type: "Planning",
@@ -826,7 +847,6 @@ var PageTaskExecutor = class {
826
847
  async query(demand) {
827
848
  const description = typeof demand === "string" ? demand : JSON.stringify(demand);
828
849
  const taskExecutor = new Executor(description);
829
- taskExecutor.description = description;
830
850
  const queryTask = {
831
851
  type: "Insight",
832
852
  subType: "Query",
@@ -854,9 +874,8 @@ var PageTaskExecutor = class {
854
874
  };
855
875
  }
856
876
  async assert(assertion) {
857
- const description = assertion;
877
+ const description = `assert: ${assertion}`;
858
878
  const taskExecutor = new Executor(description);
859
- taskExecutor.description = description;
860
879
  const assertionPlan = {
861
880
  type: "Assert",
862
881
  param: {
@@ -871,6 +890,64 @@ var PageTaskExecutor = class {
871
890
  executor: taskExecutor
872
891
  };
873
892
  }
893
+ async waitFor(assertion, opt) {
894
+ const description = `waitFor: ${assertion}`;
895
+ const taskExecutor = new Executor(description);
896
+ const { timeoutMs, checkIntervalMs } = opt;
897
+ assert2(assertion, "No assertion for waitFor");
898
+ assert2(timeoutMs, "No timeoutMs for waitFor");
899
+ assert2(checkIntervalMs, "No checkIntervalMs for waitFor");
900
+ const overallStartTime = Date.now();
901
+ let startTime = Date.now();
902
+ let errorThought = "";
903
+ while (Date.now() - overallStartTime < timeoutMs) {
904
+ startTime = Date.now();
905
+ const assertPlan = {
906
+ type: "AssertWithoutThrow",
907
+ param: {
908
+ assertion
909
+ }
910
+ };
911
+ const assertTask = await this.convertPlanToExecutable([assertPlan]);
912
+ await taskExecutor.append(this.wrapExecutorWithScreenshot(assertTask[0]));
913
+ const output = await taskExecutor.flush();
914
+ if (output.pass) {
915
+ return {
916
+ output: void 0,
917
+ executor: taskExecutor
918
+ };
919
+ }
920
+ errorThought = output.thought;
921
+ const now = Date.now();
922
+ if (now - startTime < checkIntervalMs) {
923
+ const timeRemaining = checkIntervalMs - (now - startTime);
924
+ const sleepPlan = {
925
+ type: "Sleep",
926
+ param: {
927
+ timeMs: timeRemaining
928
+ }
929
+ };
930
+ const sleepTask = await this.convertPlanToExecutable([sleepPlan]);
931
+ await taskExecutor.append(
932
+ this.wrapExecutorWithScreenshot(sleepTask[0])
933
+ );
934
+ await taskExecutor.flush();
935
+ }
936
+ }
937
+ const errorPlan = {
938
+ type: "Error",
939
+ param: {
940
+ thought: `waitFor timeout: ${errorThought}`
941
+ }
942
+ };
943
+ const errorTask = await this.convertPlanToExecutable([errorPlan]);
944
+ await taskExecutor.append(errorTask[0]);
945
+ await taskExecutor.flush();
946
+ return {
947
+ output: void 0,
948
+ executor: taskExecutor
949
+ };
950
+ }
874
951
  };
875
952
 
876
953
  // src/common/agent.ts
@@ -880,6 +957,7 @@ var PageAgent = class {
880
957
  this.opts = Object.assign(
881
958
  {
882
959
  generateReport: true,
960
+ autoPrintReportMsg: true,
883
961
  groupName: "Midscene Report",
884
962
  groupDescription: ""
885
963
  },
@@ -905,7 +983,7 @@ var PageAgent = class {
905
983
  return stringifyDumpData(this.dump);
906
984
  }
907
985
  writeOutActionDumps() {
908
- const generateReport = this.opts.generateReport;
986
+ const { generateReport, autoPrintReportMsg } = this.opts;
909
987
  this.reportFile = writeLogFile({
910
988
  fileName: this.reportFileName,
911
989
  fileExt: groupedActionDumpFileExt,
@@ -913,7 +991,7 @@ var PageAgent = class {
913
991
  type: "dump",
914
992
  generateReport
915
993
  });
916
- if (generateReport) {
994
+ if (generateReport && autoPrintReportMsg) {
917
995
  printReportMsg(this.reportFile);
918
996
  }
919
997
  }
@@ -944,11 +1022,25 @@ ${errorTask == null ? void 0 : errorTask.errorStack}`);
944
1022
  this.writeOutActionDumps();
945
1023
  if (!(output == null ? void 0 : output.pass)) {
946
1024
  const errMsg = msg || `Assertion failed: ${assertion}`;
947
- const reasonMsg = `Reason: ${output == null ? void 0 : output.thought} || (no_reason)`;
1025
+ const reasonMsg = `Reason: ${(output == null ? void 0 : output.thought) || "(no_reason)"}`;
948
1026
  throw new Error(`${errMsg}
949
1027
  ${reasonMsg}`);
950
1028
  }
951
1029
  }
1030
+ async aiWaitFor(assertion, opt) {
1031
+ const { executor } = await this.taskExecutor.waitFor(assertion, {
1032
+ timeoutMs: (opt == null ? void 0 : opt.timeoutMs) || 15 * 1e3,
1033
+ checkIntervalMs: (opt == null ? void 0 : opt.checkIntervalMs) || 3 * 1e3,
1034
+ assertion
1035
+ });
1036
+ this.appendExecutionDump(executor.dump());
1037
+ this.writeOutActionDumps();
1038
+ if (executor.isInErrorState()) {
1039
+ const errorTask = executor.latestErrorTask();
1040
+ throw new Error(`${errorTask == null ? void 0 : errorTask.error}
1041
+ ${errorTask == null ? void 0 : errorTask.errorStack}`);
1042
+ }
1043
+ }
952
1044
  async ai(taskPrompt, type = "action") {
953
1045
  if (type === "action") {
954
1046
  return this.aiAction(taskPrompt);
@@ -1124,6 +1216,13 @@ var PlaywrightAiFixture = () => {
1124
1216
  await agent.aiAssert(assertion, errorMsg);
1125
1217
  });
1126
1218
  updateDumpAnnotation(testInfo, agent.dumpDataString());
1219
+ },
1220
+ aiWaitFor: async ({ page }, use, testInfo) => {
1221
+ const agent = agentForPage(page, testInfo);
1222
+ await use(async (assertion, opt) => {
1223
+ await agent.aiWaitFor(assertion, opt);
1224
+ });
1225
+ updateDumpAnnotation(testInfo, agent.dumpDataString());
1127
1226
  }
1128
1227
  };
1129
1228
  };
package/dist/lib/index.js CHANGED
@@ -486,17 +486,24 @@ async function alignElements(screenshotBuffer, elements, page) {
486
486
  return item.rect.height >= sizeThreshold && item.rect.width >= sizeThreshold;
487
487
  });
488
488
  for (const item of validElements) {
489
- const { rect } = item;
489
+ const { rect, id, content, attributes, locator } = item;
490
490
  const aligned = await (0, import_image.alignCoordByTrim)(screenshotBuffer, rect);
491
+ if (aligned.width < 0)
492
+ continue;
491
493
  item.rect = aligned;
492
494
  item.center = [
493
495
  Math.round(aligned.left + aligned.width / 2),
494
496
  Math.round(aligned.top + aligned.height / 2)
495
497
  ];
496
498
  textsAligned.push(
497
- new WebElementInfo(__spreadProps(__spreadValues({}, item), {
499
+ new WebElementInfo({
500
+ rect,
501
+ locator,
502
+ id,
503
+ content,
504
+ attributes,
498
505
  page
499
- }))
506
+ })
500
507
  );
501
508
  }
502
509
  return textsAligned;
@@ -620,7 +627,7 @@ var PageTaskExecutor = class {
620
627
  };
621
628
  return taskFind;
622
629
  }
623
- if (plan2.type === "Assert") {
630
+ if (plan2.type === "Assert" || plan2.type === "AssertWithoutThrow") {
624
631
  const assertPlan = plan2;
625
632
  const taskAssert = {
626
633
  type: "Insight",
@@ -637,13 +644,16 @@ var PageTaskExecutor = class {
637
644
  assertPlan.param.assertion
638
645
  );
639
646
  if (!assertion.pass) {
640
- task.output = assertion;
641
- task.log = {
642
- dump: insightDump
643
- };
644
- throw new Error(
645
- assertion.thought || "Assertion failed without reason"
646
- );
647
+ if (plan2.type === "Assert") {
648
+ task.output = assertion;
649
+ task.log = {
650
+ dump: insightDump
651
+ };
652
+ throw new Error(
653
+ assertion.thought || "Assertion failed without reason"
654
+ );
655
+ }
656
+ task.error = assertion.thought;
647
657
  }
648
658
  return {
649
659
  output: assertion,
@@ -759,7 +769,19 @@ var PageTaskExecutor = class {
759
769
  return taskActionSleep;
760
770
  }
761
771
  if (plan2.type === "Error") {
762
- throw new Error(`Got a task plan with type Error: ${plan2.thought}`);
772
+ const taskActionError = {
773
+ type: "Action",
774
+ subType: "Error",
775
+ param: plan2.param,
776
+ executor: async (taskParam) => {
777
+ (0, import_node_assert2.default)(
778
+ taskParam.thought,
779
+ "An error occurred, but no thought provided"
780
+ );
781
+ throw new Error(taskParam.thought);
782
+ }
783
+ };
784
+ return taskActionError;
763
785
  }
764
786
  throw new Error(`Unknown or Unsupported task type: ${plan2.type}`);
765
787
  }).map((task) => {
@@ -769,7 +791,6 @@ var PageTaskExecutor = class {
769
791
  }
770
792
  async action(userPrompt) {
771
793
  const taskExecutor = new import_core.Executor(userPrompt);
772
- taskExecutor.description = userPrompt;
773
794
  let plans = [];
774
795
  const planningTask = {
775
796
  type: "Planning",
@@ -829,7 +850,6 @@ var PageTaskExecutor = class {
829
850
  async query(demand) {
830
851
  const description = typeof demand === "string" ? demand : JSON.stringify(demand);
831
852
  const taskExecutor = new import_core.Executor(description);
832
- taskExecutor.description = description;
833
853
  const queryTask = {
834
854
  type: "Insight",
835
855
  subType: "Query",
@@ -857,9 +877,8 @@ var PageTaskExecutor = class {
857
877
  };
858
878
  }
859
879
  async assert(assertion) {
860
- const description = assertion;
880
+ const description = `assert: ${assertion}`;
861
881
  const taskExecutor = new import_core.Executor(description);
862
- taskExecutor.description = description;
863
882
  const assertionPlan = {
864
883
  type: "Assert",
865
884
  param: {
@@ -874,6 +893,64 @@ var PageTaskExecutor = class {
874
893
  executor: taskExecutor
875
894
  };
876
895
  }
896
+ async waitFor(assertion, opt) {
897
+ const description = `waitFor: ${assertion}`;
898
+ const taskExecutor = new import_core.Executor(description);
899
+ const { timeoutMs, checkIntervalMs } = opt;
900
+ (0, import_node_assert2.default)(assertion, "No assertion for waitFor");
901
+ (0, import_node_assert2.default)(timeoutMs, "No timeoutMs for waitFor");
902
+ (0, import_node_assert2.default)(checkIntervalMs, "No checkIntervalMs for waitFor");
903
+ const overallStartTime = Date.now();
904
+ let startTime = Date.now();
905
+ let errorThought = "";
906
+ while (Date.now() - overallStartTime < timeoutMs) {
907
+ startTime = Date.now();
908
+ const assertPlan = {
909
+ type: "AssertWithoutThrow",
910
+ param: {
911
+ assertion
912
+ }
913
+ };
914
+ const assertTask = await this.convertPlanToExecutable([assertPlan]);
915
+ await taskExecutor.append(this.wrapExecutorWithScreenshot(assertTask[0]));
916
+ const output = await taskExecutor.flush();
917
+ if (output.pass) {
918
+ return {
919
+ output: void 0,
920
+ executor: taskExecutor
921
+ };
922
+ }
923
+ errorThought = output.thought;
924
+ const now = Date.now();
925
+ if (now - startTime < checkIntervalMs) {
926
+ const timeRemaining = checkIntervalMs - (now - startTime);
927
+ const sleepPlan = {
928
+ type: "Sleep",
929
+ param: {
930
+ timeMs: timeRemaining
931
+ }
932
+ };
933
+ const sleepTask = await this.convertPlanToExecutable([sleepPlan]);
934
+ await taskExecutor.append(
935
+ this.wrapExecutorWithScreenshot(sleepTask[0])
936
+ );
937
+ await taskExecutor.flush();
938
+ }
939
+ }
940
+ const errorPlan = {
941
+ type: "Error",
942
+ param: {
943
+ thought: `waitFor timeout: ${errorThought}`
944
+ }
945
+ };
946
+ const errorTask = await this.convertPlanToExecutable([errorPlan]);
947
+ await taskExecutor.append(errorTask[0]);
948
+ await taskExecutor.flush();
949
+ return {
950
+ output: void 0,
951
+ executor: taskExecutor
952
+ };
953
+ }
877
954
  };
878
955
 
879
956
  // src/common/agent.ts
@@ -883,6 +960,7 @@ var PageAgent = class {
883
960
  this.opts = Object.assign(
884
961
  {
885
962
  generateReport: true,
963
+ autoPrintReportMsg: true,
886
964
  groupName: "Midscene Report",
887
965
  groupDescription: ""
888
966
  },
@@ -908,7 +986,7 @@ var PageAgent = class {
908
986
  return (0, import_utils4.stringifyDumpData)(this.dump);
909
987
  }
910
988
  writeOutActionDumps() {
911
- const generateReport = this.opts.generateReport;
989
+ const { generateReport, autoPrintReportMsg } = this.opts;
912
990
  this.reportFile = (0, import_utils4.writeLogFile)({
913
991
  fileName: this.reportFileName,
914
992
  fileExt: import_utils4.groupedActionDumpFileExt,
@@ -916,7 +994,7 @@ var PageAgent = class {
916
994
  type: "dump",
917
995
  generateReport
918
996
  });
919
- if (generateReport) {
997
+ if (generateReport && autoPrintReportMsg) {
920
998
  printReportMsg(this.reportFile);
921
999
  }
922
1000
  }
@@ -947,11 +1025,25 @@ ${errorTask == null ? void 0 : errorTask.errorStack}`);
947
1025
  this.writeOutActionDumps();
948
1026
  if (!(output == null ? void 0 : output.pass)) {
949
1027
  const errMsg = msg || `Assertion failed: ${assertion}`;
950
- const reasonMsg = `Reason: ${output == null ? void 0 : output.thought} || (no_reason)`;
1028
+ const reasonMsg = `Reason: ${(output == null ? void 0 : output.thought) || "(no_reason)"}`;
951
1029
  throw new Error(`${errMsg}
952
1030
  ${reasonMsg}`);
953
1031
  }
954
1032
  }
1033
+ async aiWaitFor(assertion, opt) {
1034
+ const { executor } = await this.taskExecutor.waitFor(assertion, {
1035
+ timeoutMs: (opt == null ? void 0 : opt.timeoutMs) || 15 * 1e3,
1036
+ checkIntervalMs: (opt == null ? void 0 : opt.checkIntervalMs) || 3 * 1e3,
1037
+ assertion
1038
+ });
1039
+ this.appendExecutionDump(executor.dump());
1040
+ this.writeOutActionDumps();
1041
+ if (executor.isInErrorState()) {
1042
+ const errorTask = executor.latestErrorTask();
1043
+ throw new Error(`${errorTask == null ? void 0 : errorTask.error}
1044
+ ${errorTask == null ? void 0 : errorTask.errorStack}`);
1045
+ }
1046
+ }
955
1047
  async ai(taskPrompt, type = "action") {
956
1048
  if (type === "action") {
957
1049
  return this.aiAction(taskPrompt);
@@ -1123,6 +1215,13 @@ var PlaywrightAiFixture = () => {
1123
1215
  await agent.aiAssert(assertion, errorMsg);
1124
1216
  });
1125
1217
  updateDumpAnnotation(testInfo, agent.dumpDataString());
1218
+ },
1219
+ aiWaitFor: async ({ page }, use, testInfo) => {
1220
+ const agent = agentForPage(page, testInfo);
1221
+ await use(async (assertion, opt) => {
1222
+ await agent.aiWaitFor(assertion, opt);
1223
+ });
1224
+ updateDumpAnnotation(testInfo, agent.dumpDataString());
1126
1225
  }
1127
1226
  };
1128
1227
  };