donobu
Version:
Create browser automations with an LLM agent and replay them as Playwright scripts.
85 lines • 4.18 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.installRecorder = installRecorder;
const PageInteractionTracker_1 = require("../../../bindings/PageInteractionTracker");
const donobu_namespace_1 = require("../../../browser-side-scripts/donobu-namespace");
const page_interactions_tracker_1 = require("../../../browser-side-scripts/page-interactions-tracker");
const smart_selector_generator_1 = require("../../../browser-side-scripts/smart-selector-generator");
const Logger_1 = require("../../../utils/Logger");
const PlaywrightUtils_1 = require("../../../utils/PlaywrightUtils");
/**
* Installs Donobu's standard interaction tracking infrastructure on the
* page, using the same browser-side scripts (smart selectors, event
* listeners) and server-side handling ({@link PageInteractionTracker})
* that the normal Donobu flow engine uses.
*
* This ensures:
* - High-quality selectors (data-testid, aria-label, text, CSS, XPath)
* - Proper keystroke merging (consecutive keys → inputText tool calls)
* - No redundant navigation recording (only clicks/keydowns are tracked)
* - Actions persist through the normal persistence layer (visible in UI)
* - Post-action screenshots are captured automatically
*/
async function installRecorder(page, persistence, flowMetadata) {
const invokedToolCalls = [];
// Create a lightweight host with state locked to PAUSED so the tracker
// records all events. Uses a shallow copy of metadata so we don't
// mutate the original flow state.
const tbdMetadata = { ...flowMetadata, state: 'PAUSED' };
const host = {
metadata: tbdMetadata,
invokedToolCalls,
persistence,
};
// Register the PageInteractionTracker binding on the browser context.
// This also sets up addInitScript for namespace + smart selectors on
// future navigations, and a framenavigated listener for new pages.
await PageInteractionTracker_1.PageInteractionTracker.register(host, page.context());
// The register() call installs init scripts for future page loads,
// but the current page needs the scripts evaluated manually.
try {
await page.evaluate(donobu_namespace_1.installDonobuNamespace);
await page.evaluate(smart_selector_generator_1.installSmartSelectorGenerator);
await page.evaluate(page_interactions_tracker_1.installPageInteractionsTracker);
}
catch {
// May fail if the page is about:blank — the init scripts will
// kick in on the next navigation.
}
// Re-install the interaction tracker after navigations on the current
// page. The namespace and smart selector scripts are already handled
// by addInitScript, but the tracker needs manual re-evaluation.
page.on('framenavigated', async (frame) => {
try {
await frame.evaluate(page_interactions_tracker_1.installPageInteractionsTracker);
}
catch (error) {
if (!PlaywrightUtils_1.PlaywrightUtils.isPageClosedError(error)) {
Logger_1.appLogger.debug('tbd: failed to reinstall interaction tracker after navigation', error);
}
}
});
let lastDrainIndex = 0;
return {
drainRecordedActions() {
const newCalls = invokedToolCalls.slice(lastDrainIndex);
lastDrainIndex = invokedToolCalls.length;
return newCalls.map((tc) => {
const parameters = { ...tc.parameters };
// When PageInteractionTracker merges consecutive keystrokes into
// an inputText tool call, the selector lives in outcome.metadata
// rather than in parameters. The codegen expects parameters.selector,
// so backfill it from metadata when missing.
if (!parameters.selector && tc.outcome?.metadata?.element) {
parameters.selector = tc.outcome.metadata;
}
return {
name: tc.toolName,
parameters,
toolCallId: tc.id,
};
});
},
};
}
//# sourceMappingURL=actionRecorder.js.map