@stablyai/playwright-base 0.2.1 → 1.0.0-next.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/dist/index.cjs CHANGED
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ Agent: () => Agent,
33
34
  augmentBrowser: () => augmentBrowser,
34
35
  augmentBrowserContext: () => augmentBrowserContext,
35
36
  augmentBrowserType: () => augmentBrowserType,
@@ -42,7 +43,7 @@ __export(index_exports, {
42
43
  module.exports = __toCommonJS(index_exports);
43
44
 
44
45
  // src/expect.ts
45
- var import_internal_playwright_test = require("@stablyai/internal-playwright-test");
46
+ var import_test = require("@playwright/test");
46
47
 
47
48
  // src/runtime.ts
48
49
  var configuredApiKey = process.env.STABLY_API_KEY;
@@ -70,11 +71,16 @@ var isObject = (value) => {
70
71
  // src/ai/metadata.ts
71
72
  var SDK_METADATA_HEADERS = {
72
73
  "X-Client-Name": "stably-playwright-sdk-js",
73
- "X-Client-Version": "0.2.1"
74
+ "X-Client-Version": "1.0.0-next.1"
74
75
  };
75
76
 
76
77
  // src/ai/verify-prompt.ts
77
- var PROMPT_ASSERTION_ENDPOINT = "https://api.stably.ai/internal/v1/assert";
78
+ var PROMPT_ASSERTION_PATH = "internal/v1/assert";
79
+ var STABLY_API_URL = process.env.STABLY_API_URL || "https://api.stably.ai";
80
+ var PROMPT_ASSERTION_ENDPOINT = new URL(
81
+ PROMPT_ASSERTION_PATH,
82
+ STABLY_API_URL
83
+ ).toString();
78
84
  var parseSuccessResponse = (value) => {
79
85
  if (!isObject(value)) {
80
86
  throw new Error("Verify prompt returned unexpected response shape");
@@ -417,7 +423,7 @@ var stablyPlaywrightMatchers = {
417
423
  const targetType = isPage(target) ? "page" : "locator";
418
424
  const screenshot = await takeStableScreenshot(target, options);
419
425
  const verifyResult = await verifyPrompt({ prompt: condition, screenshot });
420
- const testInfo = import_internal_playwright_test.test.info();
426
+ const testInfo = import_test.test.info();
421
427
  testInfo.attachments.push({
422
428
  body: Buffer.from(
423
429
  JSON.stringify(
@@ -449,7 +455,7 @@ var stablyPlaywrightMatchers = {
449
455
  };
450
456
 
451
457
  // src/playwright-augment/methods/agent.ts
452
- var import_internal_playwright_test2 = require("@stablyai/internal-playwright-test");
458
+ var import_test2 = require("@playwright/test");
453
459
 
454
460
  // src/utils/truncate.ts
455
461
  var truncate = (inp, length) => inp.length <= length || inp.length <= 3 ? inp : `${inp.slice(0, length - 3)}...`;
@@ -657,16 +663,48 @@ ${ariaSnapshot}` }
657
663
 
658
664
  // src/playwright-augment/methods/agent.ts
659
665
  var AGENT_PATH = "internal/v3/agent";
660
- var STABLY_API_URL = process.env.STABLY_API_URL || "https://api.stably.ai";
661
- var AGENT_ENDPOINT = new URL(AGENT_PATH, STABLY_API_URL).toString();
662
- function createAgentStub() {
663
- let thoughtsIndex = 0;
664
- return async (prompt, options) => {
666
+ var STABLY_API_URL2 = process.env.STABLY_API_URL || "https://api.stably.ai";
667
+ var AGENT_ENDPOINT = new URL(AGENT_PATH, STABLY_API_URL2).toString();
668
+ var Agent = class {
669
+ constructor(browserContext) {
670
+ this.browserContext = browserContext;
671
+ }
672
+ /**
673
+ * Instructs the agent to perform actions on the page using natural language.
674
+ *
675
+ * The agent will analyze the page, plan actions, and execute them autonomously.
676
+ * It can perform multiple actions such as clicking, typing, navigating, and verifying
677
+ * page state based on the provided instruction.
678
+ *
679
+ * @param prompt - Natural language instruction describing what the agent should do
680
+ * @param options - Configuration for the agent's behavior including the page to operate on
681
+ * @returns Promise that resolves with the result of the agent's actions
682
+ *
683
+ * @example
684
+ * ```typescript
685
+ * // Simple action
686
+ * await agent.act('Click the login button', { page });
687
+ *
688
+ * // Complex multi-step action
689
+ * await agent.act('Navigate to settings, enable notifications, and save changes', { page });
690
+ *
691
+ * // With custom options
692
+ * const result = await agent.act('Complete the checkout process', {
693
+ * page,
694
+ * maxCycles: 20,
695
+ * model: 'gpt-4'
696
+ * });
697
+ *
698
+ * if (result.success) {
699
+ * console.log('Agent completed the task successfully');
700
+ * }
701
+ * ```
702
+ */
703
+ async act(prompt, options) {
665
704
  const apiKey = requireApiKey();
666
705
  const maxCycles = options.maxCycles ?? 30;
667
- const browserContext = options.page.context();
668
706
  const tabManager = /* @__PURE__ */ new Map();
669
- browserContext.pages().forEach((page, index) => {
707
+ this.browserContext.pages().forEach((page, index) => {
670
708
  tabManager.set(page, `page${index + 1}`);
671
709
  });
672
710
  let activePage = options.page;
@@ -689,14 +727,14 @@ function createAgentStub() {
689
727
  const alias = tabManager.get(page);
690
728
  newPageOpenedMsg = `opened new tab ${alias} (${page.url()})`;
691
729
  };
692
- browserContext.on("page", onNewPage);
693
- return await import_internal_playwright_test2.test.step(`[Agent] ${prompt}`, async () => {
730
+ this.browserContext.on("page", onNewPage);
731
+ return await import_test2.test.step(`[Agent] ${prompt}`, async () => {
694
732
  try {
695
733
  for (let i = 0; i < maxCycles; i++) {
696
734
  if (agentMessage.shouldTerminate) {
697
735
  break;
698
736
  }
699
- const agentResponses = await import_internal_playwright_test2.test.step(`[Thinking ${thoughtsIndex + 1}]`, async (stepInfo) => {
737
+ const agentResponses = await import_test2.test.step(`[Thinking ${i + 1}]`, async (stepInfo) => {
700
738
  const screenshot = await takeStableScreenshot(activePage);
701
739
  const response = await fetch(AGENT_ENDPOINT, {
702
740
  body: constructAgentPayload({
@@ -726,13 +764,12 @@ function createAgentStub() {
726
764
  const reasoningText = reasoningTexts.join("\n\n");
727
765
  const truncatedReasoningText = truncate(reasoningText, 120);
728
766
  await stepInfo.attach(
729
- `[Thinking ${thoughtsIndex + 1}] ${truncatedReasoningText}`,
767
+ `[Thinking ${i + 1}] ${truncatedReasoningText}`,
730
768
  {
731
769
  body: reasoningText,
732
770
  contentType: "text/plain"
733
771
  }
734
772
  );
735
- thoughtsIndex++;
736
773
  return responseJson;
737
774
  });
738
775
  let combinedMessages = [];
@@ -746,7 +783,7 @@ function createAgentStub() {
746
783
  } = await execResponse({
747
784
  activePage,
748
785
  agentResponse,
749
- browserContext,
786
+ browserContext: this.browserContext,
750
787
  tabManager
751
788
  });
752
789
  activePage = newActivePage;
@@ -767,17 +804,19 @@ function createAgentStub() {
767
804
  };
768
805
  }
769
806
  } finally {
770
- browserContext.off("page", onNewPage);
807
+ this.browserContext.off("page", onNewPage);
771
808
  }
772
809
  return { success: finalSuccess ?? false };
773
810
  });
774
- };
775
- }
811
+ }
812
+ };
813
+ var createNewAgent = (browserContext) => new Agent(browserContext);
776
814
 
777
815
  // src/ai/extract.ts
816
+ var import_test3 = require("@playwright/test");
778
817
  var EXTRACT_PATH = "internal/v2/extract";
779
- var STABLY_API_URL2 = process.env.STABLY_API_URL || "https://api.stably.ai";
780
- var EXTRACT_ENDPOINT = new URL(EXTRACT_PATH, STABLY_API_URL2).toString();
818
+ var STABLY_API_URL3 = process.env.STABLY_API_URL || "https://api.stably.ai";
819
+ var EXTRACT_ENDPOINT = new URL(EXTRACT_PATH, STABLY_API_URL3).toString();
781
820
  var zodV4 = (() => {
782
821
  try {
783
822
  return require("zod/v4/core");
@@ -807,7 +846,10 @@ var ExtractValidationError = class extends Error {
807
846
  async function validateWithSchema(schema, value) {
808
847
  const result = await schema.safeParseAsync(value);
809
848
  if (!result.success) {
810
- throw new ExtractValidationError("Validation failed", result.error.issues);
849
+ throw new ExtractValidationError(
850
+ "AI is unable to return the data in the desired format",
851
+ result.error.issues
852
+ );
811
853
  }
812
854
  return result.data;
813
855
  }
@@ -825,36 +867,47 @@ async function extract({
825
867
  // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
826
868
  schema
827
869
  ) : void 0;
828
- const apiKey = requireApiKey();
829
- const form = new FormData();
830
- form.append("prompt", prompt);
831
- if (jsonSchema) {
832
- form.append("jsonSchema", JSON.stringify(jsonSchema));
833
- }
834
- const pngBuffer = await pageOrLocator.screenshot({ type: "png" });
835
- const u8 = Uint8Array.from(pngBuffer);
836
- const blob = new Blob([u8], { type: "image/png" });
837
- form.append("image", blob, "screenshot.png");
838
- const response = await fetch(EXTRACT_ENDPOINT, {
839
- body: form,
840
- headers: {
841
- ...SDK_METADATA_HEADERS,
842
- Authorization: `Bearer ${apiKey}`
843
- },
844
- method: "POST"
845
- });
846
- const raw = await response.json().catch(() => void 0);
847
- if (response.ok) {
848
- if (!isExtractionResponse(raw)) {
849
- throw new Error("Extract returned unexpected response shape");
870
+ return await import_test3.test.step(`[Extract] ${prompt}`, async (stepInfo) => {
871
+ const apiKey = requireApiKey();
872
+ const form = new FormData();
873
+ form.append("prompt", prompt);
874
+ if (jsonSchema) {
875
+ form.append("jsonSchema", JSON.stringify(jsonSchema));
850
876
  }
851
- if (!raw.success) {
852
- throw new Error(`Extract failed: ${raw.error}`);
877
+ const pngBuffer = await pageOrLocator.screenshot({ type: "png" });
878
+ const u8 = Uint8Array.from(pngBuffer);
879
+ const blob = new Blob([u8], { type: "image/png" });
880
+ form.append("image", blob, "screenshot.png");
881
+ const response = await fetch(EXTRACT_ENDPOINT, {
882
+ body: form,
883
+ headers: {
884
+ ...SDK_METADATA_HEADERS,
885
+ Authorization: `Bearer ${apiKey}`
886
+ },
887
+ method: "POST"
888
+ });
889
+ const raw = await response.json().catch(() => void 0);
890
+ if (response.ok) {
891
+ if (!isExtractionResponse(raw)) {
892
+ throw new Error("Extract returned unexpected response shape");
893
+ }
894
+ if (!raw.success) {
895
+ await stepInfo.attach("[Extract] error", {
896
+ body: raw.error,
897
+ contentType: "text/plain"
898
+ });
899
+ throw new Error(`Extract failed: ${raw.error}`);
900
+ }
901
+ const { value } = raw;
902
+ const body = typeof value === "string" ? value : JSON.stringify(value, null, 2);
903
+ await stepInfo.attach("[Extract] result", {
904
+ body,
905
+ contentType: "text/plain"
906
+ });
907
+ return schema ? await validateWithSchema(schema, value) : typeof value === "string" ? value : JSON.stringify(value);
853
908
  }
854
- const { value } = raw;
855
- return schema ? await validateWithSchema(schema, value) : typeof value === "string" ? value : JSON.stringify(value);
856
- }
857
- throw new Error(isErrorResponse(raw) ? raw.error : "Extract failed");
909
+ throw new Error(isErrorResponse(raw) ? raw.error : "Extract failed");
910
+ });
858
911
  }
859
912
 
860
913
  // src/playwright-augment/methods/extract.ts
@@ -876,9 +929,6 @@ var createPageExtract = (page) => createExtract(page);
876
929
 
877
930
  // src/playwright-augment/augment.ts
878
931
  var LOCATOR_PATCHED = Symbol.for("stably.playwright.locatorPatched");
879
- var LOCATOR_DESCRIBE_WRAPPED = Symbol.for(
880
- "stably.playwright.locatorDescribeWrapped"
881
- );
882
932
  var PAGE_PATCHED = Symbol.for("stably.playwright.pagePatched");
883
933
  var CONTEXT_PATCHED = Symbol.for("stably.playwright.contextPatched");
884
934
  var BROWSER_PATCHED = Symbol.for("stably.playwright.browserPatched");
@@ -896,15 +946,6 @@ function augmentLocator(locator) {
896
946
  return locator;
897
947
  }
898
948
  defineHiddenProperty(locator, "extract", createLocatorExtract(locator));
899
- const markerTarget = locator;
900
- if (typeof locator.describe === "function" && !markerTarget[LOCATOR_DESCRIBE_WRAPPED]) {
901
- const originalDescribe = locator.describe.bind(locator);
902
- locator.describe = (description, options) => {
903
- const result = originalDescribe(description, options);
904
- return result ? augmentLocator(result) : result;
905
- };
906
- defineHiddenProperty(locator, LOCATOR_DESCRIBE_WRAPPED, true);
907
- }
908
949
  defineHiddenProperty(locator, LOCATOR_PATCHED, true);
909
950
  return locator;
910
951
  }
@@ -932,13 +973,12 @@ function augmentBrowserContext(context) {
932
973
  };
933
974
  const originalPages = context.pages?.bind(context);
934
975
  if (originalPages) {
935
- context.pages = () => originalPages().map(
936
- (page) => augmentPage(page)
937
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
938
- );
976
+ context.pages = () => originalPages().map((page) => augmentPage(page));
939
977
  }
940
- if (!context.agent) {
941
- defineHiddenProperty(context, "agent", createAgentStub());
978
+ if (!context.newAgent) {
979
+ defineHiddenProperty(context, "newAgent", () => {
980
+ return createNewAgent(context);
981
+ });
942
982
  }
943
983
  defineHiddenProperty(context, CONTEXT_PATCHED, true);
944
984
  return context;
@@ -960,10 +1000,13 @@ function augmentBrowser(browser) {
960
1000
  const originalContexts = browser.contexts.bind(browser);
961
1001
  browser.contexts = () => originalContexts().map(
962
1002
  (context) => augmentBrowserContext(context)
963
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
964
1003
  );
965
- if (!browser.agent) {
966
- defineHiddenProperty(browser, "agent", createAgentStub());
1004
+ if (!browser.newAgent) {
1005
+ defineHiddenProperty(browser, "newAgent", () => {
1006
+ const contexts = browser.contexts();
1007
+ const context = contexts.length > 0 ? contexts[0] : browser.contexts()[0];
1008
+ return createNewAgent(context);
1009
+ });
967
1010
  }
968
1011
  defineHiddenProperty(browser, BROWSER_PATCHED, true);
969
1012
  return browser;
@@ -993,10 +1036,7 @@ function augmentBrowserType(browserType) {
993
1036
  return augmentBrowser(browser);
994
1037
  };
995
1038
  }
996
- const originalLaunchPersistentContext = (
997
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
998
- browserType.launchPersistentContext?.bind(browserType)
999
- );
1039
+ const originalLaunchPersistentContext = browserType.launchPersistentContext?.bind(browserType);
1000
1040
  if (originalLaunchPersistentContext) {
1001
1041
  browserType.launchPersistentContext = async (...args) => {
1002
1042
  const context = await originalLaunchPersistentContext(...args);
@@ -1008,6 +1048,7 @@ function augmentBrowserType(browserType) {
1008
1048
  }
1009
1049
  // Annotate the CommonJS export names for ESM import in node:
1010
1050
  0 && (module.exports = {
1051
+ Agent,
1011
1052
  augmentBrowser,
1012
1053
  augmentBrowserContext,
1013
1054
  augmentBrowserType,