donobu 2.46.1 → 2.46.3

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.
Files changed (57) hide show
  1. package/dist/assets/generated/version +1 -1
  2. package/dist/esm/assets/generated/version +1 -1
  3. package/dist/esm/lib/DonobuExtendedPage.d.ts +12 -10
  4. package/dist/esm/lib/DonobuExtendedPage.d.ts.map +1 -1
  5. package/dist/esm/lib/PageAi.d.ts +103 -77
  6. package/dist/esm/lib/PageAi.d.ts.map +1 -1
  7. package/dist/esm/lib/PageAi.js +152 -272
  8. package/dist/esm/lib/PageAi.js.map +1 -1
  9. package/dist/esm/lib/originalGotoRegistry.d.ts +5 -0
  10. package/dist/esm/lib/originalGotoRegistry.d.ts.map +1 -0
  11. package/dist/esm/lib/originalGotoRegistry.js +30 -0
  12. package/dist/esm/lib/originalGotoRegistry.js.map +1 -0
  13. package/dist/esm/lib/pageAi/cache.d.ts +84 -0
  14. package/dist/esm/lib/pageAi/cache.d.ts.map +1 -0
  15. package/dist/esm/lib/pageAi/cache.js +207 -0
  16. package/dist/esm/lib/pageAi/cache.js.map +1 -0
  17. package/dist/esm/lib/pageAi/cacheEntryBuilder.d.ts +17 -0
  18. package/dist/esm/lib/pageAi/cacheEntryBuilder.d.ts.map +1 -0
  19. package/dist/esm/lib/pageAi/cacheEntryBuilder.js +25 -0
  20. package/dist/esm/lib/pageAi/cacheEntryBuilder.js.map +1 -0
  21. package/dist/esm/lib/testExtension.d.ts.map +1 -1
  22. package/dist/esm/lib/testExtension.js +284 -282
  23. package/dist/esm/lib/testExtension.js.map +1 -1
  24. package/dist/esm/lib/utils/selfHealing.js +2 -2
  25. package/dist/esm/lib/utils/selfHealing.js.map +1 -1
  26. package/dist/esm/managers/CodeGenerator.d.ts +8 -0
  27. package/dist/esm/managers/CodeGenerator.d.ts.map +1 -1
  28. package/dist/esm/managers/CodeGenerator.js +104 -1
  29. package/dist/esm/managers/CodeGenerator.js.map +1 -1
  30. package/dist/lib/DonobuExtendedPage.d.ts +12 -10
  31. package/dist/lib/DonobuExtendedPage.d.ts.map +1 -1
  32. package/dist/lib/PageAi.d.ts +103 -77
  33. package/dist/lib/PageAi.d.ts.map +1 -1
  34. package/dist/lib/PageAi.js +152 -272
  35. package/dist/lib/PageAi.js.map +1 -1
  36. package/dist/lib/originalGotoRegistry.d.ts +5 -0
  37. package/dist/lib/originalGotoRegistry.d.ts.map +1 -0
  38. package/dist/lib/originalGotoRegistry.js +30 -0
  39. package/dist/lib/originalGotoRegistry.js.map +1 -0
  40. package/dist/lib/pageAi/cache.d.ts +84 -0
  41. package/dist/lib/pageAi/cache.d.ts.map +1 -0
  42. package/dist/lib/pageAi/cache.js +207 -0
  43. package/dist/lib/pageAi/cache.js.map +1 -0
  44. package/dist/lib/pageAi/cacheEntryBuilder.d.ts +17 -0
  45. package/dist/lib/pageAi/cacheEntryBuilder.d.ts.map +1 -0
  46. package/dist/lib/pageAi/cacheEntryBuilder.js +25 -0
  47. package/dist/lib/pageAi/cacheEntryBuilder.js.map +1 -0
  48. package/dist/lib/testExtension.d.ts.map +1 -1
  49. package/dist/lib/testExtension.js +284 -282
  50. package/dist/lib/testExtension.js.map +1 -1
  51. package/dist/lib/utils/selfHealing.js +2 -2
  52. package/dist/lib/utils/selfHealing.js.map +1 -1
  53. package/dist/managers/CodeGenerator.d.ts +8 -0
  54. package/dist/managers/CodeGenerator.d.ts.map +1 -1
  55. package/dist/managers/CodeGenerator.js +104 -1
  56. package/dist/managers/CodeGenerator.js.map +1 -1
  57. package/package.json +1 -1
