tamash-playwright 0.1.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/.env.example +26 -0
- package/README.md +108 -0
- package/dist/bindings/index.d.ts +8 -0
- package/dist/bindings/index.d.ts.map +1 -0
- package/dist/bindings/index.js +20 -0
- package/dist/bindings/index.js.map +1 -0
- package/dist/bindings/locator.binding.d.ts +8 -0
- package/dist/bindings/locator.binding.d.ts.map +1 -0
- package/dist/bindings/locator.binding.js +141 -0
- package/dist/bindings/locator.binding.js.map +1 -0
- package/dist/healer/index.d.ts +29 -0
- package/dist/healer/index.d.ts.map +1 -0
- package/dist/healer/index.js +256 -0
- package/dist/healer/index.js.map +1 -0
- package/dist/healer/providers/anthropic.d.ts +3 -0
- package/dist/healer/providers/anthropic.d.ts.map +1 -0
- package/dist/healer/providers/anthropic.js +52 -0
- package/dist/healer/providers/anthropic.js.map +1 -0
- package/dist/healer/providers/gemini.d.ts +3 -0
- package/dist/healer/providers/gemini.d.ts.map +1 -0
- package/dist/healer/providers/gemini.js +66 -0
- package/dist/healer/providers/gemini.js.map +1 -0
- package/dist/healer/providers/index.d.ts +4 -0
- package/dist/healer/providers/index.d.ts.map +1 -0
- package/dist/healer/providers/index.js +34 -0
- package/dist/healer/providers/index.js.map +1 -0
- package/dist/healer/providers/ollama.d.ts +3 -0
- package/dist/healer/providers/ollama.d.ts.map +1 -0
- package/dist/healer/providers/ollama.js +76 -0
- package/dist/healer/providers/ollama.js.map +1 -0
- package/dist/healer/providers/openai.d.ts +3 -0
- package/dist/healer/providers/openai.d.ts.map +1 -0
- package/dist/healer/providers/openai.js +64 -0
- package/dist/healer/providers/openai.js.map +1 -0
- package/dist/healer/providers/prompt.d.ts +12 -0
- package/dist/healer/providers/prompt.d.ts.map +1 -0
- package/dist/healer/providers/prompt.js +87 -0
- package/dist/healer/providers/prompt.js.map +1 -0
- package/dist/healer/providers/types.d.ts +43 -0
- package/dist/healer/providers/types.d.ts.map +1 -0
- package/dist/healer/providers/types.js +3 -0
- package/dist/healer/providers/types.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/package.json +40 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createAnthropicProvider = createAnthropicProvider;
|
|
7
|
+
const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
|
|
8
|
+
const prompt_1 = require("./prompt");
|
|
9
|
+
// Fallback only — normally overridden by SuggestSelectorInput.timeoutMs, which mirrors the
|
|
10
|
+
// Playwright project's configured actionTimeout so the healer never outlasts the action budget.
|
|
11
|
+
const REQUEST_TIMEOUT_MS = 15000;
|
|
12
|
+
function createAnthropicProvider() {
|
|
13
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
14
|
+
const model = process.env.ANTHROPIC_MODEL;
|
|
15
|
+
if (!apiKey || !model) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const client = new sdk_1.default({ apiKey });
|
|
19
|
+
return {
|
|
20
|
+
name: `anthropic:${model}`,
|
|
21
|
+
async suggestSelector(input) {
|
|
22
|
+
try {
|
|
23
|
+
const response = await client.messages.create({
|
|
24
|
+
model,
|
|
25
|
+
max_tokens: 1024,
|
|
26
|
+
system: prompt_1.SYSTEM_PROMPT,
|
|
27
|
+
messages: [{ role: 'user', content: (0, prompt_1.buildUserPrompt)(input) }],
|
|
28
|
+
}, { timeout: input.timeoutMs ?? REQUEST_TIMEOUT_MS });
|
|
29
|
+
const textBlock = response.content.find((block) => block.type === 'text');
|
|
30
|
+
if (!textBlock || textBlock.type !== 'text') {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const suggestion = (0, prompt_1.parseSuggestion)(textBlock.text);
|
|
34
|
+
if (!suggestion) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const inputTokens = response.usage.input_tokens;
|
|
38
|
+
const outputTokens = response.usage.output_tokens;
|
|
39
|
+
return {
|
|
40
|
+
suggestion,
|
|
41
|
+
usage: { inputTokens, outputTokens, totalTokens: inputTokens + outputTokens },
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
46
|
+
console.warn(`[self-healer] Anthropic provider error: ${reason}`);
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=anthropic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../../src/healer/providers/anthropic.ts"],"names":[],"mappings":";;;;;;AAAA,4DAA0C;AAE1C,qCAA2E;AAE3E,2FAA2F;AAC3F,gGAAgG;AAChG,MAAM,kBAAkB,GAAG,KAAK,CAAC;AAEjC;IACE,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;IAC7C,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;IAE1C,IAAI,CAAC,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,aAAS,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IAEzC,OAAO;QACL,IAAI,EAAE,aAAa,KAAK,EAAE;QAC1B,KAAK,CAAC,eAAe,CAAC,KAA2B;YAC/C,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAC3C;oBACE,KAAK;oBACL,UAAU,EAAE,IAAI;oBAChB,MAAM,EAAE,sBAAa;oBACrB,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAA,wBAAe,EAAC,KAAK,CAAC,EAAE,CAAC;iBAC9D,EACD,EAAE,OAAO,EAAE,KAAK,CAAC,SAAS,IAAI,kBAAkB,EAAE,CACnD,CAAC;gBAEF,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;gBAC1E,IAAI,CAAC,SAAS,IAAI,SAAS,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;oBAC5C,OAAO,IAAI,CAAC;gBACd,CAAC;gBAED,MAAM,UAAU,GAAG,IAAA,wBAAe,EAAC,SAAS,CAAC,IAAI,CAAC,CAAC;gBACnD,IAAI,CAAC,UAAU,EAAE,CAAC;oBAChB,OAAO,IAAI,CAAC;gBACd,CAAC;gBAED,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC;gBAChD,MAAM,YAAY,GAAG,QAAQ,CAAC,KAAK,CAAC,aAAa,CAAC;gBAClD,OAAO;oBACL,UAAU;oBACV,KAAK,EAAE,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,GAAG,YAAY,EAAE;iBAC9E,CAAC;YACJ,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACtE,OAAO,CAAC,IAAI,CAAC,2CAA2C,MAAM,EAAE,CAAC,CAAC;gBAClE,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gemini.d.ts","sourceRoot":"","sources":["../../../src/healer/providers/gemini.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAwC,MAAM,SAAS,CAAC;AAOlF,wBAAgB,oBAAoB,IAAI,YAAY,GAAG,IAAI,CAkE1D"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createGeminiProvider = createGeminiProvider;
|
|
4
|
+
const prompt_1 = require("./prompt");
|
|
5
|
+
// Fallback only — normally overridden by SuggestSelectorInput.timeoutMs, which mirrors the
|
|
6
|
+
// Playwright project's configured actionTimeout so the healer never outlasts the action budget.
|
|
7
|
+
const REQUEST_TIMEOUT_MS = 15000;
|
|
8
|
+
function createGeminiProvider() {
|
|
9
|
+
const apiKey = process.env.GEMINI_API_KEY;
|
|
10
|
+
const model = process.env.GEMINI_MODEL;
|
|
11
|
+
if (!apiKey || !model) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
// Gemini's OpenAI-compatible surface — same request/response shape as OpenAI's Chat
|
|
15
|
+
// Completions API, so this reuses the same prompt/parsing logic as providers/openai.ts.
|
|
16
|
+
const baseUrl = (process.env.GEMINI_BASE_URL ?? 'https://generativelanguage.googleapis.com/v1beta/openai').replace(/\/+$/, '');
|
|
17
|
+
return {
|
|
18
|
+
name: `gemini:${model}`,
|
|
19
|
+
async suggestSelector(input) {
|
|
20
|
+
const controller = new AbortController();
|
|
21
|
+
const timeout = setTimeout(() => controller.abort(), input.timeoutMs ?? REQUEST_TIMEOUT_MS);
|
|
22
|
+
try {
|
|
23
|
+
const response = await fetch(`${baseUrl}/chat/completions`, {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
signal: controller.signal,
|
|
26
|
+
headers: {
|
|
27
|
+
'content-type': 'application/json',
|
|
28
|
+
authorization: `Bearer ${apiKey}`,
|
|
29
|
+
},
|
|
30
|
+
body: JSON.stringify({
|
|
31
|
+
model,
|
|
32
|
+
temperature: 0,
|
|
33
|
+
response_format: { type: 'json_object' },
|
|
34
|
+
messages: [
|
|
35
|
+
{ role: 'system', content: prompt_1.SYSTEM_PROMPT },
|
|
36
|
+
{ role: 'user', content: (0, prompt_1.buildUserPrompt)(input) },
|
|
37
|
+
],
|
|
38
|
+
}),
|
|
39
|
+
});
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
console.warn(`[self-healer] Gemini request failed: ${response.status} ${response.statusText}`);
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const payload = await response.json();
|
|
45
|
+
const content = payload.choices?.[0]?.message?.content;
|
|
46
|
+
if (!content) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const suggestion = (0, prompt_1.parseSuggestion)(content);
|
|
50
|
+
if (!suggestion) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return { suggestion, usage: (0, prompt_1.extractOpenAICompatibleUsage)(payload) };
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
57
|
+
console.warn(`[self-healer] Gemini provider error: ${reason}`);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
clearTimeout(timeout);
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=gemini.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gemini.js","sourceRoot":"","sources":["../../../src/healer/providers/gemini.ts"],"names":[],"mappings":";;;AACA,qCAAyG;AAEzG,2FAA2F;AAC3F,gGAAgG;AAChG,MAAM,kBAAkB,GAAG,KAAK,CAAC;AAEjC;IACE,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IAC1C,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IAEvC,IAAI,CAAC,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,oFAAoF;IACpF,wFAAwF;IACxF,MAAM,OAAO,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,yDAAyD,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAE/H,OAAO;QACL,IAAI,EAAE,UAAU,KAAK,EAAE;QACvB,KAAK,CAAC,eAAe,CAAC,KAA2B;YAC/C,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;YACzC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,KAAK,CAAC,SAAS,IAAI,kBAAkB,CAAC,CAAC;YAE5F,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,mBAAmB,EAAE;oBAC1D,MAAM,EAAE,MAAM;oBACd,MAAM,EAAE,UAAU,CAAC,MAAM;oBACzB,OAAO,EAAE;wBACP,cAAc,EAAE,kBAAkB;wBAClC,aAAa,EAAE,UAAU,MAAM,EAAE;qBAClC;oBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;wBACnB,KAAK;wBACL,WAAW,EAAE,CAAC;wBACd,eAAe,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE;wBACxC,QAAQ,EAAE;4BACR,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,sBAAa,EAAE;4BAC1C,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAA,wBAAe,EAAC,KAAK,CAAC,EAAE;yBAClD;qBACF,CAAC;iBACH,CAAC,CAAC;gBAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;oBACjB,OAAO,CAAC,IAAI,CAAC,wCAAwC,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;oBAC/F,OAAO,IAAI,CAAC;gBACd,CAAC;gBAED,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,EAGlC,CAAC;gBACF,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC;gBACvD,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,OAAO,IAAI,CAAC;gBACd,CAAC;gBAED,MAAM,UAAU,GAAG,IAAA,wBAAe,EAAC,OAAO,CAAC,CAAC;gBAC5C,IAAI,CAAC,UAAU,EAAE,CAAC;oBAChB,OAAO,IAAI,CAAC;gBACd,CAAC;gBAED,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,IAAA,qCAA4B,EAAC,OAAO,CAAC,EAAE,CAAC;YACtE,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACtE,OAAO,CAAC,IAAI,CAAC,wCAAwC,MAAM,EAAE,CAAC,CAAC;gBAC/D,OAAO,IAAI,CAAC;YACd,CAAC;oBAAS,CAAC;gBACT,YAAY,CAAC,OAAO,CAAC,CAAC;YACxB,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/healer/providers/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAOlH,wBAAgB,eAAe,IAAI,YAAY,GAAG,IAAI,CAuBrD"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getHealProvider = getHealProvider;
|
|
4
|
+
const ollama_1 = require("./ollama");
|
|
5
|
+
const openai_1 = require("./openai");
|
|
6
|
+
const anthropic_1 = require("./anthropic");
|
|
7
|
+
const gemini_1 = require("./gemini");
|
|
8
|
+
let cachedProvider;
|
|
9
|
+
// HEALER_PROVIDER picks which AI provider backs self-healing; each provider gracefully
|
|
10
|
+
// returns null (disabling healing for that action) if its own API key/model env vars
|
|
11
|
+
// aren't set, so switching providers is just an env change, no code change.
|
|
12
|
+
function getHealProvider() {
|
|
13
|
+
if (cachedProvider !== undefined) {
|
|
14
|
+
return cachedProvider;
|
|
15
|
+
}
|
|
16
|
+
switch (process.env.HEALER_PROVIDER) {
|
|
17
|
+
case 'ollama':
|
|
18
|
+
cachedProvider = (0, ollama_1.createOllamaProvider)();
|
|
19
|
+
break;
|
|
20
|
+
case 'openai':
|
|
21
|
+
cachedProvider = (0, openai_1.createOpenAIProvider)();
|
|
22
|
+
break;
|
|
23
|
+
case 'anthropic':
|
|
24
|
+
cachedProvider = (0, anthropic_1.createAnthropicProvider)();
|
|
25
|
+
break;
|
|
26
|
+
case 'gemini':
|
|
27
|
+
cachedProvider = (0, gemini_1.createGeminiProvider)();
|
|
28
|
+
break;
|
|
29
|
+
default:
|
|
30
|
+
cachedProvider = null;
|
|
31
|
+
}
|
|
32
|
+
return cachedProvider;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/healer/providers/index.ts"],"names":[],"mappings":";;;AAAA,qCAAgD;AAChD,qCAAgD;AAChD,2CAAsD;AACtD,qCAAgD;AAKhD,IAAI,cAA+C,CAAC;AAEpD,uFAAuF;AACvF,qFAAqF;AACrF,4EAA4E;AAC5E;IACE,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;QACjC,OAAO,cAAc,CAAC;IACxB,CAAC;IAED,QAAQ,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC;QACpC,KAAK,QAAQ;YACX,cAAc,GAAG,IAAA,6BAAoB,GAAE,CAAC;YACxC,MAAM;QACR,KAAK,QAAQ;YACX,cAAc,GAAG,IAAA,6BAAoB,GAAE,CAAC;YACxC,MAAM;QACR,KAAK,WAAW;YACd,cAAc,GAAG,IAAA,mCAAuB,GAAE,CAAC;YAC3C,MAAM;QACR,KAAK,QAAQ;YACX,cAAc,GAAG,IAAA,6BAAoB,GAAE,CAAC;YACxC,MAAM;QACR;YACE,cAAc,GAAG,IAAI,CAAC;IAC1B,CAAC;IAED,OAAO,cAAc,CAAC;AACxB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ollama.d.ts","sourceRoot":"","sources":["../../../src/healer/providers/ollama.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAwC,MAAM,SAAS,CAAC;AAOlF,wBAAgB,oBAAoB,IAAI,YAAY,GAAG,IAAI,CA6E1D"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createOllamaProvider = createOllamaProvider;
|
|
4
|
+
const prompt_1 = require("./prompt");
|
|
5
|
+
// Fallback only — normally overridden by SuggestSelectorInput.timeoutMs, which mirrors the
|
|
6
|
+
// Playwright project's configured actionTimeout so the healer never outlasts the action budget.
|
|
7
|
+
const REQUEST_TIMEOUT_MS = 15000;
|
|
8
|
+
function createOllamaProvider() {
|
|
9
|
+
const apiKey = process.env.OLLAMA_API_KEY;
|
|
10
|
+
const model = process.env.OLLAMA_MODEL;
|
|
11
|
+
if (!apiKey || !model) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
const baseUrl = (process.env.OLLAMA_BASE_URL ?? 'https://ollama.com').replace(/\/+$/, '');
|
|
15
|
+
return {
|
|
16
|
+
name: `ollama:${model}`,
|
|
17
|
+
async suggestSelector(input) {
|
|
18
|
+
const controller = new AbortController();
|
|
19
|
+
const timeout = setTimeout(() => controller.abort(), input.timeoutMs ?? REQUEST_TIMEOUT_MS);
|
|
20
|
+
try {
|
|
21
|
+
// Ollama Cloud's native endpoint (`/api/chat`), not the OpenAI-compatible `/v1/chat/completions`
|
|
22
|
+
// path — the latter 410s on this host.
|
|
23
|
+
const response = await fetch(`${baseUrl}/api/chat`, {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
signal: controller.signal,
|
|
26
|
+
headers: {
|
|
27
|
+
'content-type': 'application/json',
|
|
28
|
+
authorization: `Bearer ${apiKey}`,
|
|
29
|
+
},
|
|
30
|
+
body: JSON.stringify({
|
|
31
|
+
model,
|
|
32
|
+
stream: false,
|
|
33
|
+
format: 'json',
|
|
34
|
+
options: { temperature: 0 },
|
|
35
|
+
messages: [
|
|
36
|
+
{ role: 'system', content: prompt_1.SYSTEM_PROMPT },
|
|
37
|
+
{ role: 'user', content: (0, prompt_1.buildUserPrompt)(input) },
|
|
38
|
+
],
|
|
39
|
+
}),
|
|
40
|
+
});
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
console.warn(`[self-healer] Ollama request failed: ${response.status} ${response.statusText}`);
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const payload = await response.json();
|
|
46
|
+
const content = payload.message?.content;
|
|
47
|
+
if (!content) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const suggestion = (0, prompt_1.parseSuggestion)(content);
|
|
51
|
+
if (!suggestion) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const inputTokens = payload.prompt_eval_count;
|
|
55
|
+
const outputTokens = payload.eval_count;
|
|
56
|
+
return {
|
|
57
|
+
suggestion,
|
|
58
|
+
usage: {
|
|
59
|
+
inputTokens,
|
|
60
|
+
outputTokens,
|
|
61
|
+
totalTokens: inputTokens !== undefined && outputTokens !== undefined ? inputTokens + outputTokens : undefined,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
67
|
+
console.warn(`[self-healer] Ollama provider error: ${reason}`);
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
clearTimeout(timeout);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=ollama.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ollama.js","sourceRoot":"","sources":["../../../src/healer/providers/ollama.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../../src/healer/providers/openai.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAwC,MAAM,SAAS,CAAC;AAOlF,wBAAgB,oBAAoB,IAAI,YAAY,GAAG,IAAI,CAgE1D"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createOpenAIProvider = createOpenAIProvider;
|
|
4
|
+
const prompt_1 = require("./prompt");
|
|
5
|
+
// Fallback only — normally overridden by SuggestSelectorInput.timeoutMs, which mirrors the
|
|
6
|
+
// Playwright project's configured actionTimeout so the healer never outlasts the action budget.
|
|
7
|
+
const REQUEST_TIMEOUT_MS = 15000;
|
|
8
|
+
function createOpenAIProvider() {
|
|
9
|
+
const apiKey = process.env.OPENAI_API_KEY;
|
|
10
|
+
const model = process.env.OPENAI_MODEL;
|
|
11
|
+
if (!apiKey || !model) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
const baseUrl = (process.env.OPENAI_BASE_URL ?? 'https://api.openai.com/v1').replace(/\/+$/, '');
|
|
15
|
+
return {
|
|
16
|
+
name: `openai:${model}`,
|
|
17
|
+
async suggestSelector(input) {
|
|
18
|
+
const controller = new AbortController();
|
|
19
|
+
const timeout = setTimeout(() => controller.abort(), input.timeoutMs ?? REQUEST_TIMEOUT_MS);
|
|
20
|
+
try {
|
|
21
|
+
const response = await fetch(`${baseUrl}/chat/completions`, {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
signal: controller.signal,
|
|
24
|
+
headers: {
|
|
25
|
+
'content-type': 'application/json',
|
|
26
|
+
authorization: `Bearer ${apiKey}`,
|
|
27
|
+
},
|
|
28
|
+
body: JSON.stringify({
|
|
29
|
+
model,
|
|
30
|
+
temperature: 0,
|
|
31
|
+
response_format: { type: 'json_object' },
|
|
32
|
+
messages: [
|
|
33
|
+
{ role: 'system', content: prompt_1.SYSTEM_PROMPT },
|
|
34
|
+
{ role: 'user', content: (0, prompt_1.buildUserPrompt)(input) },
|
|
35
|
+
],
|
|
36
|
+
}),
|
|
37
|
+
});
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
console.warn(`[self-healer] OpenAI request failed: ${response.status} ${response.statusText}`);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const payload = await response.json();
|
|
43
|
+
const content = payload.choices?.[0]?.message?.content;
|
|
44
|
+
if (!content) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const suggestion = (0, prompt_1.parseSuggestion)(content);
|
|
48
|
+
if (!suggestion) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
return { suggestion, usage: (0, prompt_1.extractOpenAICompatibleUsage)(payload) };
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
55
|
+
console.warn(`[self-healer] OpenAI provider error: ${reason}`);
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
clearTimeout(timeout);
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=openai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../../../src/healer/providers/openai.ts"],"names":[],"mappings":";;;AACA,qCAAyG;AAEzG,2FAA2F;AAC3F,gGAAgG;AAChG,MAAM,kBAAkB,GAAG,KAAK,CAAC;AAEjC;IACE,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IAC1C,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IAEvC,IAAI,CAAC,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,OAAO,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,2BAA2B,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAEjG,OAAO;QACL,IAAI,EAAE,UAAU,KAAK,EAAE;QACvB,KAAK,CAAC,eAAe,CAAC,KAA2B;YAC/C,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;YACzC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,KAAK,CAAC,SAAS,IAAI,kBAAkB,CAAC,CAAC;YAE5F,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,mBAAmB,EAAE;oBAC1D,MAAM,EAAE,MAAM;oBACd,MAAM,EAAE,UAAU,CAAC,MAAM;oBACzB,OAAO,EAAE;wBACP,cAAc,EAAE,kBAAkB;wBAClC,aAAa,EAAE,UAAU,MAAM,EAAE;qBAClC;oBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;wBACnB,KAAK;wBACL,WAAW,EAAE,CAAC;wBACd,eAAe,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE;wBACxC,QAAQ,EAAE;4BACR,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,sBAAa,EAAE;4BAC1C,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAA,wBAAe,EAAC,KAAK,CAAC,EAAE;yBAClD;qBACF,CAAC;iBACH,CAAC,CAAC;gBAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;oBACjB,OAAO,CAAC,IAAI,CAAC,wCAAwC,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;oBAC/F,OAAO,IAAI,CAAC;gBACd,CAAC;gBAED,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,EAGlC,CAAC;gBACF,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC;gBACvD,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,OAAO,IAAI,CAAC;gBACd,CAAC;gBAED,MAAM,UAAU,GAAG,IAAA,wBAAe,EAAC,OAAO,CAAC,CAAC;gBAC5C,IAAI,CAAC,UAAU,EAAE,CAAC;oBAChB,OAAO,IAAI,CAAC;gBACd,CAAC;gBAED,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,IAAA,qCAA4B,EAAC,OAAO,CAAC,EAAE,CAAC;YACtE,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACtE,OAAO,CAAC,IAAI,CAAC,wCAAwC,MAAM,EAAE,CAAC,CAAC;gBAC/D,OAAO,IAAI,CAAC;YACd,CAAC;oBAAS,CAAC;gBACT,YAAY,CAAC,OAAO,CAAC,CAAC;YACxB,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SelectorSuggestion, SuggestSelectorInput, TokenUsage } from './types';
|
|
2
|
+
export declare const SYSTEM_PROMPT = "You are a Playwright self-healing assistant.\nYou will be given an ARIA accessibility snapshot of a web page, an action that just failed, and a human description of the element the test intended to interact with.\nPick the single best-matching element that is LITERALLY present in the snapshot and respond with strict JSON only (no markdown, no prose) matching one of:\n{\"strategy\":\"role\",\"role\":\"<aria role>\",\"name\":\"<accessible name>\"}\n{\"strategy\":\"text\",\"text\":\"<visible text>\"}\n{\"strategy\":\"testId\",\"testId\":\"<test id>\"}\n{\"strategy\":\"label\",\"label\":\"<label text>\"}\n{\"strategy\":\"placeholder\",\"placeholder\":\"<placeholder text>\"}\n{\"strategy\":\"css\",\"css\":\"<css selector>\"}\n{\"strategy\":\"none\"}\nIf nothing in the snapshot plausibly matches the description, respond with {\"strategy\":\"none\"}. Never invent an element that isn't in the snapshot.";
|
|
3
|
+
export declare function buildUserPrompt(input: SuggestSelectorInput): string;
|
|
4
|
+
export declare function parseSuggestion(content: string): SelectorSuggestion | null;
|
|
5
|
+
export declare function extractOpenAICompatibleUsage(payload: {
|
|
6
|
+
usage?: {
|
|
7
|
+
prompt_tokens?: number;
|
|
8
|
+
completion_tokens?: number;
|
|
9
|
+
total_tokens?: number;
|
|
10
|
+
};
|
|
11
|
+
}): TokenUsage | undefined;
|
|
12
|
+
//# sourceMappingURL=prompt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../../../src/healer/providers/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAIpF,eAAO,MAAM,aAAa,g5BAU0H,CAAC;AAErJ,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAWnE;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CA4C1E;AAID,wBAAgB,4BAA4B,CAAC,OAAO,EAAE;IAAE,KAAK,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAAG,UAAU,GAAG,SAAS,CAWvK"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SYSTEM_PROMPT = void 0;
|
|
4
|
+
exports.buildUserPrompt = buildUserPrompt;
|
|
5
|
+
exports.parseSuggestion = parseSuggestion;
|
|
6
|
+
exports.extractOpenAICompatibleUsage = extractOpenAICompatibleUsage;
|
|
7
|
+
const MAX_SNAPSHOT_CHARS = 8000;
|
|
8
|
+
exports.SYSTEM_PROMPT = `You are a Playwright self-healing assistant.
|
|
9
|
+
You will be given an ARIA accessibility snapshot of a web page, an action that just failed, and a human description of the element the test intended to interact with.
|
|
10
|
+
Pick the single best-matching element that is LITERALLY present in the snapshot and respond with strict JSON only (no markdown, no prose) matching one of:
|
|
11
|
+
{"strategy":"role","role":"<aria role>","name":"<accessible name>"}
|
|
12
|
+
{"strategy":"text","text":"<visible text>"}
|
|
13
|
+
{"strategy":"testId","testId":"<test id>"}
|
|
14
|
+
{"strategy":"label","label":"<label text>"}
|
|
15
|
+
{"strategy":"placeholder","placeholder":"<placeholder text>"}
|
|
16
|
+
{"strategy":"css","css":"<css selector>"}
|
|
17
|
+
{"strategy":"none"}
|
|
18
|
+
If nothing in the snapshot plausibly matches the description, respond with {"strategy":"none"}. Never invent an element that isn't in the snapshot.`;
|
|
19
|
+
function buildUserPrompt(input) {
|
|
20
|
+
const snapshot = input.ariaSnapshot.length > MAX_SNAPSHOT_CHARS
|
|
21
|
+
? `${input.ariaSnapshot.slice(0, MAX_SNAPSHOT_CHARS)}\n... (truncated)`
|
|
22
|
+
: input.ariaSnapshot;
|
|
23
|
+
return [
|
|
24
|
+
`Failed action: ${input.action}`,
|
|
25
|
+
`Element description: ${input.description ?? '(none provided)'}`,
|
|
26
|
+
'ARIA snapshot:',
|
|
27
|
+
snapshot,
|
|
28
|
+
].join('\n\n');
|
|
29
|
+
}
|
|
30
|
+
function parseSuggestion(content) {
|
|
31
|
+
let parsed;
|
|
32
|
+
try {
|
|
33
|
+
parsed = JSON.parse(content);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
const match = content.match(/\{[\s\S]*\}/);
|
|
37
|
+
if (!match) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
parsed = JSON.parse(match[0]);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (!parsed || typeof parsed !== 'object' || !('strategy' in parsed)) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const suggestion = parsed;
|
|
51
|
+
if (suggestion.strategy === 'none') {
|
|
52
|
+
return suggestion;
|
|
53
|
+
}
|
|
54
|
+
if (suggestion.strategy === 'role' && typeof suggestion.role === 'string') {
|
|
55
|
+
return suggestion;
|
|
56
|
+
}
|
|
57
|
+
if (suggestion.strategy === 'text' && typeof suggestion.text === 'string') {
|
|
58
|
+
return suggestion;
|
|
59
|
+
}
|
|
60
|
+
if (suggestion.strategy === 'testId' && typeof suggestion.testId === 'string') {
|
|
61
|
+
return suggestion;
|
|
62
|
+
}
|
|
63
|
+
if (suggestion.strategy === 'label' && typeof suggestion.label === 'string') {
|
|
64
|
+
return suggestion;
|
|
65
|
+
}
|
|
66
|
+
if (suggestion.strategy === 'placeholder' && typeof suggestion.placeholder === 'string') {
|
|
67
|
+
return suggestion;
|
|
68
|
+
}
|
|
69
|
+
if (suggestion.strategy === 'css' && typeof suggestion.css === 'string') {
|
|
70
|
+
return suggestion;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
// Shared by any provider whose HTTP response mimics OpenAI's Chat Completions shape
|
|
75
|
+
// (OpenAI itself, and Gemini's OpenAI-compatible endpoint).
|
|
76
|
+
function extractOpenAICompatibleUsage(payload) {
|
|
77
|
+
const usage = payload.usage;
|
|
78
|
+
if (!usage) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
inputTokens: usage.prompt_tokens,
|
|
83
|
+
outputTokens: usage.completion_tokens,
|
|
84
|
+
totalTokens: usage.total_tokens,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../../src/healer/providers/prompt.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export type SelectorSuggestion = {
|
|
2
|
+
strategy: 'none';
|
|
3
|
+
} | {
|
|
4
|
+
strategy: 'role';
|
|
5
|
+
role: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
} | {
|
|
8
|
+
strategy: 'text';
|
|
9
|
+
text: string;
|
|
10
|
+
} | {
|
|
11
|
+
strategy: 'testId';
|
|
12
|
+
testId: string;
|
|
13
|
+
} | {
|
|
14
|
+
strategy: 'label';
|
|
15
|
+
label: string;
|
|
16
|
+
} | {
|
|
17
|
+
strategy: 'placeholder';
|
|
18
|
+
placeholder: string;
|
|
19
|
+
} | {
|
|
20
|
+
strategy: 'css';
|
|
21
|
+
css: string;
|
|
22
|
+
};
|
|
23
|
+
export type SuggestSelectorInput = {
|
|
24
|
+
action: string;
|
|
25
|
+
description?: string;
|
|
26
|
+
ariaSnapshot: string;
|
|
27
|
+
/** Provider request timeout in ms. Defaults to the provider's own constant when omitted. */
|
|
28
|
+
timeoutMs?: number;
|
|
29
|
+
};
|
|
30
|
+
export type TokenUsage = {
|
|
31
|
+
inputTokens?: number;
|
|
32
|
+
outputTokens?: number;
|
|
33
|
+
totalTokens?: number;
|
|
34
|
+
};
|
|
35
|
+
export type ProviderResult = {
|
|
36
|
+
suggestion: SelectorSuggestion;
|
|
37
|
+
usage?: TokenUsage;
|
|
38
|
+
};
|
|
39
|
+
export type HealProvider = {
|
|
40
|
+
name: string;
|
|
41
|
+
suggestSelector(input: SuggestSelectorInput): Promise<ProviderResult | null>;
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/healer/providers/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAC1B;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GACpB;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACjD;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACtC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,QAAQ,EAAE,aAAa,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAErC,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,4FAA4F;IAC5F,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,EAAE,kBAAkB,CAAC;IAC/B,KAAK,CAAC,EAAE,UAAU,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;CAC9E,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/healer/providers/types.ts"],"names":[],"mappings":""}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { test, expect } from './bindings';
|
|
2
|
+
export type { BindingFixtures } from './bindings';
|
|
3
|
+
export { bindPageActions, bindFrameActions, bindLocatorActions, bindTargetActions } from './bindings/locator.binding';
|
|
4
|
+
export { healActionFailure, getHealingReports } from './healer';
|
|
5
|
+
export type { SelfHealingReport } from './healer';
|
|
6
|
+
export type { HealProvider, SelectorSuggestion, SuggestSelectorInput, TokenUsage, ProviderResult } from './healer/providers/types';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAC1C,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAEtH,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAChE,YAAY,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAElD,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getHealingReports = exports.healActionFailure = exports.bindTargetActions = exports.bindLocatorActions = exports.bindFrameActions = exports.bindPageActions = exports.expect = exports.test = void 0;
|
|
4
|
+
var bindings_1 = require("./bindings");
|
|
5
|
+
Object.defineProperty(exports, "test", { enumerable: true, get: function () { return bindings_1.test; } });
|
|
6
|
+
Object.defineProperty(exports, "expect", { enumerable: true, get: function () { return bindings_1.expect; } });
|
|
7
|
+
var locator_binding_1 = require("./bindings/locator.binding");
|
|
8
|
+
Object.defineProperty(exports, "bindPageActions", { enumerable: true, get: function () { return locator_binding_1.bindPageActions; } });
|
|
9
|
+
Object.defineProperty(exports, "bindFrameActions", { enumerable: true, get: function () { return locator_binding_1.bindFrameActions; } });
|
|
10
|
+
Object.defineProperty(exports, "bindLocatorActions", { enumerable: true, get: function () { return locator_binding_1.bindLocatorActions; } });
|
|
11
|
+
Object.defineProperty(exports, "bindTargetActions", { enumerable: true, get: function () { return locator_binding_1.bindTargetActions; } });
|
|
12
|
+
var healer_1 = require("./healer");
|
|
13
|
+
Object.defineProperty(exports, "healActionFailure", { enumerable: true, get: function () { return healer_1.healActionFailure; } });
|
|
14
|
+
Object.defineProperty(exports, "getHealingReports", { enumerable: true, get: function () { return healer_1.getHealingReports; } });
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,uCAA0C;AAAjC,gGAAA,IAAI,OAAA;AAAE,kGAAA,MAAM,OAAA;AAErB,8DAAsH;AAA7G,kHAAA,eAAe,OAAA;AAAE,mHAAA,gBAAgB,OAAA;AAAE,qHAAA,kBAAkB,OAAA;AAAE,oHAAA,iBAAiB,OAAA;AAEjF,mCAAgE;AAAvD,2GAAA,iBAAiB,OAAA;AAAE,2GAAA,iBAAiB,OAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tamash-playwright",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Self-healing bindings for Playwright: wraps page/locator/frame actions and automatically recovers broken selectors using an AI model (Ollama, OpenAI, Anthropic, or Gemini).",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"README.md",
|
|
10
|
+
".env.example"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc -p tsconfig.json",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"playwright",
|
|
18
|
+
"self-healing",
|
|
19
|
+
"test-automation",
|
|
20
|
+
"llm",
|
|
21
|
+
"ollama",
|
|
22
|
+
"openai",
|
|
23
|
+
"anthropic",
|
|
24
|
+
"gemini"
|
|
25
|
+
],
|
|
26
|
+
"author": "",
|
|
27
|
+
"license": "ISC",
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"@playwright/test": ">=1.40.0"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@anthropic-ai/sdk": "^0.115.0",
|
|
33
|
+
"dotenv": "^16.0.1"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@playwright/test": "^1.62.1",
|
|
37
|
+
"@types/node": "^26.1.2",
|
|
38
|
+
"typescript": "^7.0.2"
|
|
39
|
+
}
|
|
40
|
+
}
|