@stablyai/playwright-base 0.2.1 → 1.0.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/dist/index.cjs +118 -77
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +140 -33
- package/dist/index.d.ts +140 -33
- package/dist/index.mjs +115 -75
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -4
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
|
|
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.
|
|
74
|
+
"X-Client-Version": "1.0.0-next.1"
|
|
74
75
|
};
|
|
75
76
|
|
|
76
77
|
// src/ai/verify-prompt.ts
|
|
77
|
-
var
|
|
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 =
|
|
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
|
|
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
|
|
661
|
-
var AGENT_ENDPOINT = new URL(AGENT_PATH,
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
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
|
|
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
|
|
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 ${
|
|
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
|
|
780
|
-
var EXTRACT_ENDPOINT = new URL(EXTRACT_PATH,
|
|
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(
|
|
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
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
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
|
-
|
|
852
|
-
|
|
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
|
-
|
|
855
|
-
|
|
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.
|
|
941
|
-
defineHiddenProperty(context, "
|
|
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.
|
|
966
|
-
defineHiddenProperty(browser, "
|
|
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,
|