@@ -52,6 +52,7 @@ const GptApiKeysNotSetupException_1 = require("../exceptions/GptApiKeysNotSetupE
52
52
  const SmartSelector_1 = require("./SmartSelector");
53
53
  const PageAi_1 = require("./PageAi");
54
54
  const path_1 = require("path");
55
+ const originalGotoRegistry_1 = require("./originalGotoRegistry");
55
56
  __exportStar(require("playwright/test"), exports);
56
57
  const PLACEHOLDER_FLOW_URL = 'https://example.com';
57
58
  /**
@@ -78,255 +79,15 @@ exports.test = test_1.test.extend({
78
79
  ],
79
80
  // Override the default page fixture
80
81
  page: async ({ page, gptClient, headless }, use, testInfo) => {
81
- page._initialBrowserState = await getBrowserState(page, testInfo);
82
- gptClient = await buttonUpGptClient(gptClient);
83
- const donobu = await (0, donobuTestStack_1.getOrCreateDonobuStack)();
84
- const persistenceLayer = await donobu.flowsPersistenceFactory.createPersistenceLayer();
85
82
  await PlaywrightUtils_1.PlaywrightUtils.setupBasicBrowserContext(page.context());
86
- // Save the oringal goto method since we override it later. We need the
87
- // original so we can delegate to it in our override.
88
- page._originalGoto = page.goto;
89
- page.ai = async (instruction, options) => {
90
- if (!gptClient) {
91
- throw new Error(`No AI connection available. Establish a connection by setting an API key via an environment variable.
92
- Valid options:
93
- - ${envVars_1.ENV_NAMES.ANTHROPIC_API_KEY}
94
- - ${envVars_1.ENV_NAMES.GOOGLE_GENERATIVE_AI_API_KEY}
95
- - ${envVars_1.ENV_NAMES.OPENAI_API_KEY}`);
96
- }
97
- else {
98
- const cacheFilepath = (0, path_1.join)((0, path_1.dirname)(testInfo.file), 'donobu.json');
99
- const pageAi = new PageAi_1.PageAi(donobu, persistenceLayer, gptClient, cacheFilepath);
100
- const revisedOptions = {
101
- envVars: page._donobuFlowMetadata.envVars ?? [],
102
- ...(options ? options : {}),
103
- };
104
- if (testInfo.retry > 0 && isSelfHealingEnabled(testInfo)) {
105
- // Blow the cache so that when we are self-healing the `page.ai` call
106
- // will use a new autonomous flow.
107
- await pageAi.deleteCachedFlow(page, instruction, revisedOptions);
108
- }
109
- return pageAi.ai(page, instruction, revisedOptions);
110
- }
111
- };
112
- page.find = (selector, options) => {
113
- return new SmartSelector_1.SmartSelectorImpl(page, {
114
- element: [selector, ...(options?.failover ?? [])],
115
- frame: options?.frame,
116
- });
117
- };
118
- // Intercept 'page.goto' so that Donobu can later re-run it as a standard
119
- // Donobu action if necessary. This can happen if you run a Playwright
120
- // test using npx, but later decide to rerun that run using the Donobu UI.
121
- page.goto = async (url, options) => {
122
- const startedAt = new Date().getTime();
123
- const flowId = page._donobuFlowMetadata.id;
124
- // Update the target website to whatever the first place we are
125
- // navigating to is.
126
- if (page._donobuFlowMetadata.targetWebsite === PLACEHOLDER_FLOW_URL) {
127
- page._donobuFlowMetadata = {
128
- ...page._donobuFlowMetadata,
129
- targetWebsite: url,
130
- };
131
- await persistenceLayer.setFlowMetadata(page._donobuFlowMetadata);
132
- }
133
- try {
134
- const resp = await page._originalGoto.call(page, url, options);
135
- const pageTitle = await page.title();
136
- const postCallImage = await PlaywrightUtils_1.PlaywrightUtils.takeViewportScreenshot(page);
137
- const postCallImageId = await persistenceLayer.saveScreenShot(flowId, postCallImage);
138
- const completedAt = new Date().getTime();
139
- await persistenceLayer.saveToolCall(flowId, {
140
- id: MiscUtils_1.MiscUtils.createAdHocToolCallId(),
141
- toolName: GoToWebpageTool_1.GoToWebpageTool.NAME,
142
- parameters: {
143
- url: url,
144
- },
145
- outcome: {
146
- isSuccessful: true,
147
- forLlm: `Successfully navigated to ${url}`,
148
- metadata: {
149
- pageTitle: pageTitle,
150
- resolvedUrl: page.url(),
151
- },
152
- },
153
- postCallImageId: postCallImageId,
154
- page: url,
155
- startedAt: startedAt,
156
- completedAt: completedAt,
157
- });
158
- return resp;
159
- }
160
- catch (error) {
161
- const postCallImage = await PlaywrightUtils_1.PlaywrightUtils.takeViewportScreenshot(page);
162
- const postCallImageId = await persistenceLayer.saveScreenShot(flowId, postCallImage);
163
- const completedAt = new Date().getTime();
164
- await persistenceLayer.saveToolCall(flowId, {
165
- id: MiscUtils_1.MiscUtils.createAdHocToolCallId(),
166
- toolName: GoToWebpageTool_1.GoToWebpageTool.NAME,
167
- parameters: {
168
- url: url,
169
- },
170
- outcome: {
171
- isSuccessful: false,
172
- forLlm: `FAILED! ${typeof error}: ${error.message}`,
173
- metadata: null,
174
- },
175
- postCallImageId: postCallImageId,
176
- page: url,
177
- startedAt: startedAt,
178
- completedAt: completedAt,
179
- });
180
- // Mark test as failed for later callback
181
- testInfo.status = 'failed';
182
- throw error;
183
- }
184
- };
185
- page.run = async (toolName, toolParams, gptClientOverride) => {
186
- let finalGptClient = null;
187
- if (gptClientOverride) {
188
- if (!(gptClientOverride instanceof GptClient_1.GptClient)) {
189
- finalGptClient = new VercelAiGptClient_1.VercelAiGptClient(gptClientOverride);
190
- }
191
- else {
192
- finalGptClient = gptClientOverride;
193
- }
194
- }
195
- else if (gptClient) {
196
- finalGptClient = gptClient;
197
- }
198
- const pageInspector = new PageInspector_1.PageInspector();
199
- const controlPanel = new ControlPanel_1.NoOpControlPanel();
200
- const clonedParams = toolParams
201
- ? JSON.parse(JSON.stringify(toolParams))
202
- : {};
203
- clonedParams.rationale || (clonedParams.rationale = '');
204
- const envData = await donobu.flowsManager.buildEnvData(page._donobuFlowMetadata.envVars ?? []);
205
- const toolCallContext = {
206
- flowsManager: donobu.flowsManager,
207
- envData: envData,
208
- pageInspector: pageInspector,
209
- controlPanel: controlPanel,
210
- persistence: persistenceLayer,
211
- gptClient: finalGptClient,
212
- interactionVisualizer: new InteractionVisualizer_1.InteractionVisualizer(0),
213
- proposedToolCalls: [],
214
- invokedToolCalls: [],
215
- page: { current: page },
216
- metadata: page._donobuFlowMetadata,
217
- toolCallId: clonedParams.toolCallId ?? MiscUtils_1.MiscUtils.createAdHocToolCallId(),
218
- };
219
- try {
220
- const tools = await ToolManager_1.ToolManager.allTools();
221
- const result = await new ToolManager_1.ToolManager(tools).invokeTool(toolCallContext, toolName, clonedParams, false);
222
- // Check if tool execution was successful
223
- if (!result.outcome.isSuccessful) {
224
- throw new Error(`${toolName} execution failed: ${JSON.stringify(result, null, 2)}`);
225
- }
226
- return result.outcome;
227
- }
228
- catch (error) {
229
- // Mark test as failed if an exception occurs during tool execution
230
- testInfo.status = 'failed';
231
- throw error;
232
- }
233
- };
234
- page.analyzePageText = async (params, gptClientOverride) => {
235
- return page.run(AnalyzePageTextTool_1.AnalyzePageTextTool.NAME, params, gptClientOverride);
236
- };
237
- page.assertPageText = async (params, gptClientOverride) => {
238
- return page.run(AssertPageTextTool_1.AssertPageTextTool.NAME, params, gptClientOverride);
239
- };
240
- page.changeTab = async (url) => {
241
- // The underlying tool call is mostly performative. We do it so we can
242
- // record the operation occurred, but the actual page reassignment happens
243
- // at the call site of this function.
244
- const result = await page.run(ChangeWebBrowserTabTool_1.ChangeWebBrowserTabTool.NAME, {
245
- tabUrl: url,
246
- });
247
- if (result.isSuccessful) {
248
- // We need to return a real Page object, not just the raw URL, so we
249
- // loop through the pages of the current context, knowing we will find
250
- // a match.
251
- return page
252
- .context()
253
- .pages()
254
- .find((tab) => tab.url() === url);
255
- }
256
- else {
257
- throw Error(result.forLlm);
258
- }
259
- };
260
- page.chooseSelectOption = async (params) => {
261
- return page.run(ChooseSelectOptionTool_1.ChooseSelectOptionTool.NAME, params);
262
- };
263
- page.clickElement = async (params) => {
264
- return page.run(ClickTool_1.ClickTool.NAME, params);
265
- };
266
- page.createCookieReport = async (gptClientOverride) => {
267
- return page.run(CreateBrowserCookieReportTool_1.CreateBrowserCookieReportTool.NAME, {}, gptClientOverride);
268
- };
269
- page.extract = async (params, gptClientOverride) => {
270
- let finalGptClient = null;
271
- if (gptClientOverride) {
272
- if (!(gptClientOverride instanceof GptClient_1.GptClient)) {
273
- finalGptClient = new VercelAiGptClient_1.VercelAiGptClient(gptClientOverride);
274
- }
275
- else {
276
- finalGptClient = gptClientOverride;
277
- }
278
- }
279
- else if (gptClient) {
280
- finalGptClient = gptClient;
281
- }
282
- if (!finalGptClient) {
283
- testInfo.status = 'failed';
284
- throw Error('Cannot extract an object from page without a GPT client set up!');
285
- }
286
- try {
287
- const toolCallHistory = await persistenceLayer.getToolCalls(page._donobuFlowMetadata.id);
288
- const structuredOutputMessage = await (0, DonobuFlow_1.extractFromPage)(params.instruction ??
289
- 'Generate an object conforming to the given JSON-schema', params.schema, page, toolCallHistory, finalGptClient);
290
- page._donobuFlowMetadata.resultJsonSchema = v4_1.z.toJSONSchema(params.schema);
291
- page._donobuFlowMetadata.result = JSON.parse(JSON.stringify(structuredOutputMessage.output));
292
- await persistenceLayer.setFlowMetadata(page._donobuFlowMetadata);
293
- return structuredOutputMessage.output;
294
- }
295
- catch (error) {
296
- testInfo.status = 'failed';
297
- throw error;
298
- }
299
- };
300
- page.hoverOverElement = async (params) => {
301
- return page.run(HoverOverElementTool_1.HoverOverElementTool.NAME, params);
302
- };
303
- page.inputRandomizedEmailAddress = async (params) => {
304
- return page.run(InputRandomizedEmailAddressTool_1.InputRandomizedEmailAddressTool.NAME, params);
305
- };
306
- page.inputText = async (params) => {
307
- return page.run(InputTextTool_1.InputTextTool.NAME, params);
308
- };
309
- page.pressKey = async (params) => {
310
- return page.run(PressKeyTool_1.PressKeyTool.NAME, params);
311
- };
312
- page.runAccessibilityTest = async () => {
313
- return page.run(RunAccessibilityTestTool_1.RunAccessibilityTestTool.NAME);
314
- };
315
- page.scroll = async (params) => {
316
- return page.run(ScrollPageTool_1.ScrollPageTool.NAME, params);
317
- };
318
- page.visuallyAssert = async (params, gptClientOverride) => {
319
- return page.run(AssertTool_1.AssertTool.NAME, params, gptClientOverride);
320
- };
321
- try {
322
- const overallObjective = testInfo.annotations.find((v) => {
323
- return v.type === 'objective';
324
- })?.description ?? null;
325
- // Get the implied allowed environment variables by inspecting the objective.
326
- const allowedEnvVarsByName = DonobuFlowsManager_1.DonobuFlowsManager.distillAllowedEnvVariableNames(overallObjective, testInfo.annotations
327
- .filter((a) => a.type == 'ENV' && a.description)
328
- .map((a) => a.description));
329
- const donobuTestFlowMetadata = {
83
+ const overallObjective = testInfo.annotations.find((v) => v.type === 'objective')?.description ??
84
+ null;
85
+ const donobuStack = await (0, donobuTestStack_1.getOrCreateDonobuStack)();
86
+ const persistence = await donobuStack.flowsPersistenceFactory.createPersistenceLayer();
87
+ const initialBrowserState = await getBrowserState(page, testInfo);
88
+ const resolvedGptClient = await buttonUpGptClient(gptClient);
89
+ const sharedState = {
90
+ donobuFlowMetadata: {
330
91
  id: (0, uuid_1.v4)(),
331
92
  name: testInfo.title,
332
93
  createdWithDonobuVersion: MiscUtils_1.MiscUtils.DONOBU_VERSION,
@@ -338,7 +99,9 @@ Valid options:
338
99
  headless: headless,
339
100
  },
340
101
  },
341
- envVars: allowedEnvVarsByName,
102
+ envVars: DonobuFlowsManager_1.DonobuFlowsManager.distillAllowedEnvVariableNames(overallObjective, testInfo.annotations
103
+ .filter((a) => a.type == 'ENV' && a.description)
104
+ .map((a) => a.description)),
342
105
  gptConfigName: null,
343
106
  hasGptConfigNameOverride: false,
344
107
  customTools: null,
@@ -358,54 +121,48 @@ Valid options:
358
121
  completedAt: null,
359
122
  state: 'RUNNING_ACTION',
360
123
  nextState: null,
361
- };
362
- await persistenceLayer.setFlowMetadata(donobuTestFlowMetadata);
363
- page._donobuFlowMetadata = donobuTestFlowMetadata;
364
- // Let Playwright continue and give tests the patched page.
365
- await use(page);
124
+ },
125
+ donobuStack: donobuStack,
126
+ pageAi: undefined,
127
+ persistence: persistence,
128
+ initialBrowserState: initialBrowserState,
129
+ gptClient: resolvedGptClient,
130
+ };
131
+ const extend = createDonobuPageExtender(sharedState, testInfo);
132
+ const extendedPage = extend(page);
133
+ try {
134
+ await sharedState.persistence.setFlowMetadata(sharedState.donobuFlowMetadata);
135
+ await use(extendedPage);
366
136
  }
367
137
  catch (error) {
368
- // Mark test as failed if an uncaught exception occurs during the test
369
138
  testInfo.status = 'failed';
370
139
  throw error;
371
140
  }
372
141
  finally {
373
142
  try {
374
- if (page._donobuFlowMetadata) {
375
- // Update metadata with final status.
376
- page._donobuFlowMetadata.state =
377
- testInfo.status === 'failed' || testInfo.status === 'timedOut'
378
- ? 'FAILED'
379
- : 'SUCCESS';
380
- page._donobuFlowMetadata.completedAt = new Date().getTime();
381
- await persistenceLayer.setFlowMetadata(page._donobuFlowMetadata);
382
- await testInfo.attach('test-flow-metadata.json', {
383
- body: JSON.stringify(page._donobuFlowMetadata, null, 2),
384
- contentType: 'application/json',
385
- });
386
- // Persist the final browser state.
387
- const browserState = await BrowserUtils_1.BrowserUtils.getBrowserStorageState(page.context());
388
- await persistenceLayer.setBrowserState(page._donobuFlowMetadata.id, browserState);
389
- }
143
+ sharedState.donobuFlowMetadata.state =
144
+ testInfo.status === 'failed' || testInfo.status === 'timedOut'
145
+ ? 'FAILED'
146
+ : 'SUCCESS';
147
+ sharedState.donobuFlowMetadata.completedAt = new Date().getTime();
148
+ await sharedState.persistence.setFlowMetadata(sharedState.donobuFlowMetadata);
149
+ await testInfo.attach('test-flow-metadata.json', {
150
+ body: JSON.stringify(sharedState.donobuFlowMetadata, null, 2),
151
+ contentType: 'application/json',
152
+ });
153
+ const browserState = await BrowserUtils_1.BrowserUtils.getBrowserStorageState(extendedPage.context());
154
+ await sharedState.persistence.setBrowserState(sharedState.donobuFlowMetadata.id, browserState);
390
155
  }
391
156
  catch (error) {
392
- // Log but don't throw, to ensure cleanup continues.
393
157
  Logger_1.appLogger.error('Error during test cleanup:', error);
394
158
  }
395
- // Only attempt wholesale test-file-level self-healing for a fail test if
396
- // we are working with legacy tests (i.e. use an overallObjective).
397
- if (testInfo.status === 'failed' &&
398
- page._donobuFlowMetadata.overallObjective !== null &&
399
- isSelfHealingEnabled(testInfo)) {
400
- if (!page._donobuFlowMetadata) {
401
- Logger_1.appLogger.error('Will not self-heal due to no Donobu metadata being found.');
402
- }
403
- else if (!gptClient) {
159
+ if (testInfo.status === 'failed' && isSelfHealingEnabled(testInfo)) {
160
+ if (!sharedState.gptClient) {
404
161
  Logger_1.appLogger.warn('Will not self-heal due to no GPT client being set up.');
405
162
  }
406
163
  else {
407
164
  try {
408
- await (0, selfHealing_1.selfHeal)(gptClient, testInfo, page);
165
+ await (0, selfHealing_1.selfHeal)(sharedState.gptClient, testInfo, extendedPage);
409
166
  }
410
167
  catch (selfHealError) {
411
168
  Logger_1.appLogger.error('Error when attempting to self heal:', selfHealError);
@@ -415,6 +172,251 @@ Valid options:
415
172
  }
416
173
  },
417
174
  });
175
+ function createDonobuPageExtender(sharedState, testInfo) {
176
+ const extend = (target) => {
177
+ if (!(0, originalGotoRegistry_1.hasOriginalGoto)(target)) {
178
+ (0, originalGotoRegistry_1.registerOriginalGoto)(target, target.goto);
179
+ }
180
+ const extended = target;
181
+ extended._dnb = sharedState;
182
+ attachDonobuMethods(extended, sharedState, testInfo, extend);
183
+ return extended;
184
+ };
185
+ return extend;
186
+ }
187
+ function attachDonobuMethods(page, sharedState, testInfo, extend) {
188
+ page.ai = async (instruction, options, gptClientOverride) => {
189
+ let finalGptClient = null;
190
+ if (gptClientOverride) {
191
+ if (!(gptClientOverride instanceof GptClient_1.GptClient)) {
192
+ finalGptClient = new VercelAiGptClient_1.VercelAiGptClient(gptClientOverride);
193
+ }
194
+ else {
195
+ finalGptClient = gptClientOverride;
196
+ }
197
+ }
198
+ else if (sharedState.gptClient) {
199
+ finalGptClient = sharedState.gptClient;
200
+ }
201
+ if (!finalGptClient) {
202
+ throw new Error(`No AI connection available. Establish a connection by setting an API key via an environment variable.
203
+ Valid options:
204
+ - ${envVars_1.ENV_NAMES.ANTHROPIC_API_KEY}
205
+ - ${envVars_1.ENV_NAMES.GOOGLE_GENERATIVE_AI_API_KEY}
206
+ - ${envVars_1.ENV_NAMES.OPENAI_API_KEY}`);
207
+ }
208
+ if (!sharedState.pageAi) {
209
+ const cacheFilepath = (0, path_1.join)((0, path_1.dirname)(testInfo.file), 'donobu.json');
210
+ sharedState.pageAi = PageAi_1.PageAi.withFileCache(sharedState.donobuStack, sharedState.persistence, finalGptClient, cacheFilepath);
211
+ }
212
+ const revisedOptions = {
213
+ envVars: sharedState.donobuFlowMetadata.envVars ?? [],
214
+ ...(options ? options : {}),
215
+ };
216
+ if (testInfo.retry > 0) {
217
+ await sharedState.pageAi.invalidate(page, instruction, revisedOptions);
218
+ }
219
+ return sharedState.pageAi.ai(page, instruction, revisedOptions);
220
+ };
221
+ page.find = (selector, options) => {
222
+ return new SmartSelector_1.SmartSelectorImpl(page, {
223
+ element: [selector, ...(options?.failover ?? [])],
224
+ frame: options?.frame,
225
+ });
226
+ };
227
+ page.goto = async (url, options) => {
228
+ const startedAt = new Date().getTime();
229
+ const flowId = sharedState.donobuFlowMetadata.id;
230
+ if (sharedState.donobuFlowMetadata.targetWebsite === PLACEHOLDER_FLOW_URL) {
231
+ sharedState.donobuFlowMetadata = {
232
+ ...sharedState.donobuFlowMetadata,
233
+ targetWebsite: url,
234
+ };
235
+ await sharedState.persistence.setFlowMetadata(sharedState.donobuFlowMetadata);
236
+ }
237
+ try {
238
+ const originalGoto = (0, originalGotoRegistry_1.getOriginalGoto)(page);
239
+ const resp = await originalGoto.call(page, url, options);
240
+ const pageTitle = await page.title();
241
+ const postCallImage = await PlaywrightUtils_1.PlaywrightUtils.takeViewportScreenshot(page);
242
+ const postCallImageId = await sharedState.persistence.saveScreenShot(flowId, postCallImage);
243
+ const completedAt = new Date().getTime();
244
+ await sharedState.persistence.saveToolCall(flowId, {
245
+ id: MiscUtils_1.MiscUtils.createAdHocToolCallId(),
246
+ toolName: GoToWebpageTool_1.GoToWebpageTool.NAME,
247
+ parameters: {
248
+ url: url,
249
+ },
250
+ outcome: {
251
+ isSuccessful: true,
252
+ forLlm: `Successfully navigated to ${url}`,
253
+ metadata: {
254
+ pageTitle: pageTitle,
255
+ resolvedUrl: page.url(),
256
+ },
257
+ },
258
+ postCallImageId: postCallImageId,
259
+ page: url,
260
+ startedAt: startedAt,
261
+ completedAt: completedAt,
262
+ });
263
+ return resp;
264
+ }
265
+ catch (error) {
266
+ const postCallImage = await PlaywrightUtils_1.PlaywrightUtils.takeViewportScreenshot(page);
267
+ const postCallImageId = await sharedState.persistence.saveScreenShot(flowId, postCallImage);
268
+ const completedAt = new Date().getTime();
269
+ await sharedState.persistence.saveToolCall(flowId, {
270
+ id: MiscUtils_1.MiscUtils.createAdHocToolCallId(),
271
+ toolName: GoToWebpageTool_1.GoToWebpageTool.NAME,
272
+ parameters: {
273
+ url: url,
274
+ },
275
+ outcome: {
276
+ isSuccessful: false,
277
+ forLlm: `FAILED! ${typeof error}: ${error.message}`,
278
+ metadata: null,
279
+ },
280
+ postCallImageId: postCallImageId,
281
+ page: url,
282
+ startedAt: startedAt,
283
+ completedAt: completedAt,
284
+ });
285
+ testInfo.status = 'failed';
286
+ throw error;
287
+ }
288
+ };
289
+ page.run = async (toolName, toolParams, gptClientOverride) => {
290
+ let finalGptClient = null;
291
+ if (gptClientOverride) {
292
+ if (!(gptClientOverride instanceof GptClient_1.GptClient)) {
293
+ finalGptClient = new VercelAiGptClient_1.VercelAiGptClient(gptClientOverride);
294
+ }
295
+ else {
296
+ finalGptClient = gptClientOverride;
297
+ }
298
+ }
299
+ else if (sharedState.gptClient) {
300
+ finalGptClient = sharedState.gptClient;
301
+ }
302
+ const pageInspector = new PageInspector_1.PageInspector();
303
+ const controlPanel = new ControlPanel_1.NoOpControlPanel();
304
+ const clonedParams = toolParams
305
+ ? JSON.parse(JSON.stringify(toolParams))
306
+ : {};
307
+ clonedParams.rationale || (clonedParams.rationale = '');
308
+ const envData = await sharedState.donobuStack.flowsManager.buildEnvData(sharedState.donobuFlowMetadata.envVars ?? []);
309
+ const toolCallContext = {
310
+ flowsManager: sharedState.donobuStack.flowsManager,
311
+ envData: envData,
312
+ pageInspector: pageInspector,
313
+ controlPanel: controlPanel,
314
+ persistence: sharedState.persistence,
315
+ gptClient: finalGptClient,
316
+ interactionVisualizer: new InteractionVisualizer_1.InteractionVisualizer(0),
317
+ proposedToolCalls: [],
318
+ invokedToolCalls: [],
319
+ page: { current: page },
320
+ metadata: sharedState.donobuFlowMetadata,
321
+ toolCallId: clonedParams.toolCallId ?? MiscUtils_1.MiscUtils.createAdHocToolCallId(),
322
+ };
323
+ try {
324
+ const tools = await ToolManager_1.ToolManager.allTools();
325
+ const result = await new ToolManager_1.ToolManager(tools).invokeTool(toolCallContext, toolName, clonedParams, false);
326
+ if (!result.outcome.isSuccessful) {
327
+ throw new Error(`${toolName} execution failed: ${JSON.stringify(result, null, 2)}`);
328
+ }
329
+ return result.outcome;
330
+ }
331
+ catch (error) {
332
+ testInfo.status = 'failed';
333
+ throw error;
334
+ }
335
+ };
336
+ page.analyzePageText = async (params, gptClientOverride) => {
337
+ return page.run(AnalyzePageTextTool_1.AnalyzePageTextTool.NAME, params, gptClientOverride);
338
+ };
339
+ page.assertPageText = async (params, gptClientOverride) => {
340
+ return page.run(AssertPageTextTool_1.AssertPageTextTool.NAME, params, gptClientOverride);
341
+ };
342
+ page.changeTab = async (url) => {
343
+ const result = await page.run(ChangeWebBrowserTabTool_1.ChangeWebBrowserTabTool.NAME, {
344
+ tabUrl: url,
345
+ });
346
+ if (result.isSuccessful) {
347
+ const matchingPage = page
348
+ .context()
349
+ .pages()
350
+ .find((tab) => tab.url() === url);
351
+ if (!matchingPage) {
352
+ throw new Error(`Unable to locate tab with URL ${url} after switching.`);
353
+ }
354
+ return extend(matchingPage);
355
+ }
356
+ throw Error(result.forLlm);
357
+ };
358
+ page.chooseSelectOption = async (params) => {
359
+ return page.run(ChooseSelectOptionTool_1.ChooseSelectOptionTool.NAME, params);
360
+ };
361
+ page.clickElement = async (params) => {
362
+ return page.run(ClickTool_1.ClickTool.NAME, params);
363
+ };
364
+ page.createCookieReport = async (gptClientOverride) => {
365
+ return page.run(CreateBrowserCookieReportTool_1.CreateBrowserCookieReportTool.NAME, {}, gptClientOverride);
366
+ };
367
+ page.extract = async (params, gptClientOverride) => {
368
+ let finalGptClient = null;
369
+ if (gptClientOverride) {
370
+ if (!(gptClientOverride instanceof GptClient_1.GptClient)) {
371
+ finalGptClient = new VercelAiGptClient_1.VercelAiGptClient(gptClientOverride);
372
+ }
373
+ else {
374
+ finalGptClient = gptClientOverride;
375
+ }
376
+ }
377
+ else if (sharedState.gptClient) {
378
+ finalGptClient = sharedState.gptClient;
379
+ }
380
+ if (!finalGptClient) {
381
+ testInfo.status = 'failed';
382
+ throw Error('Cannot extract an object from page without a GPT client set up!');
383
+ }
384
+ try {
385
+ const toolCallHistory = await sharedState.persistence.getToolCalls(sharedState.donobuFlowMetadata.id);
386
+ const structuredOutputMessage = await (0, DonobuFlow_1.extractFromPage)(params.instruction ??
387
+ 'Generate an object conforming to the given JSON-schema', params.schema, page, toolCallHistory, finalGptClient);
388
+ sharedState.donobuFlowMetadata.resultJsonSchema = v4_1.z.toJSONSchema(params.schema);
389
+ sharedState.donobuFlowMetadata.result = JSON.parse(JSON.stringify(structuredOutputMessage.output));
390
+ await sharedState.persistence.setFlowMetadata(sharedState.donobuFlowMetadata);
391
+ return structuredOutputMessage.output;
392
+ }
393
+ catch (error) {
394
+ testInfo.status = 'failed';
395
+ throw error;
396
+ }
397
+ };
398
+ page.hoverOverElement = async (params) => {
399
+ return page.run(HoverOverElementTool_1.HoverOverElementTool.NAME, params);
400
+ };
401
+ page.inputRandomizedEmailAddress = async (params) => {
402
+ return page.run(InputRandomizedEmailAddressTool_1.InputRandomizedEmailAddressTool.NAME, params);
403
+ };
404
+ page.inputText = async (params) => {
405
+ return page.run(InputTextTool_1.InputTextTool.NAME, params);
406
+ };
407
+ page.pressKey = async (params) => {
408
+ return page.run(PressKeyTool_1.PressKeyTool.NAME, params);
409
+ };
410
+ page.runAccessibilityTest = async () => {
411
+ return page.run(RunAccessibilityTestTool_1.RunAccessibilityTestTool.NAME);
412
+ };
413
+ page.scroll = async (params) => {
414
+ return page.run(ScrollPageTool_1.ScrollPageTool.NAME, params);
415
+ };
416
+ page.visuallyAssert = async (params, gptClientOverride) => {
417
+ return page.run(AssertTool_1.AssertTool.NAME, params, gptClientOverride);
418
+ };
419
+ }
418
420
  function isSelfHealingEnabled(testInfo) {
419
421
  if (envVars_1.ENV_NAMES.SELF_HEAL_TESTS_ENABLED in testInfo.project.metadata) {
420
422
  // Check project-specific metadata. This takes priority if it exists.